From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- sc/source/core/data/attarray.cxx | 2690 +++++ sc/source/core/data/attrib.cxx | 889 ++ sc/source/core/data/autonamecache.cxx | 90 + sc/source/core/data/bcaslot.cxx | 1300 +++ sc/source/core/data/bigrange.cxx | 25 + sc/source/core/data/celltextattr.cxx | 21 + sc/source/core/data/cellvalue.cxx | 691 ++ sc/source/core/data/cellvalues.cxx | 357 + sc/source/core/data/clipcontext.cxx | 440 + sc/source/core/data/clipparam.cxx | 189 + sc/source/core/data/colcontainer.cxx | 55 + sc/source/core/data/colorscale.cxx | 1445 +++ sc/source/core/data/column.cxx | 3391 ++++++ sc/source/core/data/column2.cxx | 3793 +++++++ sc/source/core/data/column3.cxx | 3726 +++++++ sc/source/core/data/column4.cxx | 2225 ++++ sc/source/core/data/columniterator.cxx | 209 + sc/source/core/data/columnset.cxx | 67 + sc/source/core/data/columnspanset.cxx | 369 + sc/source/core/data/compressedarray.cxx | 418 + sc/source/core/data/conditio.cxx | 2335 +++++ sc/source/core/data/dbdocutl.cxx | 201 + sc/source/core/data/dociter.cxx | 1779 ++++ sc/source/core/data/docparam.cxx | 25 + sc/source/core/data/docpool.cxx | 619 ++ sc/source/core/data/documen2.cxx | 1471 +++ sc/source/core/data/documen3.cxx | 2155 ++++ sc/source/core/data/documen4.cxx | 1367 +++ sc/source/core/data/documen5.cxx | 657 ++ sc/source/core/data/documen6.cxx | 210 + sc/source/core/data/documen7.cxx | 621 ++ sc/source/core/data/documen8.cxx | 1329 +++ sc/source/core/data/documen9.cxx | 681 ++ sc/source/core/data/document.cxx | 7089 +++++++++++++ sc/source/core/data/document10.cxx | 1106 ++ sc/source/core/data/documentimport.cxx | 859 ++ sc/source/core/data/documentstreamaccess.cxx | 147 + sc/source/core/data/dpcache.cxx | 1522 +++ sc/source/core/data/dpdimsave.cxx | 791 ++ sc/source/core/data/dpfilteredcache.cxx | 433 + sc/source/core/data/dpglobal.cxx | 20 + sc/source/core/data/dpgroup.cxx | 1030 ++ sc/source/core/data/dpitemdata.cxx | 371 + sc/source/core/data/dpnumgroupinfo.cxx | 35 + sc/source/core/data/dpobject.cxx | 3952 +++++++ sc/source/core/data/dpoutput.cxx | 1781 ++++ sc/source/core/data/dpoutputgeometry.cxx | 255 + sc/source/core/data/dpresfilter.cxx | 267 + sc/source/core/data/dpsave.cxx | 1383 +++ sc/source/core/data/dpsdbtab.cxx | 169 + sc/source/core/data/dpshttab.cxx | 323 + sc/source/core/data/dptabdat.cxx | 292 + sc/source/core/data/dptabres.cxx | 4117 ++++++++ sc/source/core/data/dptabsrc.cxx | 2613 +++++ sc/source/core/data/dputil.cxx | 420 + sc/source/core/data/drawpage.cxx | 51 + sc/source/core/data/drwlayer.cxx | 2676 +++++ sc/source/core/data/edittextiterator.cxx | 97 + sc/source/core/data/fillinfo.cxx | 1064 ++ sc/source/core/data/formulacell.cxx | 5571 ++++++++++ sc/source/core/data/formulaiter.cxx | 77 + sc/source/core/data/funcdesc.cxx | 1267 +++ sc/source/core/data/global.cxx | 1160 +++ sc/source/core/data/global2.cxx | 598 ++ sc/source/core/data/globalx.cxx | 142 + sc/source/core/data/grouptokenconverter.cxx | 316 + sc/source/core/data/listenercontext.cxx | 95 + sc/source/core/data/markarr.cxx | 462 + sc/source/core/data/markdata.cxx | 950 ++ sc/source/core/data/markmulti.cxx | 485 + sc/source/core/data/mtvcellfunc.cxx | 31 + sc/source/core/data/mtvelements.cxx | 198 + sc/source/core/data/olinetab.cxx | 873 ++ sc/source/core/data/pagepar.cxx | 56 + sc/source/core/data/patattr.cxx | 1422 +++ sc/source/core/data/pivot2.cxx | 161 + sc/source/core/data/poolhelp.cxx | 118 + sc/source/core/data/postit.cxx | 1306 +++ sc/source/core/data/queryevaluator.cxx | 961 ++ sc/source/core/data/queryiter.cxx | 1476 +++ sc/source/core/data/refupdatecontext.cxx | 139 + sc/source/core/data/rowheightcontext.cxx | 38 + sc/source/core/data/segmenttree.cxx | 711 ++ sc/source/core/data/sheetevents.cxx | 123 + sc/source/core/data/simpleformulacalc.cxx | 155 + sc/source/core/data/sortparam.cxx | 306 + sc/source/core/data/stlpool.cxx | 447 + sc/source/core/data/stlsheet.cxx | 303 + sc/source/core/data/subtotalparam.cxx | 199 + sc/source/core/data/tabbgcolor.cxx | 36 + sc/source/core/data/table1.cxx | 2729 +++++ sc/source/core/data/table2.cxx | 4372 ++++++++ sc/source/core/data/table3.cxx | 3099 ++++++ sc/source/core/data/table4.cxx | 2990 ++++++ sc/source/core/data/table5.cxx | 1295 +++ sc/source/core/data/table6.cxx | 1155 +++ sc/source/core/data/table7.cxx | 652 ++ sc/source/core/data/tabprotection.cxx | 724 ++ sc/source/core/data/types.cxx | 32 + sc/source/core/data/userdat.cxx | 51 + sc/source/core/data/validat.cxx | 1087 ++ sc/source/core/inc/addinhelpid.hxx | 45 + sc/source/core/inc/addinlis.hxx | 84 + sc/source/core/inc/adiasync.hxx | 77 + sc/source/core/inc/bcaslot.hxx | 396 + sc/source/core/inc/cellkeytranslator.hxx | 83 + sc/source/core/inc/ddelink.hxx | 84 + sc/source/core/inc/doubleref.hxx | 178 + sc/source/core/inc/formulagroupcl.hxx | 29 + sc/source/core/inc/grouptokenconverter.hxx | 38 + sc/source/core/inc/interpre.hxx | 1165 +++ sc/source/core/inc/jumpmatrix.hxx | 122 + sc/source/core/inc/parclass.hxx | 144 + sc/source/core/inc/poolhelp.hxx | 65 + sc/source/core/inc/refupdat.hxx | 71 + sc/source/core/inc/scrdata.hxx | 37 + sc/source/core/inc/sharedstringpoolpurge.hxx | 48 + sc/source/core/inc/webservicelink.hxx | 49 + sc/source/core/opencl/cl-test.ods | Bin 0 -> 18569 bytes sc/source/core/opencl/formulagroupcl.cxx | 4438 ++++++++ sc/source/core/opencl/op_addin.cxx | 236 + sc/source/core/opencl/op_addin.hxx | 33 + sc/source/core/opencl/op_array.cxx | 191 + sc/source/core/opencl/op_array.hxx | 40 + sc/source/core/opencl/op_database.cxx | 1642 +++ sc/source/core/opencl/op_database.hxx | 106 + sc/source/core/opencl/op_financial.cxx | 4791 +++++++++ sc/source/core/opencl/op_financial.hxx | 579 ++ sc/source/core/opencl/op_logical.cxx | 360 + sc/source/core/opencl/op_logical.hxx | 58 + sc/source/core/opencl/op_math.cxx | 3207 ++++++ sc/source/core/opencl/op_math.hxx | 499 + sc/source/core/opencl/op_spreadsheet.cxx | 286 + sc/source/core/opencl/op_spreadsheet.hxx | 27 + sc/source/core/opencl/op_statistical.cxx | 9828 ++++++++++++++++++ sc/source/core/opencl/op_statistical.hxx | 550 + sc/source/core/opencl/opbase.cxx | 390 + sc/source/core/opencl/opbase.hxx | 246 + sc/source/core/opencl/opinlinefun_finacial.cxx | 1882 ++++ sc/source/core/opencl/opinlinefun_math.hxx | 90 + sc/source/core/opencl/opinlinefun_statistical.cxx | 1366 +++ sc/source/core/tool/addincfg.cxx | 53 + sc/source/core/tool/addincol.cxx | 1694 +++ sc/source/core/tool/addinhelpid.cxx | 209 + sc/source/core/tool/addinlis.cxx | 133 + sc/source/core/tool/address.cxx | 2532 +++++ sc/source/core/tool/adiasync.cxx | 136 + sc/source/core/tool/appoptio.cxx | 654 ++ sc/source/core/tool/arraysum.hxx | 36 + sc/source/core/tool/arraysumSSE2.cxx | 122 + sc/source/core/tool/autoform.cxx | 934 ++ sc/source/core/tool/calcconfig.cxx | 241 + sc/source/core/tool/callform.cxx | 411 + sc/source/core/tool/cellform.cxx | 214 + sc/source/core/tool/cellkeytranslator.cxx | 232 + sc/source/core/tool/cellkeywords.inl | 199 + sc/source/core/tool/chartarr.cxx | 377 + sc/source/core/tool/charthelper.cxx | 432 + sc/source/core/tool/chartlis.cxx | 630 ++ sc/source/core/tool/chartlock.cxx | 180 + sc/source/core/tool/chartpos.cxx | 523 + sc/source/core/tool/chgtrack.cxx | 4671 +++++++++ sc/source/core/tool/chgviset.cxx | 150 + sc/source/core/tool/compare.cxx | 334 + sc/source/core/tool/compiler.cxx | 6568 ++++++++++++ sc/source/core/tool/consoli.cxx | 544 + sc/source/core/tool/dbdata.cxx | 1637 +++ sc/source/core/tool/ddelink.cxx | 266 + sc/source/core/tool/defaultsoptions.cxx | 151 + sc/source/core/tool/detdata.cxx | 88 + sc/source/core/tool/detfunc.cxx | 1755 ++++ sc/source/core/tool/docoptio.cxx | 371 + sc/source/core/tool/doubleref.cxx | 474 + sc/source/core/tool/editdataarray.cxx | 75 + sc/source/core/tool/editutil.cxx | 935 ++ sc/source/core/tool/filtopt.cxx | 74 + sc/source/core/tool/formulagroup.cxx | 244 + sc/source/core/tool/formulalogger.cxx | 357 + sc/source/core/tool/formulaopt.cxx | 652 ++ sc/source/core/tool/formulaparserpool.cxx | 142 + sc/source/core/tool/formularesult.cxx | 640 ++ sc/source/core/tool/grouparealistener.cxx | 352 + sc/source/core/tool/hints.cxx | 113 + sc/source/core/tool/inputopt.cxx | 162 + sc/source/core/tool/interpr1.cxx | 10198 +++++++++++++++++++ sc/source/core/tool/interpr2.cxx | 3679 +++++++ sc/source/core/tool/interpr3.cxx | 5579 ++++++++++ sc/source/core/tool/interpr4.cxx | 4795 +++++++++ sc/source/core/tool/interpr5.cxx | 3345 ++++++ sc/source/core/tool/interpr6.cxx | 1010 ++ sc/source/core/tool/interpr7.cxx | 556 + sc/source/core/tool/interpr8.cxx | 2008 ++++ sc/source/core/tool/interpretercontext.cxx | 215 + sc/source/core/tool/jumpmatrix.cxx | 273 + sc/source/core/tool/listenerquery.cxx | 90 + sc/source/core/tool/lookupcache.cxx | 128 + sc/source/core/tool/math.cxx | 72 + sc/source/core/tool/matrixoperators.cxx | 41 + sc/source/core/tool/navicfg.cxx | 60 + sc/source/core/tool/numformat.cxx | 65 + sc/source/core/tool/odffmap.cxx | 142 + sc/source/core/tool/optutil.cxx | 67 + sc/source/core/tool/orcusxml.cxx | 30 + sc/source/core/tool/parclass.cxx | 710 ++ sc/source/core/tool/printopt.cxx | 131 + sc/source/core/tool/prnsave.cxx | 127 + sc/source/core/tool/progress.cxx | 179 + sc/source/core/tool/queryentry.cxx | 207 + sc/source/core/tool/queryparam.cxx | 464 + sc/source/core/tool/rangecache.cxx | 200 + sc/source/core/tool/rangelst.cxx | 1532 +++ sc/source/core/tool/rangenam.cxx | 899 ++ sc/source/core/tool/rangeseq.cxx | 446 + sc/source/core/tool/rangeutl.cxx | 1068 ++ sc/source/core/tool/rechead.cxx | 148 + sc/source/core/tool/recursionhelper.cxx | 304 + sc/source/core/tool/refdata.cxx | 599 ++ sc/source/core/tool/reffind.cxx | 334 + sc/source/core/tool/refhint.cxx | 80 + sc/source/core/tool/refreshtimer.cxx | 140 + sc/source/core/tool/reftokenhelper.cxx | 486 + sc/source/core/tool/refupdat.cxx | 592 ++ sc/source/core/tool/scmatrix.cxx | 3420 +++++++ sc/source/core/tool/scopetools.cxx | 122 + sc/source/core/tool/sharedformula.cxx | 442 + sc/source/core/tool/sharedstringpoolpurge.cxx | 58 + sc/source/core/tool/stringutil.cxx | 466 + sc/source/core/tool/stylehelper.cxx | 162 + sc/source/core/tool/subtotal.cxx | 204 + sc/source/core/tool/token.cxx | 5389 ++++++++++ sc/source/core/tool/tokenstringcontext.cxx | 136 + sc/source/core/tool/typedstrdata.cxx | 103 + sc/source/core/tool/unitconv.cxx | 118 + sc/source/core/tool/userlist.cxx | 360 + sc/source/core/tool/viewopti.cxx | 611 ++ sc/source/core/tool/webservicelink.cxx | 98 + sc/source/core/tool/zforauto.cxx | 88 + sc/source/filter/dif/difexp.cxx | 279 + sc/source/filter/dif/difimp.cxx | 674 ++ sc/source/filter/excel/colrowst.cxx | 359 + sc/source/filter/excel/excdoc.cxx | 905 ++ sc/source/filter/excel/excel.cxx | 494 + sc/source/filter/excel/excform.cxx | 1911 ++++ sc/source/filter/excel/excform8.cxx | 1671 +++ sc/source/filter/excel/excimp8.cxx | 817 ++ sc/source/filter/excel/excrecds.cxx | 1177 +++ sc/source/filter/excel/exctools.cxx | 245 + sc/source/filter/excel/expop2.cxx | 150 + sc/source/filter/excel/export/SparklineExt.cxx | 243 + sc/source/filter/excel/fontbuff.cxx | 130 + sc/source/filter/excel/frmbase.cxx | 209 + sc/source/filter/excel/impop.cxx | 1414 +++ sc/source/filter/excel/namebuff.cxx | 187 + sc/source/filter/excel/ooxml-export-TODO.txt | 164 + sc/source/filter/excel/read.cxx | 1314 +++ sc/source/filter/excel/tokstack.cxx | 752 ++ sc/source/filter/excel/xechart.cxx | 3475 +++++++ sc/source/filter/excel/xecontent.cxx | 2211 ++++ sc/source/filter/excel/xedbdata.cxx | 261 + sc/source/filter/excel/xeescher.cxx | 2046 ++++ sc/source/filter/excel/xeextlst.cxx | 652 ++ sc/source/filter/excel/xeformula.cxx | 2709 +++++ sc/source/filter/excel/xehelper.cxx | 1071 ++ sc/source/filter/excel/xelink.cxx | 2660 +++++ sc/source/filter/excel/xename.cxx | 870 ++ sc/source/filter/excel/xepage.cxx | 512 + sc/source/filter/excel/xepivot.cxx | 1702 ++++ sc/source/filter/excel/xepivotxml.cxx | 1187 +++ sc/source/filter/excel/xerecord.cxx | 264 + sc/source/filter/excel/xeroot.cxx | 360 + sc/source/filter/excel/xestream.cxx | 1259 +++ sc/source/filter/excel/xestring.cxx | 565 + sc/source/filter/excel/xestyle.cxx | 3306 ++++++ sc/source/filter/excel/xetable.cxx | 2841 ++++++ sc/source/filter/excel/xeview.cxx | 537 + sc/source/filter/excel/xichart.cxx | 4415 ++++++++ sc/source/filter/excel/xicontent.cxx | 1442 +++ sc/source/filter/excel/xiescher.cxx | 4453 ++++++++ sc/source/filter/excel/xiformula.cxx | 107 + sc/source/filter/excel/xihelper.cxx | 896 ++ sc/source/filter/excel/xilink.cxx | 962 ++ sc/source/filter/excel/xiname.cxx | 322 + sc/source/filter/excel/xipage.cxx | 401 + sc/source/filter/excel/xipivot.cxx | 1737 ++++ sc/source/filter/excel/xiroot.cxx | 298 + sc/source/filter/excel/xistream.cxx | 1084 ++ sc/source/filter/excel/xistring.cxx | 212 + sc/source/filter/excel/xistyle.cxx | 2084 ++++ sc/source/filter/excel/xiview.cxx | 300 + sc/source/filter/excel/xladdress.cxx | 161 + sc/source/filter/excel/xlchart.cxx | 1283 +++ sc/source/filter/excel/xlescher.cxx | 335 + sc/source/filter/excel/xlformula.cxx | 1022 ++ sc/source/filter/excel/xlpage.cxx | 262 + sc/source/filter/excel/xlpivot.cxx | 1043 ++ sc/source/filter/excel/xlroot.cxx | 438 + sc/source/filter/excel/xlstyle.cxx | 1744 ++++ sc/source/filter/excel/xltoolbar.cxx | 439 + sc/source/filter/excel/xltoolbar.hxx | 106 + sc/source/filter/excel/xltools.cxx | 744 ++ sc/source/filter/excel/xltracer.cxx | 133 + sc/source/filter/excel/xlview.cxx | 103 + sc/source/filter/ftools/fapihelper.cxx | 369 + sc/source/filter/ftools/fprogressbar.cxx | 234 + sc/source/filter/ftools/ftools.cxx | 365 + sc/source/filter/ftools/sharedformulagroups.cxx | 40 + sc/source/filter/html/htmlexp.cxx | 1378 +++ sc/source/filter/html/htmlexp2.cxx | 223 + sc/source/filter/html/htmlimp.cxx | 239 + sc/source/filter/html/htmlpars.cxx | 3168 ++++++ sc/source/filter/importfilterdata.cxx | 19 + sc/source/filter/inc/SparklineFragment.hxx | 74 + sc/source/filter/inc/XclExpChangeTrack.hxx | 610 ++ sc/source/filter/inc/XclImpChangeTrack.hxx | 144 + sc/source/filter/inc/addressconverter.hxx | 505 + sc/source/filter/inc/autofilterbuffer.hxx | 286 + sc/source/filter/inc/autofiltercontext.hxx | 114 + sc/source/filter/inc/biffhelper.hxx | 621 ++ sc/source/filter/inc/chartsheetfragment.hxx | 51 + sc/source/filter/inc/colrowst.hxx | 86 + sc/source/filter/inc/commentsbuffer.hxx | 91 + sc/source/filter/inc/commentsfragment.hxx | 53 + sc/source/filter/inc/condformatbuffer.hxx | 322 + sc/source/filter/inc/condformatcontext.hxx | 89 + sc/source/filter/inc/connectionsbuffer.hxx | 160 + sc/source/filter/inc/connectionsfragment.hxx | 60 + sc/source/filter/inc/decl.h | 25 + sc/source/filter/inc/defnamesbuffer.hxx | 182 + sc/source/filter/inc/dif.hxx | 152 + sc/source/filter/inc/drawingbase.hxx | 133 + sc/source/filter/inc/drawingfragment.hxx | 203 + sc/source/filter/inc/eeimport.hxx | 62 + sc/source/filter/inc/eeparser.hxx | 132 + sc/source/filter/inc/excdefs.hxx | 93 + sc/source/filter/inc/excdoc.hxx | 99 + sc/source/filter/inc/excelchartconverter.hxx | 47 + sc/source/filter/inc/excelfilter.hxx | 61 + sc/source/filter/inc/excelhandlers.hxx | 80 + sc/source/filter/inc/excelvbaproject.hxx | 48 + sc/source/filter/inc/excform.hxx | 141 + sc/source/filter/inc/excimp8.hxx | 115 + sc/source/filter/inc/excrecds.hxx | 457 + sc/source/filter/inc/excscen.hxx | 70 + sc/source/filter/inc/exp_op.hxx | 62 + sc/source/filter/inc/expbase.hxx | 63 + sc/source/filter/inc/export/SparklineExt.hxx | 55 + sc/source/filter/inc/externallinkbuffer.hxx | 350 + sc/source/filter/inc/externallinkfragment.hxx | 99 + sc/source/filter/inc/extlstcontext.hxx | 146 + sc/source/filter/inc/fapihelper.hxx | 294 + sc/source/filter/inc/flttypes.hxx | 39 + sc/source/filter/inc/formel.hxx | 188 + sc/source/filter/inc/formulabase.hxx | 780 ++ sc/source/filter/inc/formulabuffer.hxx | 119 + sc/source/filter/inc/formulaparser.hxx | 131 + sc/source/filter/inc/fprogressbar.hxx | 223 + sc/source/filter/inc/ftools.hxx | 295 + sc/source/filter/inc/htmlexp.hxx | 185 + sc/source/filter/inc/htmlimp.hxx | 38 + sc/source/filter/inc/htmlpars.hxx | 630 ++ sc/source/filter/inc/imp_op.hxx | 200 + sc/source/filter/inc/lotattr.hxx | 134 + sc/source/filter/inc/lotfntbf.hxx | 59 + sc/source/filter/inc/lotform.hxx | 114 + sc/source/filter/inc/lotimpop.hxx | 136 + sc/source/filter/inc/lotrange.hxx | 114 + sc/source/filter/inc/namebuff.hxx | 190 + sc/source/filter/inc/numberformatsbuffer.hxx | 116 + sc/source/filter/inc/ooxformulaparser.hxx | 89 + sc/source/filter/inc/op.h | 58 + sc/source/filter/inc/optab.h | 48 + sc/source/filter/inc/orcusfiltersimpl.hxx | 47 + sc/source/filter/inc/orcusinterface.hxx | 656 ++ sc/source/filter/inc/otlnbuff.hxx | 50 + sc/source/filter/inc/pagesettings.hxx | 186 + sc/source/filter/inc/pivotcachebuffer.hxx | 458 + sc/source/filter/inc/pivotcachefragment.hxx | 91 + sc/source/filter/inc/pivottablebuffer.hxx | 404 + sc/source/filter/inc/pivottablefragment.hxx | 83 + sc/source/filter/inc/qpro.hxx | 57 + sc/source/filter/inc/qproform.hxx | 71 + sc/source/filter/inc/qprostyle.hxx | 56 + sc/source/filter/inc/querytablebuffer.hxx | 83 + sc/source/filter/inc/querytablefragment.hxx | 47 + sc/source/filter/inc/revisionfragment.hxx | 76 + sc/source/filter/inc/richstring.hxx | 264 + sc/source/filter/inc/richstringcontext.hxx | 55 + sc/source/filter/inc/root.hxx | 74 + sc/source/filter/inc/rtfexp.hxx | 42 + sc/source/filter/inc/rtfimp.hxx | 30 + sc/source/filter/inc/rtfparse.hxx | 78 + sc/source/filter/inc/scenariobuffer.hxx | 126 + sc/source/filter/inc/scenariocontext.hxx | 63 + sc/source/filter/inc/scfobj.hxx | 34 + sc/source/filter/inc/scmem.h | 29 + sc/source/filter/inc/sharedformulagroups.hxx | 51 + sc/source/filter/inc/sharedstringsbuffer.hxx | 48 + sc/source/filter/inc/sharedstringsfragment.hxx | 43 + sc/source/filter/inc/sheetdatabuffer.hxx | 245 + sc/source/filter/inc/sheetdatacontext.hxx | 124 + sc/source/filter/inc/stylesbuffer.hxx | 891 ++ sc/source/filter/inc/stylesfragment.hxx | 129 + sc/source/filter/inc/tablebuffer.hxx | 124 + sc/source/filter/inc/tablecolumnsbuffer.hxx | 96 + sc/source/filter/inc/tablecolumnscontext.hxx | 63 + sc/source/filter/inc/tablefragment.hxx | 47 + sc/source/filter/inc/themebuffer.hxx | 50 + sc/source/filter/inc/tokstack.hxx | 426 + sc/source/filter/inc/tool.h | 133 + sc/source/filter/inc/unitconverter.hxx | 96 + sc/source/filter/inc/viewsettings.hxx | 189 + sc/source/filter/inc/workbookfragment.hxx | 64 + sc/source/filter/inc/workbookhelper.hxx | 275 + sc/source/filter/inc/workbooksettings.hxx | 119 + sc/source/filter/inc/worksheetbuffer.hxx | 108 + sc/source/filter/inc/worksheetfragment.hxx | 182 + sc/source/filter/inc/worksheethelper.hxx | 305 + sc/source/filter/inc/worksheetsettings.hxx | 115 + sc/source/filter/inc/xcl97esc.hxx | 175 + sc/source/filter/inc/xcl97rec.hxx | 600 ++ sc/source/filter/inc/xechart.hxx | 1191 +++ sc/source/filter/inc/xecontent.hxx | 416 + sc/source/filter/inc/xedbdata.hxx | 66 + sc/source/filter/inc/xeescher.hxx | 464 + sc/source/filter/inc/xeextlst.hxx | 208 + sc/source/filter/inc/xeformula.hxx | 91 + sc/source/filter/inc/xehelper.hxx | 438 + sc/source/filter/inc/xelink.hxx | 222 + sc/source/filter/inc/xename.hxx | 73 + sc/source/filter/inc/xepage.hxx | 124 + sc/source/filter/inc/xepivot.hxx | 436 + sc/source/filter/inc/xepivotxml.hxx | 90 + sc/source/filter/inc/xerecord.hxx | 421 + sc/source/filter/inc/xeroot.hxx | 191 + sc/source/filter/inc/xestream.hxx | 359 + sc/source/filter/inc/xestring.hxx | 264 + sc/source/filter/inc/xestyle.hxx | 773 ++ sc/source/filter/inc/xetable.hxx | 1028 ++ sc/source/filter/inc/xeview.hxx | 164 + sc/source/filter/inc/xichart.hxx | 1433 +++ sc/source/filter/inc/xicontent.hxx | 330 + sc/source/filter/inc/xiescher.hxx | 1212 +++ sc/source/filter/inc/xiformula.hxx | 54 + sc/source/filter/inc/xihelper.hxx | 350 + sc/source/filter/inc/xilink.hxx | 227 + sc/source/filter/inc/xiname.hxx | 107 + sc/source/filter/inc/xipage.hxx | 70 + sc/source/filter/inc/xipivot.hxx | 427 + sc/source/filter/inc/xiroot.hxx | 219 + sc/source/filter/inc/xistream.hxx | 552 + sc/source/filter/inc/xistring.hxx | 104 + sc/source/filter/inc/xistyle.hxx | 669 ++ sc/source/filter/inc/xiview.hxx | 83 + sc/source/filter/inc/xladdress.hxx | 169 + sc/source/filter/inc/xlchart.hxx | 1437 +++ sc/source/filter/inc/xlconst.hxx | 259 + sc/source/filter/inc/xlcontent.hxx | 187 + sc/source/filter/inc/xlescher.hxx | 434 + sc/source/filter/inc/xlformula.hxx | 551 + sc/source/filter/inc/xllink.hxx | 99 + sc/source/filter/inc/xlname.hxx | 63 + sc/source/filter/inc/xlpage.hxx | 166 + sc/source/filter/inc/xlpivot.hxx | 774 ++ sc/source/filter/inc/xlroot.hxx | 269 + sc/source/filter/inc/xlstream.hxx | 38 + sc/source/filter/inc/xlstring.hxx | 87 + sc/source/filter/inc/xlstyle.hxx | 596 ++ sc/source/filter/inc/xltable.hxx | 167 + sc/source/filter/inc/xltools.hxx | 256 + sc/source/filter/inc/xltracer.hxx | 86 + sc/source/filter/inc/xlview.hxx | 162 + sc/source/filter/lotus/filter.cxx | 211 + sc/source/filter/lotus/lotattr.cxx | 250 + sc/source/filter/lotus/lotfilter.hxx | 63 + sc/source/filter/lotus/lotform.cxx | 2106 ++++ sc/source/filter/lotus/lotimpop.cxx | 489 + sc/source/filter/lotus/lotread.cxx | 336 + sc/source/filter/lotus/lotus.cxx | 100 + sc/source/filter/lotus/memory.cxx | 55 + sc/source/filter/lotus/op.cxx | 684 ++ sc/source/filter/lotus/optab.cxx | 235 + sc/source/filter/lotus/tool.cxx | 524 + sc/source/filter/oox/SparklineFragment.cxx | 273 + sc/source/filter/oox/addressconverter.cxx | 483 + sc/source/filter/oox/autofilterbuffer.cxx | 955 ++ sc/source/filter/oox/autofiltercontext.cxx | 219 + sc/source/filter/oox/biffhelper.cxx | 99 + sc/source/filter/oox/chartsheetfragment.cxx | 174 + sc/source/filter/oox/commentsbuffer.cxx | 243 + sc/source/filter/oox/commentsfragment.cxx | 144 + sc/source/filter/oox/condformatbuffer.cxx | 1399 +++ sc/source/filter/oox/condformatcontext.cxx | 264 + sc/source/filter/oox/connectionsbuffer.cxx | 315 + sc/source/filter/oox/connectionsfragment.cxx | 161 + sc/source/filter/oox/defnamesbuffer.cxx | 442 + sc/source/filter/oox/drawingbase.cxx | 295 + sc/source/filter/oox/drawingfragment.cxx | 807 ++ sc/source/filter/oox/excelchartconverter.cxx | 125 + sc/source/filter/oox/excelfilter.cxx | 215 + sc/source/filter/oox/excelhandlers.cxx | 42 + sc/source/filter/oox/excelvbaproject.cxx | 127 + sc/source/filter/oox/externallinkbuffer.cxx | 694 ++ sc/source/filter/oox/externallinkfragment.cxx | 338 + sc/source/filter/oox/extlstcontext.cxx | 432 + sc/source/filter/oox/formulabase.cxx | 1714 ++++ sc/source/filter/oox/formulabuffer.cxx | 476 + sc/source/filter/oox/formulaparser.cxx | 1855 ++++ sc/source/filter/oox/numberformatsbuffer.cxx | 2094 ++++ sc/source/filter/oox/ooxformulaparser.cxx | 174 + sc/source/filter/oox/pagesettings.cxx | 1065 ++ sc/source/filter/oox/pivotcachebuffer.cxx | 1225 +++ sc/source/filter/oox/pivotcachefragment.cxx | 325 + sc/source/filter/oox/pivottablebuffer.cxx | 1480 +++ sc/source/filter/oox/pivottablefragment.cxx | 280 + sc/source/filter/oox/querytablebuffer.cxx | 286 + sc/source/filter/oox/querytablefragment.cxx | 72 + sc/source/filter/oox/revisionfragment.cxx | 455 + sc/source/filter/oox/richstring.cxx | 597 ++ sc/source/filter/oox/richstringcontext.cxx | 91 + sc/source/filter/oox/scenariobuffer.cxx | 215 + sc/source/filter/oox/scenariocontext.cxx | 112 + sc/source/filter/oox/sharedstringsbuffer.cxx | 48 + sc/source/filter/oox/sharedstringsfragment.cxx | 88 + sc/source/filter/oox/sheetdatabuffer.cxx | 823 ++ sc/source/filter/oox/sheetdatacontext.cxx | 583 ++ sc/source/filter/oox/stylesbuffer.cxx | 3064 ++++++ sc/source/filter/oox/stylesfragment.cxx | 317 + sc/source/filter/oox/tablebuffer.cxx | 214 + sc/source/filter/oox/tablecolumnsbuffer.cxx | 128 + sc/source/filter/oox/tablecolumnscontext.cxx | 92 + sc/source/filter/oox/tablefragment.cxx | 97 + sc/source/filter/oox/themebuffer.cxx | 54 + sc/source/filter/oox/unitconverter.cxx | 228 + sc/source/filter/oox/viewsettings.cxx | 651 ++ sc/source/filter/oox/workbookfragment.cxx | 641 ++ sc/source/filter/oox/workbookhelper.cxx | 1036 ++ sc/source/filter/oox/workbooksettings.cxx | 298 + sc/source/filter/oox/worksheetbuffer.cxx | 251 + sc/source/filter/oox/worksheetfragment.cxx | 911 ++ sc/source/filter/oox/worksheethelper.cxx | 1655 +++ sc/source/filter/oox/worksheetsettings.cxx | 294 + sc/source/filter/orcus/filterdetect.cxx | 107 + sc/source/filter/orcus/interface.cxx | 2198 ++++ sc/source/filter/orcus/orcusfiltersimpl.cxx | 150 + sc/source/filter/orcus/xmlcontext.cxx | 292 + sc/source/filter/qpro/README | 4 + sc/source/filter/qpro/qpro.cxx | 320 + sc/source/filter/qpro/qproform.cxx | 752 ++ sc/source/filter/qpro/qprostyle.cxx | 139 + sc/source/filter/rtf/eeimpars.cxx | 657 ++ sc/source/filter/rtf/expbase.cxx | 75 + sc/source/filter/rtf/rtfexp.cxx | 233 + sc/source/filter/rtf/rtfimp.cxx | 47 + sc/source/filter/rtf/rtfparse.cxx | 403 + sc/source/filter/xcl97/XclExpChangeTrack.cxx | 1647 +++ sc/source/filter/xcl97/XclImpChangeTrack.cxx | 517 + sc/source/filter/xcl97/xcl97esc.cxx | 574 ++ sc/source/filter/xcl97/xcl97rec.cxx | 2026 ++++ sc/source/filter/xml/SparklineGroupsExport.cxx | 218 + sc/source/filter/xml/SparklineGroupsExport.hxx | 46 + .../filter/xml/SparklineGroupsImportContext.cxx | 333 + .../filter/xml/SparklineGroupsImportContext.hxx | 63 + .../filter/xml/XMLCalculationSettingsContext.cxx | 198 + .../filter/xml/XMLCalculationSettingsContext.hxx | 76 + sc/source/filter/xml/XMLCellRangeSourceContext.cxx | 96 + sc/source/filter/xml/XMLCellRangeSourceContext.hxx | 51 + .../filter/xml/XMLChangeTrackingExportHelper.cxx | 687 ++ .../filter/xml/XMLChangeTrackingExportHelper.hxx | 84 + .../filter/xml/XMLChangeTrackingImportHelper.cxx | 808 ++ .../filter/xml/XMLChangeTrackingImportHelper.hxx | 220 + sc/source/filter/xml/XMLCodeNameProvider.cxx | 178 + sc/source/filter/xml/XMLCodeNameProvider.hxx | 53 + sc/source/filter/xml/XMLColumnRowGroupExport.cxx | 148 + sc/source/filter/xml/XMLColumnRowGroupExport.hxx | 62 + sc/source/filter/xml/XMLConsolidationContext.cxx | 123 + sc/source/filter/xml/XMLConsolidationContext.hxx | 49 + sc/source/filter/xml/XMLConverter.cxx | 630 ++ sc/source/filter/xml/XMLConverter.hxx | 148 + sc/source/filter/xml/XMLDDELinksContext.cxx | 362 + sc/source/filter/xml/XMLDDELinksContext.hxx | 158 + sc/source/filter/xml/XMLDetectiveContext.cxx | 190 + sc/source/filter/xml/XMLDetectiveContext.hxx | 127 + sc/source/filter/xml/XMLEmptyContext.cxx | 41 + sc/source/filter/xml/XMLEmptyContext.hxx | 35 + sc/source/filter/xml/XMLExportDDELinks.cxx | 159 + sc/source/filter/xml/XMLExportDDELinks.hxx | 41 + sc/source/filter/xml/XMLExportDataPilot.cxx | 901 ++ sc/source/filter/xml/XMLExportDataPilot.hxx | 74 + sc/source/filter/xml/XMLExportDatabaseRanges.cxx | 752 ++ sc/source/filter/xml/XMLExportDatabaseRanges.hxx | 43 + sc/source/filter/xml/XMLExportIterator.cxx | 731 ++ sc/source/filter/xml/XMLExportIterator.hxx | 374 + sc/source/filter/xml/XMLExportSharedData.cxx | 142 + sc/source/filter/xml/XMLExportSharedData.hxx | 84 + sc/source/filter/xml/XMLStylesExportHelper.cxx | 1066 ++ sc/source/filter/xml/XMLStylesExportHelper.hxx | 254 + sc/source/filter/xml/XMLStylesImportHelper.cxx | 401 + sc/source/filter/xml/XMLStylesImportHelper.hxx | 147 + .../filter/xml/XMLTableHeaderFooterContext.cxx | 237 + .../filter/xml/XMLTableHeaderFooterContext.hxx | 78 + sc/source/filter/xml/XMLTableMasterPageExport.cxx | 205 + sc/source/filter/xml/XMLTableMasterPageExport.hxx | 53 + sc/source/filter/xml/XMLTableShapeImportHelper.cxx | 231 + sc/source/filter/xml/XMLTableShapeImportHelper.hxx | 52 + sc/source/filter/xml/XMLTableShapeResizer.cxx | 145 + sc/source/filter/xml/XMLTableShapeResizer.hxx | 56 + sc/source/filter/xml/XMLTableShapesContext.cxx | 53 + sc/source/filter/xml/XMLTableShapesContext.hxx | 38 + sc/source/filter/xml/XMLTableSourceContext.cxx | 106 + sc/source/filter/xml/XMLTableSourceContext.hxx | 45 + sc/source/filter/xml/XMLTrackedChangesContext.cxx | 1398 +++ sc/source/filter/xml/XMLTrackedChangesContext.hxx | 46 + sc/source/filter/xml/cachedattraccess.cxx | 55 + sc/source/filter/xml/cachedattraccess.hxx | 44 + sc/source/filter/xml/celltextparacontext.cxx | 442 + sc/source/filter/xml/celltextparacontext.hxx | 191 + sc/source/filter/xml/datastreamimport.cxx | 84 + sc/source/filter/xml/datastreamimport.hxx | 36 + sc/source/filter/xml/editattributemap.cxx | 91 + sc/source/filter/xml/editattributemap.hxx | 44 + sc/source/filter/xml/importcontext.cxx | 28 + sc/source/filter/xml/importcontext.hxx | 29 + sc/source/filter/xml/pivotsource.cxx | 120 + sc/source/filter/xml/pivotsource.hxx | 78 + sc/source/filter/xml/sheetdata.cxx | 245 + sc/source/filter/xml/xmlannoi.cxx | 171 + sc/source/filter/xml/xmlannoi.hxx | 103 + sc/source/filter/xml/xmlbodyi.cxx | 272 + sc/source/filter/xml/xmlbodyi.hxx | 56 + sc/source/filter/xml/xmlcelli.cxx | 1521 +++ sc/source/filter/xml/xmlcelli.hxx | 153 + sc/source/filter/xml/xmlcoli.cxx | 250 + sc/source/filter/xml/xmlcoli.hxx | 66 + sc/source/filter/xml/xmlcondformat.cxx | 1009 ++ sc/source/filter/xml/xmlcondformat.hxx | 154 + sc/source/filter/xml/xmlconti.cxx | 65 + sc/source/filter/xml/xmlconti.hxx | 41 + sc/source/filter/xml/xmlcvali.cxx | 557 + sc/source/filter/xml/xmlcvali.hxx | 35 + sc/source/filter/xml/xmldpimp.cxx | 1536 +++ sc/source/filter/xml/xmldpimp.hxx | 470 + sc/source/filter/xml/xmldrani.cxx | 812 ++ sc/source/filter/xml/xmldrani.hxx | 256 + sc/source/filter/xml/xmlexprt.cxx | 5454 ++++++++++ sc/source/filter/xml/xmlexprt.hxx | 280 + sc/source/filter/xml/xmlexternaltabi.cxx | 378 + sc/source/filter/xml/xmlexternaltabi.hxx | 132 + sc/source/filter/xml/xmlfilti.cxx | 784 ++ sc/source/filter/xml/xmlfilti.hxx | 265 + sc/source/filter/xml/xmlfonte.cxx | 156 + sc/source/filter/xml/xmlimprt.cxx | 1766 ++++ sc/source/filter/xml/xmlimprt.hxx | 353 + sc/source/filter/xml/xmllabri.cxx | 98 + sc/source/filter/xml/xmllabri.hxx | 57 + sc/source/filter/xml/xmlmappingi.cxx | 140 + sc/source/filter/xml/xmlmappingi.hxx | 45 + sc/source/filter/xml/xmlnexpi.cxx | 165 + sc/source/filter/xml/xmlnexpi.hxx | 106 + sc/source/filter/xml/xmlrowi.cxx | 360 + sc/source/filter/xml/xmlrowi.hxx | 72 + sc/source/filter/xml/xmlsceni.cxx | 125 + sc/source/filter/xml/xmlsceni.hxx | 52 + sc/source/filter/xml/xmlsorti.cxx | 244 + sc/source/filter/xml/xmlsorti.hxx | 81 + sc/source/filter/xml/xmlstyle.cxx | 1902 ++++ sc/source/filter/xml/xmlstyle.hxx | 340 + sc/source/filter/xml/xmlstyli.cxx | 1037 ++ sc/source/filter/xml/xmlstyli.hxx | 225 + sc/source/filter/xml/xmlsubti.cxx | 291 + sc/source/filter/xml/xmlsubti.hxx | 108 + sc/source/filter/xml/xmltabi.cxx | 470 + sc/source/filter/xml/xmltabi.hxx | 70 + sc/source/filter/xml/xmltransformationi.cxx | 637 ++ sc/source/filter/xml/xmltransformationi.hxx | 169 + sc/source/filter/xml/xmlwrap.cxx | 998 ++ sc/source/ui/Accessibility/AccessibilityHints.cxx | 62 + sc/source/ui/Accessibility/AccessibleCell.cxx | 614 ++ sc/source/ui/Accessibility/AccessibleCellBase.cxx | 590 ++ .../ui/Accessibility/AccessibleContextBase.cxx | 507 + .../ui/Accessibility/AccessibleCsvControl.cxx | 1460 +++ sc/source/ui/Accessibility/AccessibleDocument.cxx | 2224 ++++ .../ui/Accessibility/AccessibleDocumentBase.cxx | 36 + .../AccessibleDocumentPagePreview.cxx | 1567 +++ .../ui/Accessibility/AccessibleEditObject.cxx | 604 ++ .../ui/Accessibility/AccessiblePageHeader.cxx | 371 + .../ui/Accessibility/AccessiblePageHeaderArea.cxx | 279 + .../ui/Accessibility/AccessiblePreviewCell.cxx | 277 + .../Accessibility/AccessiblePreviewHeaderCell.cxx | 404 + .../ui/Accessibility/AccessiblePreviewTable.cxx | 641 ++ .../ui/Accessibility/AccessibleSpreadsheet.cxx | 1667 +++ sc/source/ui/Accessibility/AccessibleTableBase.cxx | 470 + sc/source/ui/Accessibility/AccessibleText.cxx | 1385 +++ .../ui/Accessibility/DrawModelBroadcaster.cxx | 107 + .../StatisticsDialogs/AnalysisOfVarianceDialog.cxx | 559 + .../ui/StatisticsDialogs/ChiSquareTestDialog.cxx | 91 + .../ui/StatisticsDialogs/CorrelationDialog.cxx | 39 + .../ui/StatisticsDialogs/CovarianceDialog.cxx | 44 + .../DescriptiveStatisticsDialog.cxx | 141 + .../ExponentialSmoothingDialog.cxx | 120 + sc/source/ui/StatisticsDialogs/FTestDialog.cxx | 171 + .../ui/StatisticsDialogs/FourierAnalysisDialog.cxx | 231 + .../MatrixComparisonGenerator.cxx | 113 + .../ui/StatisticsDialogs/MovingAverageDialog.cxx | 116 + .../RandomNumberGeneratorDialog.cxx | 482 + .../ui/StatisticsDialogs/RegressionDialog.cxx | 696 ++ sc/source/ui/StatisticsDialogs/SamplingDialog.cxx | 563 + .../StatisticsInputOutputDialog.cxx | 305 + .../StatisticsTwoVariableDialog.cxx | 347 + sc/source/ui/StatisticsDialogs/TTestDialog.cxx | 183 + .../TableFillingAndNavigationTools.cxx | 386 + sc/source/ui/StatisticsDialogs/ZTestDialog.cxx | 167 + sc/source/ui/app/client.cxx | 232 + sc/source/ui/app/drwtrans.cxx | 738 ++ sc/source/ui/app/inputhdl.cxx | 4635 +++++++++ sc/source/ui/app/inputwin.cxx | 2703 +++++ sc/source/ui/app/lnktrans.cxx | 76 + sc/source/ui/app/msgpool.cxx | 93 + sc/source/ui/app/rfindlst.cxx | 90 + sc/source/ui/app/scdll.cxx | 257 + sc/source/ui/app/scmod.cxx | 2325 +++++ sc/source/ui/app/seltrans.cxx | 429 + sc/source/ui/app/transobj.cxx | 921 ++ sc/source/ui/app/typemap.cxx | 140 + sc/source/ui/app/uiitems.cxx | 438 + sc/source/ui/attrdlg/attrdlg.cxx | 88 + sc/source/ui/attrdlg/scabstdlg.cxx | 55 + sc/source/ui/attrdlg/scdlgfact.cxx | 1370 +++ sc/source/ui/attrdlg/scdlgfact.hxx | 806 ++ sc/source/ui/attrdlg/scuiexp.cxx | 33 + sc/source/ui/attrdlg/tabpages.cxx | 220 + sc/source/ui/cctrl/cbnumberformat.cxx | 88 + sc/source/ui/cctrl/cbuttonw.cxx | 139 + sc/source/ui/cctrl/checklistmenu.cxx | 1737 ++++ sc/source/ui/cctrl/dpcontrol.cxx | 220 + sc/source/ui/cctrl/editfield.cxx | 63 + sc/source/ui/cctrl/tbzoomsliderctrl.cxx | 457 + sc/source/ui/condformat/colorformat.cxx | 303 + sc/source/ui/condformat/condformatdlg.cxx | 709 ++ sc/source/ui/condformat/condformatdlgentry.cxx | 1530 +++ sc/source/ui/condformat/condformatdlgitem.cxx | 66 + sc/source/ui/condformat/condformathelper.cxx | 229 + sc/source/ui/condformat/condformatmgr.cxx | 180 + sc/source/ui/dataprovider/csvdataprovider.cxx | 176 + sc/source/ui/dataprovider/dataprovider.cxx | 313 + sc/source/ui/dataprovider/datatransformation.cxx | 1275 +++ sc/source/ui/dataprovider/htmldataprovider.cxx | 280 + sc/source/ui/dataprovider/htmldataprovider.hxx | 38 + sc/source/ui/dataprovider/sqldataprovider.cxx | 169 + sc/source/ui/dataprovider/sqldataprovider.hxx | 38 + sc/source/ui/dataprovider/xmldataprovider.cxx | 126 + sc/source/ui/dataprovider/xmldataprovider.hxx | 37 + sc/source/ui/dbgui/PivotLayoutDialog.cxx | 719 ++ sc/source/ui/dbgui/PivotLayoutTreeList.cxx | 132 + sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx | 122 + sc/source/ui/dbgui/PivotLayoutTreeListData.cxx | 287 + sc/source/ui/dbgui/PivotLayoutTreeListLabel.cxx | 85 + sc/source/ui/dbgui/asciiopt.cxx | 296 + sc/source/ui/dbgui/consdlg.cxx | 535 + sc/source/ui/dbgui/csvcontrol.cxx | 284 + sc/source/ui/dbgui/csvgrid.cxx | 1418 +++ sc/source/ui/dbgui/csvruler.cxx | 666 ++ sc/source/ui/dbgui/csvsplits.cxx | 102 + sc/source/ui/dbgui/csvtablebox.cxx | 369 + sc/source/ui/dbgui/dapidata.cxx | 169 + sc/source/ui/dbgui/dapitype.cxx | 153 + sc/source/ui/dbgui/dbnamdlg.cxx | 645 ++ sc/source/ui/dbgui/dpgroupdlg.cxx | 353 + sc/source/ui/dbgui/filtdlg.cxx | 1571 +++ sc/source/ui/dbgui/foptmgr.cxx | 260 + sc/source/ui/dbgui/imoptdlg.cxx | 135 + sc/source/ui/dbgui/pfiltdlg.cxx | 507 + sc/source/ui/dbgui/pvfundlg.cxx | 973 ++ sc/source/ui/dbgui/scendlg.cxx | 163 + sc/source/ui/dbgui/scuiasciiopt.cxx | 943 ++ sc/source/ui/dbgui/scuiimoptdlg.cxx | 340 + sc/source/ui/dbgui/sfiltdlg.cxx | 435 + sc/source/ui/dbgui/sortdlg.cxx | 65 + sc/source/ui/dbgui/sortkeydlg.cxx | 77 + sc/source/ui/dbgui/subtdlg.cxx | 46 + sc/source/ui/dbgui/textimportoptions.cxx | 96 + sc/source/ui/dbgui/tpsort.cxx | 855 ++ sc/source/ui/dbgui/tpsubt.cxx | 621 ++ sc/source/ui/dbgui/validate.cxx | 927 ++ sc/source/ui/dialogs/SparklineDataRangeDialog.cxx | 202 + sc/source/ui/dialogs/SparklineDialog.cxx | 552 + sc/source/ui/dialogs/searchresults.cxx | 278 + sc/source/ui/docshell/arealink.cxx | 498 + sc/source/ui/docshell/autostyl.cxx | 191 + sc/source/ui/docshell/datastream.cxx | 549 + sc/source/ui/docshell/dbdocfun.cxx | 1790 ++++ sc/source/ui/docshell/dbdocimp.cxx | 628 ++ sc/source/ui/docshell/docfunc.cxx | 5922 +++++++++++ sc/source/ui/docshell/docfuncutil.cxx | 116 + sc/source/ui/docshell/docsh.cxx | 3482 +++++++ sc/source/ui/docshell/docsh2.cxx | 187 + sc/source/ui/docshell/docsh3.cxx | 1331 +++ sc/source/ui/docshell/docsh4.cxx | 2788 +++++ sc/source/ui/docshell/docsh5.cxx | 1037 ++ sc/source/ui/docshell/docsh6.cxx | 517 + sc/source/ui/docshell/docsh8.cxx | 1082 ++ sc/source/ui/docshell/docshimp.hxx | 38 + sc/source/ui/docshell/documentlinkmgr.cxx | 274 + sc/source/ui/docshell/editable.cxx | 162 + sc/source/ui/docshell/externalrefmgr.cxx | 3319 ++++++ sc/source/ui/docshell/impex.cxx | 2887 ++++++ sc/source/ui/docshell/macromgr.cxx | 201 + sc/source/ui/docshell/olinefun.cxx | 792 ++ sc/source/ui/docshell/pagedata.cxx | 100 + sc/source/ui/docshell/pntlock.cxx | 43 + sc/source/ui/docshell/servobj.cxx | 258 + sc/source/ui/docshell/sizedev.cxx | 63 + sc/source/ui/docshell/tablink.cxx | 594 ++ sc/source/ui/docshell/tpstat.cxx | 70 + sc/source/ui/drawfunc/chartsh.cxx | 155 + sc/source/ui/drawfunc/drawsh.cxx | 626 ++ sc/source/ui/drawfunc/drawsh2.cxx | 564 + sc/source/ui/drawfunc/drawsh4.cxx | 65 + sc/source/ui/drawfunc/drawsh5.cxx | 721 ++ sc/source/ui/drawfunc/drformsh.cxx | 55 + sc/source/ui/drawfunc/drtxtob.cxx | 1238 +++ sc/source/ui/drawfunc/drtxtob1.cxx | 124 + sc/source/ui/drawfunc/drtxtob2.cxx | 229 + sc/source/ui/drawfunc/fuconarc.cxx | 154 + sc/source/ui/drawfunc/fuconcustomshape.cxx | 199 + sc/source/ui/drawfunc/fuconpol.cxx | 288 + sc/source/ui/drawfunc/fuconrec.cxx | 449 + sc/source/ui/drawfunc/fuconstr.cxx | 252 + sc/source/ui/drawfunc/fuconuno.cxx | 122 + sc/source/ui/drawfunc/fudraw.cxx | 766 ++ sc/source/ui/drawfunc/fuins1.cxx | 450 + sc/source/ui/drawfunc/fuins2.cxx | 689 ++ sc/source/ui/drawfunc/fupoor.cxx | 280 + sc/source/ui/drawfunc/fusel.cxx | 546 + sc/source/ui/drawfunc/fusel2.cxx | 159 + sc/source/ui/drawfunc/futext.cxx | 687 ++ sc/source/ui/drawfunc/futext2.cxx | 46 + sc/source/ui/drawfunc/futext3.cxx | 182 + sc/source/ui/drawfunc/graphsh.cxx | 380 + sc/source/ui/drawfunc/mediash.cxx | 65 + sc/source/ui/drawfunc/oleobjsh.cxx | 51 + sc/source/ui/formdlg/dwfunctr.cxx | 410 + sc/source/ui/formdlg/formdata.cxx | 33 + sc/source/ui/formdlg/formula.cxx | 691 ++ sc/source/ui/inc/AccessibilityHints.hxx | 57 + sc/source/ui/inc/AccessibleCell.hxx | 167 + sc/source/ui/inc/AccessibleCellBase.hxx | 137 + sc/source/ui/inc/AccessibleContextBase.hxx | 286 + sc/source/ui/inc/AccessibleCsvControl.hxx | 511 + sc/source/ui/inc/AccessibleDocument.hxx | 263 + sc/source/ui/inc/AccessibleDocumentBase.hxx | 34 + sc/source/ui/inc/AccessibleDocumentPagePreview.hxx | 132 + sc/source/ui/inc/AccessibleEditObject.hxx | 231 + sc/source/ui/inc/AccessiblePageHeader.hxx | 90 + sc/source/ui/inc/AccessiblePageHeaderArea.hxx | 113 + sc/source/ui/inc/AccessiblePreviewCell.hxx | 99 + sc/source/ui/inc/AccessiblePreviewHeaderCell.hxx | 130 + sc/source/ui/inc/AccessiblePreviewTable.hxx | 134 + sc/source/ui/inc/AccessibleSpreadsheet.hxx | 273 + sc/source/ui/inc/AccessibleTableBase.hxx | 234 + sc/source/ui/inc/AccessibleText.hxx | 290 + sc/source/ui/inc/AnalysisOfVarianceDialog.hxx | 61 + sc/source/ui/inc/ChartRangeSelectionListener.hxx | 64 + sc/source/ui/inc/ChiSquareTestDialog.hxx | 31 + sc/source/ui/inc/ChildWindowWrapper.hxx | 81 + sc/source/ui/inc/CorrelationDialog.hxx | 29 + sc/source/ui/inc/CovarianceDialog.hxx | 30 + sc/source/ui/inc/DescriptiveStatisticsDialog.hxx | 31 + sc/source/ui/inc/DrawModelBroadcaster.hxx | 55 + sc/source/ui/inc/ExponentialSmoothingDialog.hxx | 37 + sc/source/ui/inc/FTestDialog.hxx | 31 + sc/source/ui/inc/FilterListBox.hxx | 81 + sc/source/ui/inc/FourierAnalysisDialog.hxx | 56 + sc/source/ui/inc/IAnyRefDialog.hxx | 48 + sc/source/ui/inc/MatrixComparisonGenerator.hxx | 36 + sc/source/ui/inc/MovingAverageDialog.hxx | 38 + sc/source/ui/inc/PivotLayoutDialog.hxx | 140 + sc/source/ui/inc/PivotLayoutTreeList.hxx | 41 + sc/source/ui/inc/PivotLayoutTreeListBase.hxx | 70 + sc/source/ui/inc/PivotLayoutTreeListData.hxx | 42 + sc/source/ui/inc/PivotLayoutTreeListLabel.hxx | 34 + sc/source/ui/inc/RandomNumberGeneratorDialog.hxx | 85 + sc/source/ui/inc/RegressionDialog.hxx | 82 + sc/source/ui/inc/SamplingDialog.hxx | 91 + sc/source/ui/inc/SparklineDataRangeDialog.hxx | 67 + sc/source/ui/inc/SparklineDialog.hxx | 113 + sc/source/ui/inc/SparklineRenderer.hxx | 576 ++ sc/source/ui/inc/SparklineShell.hxx | 41 + sc/source/ui/inc/StatisticsInputOutputDialog.hxx | 90 + sc/source/ui/inc/StatisticsTwoVariableDialog.hxx | 91 + sc/source/ui/inc/TTestDialog.hxx | 31 + .../ui/inc/TableFillingAndNavigationTools.hxx | 159 + sc/source/ui/inc/ZTestDialog.hxx | 31 + sc/source/ui/inc/acredlin.hxx | 163 + sc/source/ui/inc/anyrefdg.hxx | 165 + sc/source/ui/inc/areasave.hxx | 70 + sc/source/ui/inc/areasdlg.hxx | 88 + sc/source/ui/inc/asciiopt.hxx | 108 + sc/source/ui/inc/attrdlg.hxx | 40 + sc/source/ui/inc/auditsh.hxx | 49 + sc/source/ui/inc/autofmt.hxx | 91 + sc/source/ui/inc/autostyl.hxx | 81 + sc/source/ui/inc/cbnumberformat.hxx | 42 + sc/source/ui/inc/cbutton.hxx | 56 + sc/source/ui/inc/cellmergeoption.hxx | 34 + sc/source/ui/inc/cellsh.hxx | 109 + sc/source/ui/inc/chartsh.hxx | 48 + sc/source/ui/inc/checklistmenu.hxx | 381 + sc/source/ui/inc/client.hxx | 44 + sc/source/ui/inc/cliputil.hxx | 31 + sc/source/ui/inc/colorformat.hxx | 64 + sc/source/ui/inc/colrowba.hxx | 90 + sc/source/ui/inc/condformatdlg.hxx | 129 + sc/source/ui/inc/condformatdlgentry.hxx | 331 + sc/source/ui/inc/condformatdlgitem.hxx | 62 + sc/source/ui/inc/condformathelper.hxx | 36 + sc/source/ui/inc/condformatmgr.hxx | 66 + sc/source/ui/inc/condformatuno.hxx | 366 + sc/source/ui/inc/conflictsdlg.hxx | 148 + sc/source/ui/inc/consdlg.hxx | 99 + sc/source/ui/inc/content.hxx | 161 + sc/source/ui/inc/corodlg.hxx | 45 + sc/source/ui/inc/crdlg.hxx | 39 + sc/source/ui/inc/crnrdlg.hxx | 94 + sc/source/ui/inc/csvcontrol.hxx | 373 + sc/source/ui/inc/csvgrid.hxx | 315 + sc/source/ui/inc/csvruler.hxx | 181 + sc/source/ui/inc/csvsplits.hxx | 81 + sc/source/ui/inc/csvtablebox.hxx | 132 + sc/source/ui/inc/dapidata.hxx | 44 + sc/source/ui/inc/dapitype.hxx | 69 + sc/source/ui/inc/datafdlg.hxx | 70 + sc/source/ui/inc/dataprovider.hxx | 149 + sc/source/ui/inc/dataproviderdlg.hxx | 100 + sc/source/ui/inc/datastream.hxx | 127 + sc/source/ui/inc/datastreamdlg.hxx | 62 + sc/source/ui/inc/datatableview.hxx | 109 + sc/source/ui/inc/datatransformation.hxx | 229 + sc/source/ui/inc/dbdocfun.hxx | 101 + sc/source/ui/inc/dbfunc.hxx | 118 + sc/source/ui/inc/dbnamdlg.hxx | 101 + sc/source/ui/inc/delcldlg.hxx | 41 + sc/source/ui/inc/delcodlg.hxx | 54 + sc/source/ui/inc/docfunc.hxx | 264 + sc/source/ui/inc/docfuncutil.hxx | 42 + sc/source/ui/inc/docsh.hxx | 504 + sc/source/ui/inc/dpcontrol.hxx | 71 + sc/source/ui/inc/dpgroupdlg.hxx | 138 + sc/source/ui/inc/drawsh.hxx | 96 + sc/source/ui/inc/drawutil.hxx | 38 + sc/source/ui/inc/drawview.hxx | 179 + sc/source/ui/inc/drformsh.hxx | 43 + sc/source/ui/inc/drtxtob.hxx | 80 + sc/source/ui/inc/drwtrans.hxx | 99 + sc/source/ui/inc/dwfunctr.hxx | 76 + sc/source/ui/inc/editable.hxx | 89 + sc/source/ui/inc/editfield.hxx | 43 + sc/source/ui/inc/editsh.hxx | 90 + sc/source/ui/inc/filldlg.hxx | 101 + sc/source/ui/inc/filtdlg.hxx | 244 + sc/source/ui/inc/foptmgr.hxx | 83 + sc/source/ui/inc/formatsh.hxx | 75 + sc/source/ui/inc/formdata.hxx | 48 + sc/source/ui/inc/formula.hxx | 104 + sc/source/ui/inc/fuconarc.hxx | 43 + sc/source/ui/inc/fuconcustomshape.hxx | 49 + sc/source/ui/inc/fuconpol.hxx | 44 + sc/source/ui/inc/fuconrec.hxx | 44 + sc/source/ui/inc/fuconstr.hxx | 43 + sc/source/ui/inc/fuconuno.hxx | 50 + sc/source/ui/inc/fudraw.hxx | 54 + sc/source/ui/inc/fuinsert.hxx | 59 + sc/source/ui/inc/fupoor.hxx | 107 + sc/source/ui/inc/fusel.hxx | 48 + sc/source/ui/inc/futext.hxx | 56 + sc/source/ui/inc/gototabdlg.hxx | 43 + sc/source/ui/inc/graphsh.hxx | 70 + sc/source/ui/inc/gridmerg.hxx | 52 + sc/source/ui/inc/gridwin.hxx | 527 + sc/source/ui/inc/groupdlg.hxx | 36 + sc/source/ui/inc/hdrcont.hxx | 133 + sc/source/ui/inc/hfedtdlg.hxx | 152 + sc/source/ui/inc/highred.hxx | 73 + sc/source/ui/inc/hiranges.hxx | 34 + sc/source/ui/inc/imoptdlg.hxx | 60 + sc/source/ui/inc/impex.hxx | 241 + sc/source/ui/inc/inputhdl.hxx | 334 + sc/source/ui/inc/inputwin.hxx | 366 + sc/source/ui/inc/inscldlg.hxx | 42 + sc/source/ui/inc/inscodlg.hxx | 102 + sc/source/ui/inc/instbdlg.hxx | 94 + sc/source/ui/inc/invmerge.hxx | 44 + sc/source/ui/inc/lbseldlg.hxx | 38 + sc/source/ui/inc/linkarea.hxx | 72 + sc/source/ui/inc/lnktrans.hxx | 42 + sc/source/ui/inc/mediash.hxx | 45 + sc/source/ui/inc/mergecellsdialog.hxx | 35 + sc/source/ui/inc/msgpool.hxx | 57 + sc/source/ui/inc/mtrindlg.hxx | 49 + sc/source/ui/inc/mvtabdlg.hxx | 84 + sc/source/ui/inc/namecrea.hxx | 38 + sc/source/ui/inc/namedefdlg.hxx | 89 + sc/source/ui/inc/namedlg.hxx | 124 + sc/source/ui/inc/namemgrtable.hxx | 91 + sc/source/ui/inc/namepast.hxx | 53 + sc/source/ui/inc/navcitem.hxx | 40 + sc/source/ui/inc/navipi.hxx | 194 + sc/source/ui/inc/navsett.hxx | 49 + sc/source/ui/inc/notemark.hxx | 69 + sc/source/ui/inc/oleobjsh.hxx | 43 + sc/source/ui/inc/olinefun.hxx | 52 + sc/source/ui/inc/olinewin.hxx | 225 + sc/source/ui/inc/opredlin.hxx | 41 + sc/source/ui/inc/optsolver.hxx | 246 + sc/source/ui/inc/output.hxx | 388 + sc/source/ui/inc/overlayobject.hxx | 65 + sc/source/ui/inc/pagedata.hxx | 81 + sc/source/ui/inc/pfiltdlg.hxx | 93 + sc/source/ui/inc/pfuncache.hxx | 111 + sc/source/ui/inc/pgbrksh.hxx | 43 + sc/source/ui/inc/pivotsh.hxx | 52 + sc/source/ui/inc/pntlock.hxx | 56 + sc/source/ui/inc/preview.hxx | 163 + sc/source/ui/inc/prevloc.hxx | 152 + sc/source/ui/inc/prevwsh.hxx | 118 + sc/source/ui/inc/printfun.hxx | 399 + sc/source/ui/inc/protectiondlg.hxx | 70 + sc/source/ui/inc/pvfundlg.hxx | 216 + sc/source/ui/inc/redcom.hxx | 57 + sc/source/ui/inc/reffact.hxx | 217 + sc/source/ui/inc/refundo.hxx | 55 + sc/source/ui/inc/retypepassdlg.hxx | 128 + sc/source/ui/inc/rfindlst.hxx | 64 + sc/source/ui/inc/scendlg.hxx | 58 + sc/source/ui/inc/scui_def.hxx | 69 + sc/source/ui/inc/scuiasciiopt.hxx | 136 + sc/source/ui/inc/scuiautofmt.hxx | 78 + sc/source/ui/inc/scuiimoptdlg.hxx | 75 + sc/source/ui/inc/scuitphfedit.hxx | 158 + sc/source/ui/inc/searchresults.hxx | 57 + sc/source/ui/inc/select.hxx | 108 + sc/source/ui/inc/selectionstate.hxx | 55 + sc/source/ui/inc/seltrans.hxx | 72 + sc/source/ui/inc/servobj.hxx | 64 + sc/source/ui/inc/sharedocdlg.hxx | 52 + sc/source/ui/inc/shtabdlg.hxx | 47 + sc/source/ui/inc/simpref.hxx | 78 + sc/source/ui/inc/sizedev.hxx | 46 + sc/source/ui/inc/solveroptions.hxx | 125 + sc/source/ui/inc/solverutil.hxx | 39 + sc/source/ui/inc/solvrdlg.hxx | 90 + sc/source/ui/inc/sortdlg.hxx | 43 + sc/source/ui/inc/sortkeydlg.hxx | 52 + sc/source/ui/inc/spelldialog.hxx | 90 + sc/source/ui/inc/spelleng.hxx | 151 + sc/source/ui/inc/spellparam.hxx | 71 + sc/source/ui/inc/strindlg.hxx | 43 + sc/source/ui/inc/styledlg.hxx | 41 + sc/source/ui/inc/subtdlg.hxx | 35 + sc/source/ui/inc/tabbgcolordlg.hxx | 68 + sc/source/ui/inc/tabcont.hxx | 78 + sc/source/ui/inc/tabopdlg.hxx | 93 + sc/source/ui/inc/tabpages.hxx | 68 + sc/source/ui/inc/tabsplit.hxx | 44 + sc/source/ui/inc/tabview.hxx | 612 ++ sc/source/ui/inc/tabvwsh.hxx | 425 + sc/source/ui/inc/target.hxx | 39 + sc/source/ui/inc/tbzoomsliderctrl.hxx | 86 + sc/source/ui/inc/textdlgs.hxx | 47 + sc/source/ui/inc/textimportoptions.hxx | 52 + sc/source/ui/inc/tpcalc.hxx | 73 + sc/source/ui/inc/tpcompatibility.hxx | 29 + sc/source/ui/inc/tpdefaults.hxx | 43 + sc/source/ui/inc/tpformula.hxx | 85 + sc/source/ui/inc/tphf.hxx | 70 + sc/source/ui/inc/tphfedit.hxx | 86 + sc/source/ui/inc/tpprint.hxx | 40 + sc/source/ui/inc/tpsort.hxx | 152 + sc/source/ui/inc/tpstat.hxx | 43 + sc/source/ui/inc/tpsubt.hxx | 147 + sc/source/ui/inc/tptable.hxx | 80 + sc/source/ui/inc/tpusrlst.hxx | 88 + sc/source/ui/inc/tpview.hxx | 115 + sc/source/ui/inc/transobj.hxx | 112 + sc/source/ui/inc/uiitems.hxx | 277 + sc/source/ui/inc/uiobject.hxx | 47 + sc/source/ui/inc/undo/UndoDeleteSparkline.hxx | 43 + sc/source/ui/inc/undo/UndoDeleteSparklineGroup.hxx | 45 + sc/source/ui/inc/undo/UndoEditSparkline.hxx | 47 + sc/source/ui/inc/undo/UndoEditSparklineGroup.hxx | 44 + sc/source/ui/inc/undo/UndoGroupSparklines.hxx | 56 + sc/source/ui/inc/undo/UndoInsertSparkline.hxx | 45 + sc/source/ui/inc/undo/UndoUngroupSparklines.hxx | 54 + sc/source/ui/inc/undobase.hxx | 178 + sc/source/ui/inc/undoblk.hxx | 983 ++ sc/source/ui/inc/undocell.hxx | 369 + sc/source/ui/inc/undoconvert.hxx | 33 + sc/source/ui/inc/undodat.hxx | 446 + sc/source/ui/inc/undodraw.hxx | 52 + sc/source/ui/inc/undoolk.hxx | 32 + sc/source/ui/inc/undosort.hxx | 34 + sc/source/ui/inc/undostyl.hxx | 102 + sc/source/ui/inc/undotab.hxx | 471 + sc/source/ui/inc/undoutil.hxx | 50 + sc/source/ui/inc/validate.hxx | 275 + sc/source/ui/inc/viewdata.hxx | 734 ++ sc/source/ui/inc/viewfunc.hxx | 384 + sc/source/ui/inc/viewutil.hxx | 88 + sc/source/ui/inc/warnbox.hxx | 39 + sc/source/ui/inc/xmlsourcedlg.hxx | 106 + sc/source/ui/miscdlgs/acredlin.cxx | 1792 ++++ sc/source/ui/miscdlgs/anyrefdg.cxx | 784 ++ sc/source/ui/miscdlgs/autofmt.cxx | 531 + sc/source/ui/miscdlgs/conflictsdlg.cxx | 643 ++ sc/source/ui/miscdlgs/crdlg.cxx | 45 + sc/source/ui/miscdlgs/crnrdlg.cxx | 800 ++ sc/source/ui/miscdlgs/datafdlg.cxx | 355 + sc/source/ui/miscdlgs/dataproviderdlg.cxx | 1125 ++ sc/source/ui/miscdlgs/datastreamdlg.cxx | 178 + sc/source/ui/miscdlgs/datatableview.cxx | 320 + sc/source/ui/miscdlgs/delcldlg.cxx | 101 + sc/source/ui/miscdlgs/delcodlg.cxx | 125 + sc/source/ui/miscdlgs/filldlg.cxx | 290 + sc/source/ui/miscdlgs/gototabdlg.cxx | 81 + sc/source/ui/miscdlgs/groupdlg.cxx | 52 + sc/source/ui/miscdlgs/highred.cxx | 221 + sc/source/ui/miscdlgs/inscldlg.cxx | 110 + sc/source/ui/miscdlgs/inscodlg.cxx | 367 + sc/source/ui/miscdlgs/instbdlg.cxx | 356 + sc/source/ui/miscdlgs/lbseldlg.cxx | 54 + sc/source/ui/miscdlgs/linkarea.cxx | 336 + sc/source/ui/miscdlgs/mergecellsdialog.cxx | 36 + sc/source/ui/miscdlgs/mtrindlg.cxx | 104 + sc/source/ui/miscdlgs/mvtabdlg.cxx | 319 + sc/source/ui/miscdlgs/namecrea.cxx | 55 + sc/source/ui/miscdlgs/optsolver.cxx | 1075 ++ sc/source/ui/miscdlgs/protectiondlg.cxx | 154 + sc/source/ui/miscdlgs/redcom.cxx | 167 + sc/source/ui/miscdlgs/retypepassdlg.cxx | 366 + sc/source/ui/miscdlgs/scuiautofmt.cxx | 385 + sc/source/ui/miscdlgs/sharedocdlg.cxx | 213 + sc/source/ui/miscdlgs/shtabdlg.cxx | 66 + sc/source/ui/miscdlgs/simpref.cxx | 190 + sc/source/ui/miscdlgs/solveroptions.cxx | 415 + sc/source/ui/miscdlgs/solverutil.cxx | 175 + sc/source/ui/miscdlgs/solvrdlg.cxx | 282 + sc/source/ui/miscdlgs/strindlg.cxx | 42 + sc/source/ui/miscdlgs/tabbgcolordlg.cxx | 141 + sc/source/ui/miscdlgs/tabopdlg.cxx | 345 + sc/source/ui/miscdlgs/textdlgs.cxx | 94 + sc/source/ui/miscdlgs/warnbox.cxx | 52 + sc/source/ui/namedlg/namedefdlg.cxx | 323 + sc/source/ui/namedlg/namedlg.cxx | 505 + sc/source/ui/namedlg/namemgrtable.cxx | 177 + sc/source/ui/namedlg/namepast.cxx | 99 + sc/source/ui/navipi/content.cxx | 1637 +++ sc/source/ui/navipi/navcitem.cxx | 99 + sc/source/ui/navipi/navipi.cxx | 965 ++ sc/source/ui/navipi/scenwnd.cxx | 242 + sc/source/ui/optdlg/calcoptionsdlg.cxx | 135 + sc/source/ui/optdlg/calcoptionsdlg.hxx | 43 + sc/source/ui/optdlg/opredlin.cxx | 105 + sc/source/ui/optdlg/tpcalc.cxx | 277 + sc/source/ui/optdlg/tpcompatibility.cxx | 74 + sc/source/ui/optdlg/tpdefaults.cxx | 134 + sc/source/ui/optdlg/tpformula.cxx | 411 + sc/source/ui/optdlg/tpprint.cxx | 111 + sc/source/ui/optdlg/tpusrlst.cxx | 738 ++ sc/source/ui/optdlg/tpview.cxx | 619 ++ sc/source/ui/pagedlg/areasdlg.cxx | 786 ++ sc/source/ui/pagedlg/hfedtdlg.cxx | 263 + sc/source/ui/pagedlg/scuitphfedit.cxx | 855 ++ sc/source/ui/pagedlg/tphf.cxx | 262 + sc/source/ui/pagedlg/tphfedit.cxx | 258 + sc/source/ui/pagedlg/tptable.cxx | 522 + sc/source/ui/sidebar/AlignmentPropertyPanel.cxx | 339 + sc/source/ui/sidebar/AlignmentPropertyPanel.hxx | 112 + .../ui/sidebar/CellAppearancePropertyPanel.cxx | 506 + .../ui/sidebar/CellAppearancePropertyPanel.hxx | 147 + sc/source/ui/sidebar/CellBorderStyleControl.cxx | 281 + sc/source/ui/sidebar/CellBorderStyleControl.hxx | 52 + sc/source/ui/sidebar/CellLineStyleControl.cxx | 243 + sc/source/ui/sidebar/CellLineStyleControl.hxx | 54 + sc/source/ui/sidebar/CellLineStyleValueSet.cxx | 187 + sc/source/ui/sidebar/CellLineStyleValueSet.hxx | 48 + sc/source/ui/sidebar/NumberFormatControl.cxx | 77 + sc/source/ui/sidebar/NumberFormatPropertyPanel.cxx | 290 + sc/source/ui/sidebar/NumberFormatPropertyPanel.hxx | 94 + sc/source/ui/sidebar/ScPanelFactory.cxx | 143 + sc/source/ui/sidebar/ScPanelFactory.hxx | 55 + sc/source/ui/sparklines/SparklineAttributes.cxx | 277 + sc/source/ui/sparklines/SparklineData.cxx | 30 + sc/source/ui/sparklines/SparklineGroup.cxx | 34 + sc/source/ui/sparklines/SparklineList.cxx | 103 + sc/source/ui/styleui/styledlg.cxx | 139 + sc/source/ui/styleui/template.cur | Bin 0 -> 326 bytes sc/source/ui/uitest/uiobject.cxx | 410 + sc/source/ui/undo/UndoDeleteSparkline.cxx | 76 + sc/source/ui/undo/UndoDeleteSparklineGroup.cxx | 82 + sc/source/ui/undo/UndoEditSparkline.cxx | 63 + sc/source/ui/undo/UndoEditSparklineGroup.cxx | 65 + sc/source/ui/undo/UndoGroupSparklines.cxx | 91 + sc/source/ui/undo/UndoInsertSparkline.cxx | 78 + sc/source/ui/undo/UndoUngroupSparklines.cxx | 89 + sc/source/ui/undo/areasave.cxx | 192 + sc/source/ui/undo/refundo.cxx | 176 + sc/source/ui/undo/target.cxx | 24 + sc/source/ui/undo/undobase.cxx | 616 ++ sc/source/ui/undo/undoblk.cxx | 2437 +++++ sc/source/ui/undo/undoblk2.cxx | 186 + sc/source/ui/undo/undoblk3.cxx | 1743 ++++ sc/source/ui/undo/undocell.cxx | 1048 ++ sc/source/ui/undo/undocell2.cxx | 71 + sc/source/ui/undo/undoconvert.cxx | 52 + sc/source/ui/undo/undodat.cxx | 1925 ++++ sc/source/ui/undo/undodraw.cxx | 107 + sc/source/ui/undo/undoolk.cxx | 75 + sc/source/ui/undo/undorangename.cxx | 136 + sc/source/ui/undo/undosort.cxx | 87 + sc/source/ui/undo/undostyl.cxx | 280 + sc/source/ui/undo/undotab.cxx | 1570 +++ sc/source/ui/undo/undoutil.cxx | 114 + .../ui/unoobj/ChartRangeSelectionListener.cxx | 71 + sc/source/ui/unoobj/ChartTools.cxx | 174 + sc/source/ui/unoobj/PivotTableDataProvider.cxx | 904 ++ sc/source/ui/unoobj/PivotTableDataSequence.cxx | 280 + sc/source/ui/unoobj/PivotTableDataSource.cxx | 49 + sc/source/ui/unoobj/TablePivotChart.cxx | 103 + sc/source/ui/unoobj/TablePivotCharts.cxx | 279 + sc/source/ui/unoobj/addruno.cxx | 300 + sc/source/ui/unoobj/afmtuno.cxx | 724 ++ sc/source/ui/unoobj/appluno.cxx | 621 ++ sc/source/ui/unoobj/celllistsource.cxx | 433 + sc/source/ui/unoobj/celllistsource.hxx | 155 + sc/source/ui/unoobj/cellsuno.cxx | 9302 +++++++++++++++++ sc/source/ui/unoobj/cellvaluebinding.cxx | 576 ++ sc/source/ui/unoobj/cellvaluebinding.hxx | 146 + sc/source/ui/unoobj/chart2uno.cxx | 3490 +++++++ sc/source/ui/unoobj/chartuno.cxx | 738 ++ sc/source/ui/unoobj/condformatuno.cxx | 1904 ++++ sc/source/ui/unoobj/confuno.cxx | 657 ++ sc/source/ui/unoobj/convuno.cxx | 46 + sc/source/ui/unoobj/cursuno.cxx | 450 + sc/source/ui/unoobj/dapiuno.cxx | 3356 ++++++ sc/source/ui/unoobj/datauno.cxx | 2390 +++++ sc/source/ui/unoobj/defltuno.cxx | 335 + sc/source/ui/unoobj/dispuno.cxx | 369 + sc/source/ui/unoobj/docuno.cxx | 4866 +++++++++ sc/source/ui/unoobj/drdefuno.cxx | 79 + sc/source/ui/unoobj/editsrc.cxx | 272 + sc/source/ui/unoobj/eventuno.cxx | 174 + sc/source/ui/unoobj/exceldetect.cxx | 198 + sc/source/ui/unoobj/exceldetect.hxx | 34 + sc/source/ui/unoobj/fielduno.cxx | 1302 +++ sc/source/ui/unoobj/filtuno.cxx | 363 + sc/source/ui/unoobj/fmtuno.cxx | 921 ++ sc/source/ui/unoobj/forbiuno.cxx | 81 + sc/source/ui/unoobj/funcuno.cxx | 653 ++ sc/source/ui/unoobj/linkuno.cxx | 1691 +++ sc/source/ui/unoobj/listenercalls.cxx | 69 + sc/source/ui/unoobj/miscuno.cxx | 284 + sc/source/ui/unoobj/nameuno.cxx | 1158 +++ sc/source/ui/unoobj/notesuno.cxx | 232 + sc/source/ui/unoobj/optuno.cxx | 222 + sc/source/ui/unoobj/pageuno.cxx | 60 + sc/source/ui/unoobj/scdetect.cxx | 353 + sc/source/ui/unoobj/scdetect.hxx | 49 + sc/source/ui/unoobj/servuno.cxx | 620 ++ sc/source/ui/unoobj/shapeuno.cxx | 1443 +++ sc/source/ui/unoobj/srchuno.cxx | 197 + sc/source/ui/unoobj/styleuno.cxx | 1936 ++++ sc/source/ui/unoobj/targuno.cxx | 293 + sc/source/ui/unoobj/textuno.cxx | 886 ++ sc/source/ui/unoobj/tokenuno.cxx | 521 + sc/source/ui/unoobj/unodoc.cxx | 45 + sc/source/ui/unoobj/unoreflist.cxx | 54 + sc/source/ui/unoobj/viewuno.cxx | 2204 ++++ sc/source/ui/unoobj/warnpassword.cxx | 62 + sc/source/ui/vba/excelvbahelper.cxx | 399 + sc/source/ui/vba/excelvbahelper.hxx | 97 + sc/source/ui/vba/helperdecl.hxx | 47 + sc/source/ui/vba/vbaapplication.cxx | 1566 +++ sc/source/ui/vba/vbaapplication.hxx | 168 + sc/source/ui/vba/vbaassistant.cxx | 116 + sc/source/ui/vba/vbaassistant.hxx | 57 + sc/source/ui/vba/vbaaxes.cxx | 211 + sc/source/ui/vba/vbaaxes.hxx | 47 + sc/source/ui/vba/vbaaxis.cxx | 656 ++ sc/source/ui/vba/vbaaxis.hxx | 90 + sc/source/ui/vba/vbaaxistitle.cxx | 44 + sc/source/ui/vba/vbaaxistitle.hxx | 36 + sc/source/ui/vba/vbaborders.cxx | 591 ++ sc/source/ui/vba/vbaborders.hxx | 67 + sc/source/ui/vba/vbacharacters.cxx | 134 + sc/source/ui/vba/vbacharacters.hxx | 61 + sc/source/ui/vba/vbachart.cxx | 1056 ++ sc/source/ui/vba/vbachart.hxx | 103 + sc/source/ui/vba/vbachartobject.cxx | 147 + sc/source/ui/vba/vbachartobject.hxx | 61 + sc/source/ui/vba/vbachartobjects.cxx | 206 + sc/source/ui/vba/vbachartobjects.hxx | 60 + sc/source/ui/vba/vbacharttitle.cxx | 44 + sc/source/ui/vba/vbacharttitle.hxx | 35 + sc/source/ui/vba/vbacomment.cxx | 234 + sc/source/ui/vba/vbacomment.hxx | 72 + sc/source/ui/vba/vbacomments.cxx | 113 + sc/source/ui/vba/vbacomments.hxx | 49 + sc/source/ui/vba/vbacondition.cxx | 143 + sc/source/ui/vba/vbacondition.hxx | 48 + sc/source/ui/vba/vbadialog.cxx | 85 + sc/source/ui/vba/vbadialog.hxx | 40 + sc/source/ui/vba/vbadialogs.cxx | 51 + sc/source/ui/vba/vbadialogs.hxx | 43 + sc/source/ui/vba/vbaeventshelper.cxx | 898 ++ sc/source/ui/vba/vbaeventshelper.hxx | 84 + sc/source/ui/vba/vbafiledialog.cxx | 174 + sc/source/ui/vba/vbafiledialog.hxx | 58 + sc/source/ui/vba/vbafiledialogitems.cxx | 123 + sc/source/ui/vba/vbafiledialogitems.hxx | 43 + sc/source/ui/vba/vbafont.cxx | 329 + sc/source/ui/vba/vbafont.hxx | 76 + sc/source/ui/vba/vbaformat.cxx | 814 ++ sc/source/ui/vba/vbaformat.hxx | 154 + sc/source/ui/vba/vbaformatcondition.cxx | 157 + sc/source/ui/vba/vbaformatcondition.hxx | 67 + sc/source/ui/vba/vbaformatconditions.cxx | 289 + sc/source/ui/vba/vbaformatconditions.hxx | 66 + sc/source/ui/vba/vbaglobals.cxx | 265 + sc/source/ui/vba/vbaglobals.hxx | 82 + sc/source/ui/vba/vbahyperlink.cxx | 236 + sc/source/ui/vba/vbahyperlink.hxx | 86 + sc/source/ui/vba/vbahyperlinks.cxx | 277 + sc/source/ui/vba/vbahyperlinks.hxx | 136 + sc/source/ui/vba/vbainterior.cxx | 413 + sc/source/ui/vba/vbainterior.hxx | 83 + sc/source/ui/vba/vbalineshape.cxx | 34 + sc/source/ui/vba/vbalineshape.hxx | 34 + sc/source/ui/vba/vbamenu.cxx | 67 + sc/source/ui/vba/vbamenu.hxx | 37 + sc/source/ui/vba/vbamenubar.cxx | 48 + sc/source/ui/vba/vbamenubar.hxx | 33 + sc/source/ui/vba/vbamenubars.cxx | 118 + sc/source/ui/vba/vbamenubars.hxx | 40 + sc/source/ui/vba/vbamenuitem.cxx | 64 + sc/source/ui/vba/vbamenuitem.hxx | 38 + sc/source/ui/vba/vbamenuitems.cxx | 138 + sc/source/ui/vba/vbamenuitems.hxx | 42 + sc/source/ui/vba/vbamenus.cxx | 124 + sc/source/ui/vba/vbamenus.hxx | 42 + sc/source/ui/vba/vbaname.cxx | 216 + sc/source/ui/vba/vbaname.hxx | 69 + sc/source/ui/vba/vbanames.cxx | 260 + sc/source/ui/vba/vbanames.hxx | 69 + sc/source/ui/vba/vbaoleobject.cxx | 150 + sc/source/ui/vba/vbaoleobject.hxx | 57 + sc/source/ui/vba/vbaoleobjects.cxx | 184 + sc/source/ui/vba/vbaoleobjects.hxx | 47 + sc/source/ui/vba/vbaoutline.cxx | 58 + sc/source/ui/vba/vbaoutline.hxx | 43 + sc/source/ui/vba/vbaovalshape.cxx | 34 + sc/source/ui/vba/vbaovalshape.hxx | 34 + sc/source/ui/vba/vbapagebreak.cxx | 142 + sc/source/ui/vba/vbapagebreak.hxx | 87 + sc/source/ui/vba/vbapagebreaks.cxx | 322 + sc/source/ui/vba/vbapagebreaks.hxx | 83 + sc/source/ui/vba/vbapagesetup.cxx | 631 ++ sc/source/ui/vba/vbapagesetup.hxx | 90 + sc/source/ui/vba/vbapalette.cxx | 115 + sc/source/ui/vba/vbapalette.hxx | 44 + sc/source/ui/vba/vbapane.cxx | 196 + sc/source/ui/vba/vbapane.hxx | 55 + sc/source/ui/vba/vbapivotcache.cxx | 50 + sc/source/ui/vba/vbapivotcache.hxx | 40 + sc/source/ui/vba/vbapivottable.cxx | 53 + sc/source/ui/vba/vbapivottable.hxx | 40 + sc/source/ui/vba/vbapivottables.cxx | 89 + sc/source/ui/vba/vbapivottables.hxx | 49 + sc/source/ui/vba/vbarange.cxx | 5720 +++++++++++ sc/source/ui/vba/vbarange.hxx | 327 + sc/source/ui/vba/vbasheetobject.cxx | 540 + sc/source/ui/vba/vbasheetobject.hxx | 209 + sc/source/ui/vba/vbasheetobjects.cxx | 555 + sc/source/ui/vba/vbasheetobjects.hxx | 102 + sc/source/ui/vba/vbastyle.cxx | 183 + sc/source/ui/vba/vbastyle.hxx | 62 + sc/source/ui/vba/vbastyles.cxx | 199 + sc/source/ui/vba/vbastyles.hxx | 50 + sc/source/ui/vba/vbatextboxshape.cxx | 60 + sc/source/ui/vba/vbatextboxshape.hxx | 39 + sc/source/ui/vba/vbatextframe.cxx | 67 + sc/source/ui/vba/vbatextframe.hxx | 40 + sc/source/ui/vba/vbatitle.hxx | 141 + sc/source/ui/vba/vbavalidation.cxx | 382 + sc/source/ui/vba/vbavalidation.hxx | 64 + sc/source/ui/vba/vbawindow.cxx | 868 ++ sc/source/ui/vba/vbawindow.hxx | 126 + sc/source/ui/vba/vbawindows.cxx | 249 + sc/source/ui/vba/vbawindows.hxx | 48 + sc/source/ui/vba/vbaworkbook.cxx | 420 + sc/source/ui/vba/vbaworkbook.hxx | 73 + sc/source/ui/vba/vbaworkbooks.cxx | 291 + sc/source/ui/vba/vbaworkbooks.hxx | 56 + sc/source/ui/vba/vbaworksheet.cxx | 1052 ++ sc/source/ui/vba/vbaworksheet.hxx | 167 + sc/source/ui/vba/vbaworksheets.cxx | 535 + sc/source/ui/vba/vbaworksheets.hxx | 68 + sc/source/ui/vba/vbawsfunction.cxx | 298 + sc/source/ui/vba/vbawsfunction.hxx | 45 + sc/source/ui/view/SparklineShell.cxx | 54 + sc/source/ui/view/auditsh.cxx | 123 + sc/source/ui/view/cellmergeoption.cxx | 49 + sc/source/ui/view/cellsh.cxx | 1322 +++ sc/source/ui/view/cellsh1.cxx | 3454 +++++++ sc/source/ui/view/cellsh2.cxx | 1263 +++ sc/source/ui/view/cellsh3.cxx | 1067 ++ sc/source/ui/view/cellsh4.cxx | 518 + sc/source/ui/view/cliputil.cxx | 169 + sc/source/ui/view/colrowba.cxx | 383 + sc/source/ui/view/dbfunc.cxx | 447 + sc/source/ui/view/dbfunc2.cxx | 41 + sc/source/ui/view/dbfunc3.cxx | 2314 +++++ sc/source/ui/view/dbfunc4.cxx | 73 + sc/source/ui/view/drawutil.cxx | 89 + sc/source/ui/view/drawvie3.cxx | 259 + sc/source/ui/view/drawvie4.cxx | 579 ++ sc/source/ui/view/drawview.cxx | 1264 +++ sc/source/ui/view/editsh.cxx | 1367 +++ sc/source/ui/view/formatsh.cxx | 2868 ++++++ sc/source/ui/view/gridmerg.cxx | 227 + sc/source/ui/view/gridwin.cxx | 7094 +++++++++++++ sc/source/ui/view/gridwin2.cxx | 1090 ++ sc/source/ui/view/gridwin3.cxx | 399 + sc/source/ui/view/gridwin4.cxx | 2608 +++++ sc/source/ui/view/gridwin5.cxx | 392 + sc/source/ui/view/gridwin_dbgutil.cxx | 171 + sc/source/ui/view/hdrcont.cxx | 1123 ++ sc/source/ui/view/hintwin.cxx | 181 + sc/source/ui/view/imapwrap.cxx | 48 + sc/source/ui/view/imapwrap.hxx | 40 + sc/source/ui/view/invmerge.cxx | 162 + sc/source/ui/view/notemark.cxx | 203 + sc/source/ui/view/olinewin.cxx | 1040 ++ sc/source/ui/view/output.cxx | 2698 +++++ sc/source/ui/view/output2.cxx | 5123 ++++++++++ sc/source/ui/view/output3.cxx | 215 + sc/source/ui/view/overlayobject.cxx | 85 + sc/source/ui/view/pfuncache.cxx | 190 + sc/source/ui/view/pgbrksh.cxx | 53 + sc/source/ui/view/pivotsh.cxx | 167 + sc/source/ui/view/preview.cxx | 1575 +++ sc/source/ui/view/prevloc.cxx | 718 ++ sc/source/ui/view/prevwsh.cxx | 1189 +++ sc/source/ui/view/prevwsh2.cxx | 68 + sc/source/ui/view/printfun.cxx | 3229 ++++++ sc/source/ui/view/reffact.cxx | 298 + sc/source/ui/view/scextopt.cxx | 218 + sc/source/ui/view/select.cxx | 983 ++ sc/source/ui/view/selectionstate.cxx | 54 + sc/source/ui/view/spellcheckcontext.cxx | 387 + sc/source/ui/view/spelldialog.cxx | 281 + sc/source/ui/view/spelleng.cxx | 447 + sc/source/ui/view/tabcont.cxx | 669 ++ sc/source/ui/view/tabsplit.cxx | 127 + sc/source/ui/view/tabview.cxx | 3016 ++++++ sc/source/ui/view/tabview2.cxx | 1510 +++ sc/source/ui/view/tabview3.cxx | 3112 ++++++ sc/source/ui/view/tabview4.cxx | 532 + sc/source/ui/view/tabview5.cxx | 708 ++ sc/source/ui/view/tabvwsh.cxx | 120 + sc/source/ui/view/tabvwsh2.cxx | 472 + sc/source/ui/view/tabvwsh3.cxx | 1352 +++ sc/source/ui/view/tabvwsh4.cxx | 1942 ++++ sc/source/ui/view/tabvwsh5.cxx | 390 + sc/source/ui/view/tabvwsh8.cxx | 76 + sc/source/ui/view/tabvwsh9.cxx | 203 + sc/source/ui/view/tabvwsha.cxx | 896 ++ sc/source/ui/view/tabvwshb.cxx | 831 ++ sc/source/ui/view/tabvwshc.cxx | 745 ++ sc/source/ui/view/tabvwshd.cxx | 67 + sc/source/ui/view/tabvwshe.cxx | 342 + sc/source/ui/view/tabvwshf.cxx | 1055 ++ sc/source/ui/view/tabvwshg.cxx | 120 + sc/source/ui/view/tabvwshh.cxx | 261 + sc/source/ui/view/viewdata.cxx | 4366 ++++++++ sc/source/ui/view/viewfun2.cxx | 3464 +++++++ sc/source/ui/view/viewfun3.cxx | 2027 ++++ sc/source/ui/view/viewfun4.cxx | 789 ++ sc/source/ui/view/viewfun5.cxx | 818 ++ sc/source/ui/view/viewfun6.cxx | 546 + sc/source/ui/view/viewfun7.cxx | 456 + sc/source/ui/view/viewfunc.cxx | 3073 ++++++ sc/source/ui/view/viewutil.cxx | 426 + sc/source/ui/view/waitoff.cxx | 51 + sc/source/ui/xmlsource/xmlsourcedlg.cxx | 622 ++ 1493 files changed, 751704 insertions(+) create mode 100644 sc/source/core/data/attarray.cxx create mode 100644 sc/source/core/data/attrib.cxx create mode 100644 sc/source/core/data/autonamecache.cxx create mode 100644 sc/source/core/data/bcaslot.cxx create mode 100644 sc/source/core/data/bigrange.cxx create mode 100644 sc/source/core/data/celltextattr.cxx create mode 100644 sc/source/core/data/cellvalue.cxx create mode 100644 sc/source/core/data/cellvalues.cxx create mode 100644 sc/source/core/data/clipcontext.cxx create mode 100644 sc/source/core/data/clipparam.cxx create mode 100644 sc/source/core/data/colcontainer.cxx create mode 100644 sc/source/core/data/colorscale.cxx create mode 100644 sc/source/core/data/column.cxx create mode 100644 sc/source/core/data/column2.cxx create mode 100644 sc/source/core/data/column3.cxx create mode 100644 sc/source/core/data/column4.cxx create mode 100644 sc/source/core/data/columniterator.cxx create mode 100644 sc/source/core/data/columnset.cxx create mode 100644 sc/source/core/data/columnspanset.cxx create mode 100644 sc/source/core/data/compressedarray.cxx create mode 100644 sc/source/core/data/conditio.cxx create mode 100644 sc/source/core/data/dbdocutl.cxx create mode 100644 sc/source/core/data/dociter.cxx create mode 100644 sc/source/core/data/docparam.cxx create mode 100644 sc/source/core/data/docpool.cxx create mode 100644 sc/source/core/data/documen2.cxx create mode 100644 sc/source/core/data/documen3.cxx create mode 100644 sc/source/core/data/documen4.cxx create mode 100644 sc/source/core/data/documen5.cxx create mode 100644 sc/source/core/data/documen6.cxx create mode 100644 sc/source/core/data/documen7.cxx create mode 100644 sc/source/core/data/documen8.cxx create mode 100644 sc/source/core/data/documen9.cxx create mode 100644 sc/source/core/data/document.cxx create mode 100644 sc/source/core/data/document10.cxx create mode 100644 sc/source/core/data/documentimport.cxx create mode 100644 sc/source/core/data/documentstreamaccess.cxx create mode 100644 sc/source/core/data/dpcache.cxx create mode 100644 sc/source/core/data/dpdimsave.cxx create mode 100644 sc/source/core/data/dpfilteredcache.cxx create mode 100644 sc/source/core/data/dpglobal.cxx create mode 100644 sc/source/core/data/dpgroup.cxx create mode 100644 sc/source/core/data/dpitemdata.cxx create mode 100644 sc/source/core/data/dpnumgroupinfo.cxx create mode 100644 sc/source/core/data/dpobject.cxx create mode 100644 sc/source/core/data/dpoutput.cxx create mode 100644 sc/source/core/data/dpoutputgeometry.cxx create mode 100644 sc/source/core/data/dpresfilter.cxx create mode 100644 sc/source/core/data/dpsave.cxx create mode 100644 sc/source/core/data/dpsdbtab.cxx create mode 100644 sc/source/core/data/dpshttab.cxx create mode 100644 sc/source/core/data/dptabdat.cxx create mode 100644 sc/source/core/data/dptabres.cxx create mode 100644 sc/source/core/data/dptabsrc.cxx create mode 100644 sc/source/core/data/dputil.cxx create mode 100644 sc/source/core/data/drawpage.cxx create mode 100644 sc/source/core/data/drwlayer.cxx create mode 100644 sc/source/core/data/edittextiterator.cxx create mode 100644 sc/source/core/data/fillinfo.cxx create mode 100644 sc/source/core/data/formulacell.cxx create mode 100644 sc/source/core/data/formulaiter.cxx create mode 100644 sc/source/core/data/funcdesc.cxx create mode 100644 sc/source/core/data/global.cxx create mode 100644 sc/source/core/data/global2.cxx create mode 100644 sc/source/core/data/globalx.cxx create mode 100644 sc/source/core/data/grouptokenconverter.cxx create mode 100644 sc/source/core/data/listenercontext.cxx create mode 100644 sc/source/core/data/markarr.cxx create mode 100644 sc/source/core/data/markdata.cxx create mode 100644 sc/source/core/data/markmulti.cxx create mode 100644 sc/source/core/data/mtvcellfunc.cxx create mode 100644 sc/source/core/data/mtvelements.cxx create mode 100644 sc/source/core/data/olinetab.cxx create mode 100644 sc/source/core/data/pagepar.cxx create mode 100644 sc/source/core/data/patattr.cxx create mode 100644 sc/source/core/data/pivot2.cxx create mode 100644 sc/source/core/data/poolhelp.cxx create mode 100644 sc/source/core/data/postit.cxx create mode 100644 sc/source/core/data/queryevaluator.cxx create mode 100644 sc/source/core/data/queryiter.cxx create mode 100644 sc/source/core/data/refupdatecontext.cxx create mode 100644 sc/source/core/data/rowheightcontext.cxx create mode 100644 sc/source/core/data/segmenttree.cxx create mode 100644 sc/source/core/data/sheetevents.cxx create mode 100644 sc/source/core/data/simpleformulacalc.cxx create mode 100644 sc/source/core/data/sortparam.cxx create mode 100644 sc/source/core/data/stlpool.cxx create mode 100644 sc/source/core/data/stlsheet.cxx create mode 100644 sc/source/core/data/subtotalparam.cxx create mode 100644 sc/source/core/data/tabbgcolor.cxx create mode 100644 sc/source/core/data/table1.cxx create mode 100644 sc/source/core/data/table2.cxx create mode 100644 sc/source/core/data/table3.cxx create mode 100644 sc/source/core/data/table4.cxx create mode 100644 sc/source/core/data/table5.cxx create mode 100644 sc/source/core/data/table6.cxx create mode 100644 sc/source/core/data/table7.cxx create mode 100644 sc/source/core/data/tabprotection.cxx create mode 100644 sc/source/core/data/types.cxx create mode 100644 sc/source/core/data/userdat.cxx create mode 100644 sc/source/core/data/validat.cxx create mode 100644 sc/source/core/inc/addinhelpid.hxx create mode 100644 sc/source/core/inc/addinlis.hxx create mode 100644 sc/source/core/inc/adiasync.hxx create mode 100644 sc/source/core/inc/bcaslot.hxx create mode 100644 sc/source/core/inc/cellkeytranslator.hxx create mode 100644 sc/source/core/inc/ddelink.hxx create mode 100644 sc/source/core/inc/doubleref.hxx create mode 100644 sc/source/core/inc/formulagroupcl.hxx create mode 100644 sc/source/core/inc/grouptokenconverter.hxx create mode 100644 sc/source/core/inc/interpre.hxx create mode 100644 sc/source/core/inc/jumpmatrix.hxx create mode 100644 sc/source/core/inc/parclass.hxx create mode 100644 sc/source/core/inc/poolhelp.hxx create mode 100644 sc/source/core/inc/refupdat.hxx create mode 100644 sc/source/core/inc/scrdata.hxx create mode 100644 sc/source/core/inc/sharedstringpoolpurge.hxx create mode 100644 sc/source/core/inc/webservicelink.hxx create mode 100644 sc/source/core/opencl/cl-test.ods create mode 100644 sc/source/core/opencl/formulagroupcl.cxx create mode 100644 sc/source/core/opencl/op_addin.cxx create mode 100644 sc/source/core/opencl/op_addin.hxx create mode 100644 sc/source/core/opencl/op_array.cxx create mode 100644 sc/source/core/opencl/op_array.hxx create mode 100644 sc/source/core/opencl/op_database.cxx create mode 100644 sc/source/core/opencl/op_database.hxx create mode 100644 sc/source/core/opencl/op_financial.cxx create mode 100644 sc/source/core/opencl/op_financial.hxx create mode 100644 sc/source/core/opencl/op_logical.cxx create mode 100644 sc/source/core/opencl/op_logical.hxx create mode 100644 sc/source/core/opencl/op_math.cxx create mode 100644 sc/source/core/opencl/op_math.hxx create mode 100644 sc/source/core/opencl/op_spreadsheet.cxx create mode 100644 sc/source/core/opencl/op_spreadsheet.hxx create mode 100644 sc/source/core/opencl/op_statistical.cxx create mode 100644 sc/source/core/opencl/op_statistical.hxx create mode 100644 sc/source/core/opencl/opbase.cxx create mode 100644 sc/source/core/opencl/opbase.hxx create mode 100644 sc/source/core/opencl/opinlinefun_finacial.cxx create mode 100644 sc/source/core/opencl/opinlinefun_math.hxx create mode 100644 sc/source/core/opencl/opinlinefun_statistical.cxx create mode 100644 sc/source/core/tool/addincfg.cxx create mode 100644 sc/source/core/tool/addincol.cxx create mode 100644 sc/source/core/tool/addinhelpid.cxx create mode 100644 sc/source/core/tool/addinlis.cxx create mode 100644 sc/source/core/tool/address.cxx create mode 100644 sc/source/core/tool/adiasync.cxx create mode 100644 sc/source/core/tool/appoptio.cxx create mode 100644 sc/source/core/tool/arraysum.hxx create mode 100644 sc/source/core/tool/arraysumSSE2.cxx create mode 100644 sc/source/core/tool/autoform.cxx create mode 100644 sc/source/core/tool/calcconfig.cxx create mode 100644 sc/source/core/tool/callform.cxx create mode 100644 sc/source/core/tool/cellform.cxx create mode 100644 sc/source/core/tool/cellkeytranslator.cxx create mode 100644 sc/source/core/tool/cellkeywords.inl create mode 100644 sc/source/core/tool/chartarr.cxx create mode 100644 sc/source/core/tool/charthelper.cxx create mode 100644 sc/source/core/tool/chartlis.cxx create mode 100644 sc/source/core/tool/chartlock.cxx create mode 100644 sc/source/core/tool/chartpos.cxx create mode 100644 sc/source/core/tool/chgtrack.cxx create mode 100644 sc/source/core/tool/chgviset.cxx create mode 100644 sc/source/core/tool/compare.cxx create mode 100644 sc/source/core/tool/compiler.cxx create mode 100644 sc/source/core/tool/consoli.cxx create mode 100644 sc/source/core/tool/dbdata.cxx create mode 100644 sc/source/core/tool/ddelink.cxx create mode 100644 sc/source/core/tool/defaultsoptions.cxx create mode 100644 sc/source/core/tool/detdata.cxx create mode 100644 sc/source/core/tool/detfunc.cxx create mode 100644 sc/source/core/tool/docoptio.cxx create mode 100644 sc/source/core/tool/doubleref.cxx create mode 100644 sc/source/core/tool/editdataarray.cxx create mode 100644 sc/source/core/tool/editutil.cxx create mode 100644 sc/source/core/tool/filtopt.cxx create mode 100644 sc/source/core/tool/formulagroup.cxx create mode 100644 sc/source/core/tool/formulalogger.cxx create mode 100644 sc/source/core/tool/formulaopt.cxx create mode 100644 sc/source/core/tool/formulaparserpool.cxx create mode 100644 sc/source/core/tool/formularesult.cxx create mode 100644 sc/source/core/tool/grouparealistener.cxx create mode 100644 sc/source/core/tool/hints.cxx create mode 100644 sc/source/core/tool/inputopt.cxx create mode 100644 sc/source/core/tool/interpr1.cxx create mode 100644 sc/source/core/tool/interpr2.cxx create mode 100644 sc/source/core/tool/interpr3.cxx create mode 100644 sc/source/core/tool/interpr4.cxx create mode 100644 sc/source/core/tool/interpr5.cxx create mode 100644 sc/source/core/tool/interpr6.cxx create mode 100644 sc/source/core/tool/interpr7.cxx create mode 100644 sc/source/core/tool/interpr8.cxx create mode 100644 sc/source/core/tool/interpretercontext.cxx create mode 100644 sc/source/core/tool/jumpmatrix.cxx create mode 100644 sc/source/core/tool/listenerquery.cxx create mode 100644 sc/source/core/tool/lookupcache.cxx create mode 100644 sc/source/core/tool/math.cxx create mode 100644 sc/source/core/tool/matrixoperators.cxx create mode 100644 sc/source/core/tool/navicfg.cxx create mode 100644 sc/source/core/tool/numformat.cxx create mode 100644 sc/source/core/tool/odffmap.cxx create mode 100644 sc/source/core/tool/optutil.cxx create mode 100644 sc/source/core/tool/orcusxml.cxx create mode 100644 sc/source/core/tool/parclass.cxx create mode 100644 sc/source/core/tool/printopt.cxx create mode 100644 sc/source/core/tool/prnsave.cxx create mode 100644 sc/source/core/tool/progress.cxx create mode 100644 sc/source/core/tool/queryentry.cxx create mode 100644 sc/source/core/tool/queryparam.cxx create mode 100644 sc/source/core/tool/rangecache.cxx create mode 100644 sc/source/core/tool/rangelst.cxx create mode 100644 sc/source/core/tool/rangenam.cxx create mode 100644 sc/source/core/tool/rangeseq.cxx create mode 100644 sc/source/core/tool/rangeutl.cxx create mode 100644 sc/source/core/tool/rechead.cxx create mode 100644 sc/source/core/tool/recursionhelper.cxx create mode 100644 sc/source/core/tool/refdata.cxx create mode 100644 sc/source/core/tool/reffind.cxx create mode 100644 sc/source/core/tool/refhint.cxx create mode 100644 sc/source/core/tool/refreshtimer.cxx create mode 100644 sc/source/core/tool/reftokenhelper.cxx create mode 100644 sc/source/core/tool/refupdat.cxx create mode 100644 sc/source/core/tool/scmatrix.cxx create mode 100644 sc/source/core/tool/scopetools.cxx create mode 100644 sc/source/core/tool/sharedformula.cxx create mode 100644 sc/source/core/tool/sharedstringpoolpurge.cxx create mode 100644 sc/source/core/tool/stringutil.cxx create mode 100644 sc/source/core/tool/stylehelper.cxx create mode 100644 sc/source/core/tool/subtotal.cxx create mode 100644 sc/source/core/tool/token.cxx create mode 100644 sc/source/core/tool/tokenstringcontext.cxx create mode 100644 sc/source/core/tool/typedstrdata.cxx create mode 100644 sc/source/core/tool/unitconv.cxx create mode 100644 sc/source/core/tool/userlist.cxx create mode 100644 sc/source/core/tool/viewopti.cxx create mode 100644 sc/source/core/tool/webservicelink.cxx create mode 100644 sc/source/core/tool/zforauto.cxx create mode 100644 sc/source/filter/dif/difexp.cxx create mode 100644 sc/source/filter/dif/difimp.cxx create mode 100644 sc/source/filter/excel/colrowst.cxx create mode 100644 sc/source/filter/excel/excdoc.cxx create mode 100644 sc/source/filter/excel/excel.cxx create mode 100644 sc/source/filter/excel/excform.cxx create mode 100644 sc/source/filter/excel/excform8.cxx create mode 100644 sc/source/filter/excel/excimp8.cxx create mode 100644 sc/source/filter/excel/excrecds.cxx create mode 100644 sc/source/filter/excel/exctools.cxx create mode 100644 sc/source/filter/excel/expop2.cxx create mode 100644 sc/source/filter/excel/export/SparklineExt.cxx create mode 100644 sc/source/filter/excel/fontbuff.cxx create mode 100644 sc/source/filter/excel/frmbase.cxx create mode 100644 sc/source/filter/excel/impop.cxx create mode 100644 sc/source/filter/excel/namebuff.cxx create mode 100644 sc/source/filter/excel/ooxml-export-TODO.txt create mode 100644 sc/source/filter/excel/read.cxx create mode 100644 sc/source/filter/excel/tokstack.cxx create mode 100644 sc/source/filter/excel/xechart.cxx create mode 100644 sc/source/filter/excel/xecontent.cxx create mode 100644 sc/source/filter/excel/xedbdata.cxx create mode 100644 sc/source/filter/excel/xeescher.cxx create mode 100644 sc/source/filter/excel/xeextlst.cxx create mode 100644 sc/source/filter/excel/xeformula.cxx create mode 100644 sc/source/filter/excel/xehelper.cxx create mode 100644 sc/source/filter/excel/xelink.cxx create mode 100644 sc/source/filter/excel/xename.cxx create mode 100644 sc/source/filter/excel/xepage.cxx create mode 100644 sc/source/filter/excel/xepivot.cxx create mode 100644 sc/source/filter/excel/xepivotxml.cxx create mode 100644 sc/source/filter/excel/xerecord.cxx create mode 100644 sc/source/filter/excel/xeroot.cxx create mode 100644 sc/source/filter/excel/xestream.cxx create mode 100644 sc/source/filter/excel/xestring.cxx create mode 100644 sc/source/filter/excel/xestyle.cxx create mode 100644 sc/source/filter/excel/xetable.cxx create mode 100644 sc/source/filter/excel/xeview.cxx create mode 100644 sc/source/filter/excel/xichart.cxx create mode 100644 sc/source/filter/excel/xicontent.cxx create mode 100644 sc/source/filter/excel/xiescher.cxx create mode 100644 sc/source/filter/excel/xiformula.cxx create mode 100644 sc/source/filter/excel/xihelper.cxx create mode 100644 sc/source/filter/excel/xilink.cxx create mode 100644 sc/source/filter/excel/xiname.cxx create mode 100644 sc/source/filter/excel/xipage.cxx create mode 100644 sc/source/filter/excel/xipivot.cxx create mode 100644 sc/source/filter/excel/xiroot.cxx create mode 100644 sc/source/filter/excel/xistream.cxx create mode 100644 sc/source/filter/excel/xistring.cxx create mode 100644 sc/source/filter/excel/xistyle.cxx create mode 100644 sc/source/filter/excel/xiview.cxx create mode 100644 sc/source/filter/excel/xladdress.cxx create mode 100644 sc/source/filter/excel/xlchart.cxx create mode 100644 sc/source/filter/excel/xlescher.cxx create mode 100644 sc/source/filter/excel/xlformula.cxx create mode 100644 sc/source/filter/excel/xlpage.cxx create mode 100644 sc/source/filter/excel/xlpivot.cxx create mode 100644 sc/source/filter/excel/xlroot.cxx create mode 100644 sc/source/filter/excel/xlstyle.cxx create mode 100644 sc/source/filter/excel/xltoolbar.cxx create mode 100644 sc/source/filter/excel/xltoolbar.hxx create mode 100644 sc/source/filter/excel/xltools.cxx create mode 100644 sc/source/filter/excel/xltracer.cxx create mode 100644 sc/source/filter/excel/xlview.cxx create mode 100644 sc/source/filter/ftools/fapihelper.cxx create mode 100644 sc/source/filter/ftools/fprogressbar.cxx create mode 100644 sc/source/filter/ftools/ftools.cxx create mode 100644 sc/source/filter/ftools/sharedformulagroups.cxx create mode 100644 sc/source/filter/html/htmlexp.cxx create mode 100644 sc/source/filter/html/htmlexp2.cxx create mode 100644 sc/source/filter/html/htmlimp.cxx create mode 100644 sc/source/filter/html/htmlpars.cxx create mode 100644 sc/source/filter/importfilterdata.cxx create mode 100644 sc/source/filter/inc/SparklineFragment.hxx create mode 100644 sc/source/filter/inc/XclExpChangeTrack.hxx create mode 100644 sc/source/filter/inc/XclImpChangeTrack.hxx create mode 100644 sc/source/filter/inc/addressconverter.hxx create mode 100644 sc/source/filter/inc/autofilterbuffer.hxx create mode 100644 sc/source/filter/inc/autofiltercontext.hxx create mode 100644 sc/source/filter/inc/biffhelper.hxx create mode 100644 sc/source/filter/inc/chartsheetfragment.hxx create mode 100644 sc/source/filter/inc/colrowst.hxx create mode 100644 sc/source/filter/inc/commentsbuffer.hxx create mode 100644 sc/source/filter/inc/commentsfragment.hxx create mode 100644 sc/source/filter/inc/condformatbuffer.hxx create mode 100644 sc/source/filter/inc/condformatcontext.hxx create mode 100644 sc/source/filter/inc/connectionsbuffer.hxx create mode 100644 sc/source/filter/inc/connectionsfragment.hxx create mode 100644 sc/source/filter/inc/decl.h create mode 100644 sc/source/filter/inc/defnamesbuffer.hxx create mode 100644 sc/source/filter/inc/dif.hxx create mode 100644 sc/source/filter/inc/drawingbase.hxx create mode 100644 sc/source/filter/inc/drawingfragment.hxx create mode 100644 sc/source/filter/inc/eeimport.hxx create mode 100644 sc/source/filter/inc/eeparser.hxx create mode 100644 sc/source/filter/inc/excdefs.hxx create mode 100644 sc/source/filter/inc/excdoc.hxx create mode 100644 sc/source/filter/inc/excelchartconverter.hxx create mode 100644 sc/source/filter/inc/excelfilter.hxx create mode 100644 sc/source/filter/inc/excelhandlers.hxx create mode 100644 sc/source/filter/inc/excelvbaproject.hxx create mode 100644 sc/source/filter/inc/excform.hxx create mode 100644 sc/source/filter/inc/excimp8.hxx create mode 100644 sc/source/filter/inc/excrecds.hxx create mode 100644 sc/source/filter/inc/excscen.hxx create mode 100644 sc/source/filter/inc/exp_op.hxx create mode 100644 sc/source/filter/inc/expbase.hxx create mode 100644 sc/source/filter/inc/export/SparklineExt.hxx create mode 100644 sc/source/filter/inc/externallinkbuffer.hxx create mode 100644 sc/source/filter/inc/externallinkfragment.hxx create mode 100644 sc/source/filter/inc/extlstcontext.hxx create mode 100644 sc/source/filter/inc/fapihelper.hxx create mode 100644 sc/source/filter/inc/flttypes.hxx create mode 100644 sc/source/filter/inc/formel.hxx create mode 100644 sc/source/filter/inc/formulabase.hxx create mode 100644 sc/source/filter/inc/formulabuffer.hxx create mode 100644 sc/source/filter/inc/formulaparser.hxx create mode 100644 sc/source/filter/inc/fprogressbar.hxx create mode 100644 sc/source/filter/inc/ftools.hxx create mode 100644 sc/source/filter/inc/htmlexp.hxx create mode 100644 sc/source/filter/inc/htmlimp.hxx create mode 100644 sc/source/filter/inc/htmlpars.hxx create mode 100644 sc/source/filter/inc/imp_op.hxx create mode 100644 sc/source/filter/inc/lotattr.hxx create mode 100644 sc/source/filter/inc/lotfntbf.hxx create mode 100644 sc/source/filter/inc/lotform.hxx create mode 100644 sc/source/filter/inc/lotimpop.hxx create mode 100644 sc/source/filter/inc/lotrange.hxx create mode 100644 sc/source/filter/inc/namebuff.hxx create mode 100644 sc/source/filter/inc/numberformatsbuffer.hxx create mode 100644 sc/source/filter/inc/ooxformulaparser.hxx create mode 100644 sc/source/filter/inc/op.h create mode 100644 sc/source/filter/inc/optab.h create mode 100644 sc/source/filter/inc/orcusfiltersimpl.hxx create mode 100644 sc/source/filter/inc/orcusinterface.hxx create mode 100644 sc/source/filter/inc/otlnbuff.hxx create mode 100644 sc/source/filter/inc/pagesettings.hxx create mode 100644 sc/source/filter/inc/pivotcachebuffer.hxx create mode 100644 sc/source/filter/inc/pivotcachefragment.hxx create mode 100644 sc/source/filter/inc/pivottablebuffer.hxx create mode 100644 sc/source/filter/inc/pivottablefragment.hxx create mode 100644 sc/source/filter/inc/qpro.hxx create mode 100644 sc/source/filter/inc/qproform.hxx create mode 100644 sc/source/filter/inc/qprostyle.hxx create mode 100644 sc/source/filter/inc/querytablebuffer.hxx create mode 100644 sc/source/filter/inc/querytablefragment.hxx create mode 100644 sc/source/filter/inc/revisionfragment.hxx create mode 100644 sc/source/filter/inc/richstring.hxx create mode 100644 sc/source/filter/inc/richstringcontext.hxx create mode 100644 sc/source/filter/inc/root.hxx create mode 100644 sc/source/filter/inc/rtfexp.hxx create mode 100644 sc/source/filter/inc/rtfimp.hxx create mode 100644 sc/source/filter/inc/rtfparse.hxx create mode 100644 sc/source/filter/inc/scenariobuffer.hxx create mode 100644 sc/source/filter/inc/scenariocontext.hxx create mode 100644 sc/source/filter/inc/scfobj.hxx create mode 100644 sc/source/filter/inc/scmem.h create mode 100644 sc/source/filter/inc/sharedformulagroups.hxx create mode 100644 sc/source/filter/inc/sharedstringsbuffer.hxx create mode 100644 sc/source/filter/inc/sharedstringsfragment.hxx create mode 100644 sc/source/filter/inc/sheetdatabuffer.hxx create mode 100644 sc/source/filter/inc/sheetdatacontext.hxx create mode 100644 sc/source/filter/inc/stylesbuffer.hxx create mode 100644 sc/source/filter/inc/stylesfragment.hxx create mode 100644 sc/source/filter/inc/tablebuffer.hxx create mode 100644 sc/source/filter/inc/tablecolumnsbuffer.hxx create mode 100644 sc/source/filter/inc/tablecolumnscontext.hxx create mode 100644 sc/source/filter/inc/tablefragment.hxx create mode 100644 sc/source/filter/inc/themebuffer.hxx create mode 100644 sc/source/filter/inc/tokstack.hxx create mode 100644 sc/source/filter/inc/tool.h create mode 100644 sc/source/filter/inc/unitconverter.hxx create mode 100644 sc/source/filter/inc/viewsettings.hxx create mode 100644 sc/source/filter/inc/workbookfragment.hxx create mode 100644 sc/source/filter/inc/workbookhelper.hxx create mode 100644 sc/source/filter/inc/workbooksettings.hxx create mode 100644 sc/source/filter/inc/worksheetbuffer.hxx create mode 100644 sc/source/filter/inc/worksheetfragment.hxx create mode 100644 sc/source/filter/inc/worksheethelper.hxx create mode 100644 sc/source/filter/inc/worksheetsettings.hxx create mode 100644 sc/source/filter/inc/xcl97esc.hxx create mode 100644 sc/source/filter/inc/xcl97rec.hxx create mode 100644 sc/source/filter/inc/xechart.hxx create mode 100644 sc/source/filter/inc/xecontent.hxx create mode 100644 sc/source/filter/inc/xedbdata.hxx create mode 100644 sc/source/filter/inc/xeescher.hxx create mode 100644 sc/source/filter/inc/xeextlst.hxx create mode 100644 sc/source/filter/inc/xeformula.hxx create mode 100644 sc/source/filter/inc/xehelper.hxx create mode 100644 sc/source/filter/inc/xelink.hxx create mode 100644 sc/source/filter/inc/xename.hxx create mode 100644 sc/source/filter/inc/xepage.hxx create mode 100644 sc/source/filter/inc/xepivot.hxx create mode 100644 sc/source/filter/inc/xepivotxml.hxx create mode 100644 sc/source/filter/inc/xerecord.hxx create mode 100644 sc/source/filter/inc/xeroot.hxx create mode 100644 sc/source/filter/inc/xestream.hxx create mode 100644 sc/source/filter/inc/xestring.hxx create mode 100644 sc/source/filter/inc/xestyle.hxx create mode 100644 sc/source/filter/inc/xetable.hxx create mode 100644 sc/source/filter/inc/xeview.hxx create mode 100644 sc/source/filter/inc/xichart.hxx create mode 100644 sc/source/filter/inc/xicontent.hxx create mode 100644 sc/source/filter/inc/xiescher.hxx create mode 100644 sc/source/filter/inc/xiformula.hxx create mode 100644 sc/source/filter/inc/xihelper.hxx create mode 100644 sc/source/filter/inc/xilink.hxx create mode 100644 sc/source/filter/inc/xiname.hxx create mode 100644 sc/source/filter/inc/xipage.hxx create mode 100644 sc/source/filter/inc/xipivot.hxx create mode 100644 sc/source/filter/inc/xiroot.hxx create mode 100644 sc/source/filter/inc/xistream.hxx create mode 100644 sc/source/filter/inc/xistring.hxx create mode 100644 sc/source/filter/inc/xistyle.hxx create mode 100644 sc/source/filter/inc/xiview.hxx create mode 100644 sc/source/filter/inc/xladdress.hxx create mode 100644 sc/source/filter/inc/xlchart.hxx create mode 100644 sc/source/filter/inc/xlconst.hxx create mode 100644 sc/source/filter/inc/xlcontent.hxx create mode 100644 sc/source/filter/inc/xlescher.hxx create mode 100644 sc/source/filter/inc/xlformula.hxx create mode 100644 sc/source/filter/inc/xllink.hxx create mode 100644 sc/source/filter/inc/xlname.hxx create mode 100644 sc/source/filter/inc/xlpage.hxx create mode 100644 sc/source/filter/inc/xlpivot.hxx create mode 100644 sc/source/filter/inc/xlroot.hxx create mode 100644 sc/source/filter/inc/xlstream.hxx create mode 100644 sc/source/filter/inc/xlstring.hxx create mode 100644 sc/source/filter/inc/xlstyle.hxx create mode 100644 sc/source/filter/inc/xltable.hxx create mode 100644 sc/source/filter/inc/xltools.hxx create mode 100644 sc/source/filter/inc/xltracer.hxx create mode 100644 sc/source/filter/inc/xlview.hxx create mode 100644 sc/source/filter/lotus/filter.cxx create mode 100644 sc/source/filter/lotus/lotattr.cxx create mode 100644 sc/source/filter/lotus/lotfilter.hxx create mode 100644 sc/source/filter/lotus/lotform.cxx create mode 100644 sc/source/filter/lotus/lotimpop.cxx create mode 100644 sc/source/filter/lotus/lotread.cxx create mode 100644 sc/source/filter/lotus/lotus.cxx create mode 100644 sc/source/filter/lotus/memory.cxx create mode 100644 sc/source/filter/lotus/op.cxx create mode 100644 sc/source/filter/lotus/optab.cxx create mode 100644 sc/source/filter/lotus/tool.cxx create mode 100644 sc/source/filter/oox/SparklineFragment.cxx create mode 100644 sc/source/filter/oox/addressconverter.cxx create mode 100644 sc/source/filter/oox/autofilterbuffer.cxx create mode 100644 sc/source/filter/oox/autofiltercontext.cxx create mode 100644 sc/source/filter/oox/biffhelper.cxx create mode 100644 sc/source/filter/oox/chartsheetfragment.cxx create mode 100644 sc/source/filter/oox/commentsbuffer.cxx create mode 100644 sc/source/filter/oox/commentsfragment.cxx create mode 100644 sc/source/filter/oox/condformatbuffer.cxx create mode 100644 sc/source/filter/oox/condformatcontext.cxx create mode 100644 sc/source/filter/oox/connectionsbuffer.cxx create mode 100644 sc/source/filter/oox/connectionsfragment.cxx create mode 100644 sc/source/filter/oox/defnamesbuffer.cxx create mode 100644 sc/source/filter/oox/drawingbase.cxx create mode 100644 sc/source/filter/oox/drawingfragment.cxx create mode 100644 sc/source/filter/oox/excelchartconverter.cxx create mode 100644 sc/source/filter/oox/excelfilter.cxx create mode 100644 sc/source/filter/oox/excelhandlers.cxx create mode 100644 sc/source/filter/oox/excelvbaproject.cxx create mode 100644 sc/source/filter/oox/externallinkbuffer.cxx create mode 100644 sc/source/filter/oox/externallinkfragment.cxx create mode 100644 sc/source/filter/oox/extlstcontext.cxx create mode 100644 sc/source/filter/oox/formulabase.cxx create mode 100644 sc/source/filter/oox/formulabuffer.cxx create mode 100644 sc/source/filter/oox/formulaparser.cxx create mode 100644 sc/source/filter/oox/numberformatsbuffer.cxx create mode 100644 sc/source/filter/oox/ooxformulaparser.cxx create mode 100644 sc/source/filter/oox/pagesettings.cxx create mode 100644 sc/source/filter/oox/pivotcachebuffer.cxx create mode 100644 sc/source/filter/oox/pivotcachefragment.cxx create mode 100644 sc/source/filter/oox/pivottablebuffer.cxx create mode 100644 sc/source/filter/oox/pivottablefragment.cxx create mode 100644 sc/source/filter/oox/querytablebuffer.cxx create mode 100644 sc/source/filter/oox/querytablefragment.cxx create mode 100644 sc/source/filter/oox/revisionfragment.cxx create mode 100644 sc/source/filter/oox/richstring.cxx create mode 100644 sc/source/filter/oox/richstringcontext.cxx create mode 100644 sc/source/filter/oox/scenariobuffer.cxx create mode 100644 sc/source/filter/oox/scenariocontext.cxx create mode 100644 sc/source/filter/oox/sharedstringsbuffer.cxx create mode 100644 sc/source/filter/oox/sharedstringsfragment.cxx create mode 100644 sc/source/filter/oox/sheetdatabuffer.cxx create mode 100644 sc/source/filter/oox/sheetdatacontext.cxx create mode 100644 sc/source/filter/oox/stylesbuffer.cxx create mode 100644 sc/source/filter/oox/stylesfragment.cxx create mode 100644 sc/source/filter/oox/tablebuffer.cxx create mode 100644 sc/source/filter/oox/tablecolumnsbuffer.cxx create mode 100644 sc/source/filter/oox/tablecolumnscontext.cxx create mode 100644 sc/source/filter/oox/tablefragment.cxx create mode 100644 sc/source/filter/oox/themebuffer.cxx create mode 100644 sc/source/filter/oox/unitconverter.cxx create mode 100644 sc/source/filter/oox/viewsettings.cxx create mode 100644 sc/source/filter/oox/workbookfragment.cxx create mode 100644 sc/source/filter/oox/workbookhelper.cxx create mode 100644 sc/source/filter/oox/workbooksettings.cxx create mode 100644 sc/source/filter/oox/worksheetbuffer.cxx create mode 100644 sc/source/filter/oox/worksheetfragment.cxx create mode 100644 sc/source/filter/oox/worksheethelper.cxx create mode 100644 sc/source/filter/oox/worksheetsettings.cxx create mode 100644 sc/source/filter/orcus/filterdetect.cxx create mode 100644 sc/source/filter/orcus/interface.cxx create mode 100644 sc/source/filter/orcus/orcusfiltersimpl.cxx create mode 100644 sc/source/filter/orcus/xmlcontext.cxx create mode 100644 sc/source/filter/qpro/README create mode 100644 sc/source/filter/qpro/qpro.cxx create mode 100644 sc/source/filter/qpro/qproform.cxx create mode 100644 sc/source/filter/qpro/qprostyle.cxx create mode 100644 sc/source/filter/rtf/eeimpars.cxx create mode 100644 sc/source/filter/rtf/expbase.cxx create mode 100644 sc/source/filter/rtf/rtfexp.cxx create mode 100644 sc/source/filter/rtf/rtfimp.cxx create mode 100644 sc/source/filter/rtf/rtfparse.cxx create mode 100644 sc/source/filter/xcl97/XclExpChangeTrack.cxx create mode 100644 sc/source/filter/xcl97/XclImpChangeTrack.cxx create mode 100644 sc/source/filter/xcl97/xcl97esc.cxx create mode 100644 sc/source/filter/xcl97/xcl97rec.cxx create mode 100644 sc/source/filter/xml/SparklineGroupsExport.cxx create mode 100644 sc/source/filter/xml/SparklineGroupsExport.hxx create mode 100644 sc/source/filter/xml/SparklineGroupsImportContext.cxx create mode 100644 sc/source/filter/xml/SparklineGroupsImportContext.hxx create mode 100644 sc/source/filter/xml/XMLCalculationSettingsContext.cxx create mode 100644 sc/source/filter/xml/XMLCalculationSettingsContext.hxx create mode 100644 sc/source/filter/xml/XMLCellRangeSourceContext.cxx create mode 100644 sc/source/filter/xml/XMLCellRangeSourceContext.hxx create mode 100644 sc/source/filter/xml/XMLChangeTrackingExportHelper.cxx create mode 100644 sc/source/filter/xml/XMLChangeTrackingExportHelper.hxx create mode 100644 sc/source/filter/xml/XMLChangeTrackingImportHelper.cxx create mode 100644 sc/source/filter/xml/XMLChangeTrackingImportHelper.hxx create mode 100644 sc/source/filter/xml/XMLCodeNameProvider.cxx create mode 100644 sc/source/filter/xml/XMLCodeNameProvider.hxx create mode 100644 sc/source/filter/xml/XMLColumnRowGroupExport.cxx create mode 100644 sc/source/filter/xml/XMLColumnRowGroupExport.hxx create mode 100644 sc/source/filter/xml/XMLConsolidationContext.cxx create mode 100644 sc/source/filter/xml/XMLConsolidationContext.hxx create mode 100644 sc/source/filter/xml/XMLConverter.cxx create mode 100644 sc/source/filter/xml/XMLConverter.hxx create mode 100644 sc/source/filter/xml/XMLDDELinksContext.cxx create mode 100644 sc/source/filter/xml/XMLDDELinksContext.hxx create mode 100644 sc/source/filter/xml/XMLDetectiveContext.cxx create mode 100644 sc/source/filter/xml/XMLDetectiveContext.hxx create mode 100644 sc/source/filter/xml/XMLEmptyContext.cxx create mode 100644 sc/source/filter/xml/XMLEmptyContext.hxx create mode 100644 sc/source/filter/xml/XMLExportDDELinks.cxx create mode 100644 sc/source/filter/xml/XMLExportDDELinks.hxx create mode 100644 sc/source/filter/xml/XMLExportDataPilot.cxx create mode 100644 sc/source/filter/xml/XMLExportDataPilot.hxx create mode 100644 sc/source/filter/xml/XMLExportDatabaseRanges.cxx create mode 100644 sc/source/filter/xml/XMLExportDatabaseRanges.hxx create mode 100644 sc/source/filter/xml/XMLExportIterator.cxx create mode 100644 sc/source/filter/xml/XMLExportIterator.hxx create mode 100644 sc/source/filter/xml/XMLExportSharedData.cxx create mode 100644 sc/source/filter/xml/XMLExportSharedData.hxx create mode 100644 sc/source/filter/xml/XMLStylesExportHelper.cxx create mode 100644 sc/source/filter/xml/XMLStylesExportHelper.hxx create mode 100644 sc/source/filter/xml/XMLStylesImportHelper.cxx create mode 100644 sc/source/filter/xml/XMLStylesImportHelper.hxx create mode 100644 sc/source/filter/xml/XMLTableHeaderFooterContext.cxx create mode 100644 sc/source/filter/xml/XMLTableHeaderFooterContext.hxx create mode 100644 sc/source/filter/xml/XMLTableMasterPageExport.cxx create mode 100644 sc/source/filter/xml/XMLTableMasterPageExport.hxx create mode 100644 sc/source/filter/xml/XMLTableShapeImportHelper.cxx create mode 100644 sc/source/filter/xml/XMLTableShapeImportHelper.hxx create mode 100644 sc/source/filter/xml/XMLTableShapeResizer.cxx create mode 100644 sc/source/filter/xml/XMLTableShapeResizer.hxx create mode 100644 sc/source/filter/xml/XMLTableShapesContext.cxx create mode 100644 sc/source/filter/xml/XMLTableShapesContext.hxx create mode 100644 sc/source/filter/xml/XMLTableSourceContext.cxx create mode 100644 sc/source/filter/xml/XMLTableSourceContext.hxx create mode 100644 sc/source/filter/xml/XMLTrackedChangesContext.cxx create mode 100644 sc/source/filter/xml/XMLTrackedChangesContext.hxx create mode 100644 sc/source/filter/xml/cachedattraccess.cxx create mode 100644 sc/source/filter/xml/cachedattraccess.hxx create mode 100644 sc/source/filter/xml/celltextparacontext.cxx create mode 100644 sc/source/filter/xml/celltextparacontext.hxx create mode 100644 sc/source/filter/xml/datastreamimport.cxx create mode 100644 sc/source/filter/xml/datastreamimport.hxx create mode 100644 sc/source/filter/xml/editattributemap.cxx create mode 100644 sc/source/filter/xml/editattributemap.hxx create mode 100644 sc/source/filter/xml/importcontext.cxx create mode 100644 sc/source/filter/xml/importcontext.hxx create mode 100644 sc/source/filter/xml/pivotsource.cxx create mode 100644 sc/source/filter/xml/pivotsource.hxx create mode 100644 sc/source/filter/xml/sheetdata.cxx create mode 100644 sc/source/filter/xml/xmlannoi.cxx create mode 100644 sc/source/filter/xml/xmlannoi.hxx create mode 100644 sc/source/filter/xml/xmlbodyi.cxx create mode 100644 sc/source/filter/xml/xmlbodyi.hxx create mode 100644 sc/source/filter/xml/xmlcelli.cxx create mode 100644 sc/source/filter/xml/xmlcelli.hxx create mode 100644 sc/source/filter/xml/xmlcoli.cxx create mode 100644 sc/source/filter/xml/xmlcoli.hxx create mode 100644 sc/source/filter/xml/xmlcondformat.cxx create mode 100644 sc/source/filter/xml/xmlcondformat.hxx create mode 100644 sc/source/filter/xml/xmlconti.cxx create mode 100644 sc/source/filter/xml/xmlconti.hxx create mode 100644 sc/source/filter/xml/xmlcvali.cxx create mode 100644 sc/source/filter/xml/xmlcvali.hxx create mode 100644 sc/source/filter/xml/xmldpimp.cxx create mode 100644 sc/source/filter/xml/xmldpimp.hxx create mode 100644 sc/source/filter/xml/xmldrani.cxx create mode 100644 sc/source/filter/xml/xmldrani.hxx create mode 100644 sc/source/filter/xml/xmlexprt.cxx create mode 100644 sc/source/filter/xml/xmlexprt.hxx create mode 100644 sc/source/filter/xml/xmlexternaltabi.cxx create mode 100644 sc/source/filter/xml/xmlexternaltabi.hxx create mode 100644 sc/source/filter/xml/xmlfilti.cxx create mode 100644 sc/source/filter/xml/xmlfilti.hxx create mode 100644 sc/source/filter/xml/xmlfonte.cxx create mode 100644 sc/source/filter/xml/xmlimprt.cxx create mode 100644 sc/source/filter/xml/xmlimprt.hxx create mode 100644 sc/source/filter/xml/xmllabri.cxx create mode 100644 sc/source/filter/xml/xmllabri.hxx create mode 100644 sc/source/filter/xml/xmlmappingi.cxx create mode 100644 sc/source/filter/xml/xmlmappingi.hxx create mode 100644 sc/source/filter/xml/xmlnexpi.cxx create mode 100644 sc/source/filter/xml/xmlnexpi.hxx create mode 100644 sc/source/filter/xml/xmlrowi.cxx create mode 100644 sc/source/filter/xml/xmlrowi.hxx create mode 100644 sc/source/filter/xml/xmlsceni.cxx create mode 100644 sc/source/filter/xml/xmlsceni.hxx create mode 100644 sc/source/filter/xml/xmlsorti.cxx create mode 100644 sc/source/filter/xml/xmlsorti.hxx create mode 100644 sc/source/filter/xml/xmlstyle.cxx create mode 100644 sc/source/filter/xml/xmlstyle.hxx create mode 100644 sc/source/filter/xml/xmlstyli.cxx create mode 100644 sc/source/filter/xml/xmlstyli.hxx create mode 100644 sc/source/filter/xml/xmlsubti.cxx create mode 100644 sc/source/filter/xml/xmlsubti.hxx create mode 100644 sc/source/filter/xml/xmltabi.cxx create mode 100644 sc/source/filter/xml/xmltabi.hxx create mode 100644 sc/source/filter/xml/xmltransformationi.cxx create mode 100644 sc/source/filter/xml/xmltransformationi.hxx create mode 100644 sc/source/filter/xml/xmlwrap.cxx create mode 100644 sc/source/ui/Accessibility/AccessibilityHints.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleCell.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleCellBase.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleContextBase.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleCsvControl.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleDocument.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleDocumentBase.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleDocumentPagePreview.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleEditObject.cxx create mode 100644 sc/source/ui/Accessibility/AccessiblePageHeader.cxx create mode 100644 sc/source/ui/Accessibility/AccessiblePageHeaderArea.cxx create mode 100644 sc/source/ui/Accessibility/AccessiblePreviewCell.cxx create mode 100644 sc/source/ui/Accessibility/AccessiblePreviewHeaderCell.cxx create mode 100644 sc/source/ui/Accessibility/AccessiblePreviewTable.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleSpreadsheet.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleTableBase.cxx create mode 100644 sc/source/ui/Accessibility/AccessibleText.cxx create mode 100644 sc/source/ui/Accessibility/DrawModelBroadcaster.cxx create mode 100644 sc/source/ui/StatisticsDialogs/AnalysisOfVarianceDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/ChiSquareTestDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/CorrelationDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/CovarianceDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/DescriptiveStatisticsDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/ExponentialSmoothingDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/FTestDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/FourierAnalysisDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/MatrixComparisonGenerator.cxx create mode 100644 sc/source/ui/StatisticsDialogs/MovingAverageDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/RandomNumberGeneratorDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/RegressionDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/SamplingDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/StatisticsInputOutputDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/StatisticsTwoVariableDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/TTestDialog.cxx create mode 100644 sc/source/ui/StatisticsDialogs/TableFillingAndNavigationTools.cxx create mode 100644 sc/source/ui/StatisticsDialogs/ZTestDialog.cxx create mode 100644 sc/source/ui/app/client.cxx create mode 100644 sc/source/ui/app/drwtrans.cxx create mode 100644 sc/source/ui/app/inputhdl.cxx create mode 100644 sc/source/ui/app/inputwin.cxx create mode 100644 sc/source/ui/app/lnktrans.cxx create mode 100644 sc/source/ui/app/msgpool.cxx create mode 100644 sc/source/ui/app/rfindlst.cxx create mode 100644 sc/source/ui/app/scdll.cxx create mode 100644 sc/source/ui/app/scmod.cxx create mode 100644 sc/source/ui/app/seltrans.cxx create mode 100644 sc/source/ui/app/transobj.cxx create mode 100644 sc/source/ui/app/typemap.cxx create mode 100644 sc/source/ui/app/uiitems.cxx create mode 100644 sc/source/ui/attrdlg/attrdlg.cxx create mode 100644 sc/source/ui/attrdlg/scabstdlg.cxx create mode 100644 sc/source/ui/attrdlg/scdlgfact.cxx create mode 100644 sc/source/ui/attrdlg/scdlgfact.hxx create mode 100644 sc/source/ui/attrdlg/scuiexp.cxx create mode 100644 sc/source/ui/attrdlg/tabpages.cxx create mode 100644 sc/source/ui/cctrl/cbnumberformat.cxx create mode 100644 sc/source/ui/cctrl/cbuttonw.cxx create mode 100644 sc/source/ui/cctrl/checklistmenu.cxx create mode 100644 sc/source/ui/cctrl/dpcontrol.cxx create mode 100644 sc/source/ui/cctrl/editfield.cxx create mode 100644 sc/source/ui/cctrl/tbzoomsliderctrl.cxx create mode 100644 sc/source/ui/condformat/colorformat.cxx create mode 100644 sc/source/ui/condformat/condformatdlg.cxx create mode 100644 sc/source/ui/condformat/condformatdlgentry.cxx create mode 100644 sc/source/ui/condformat/condformatdlgitem.cxx create mode 100644 sc/source/ui/condformat/condformathelper.cxx create mode 100644 sc/source/ui/condformat/condformatmgr.cxx create mode 100644 sc/source/ui/dataprovider/csvdataprovider.cxx create mode 100644 sc/source/ui/dataprovider/dataprovider.cxx create mode 100644 sc/source/ui/dataprovider/datatransformation.cxx create mode 100644 sc/source/ui/dataprovider/htmldataprovider.cxx create mode 100644 sc/source/ui/dataprovider/htmldataprovider.hxx create mode 100644 sc/source/ui/dataprovider/sqldataprovider.cxx create mode 100644 sc/source/ui/dataprovider/sqldataprovider.hxx create mode 100644 sc/source/ui/dataprovider/xmldataprovider.cxx create mode 100644 sc/source/ui/dataprovider/xmldataprovider.hxx create mode 100644 sc/source/ui/dbgui/PivotLayoutDialog.cxx create mode 100644 sc/source/ui/dbgui/PivotLayoutTreeList.cxx create mode 100644 sc/source/ui/dbgui/PivotLayoutTreeListBase.cxx create mode 100644 sc/source/ui/dbgui/PivotLayoutTreeListData.cxx create mode 100644 sc/source/ui/dbgui/PivotLayoutTreeListLabel.cxx create mode 100644 sc/source/ui/dbgui/asciiopt.cxx create mode 100644 sc/source/ui/dbgui/consdlg.cxx create mode 100644 sc/source/ui/dbgui/csvcontrol.cxx create mode 100644 sc/source/ui/dbgui/csvgrid.cxx create mode 100644 sc/source/ui/dbgui/csvruler.cxx create mode 100644 sc/source/ui/dbgui/csvsplits.cxx create mode 100644 sc/source/ui/dbgui/csvtablebox.cxx create mode 100644 sc/source/ui/dbgui/dapidata.cxx create mode 100644 sc/source/ui/dbgui/dapitype.cxx create mode 100644 sc/source/ui/dbgui/dbnamdlg.cxx create mode 100644 sc/source/ui/dbgui/dpgroupdlg.cxx create mode 100644 sc/source/ui/dbgui/filtdlg.cxx create mode 100644 sc/source/ui/dbgui/foptmgr.cxx create mode 100644 sc/source/ui/dbgui/imoptdlg.cxx create mode 100644 sc/source/ui/dbgui/pfiltdlg.cxx create mode 100644 sc/source/ui/dbgui/pvfundlg.cxx create mode 100644 sc/source/ui/dbgui/scendlg.cxx create mode 100644 sc/source/ui/dbgui/scuiasciiopt.cxx create mode 100644 sc/source/ui/dbgui/scuiimoptdlg.cxx create mode 100644 sc/source/ui/dbgui/sfiltdlg.cxx create mode 100644 sc/source/ui/dbgui/sortdlg.cxx create mode 100644 sc/source/ui/dbgui/sortkeydlg.cxx create mode 100644 sc/source/ui/dbgui/subtdlg.cxx create mode 100644 sc/source/ui/dbgui/textimportoptions.cxx create mode 100644 sc/source/ui/dbgui/tpsort.cxx create mode 100644 sc/source/ui/dbgui/tpsubt.cxx create mode 100644 sc/source/ui/dbgui/validate.cxx create mode 100644 sc/source/ui/dialogs/SparklineDataRangeDialog.cxx create mode 100644 sc/source/ui/dialogs/SparklineDialog.cxx create mode 100644 sc/source/ui/dialogs/searchresults.cxx create mode 100644 sc/source/ui/docshell/arealink.cxx create mode 100644 sc/source/ui/docshell/autostyl.cxx create mode 100644 sc/source/ui/docshell/datastream.cxx create mode 100644 sc/source/ui/docshell/dbdocfun.cxx create mode 100644 sc/source/ui/docshell/dbdocimp.cxx create mode 100644 sc/source/ui/docshell/docfunc.cxx create mode 100644 sc/source/ui/docshell/docfuncutil.cxx create mode 100644 sc/source/ui/docshell/docsh.cxx create mode 100644 sc/source/ui/docshell/docsh2.cxx create mode 100644 sc/source/ui/docshell/docsh3.cxx create mode 100644 sc/source/ui/docshell/docsh4.cxx create mode 100644 sc/source/ui/docshell/docsh5.cxx create mode 100644 sc/source/ui/docshell/docsh6.cxx create mode 100644 sc/source/ui/docshell/docsh8.cxx create mode 100644 sc/source/ui/docshell/docshimp.hxx create mode 100644 sc/source/ui/docshell/documentlinkmgr.cxx create mode 100644 sc/source/ui/docshell/editable.cxx create mode 100644 sc/source/ui/docshell/externalrefmgr.cxx create mode 100644 sc/source/ui/docshell/impex.cxx create mode 100644 sc/source/ui/docshell/macromgr.cxx create mode 100644 sc/source/ui/docshell/olinefun.cxx create mode 100644 sc/source/ui/docshell/pagedata.cxx create mode 100644 sc/source/ui/docshell/pntlock.cxx create mode 100644 sc/source/ui/docshell/servobj.cxx create mode 100644 sc/source/ui/docshell/sizedev.cxx create mode 100644 sc/source/ui/docshell/tablink.cxx create mode 100644 sc/source/ui/docshell/tpstat.cxx create mode 100644 sc/source/ui/drawfunc/chartsh.cxx create mode 100644 sc/source/ui/drawfunc/drawsh.cxx create mode 100644 sc/source/ui/drawfunc/drawsh2.cxx create mode 100644 sc/source/ui/drawfunc/drawsh4.cxx create mode 100644 sc/source/ui/drawfunc/drawsh5.cxx create mode 100644 sc/source/ui/drawfunc/drformsh.cxx create mode 100644 sc/source/ui/drawfunc/drtxtob.cxx create mode 100644 sc/source/ui/drawfunc/drtxtob1.cxx create mode 100644 sc/source/ui/drawfunc/drtxtob2.cxx create mode 100644 sc/source/ui/drawfunc/fuconarc.cxx create mode 100644 sc/source/ui/drawfunc/fuconcustomshape.cxx create mode 100644 sc/source/ui/drawfunc/fuconpol.cxx create mode 100644 sc/source/ui/drawfunc/fuconrec.cxx create mode 100644 sc/source/ui/drawfunc/fuconstr.cxx create mode 100644 sc/source/ui/drawfunc/fuconuno.cxx create mode 100644 sc/source/ui/drawfunc/fudraw.cxx create mode 100644 sc/source/ui/drawfunc/fuins1.cxx create mode 100644 sc/source/ui/drawfunc/fuins2.cxx create mode 100644 sc/source/ui/drawfunc/fupoor.cxx create mode 100644 sc/source/ui/drawfunc/fusel.cxx create mode 100644 sc/source/ui/drawfunc/fusel2.cxx create mode 100644 sc/source/ui/drawfunc/futext.cxx create mode 100644 sc/source/ui/drawfunc/futext2.cxx create mode 100644 sc/source/ui/drawfunc/futext3.cxx create mode 100644 sc/source/ui/drawfunc/graphsh.cxx create mode 100644 sc/source/ui/drawfunc/mediash.cxx create mode 100644 sc/source/ui/drawfunc/oleobjsh.cxx create mode 100644 sc/source/ui/formdlg/dwfunctr.cxx create mode 100644 sc/source/ui/formdlg/formdata.cxx create mode 100644 sc/source/ui/formdlg/formula.cxx create mode 100644 sc/source/ui/inc/AccessibilityHints.hxx create mode 100644 sc/source/ui/inc/AccessibleCell.hxx create mode 100644 sc/source/ui/inc/AccessibleCellBase.hxx create mode 100644 sc/source/ui/inc/AccessibleContextBase.hxx create mode 100644 sc/source/ui/inc/AccessibleCsvControl.hxx create mode 100644 sc/source/ui/inc/AccessibleDocument.hxx create mode 100644 sc/source/ui/inc/AccessibleDocumentBase.hxx create mode 100644 sc/source/ui/inc/AccessibleDocumentPagePreview.hxx create mode 100644 sc/source/ui/inc/AccessibleEditObject.hxx create mode 100644 sc/source/ui/inc/AccessiblePageHeader.hxx create mode 100644 sc/source/ui/inc/AccessiblePageHeaderArea.hxx create mode 100644 sc/source/ui/inc/AccessiblePreviewCell.hxx create mode 100644 sc/source/ui/inc/AccessiblePreviewHeaderCell.hxx create mode 100644 sc/source/ui/inc/AccessiblePreviewTable.hxx create mode 100644 sc/source/ui/inc/AccessibleSpreadsheet.hxx create mode 100644 sc/source/ui/inc/AccessibleTableBase.hxx create mode 100644 sc/source/ui/inc/AccessibleText.hxx create mode 100644 sc/source/ui/inc/AnalysisOfVarianceDialog.hxx create mode 100644 sc/source/ui/inc/ChartRangeSelectionListener.hxx create mode 100644 sc/source/ui/inc/ChiSquareTestDialog.hxx create mode 100644 sc/source/ui/inc/ChildWindowWrapper.hxx create mode 100644 sc/source/ui/inc/CorrelationDialog.hxx create mode 100644 sc/source/ui/inc/CovarianceDialog.hxx create mode 100644 sc/source/ui/inc/DescriptiveStatisticsDialog.hxx create mode 100644 sc/source/ui/inc/DrawModelBroadcaster.hxx create mode 100644 sc/source/ui/inc/ExponentialSmoothingDialog.hxx create mode 100644 sc/source/ui/inc/FTestDialog.hxx create mode 100644 sc/source/ui/inc/FilterListBox.hxx create mode 100644 sc/source/ui/inc/FourierAnalysisDialog.hxx create mode 100644 sc/source/ui/inc/IAnyRefDialog.hxx create mode 100644 sc/source/ui/inc/MatrixComparisonGenerator.hxx create mode 100644 sc/source/ui/inc/MovingAverageDialog.hxx create mode 100644 sc/source/ui/inc/PivotLayoutDialog.hxx create mode 100644 sc/source/ui/inc/PivotLayoutTreeList.hxx create mode 100644 sc/source/ui/inc/PivotLayoutTreeListBase.hxx create mode 100644 sc/source/ui/inc/PivotLayoutTreeListData.hxx create mode 100644 sc/source/ui/inc/PivotLayoutTreeListLabel.hxx create mode 100644 sc/source/ui/inc/RandomNumberGeneratorDialog.hxx create mode 100644 sc/source/ui/inc/RegressionDialog.hxx create mode 100644 sc/source/ui/inc/SamplingDialog.hxx create mode 100644 sc/source/ui/inc/SparklineDataRangeDialog.hxx create mode 100644 sc/source/ui/inc/SparklineDialog.hxx create mode 100644 sc/source/ui/inc/SparklineRenderer.hxx create mode 100644 sc/source/ui/inc/SparklineShell.hxx create mode 100644 sc/source/ui/inc/StatisticsInputOutputDialog.hxx create mode 100644 sc/source/ui/inc/StatisticsTwoVariableDialog.hxx create mode 100644 sc/source/ui/inc/TTestDialog.hxx create mode 100644 sc/source/ui/inc/TableFillingAndNavigationTools.hxx create mode 100644 sc/source/ui/inc/ZTestDialog.hxx create mode 100644 sc/source/ui/inc/acredlin.hxx create mode 100644 sc/source/ui/inc/anyrefdg.hxx create mode 100644 sc/source/ui/inc/areasave.hxx create mode 100644 sc/source/ui/inc/areasdlg.hxx create mode 100644 sc/source/ui/inc/asciiopt.hxx create mode 100644 sc/source/ui/inc/attrdlg.hxx create mode 100644 sc/source/ui/inc/auditsh.hxx create mode 100644 sc/source/ui/inc/autofmt.hxx create mode 100644 sc/source/ui/inc/autostyl.hxx create mode 100644 sc/source/ui/inc/cbnumberformat.hxx create mode 100644 sc/source/ui/inc/cbutton.hxx create mode 100644 sc/source/ui/inc/cellmergeoption.hxx create mode 100644 sc/source/ui/inc/cellsh.hxx create mode 100644 sc/source/ui/inc/chartsh.hxx create mode 100644 sc/source/ui/inc/checklistmenu.hxx create mode 100644 sc/source/ui/inc/client.hxx create mode 100644 sc/source/ui/inc/cliputil.hxx create mode 100644 sc/source/ui/inc/colorformat.hxx create mode 100644 sc/source/ui/inc/colrowba.hxx create mode 100644 sc/source/ui/inc/condformatdlg.hxx create mode 100644 sc/source/ui/inc/condformatdlgentry.hxx create mode 100644 sc/source/ui/inc/condformatdlgitem.hxx create mode 100644 sc/source/ui/inc/condformathelper.hxx create mode 100644 sc/source/ui/inc/condformatmgr.hxx create mode 100644 sc/source/ui/inc/condformatuno.hxx create mode 100644 sc/source/ui/inc/conflictsdlg.hxx create mode 100644 sc/source/ui/inc/consdlg.hxx create mode 100644 sc/source/ui/inc/content.hxx create mode 100644 sc/source/ui/inc/corodlg.hxx create mode 100644 sc/source/ui/inc/crdlg.hxx create mode 100644 sc/source/ui/inc/crnrdlg.hxx create mode 100644 sc/source/ui/inc/csvcontrol.hxx create mode 100644 sc/source/ui/inc/csvgrid.hxx create mode 100644 sc/source/ui/inc/csvruler.hxx create mode 100644 sc/source/ui/inc/csvsplits.hxx create mode 100644 sc/source/ui/inc/csvtablebox.hxx create mode 100644 sc/source/ui/inc/dapidata.hxx create mode 100644 sc/source/ui/inc/dapitype.hxx create mode 100644 sc/source/ui/inc/datafdlg.hxx create mode 100644 sc/source/ui/inc/dataprovider.hxx create mode 100644 sc/source/ui/inc/dataproviderdlg.hxx create mode 100644 sc/source/ui/inc/datastream.hxx create mode 100644 sc/source/ui/inc/datastreamdlg.hxx create mode 100644 sc/source/ui/inc/datatableview.hxx create mode 100644 sc/source/ui/inc/datatransformation.hxx create mode 100644 sc/source/ui/inc/dbdocfun.hxx create mode 100644 sc/source/ui/inc/dbfunc.hxx create mode 100644 sc/source/ui/inc/dbnamdlg.hxx create mode 100644 sc/source/ui/inc/delcldlg.hxx create mode 100644 sc/source/ui/inc/delcodlg.hxx create mode 100644 sc/source/ui/inc/docfunc.hxx create mode 100644 sc/source/ui/inc/docfuncutil.hxx create mode 100644 sc/source/ui/inc/docsh.hxx create mode 100644 sc/source/ui/inc/dpcontrol.hxx create mode 100644 sc/source/ui/inc/dpgroupdlg.hxx create mode 100644 sc/source/ui/inc/drawsh.hxx create mode 100644 sc/source/ui/inc/drawutil.hxx create mode 100644 sc/source/ui/inc/drawview.hxx create mode 100644 sc/source/ui/inc/drformsh.hxx create mode 100644 sc/source/ui/inc/drtxtob.hxx create mode 100644 sc/source/ui/inc/drwtrans.hxx create mode 100644 sc/source/ui/inc/dwfunctr.hxx create mode 100644 sc/source/ui/inc/editable.hxx create mode 100644 sc/source/ui/inc/editfield.hxx create mode 100644 sc/source/ui/inc/editsh.hxx create mode 100644 sc/source/ui/inc/filldlg.hxx create mode 100644 sc/source/ui/inc/filtdlg.hxx create mode 100644 sc/source/ui/inc/foptmgr.hxx create mode 100644 sc/source/ui/inc/formatsh.hxx create mode 100644 sc/source/ui/inc/formdata.hxx create mode 100644 sc/source/ui/inc/formula.hxx create mode 100644 sc/source/ui/inc/fuconarc.hxx create mode 100644 sc/source/ui/inc/fuconcustomshape.hxx create mode 100644 sc/source/ui/inc/fuconpol.hxx create mode 100644 sc/source/ui/inc/fuconrec.hxx create mode 100644 sc/source/ui/inc/fuconstr.hxx create mode 100644 sc/source/ui/inc/fuconuno.hxx create mode 100644 sc/source/ui/inc/fudraw.hxx create mode 100644 sc/source/ui/inc/fuinsert.hxx create mode 100644 sc/source/ui/inc/fupoor.hxx create mode 100644 sc/source/ui/inc/fusel.hxx create mode 100644 sc/source/ui/inc/futext.hxx create mode 100644 sc/source/ui/inc/gototabdlg.hxx create mode 100644 sc/source/ui/inc/graphsh.hxx create mode 100644 sc/source/ui/inc/gridmerg.hxx create mode 100644 sc/source/ui/inc/gridwin.hxx create mode 100644 sc/source/ui/inc/groupdlg.hxx create mode 100644 sc/source/ui/inc/hdrcont.hxx create mode 100644 sc/source/ui/inc/hfedtdlg.hxx create mode 100644 sc/source/ui/inc/highred.hxx create mode 100644 sc/source/ui/inc/hiranges.hxx create mode 100644 sc/source/ui/inc/imoptdlg.hxx create mode 100644 sc/source/ui/inc/impex.hxx create mode 100644 sc/source/ui/inc/inputhdl.hxx create mode 100644 sc/source/ui/inc/inputwin.hxx create mode 100644 sc/source/ui/inc/inscldlg.hxx create mode 100644 sc/source/ui/inc/inscodlg.hxx create mode 100644 sc/source/ui/inc/instbdlg.hxx create mode 100644 sc/source/ui/inc/invmerge.hxx create mode 100644 sc/source/ui/inc/lbseldlg.hxx create mode 100644 sc/source/ui/inc/linkarea.hxx create mode 100644 sc/source/ui/inc/lnktrans.hxx create mode 100644 sc/source/ui/inc/mediash.hxx create mode 100644 sc/source/ui/inc/mergecellsdialog.hxx create mode 100644 sc/source/ui/inc/msgpool.hxx create mode 100644 sc/source/ui/inc/mtrindlg.hxx create mode 100644 sc/source/ui/inc/mvtabdlg.hxx create mode 100644 sc/source/ui/inc/namecrea.hxx create mode 100644 sc/source/ui/inc/namedefdlg.hxx create mode 100644 sc/source/ui/inc/namedlg.hxx create mode 100644 sc/source/ui/inc/namemgrtable.hxx create mode 100644 sc/source/ui/inc/namepast.hxx create mode 100644 sc/source/ui/inc/navcitem.hxx create mode 100644 sc/source/ui/inc/navipi.hxx create mode 100644 sc/source/ui/inc/navsett.hxx create mode 100644 sc/source/ui/inc/notemark.hxx create mode 100644 sc/source/ui/inc/oleobjsh.hxx create mode 100644 sc/source/ui/inc/olinefun.hxx create mode 100644 sc/source/ui/inc/olinewin.hxx create mode 100644 sc/source/ui/inc/opredlin.hxx create mode 100644 sc/source/ui/inc/optsolver.hxx create mode 100644 sc/source/ui/inc/output.hxx create mode 100644 sc/source/ui/inc/overlayobject.hxx create mode 100644 sc/source/ui/inc/pagedata.hxx create mode 100644 sc/source/ui/inc/pfiltdlg.hxx create mode 100644 sc/source/ui/inc/pfuncache.hxx create mode 100644 sc/source/ui/inc/pgbrksh.hxx create mode 100644 sc/source/ui/inc/pivotsh.hxx create mode 100644 sc/source/ui/inc/pntlock.hxx create mode 100644 sc/source/ui/inc/preview.hxx create mode 100644 sc/source/ui/inc/prevloc.hxx create mode 100644 sc/source/ui/inc/prevwsh.hxx create mode 100644 sc/source/ui/inc/printfun.hxx create mode 100644 sc/source/ui/inc/protectiondlg.hxx create mode 100644 sc/source/ui/inc/pvfundlg.hxx create mode 100644 sc/source/ui/inc/redcom.hxx create mode 100644 sc/source/ui/inc/reffact.hxx create mode 100644 sc/source/ui/inc/refundo.hxx create mode 100644 sc/source/ui/inc/retypepassdlg.hxx create mode 100644 sc/source/ui/inc/rfindlst.hxx create mode 100644 sc/source/ui/inc/scendlg.hxx create mode 100644 sc/source/ui/inc/scui_def.hxx create mode 100644 sc/source/ui/inc/scuiasciiopt.hxx create mode 100644 sc/source/ui/inc/scuiautofmt.hxx create mode 100644 sc/source/ui/inc/scuiimoptdlg.hxx create mode 100644 sc/source/ui/inc/scuitphfedit.hxx create mode 100644 sc/source/ui/inc/searchresults.hxx create mode 100644 sc/source/ui/inc/select.hxx create mode 100644 sc/source/ui/inc/selectionstate.hxx create mode 100644 sc/source/ui/inc/seltrans.hxx create mode 100644 sc/source/ui/inc/servobj.hxx create mode 100644 sc/source/ui/inc/sharedocdlg.hxx create mode 100644 sc/source/ui/inc/shtabdlg.hxx create mode 100644 sc/source/ui/inc/simpref.hxx create mode 100644 sc/source/ui/inc/sizedev.hxx create mode 100644 sc/source/ui/inc/solveroptions.hxx create mode 100644 sc/source/ui/inc/solverutil.hxx create mode 100644 sc/source/ui/inc/solvrdlg.hxx create mode 100644 sc/source/ui/inc/sortdlg.hxx create mode 100644 sc/source/ui/inc/sortkeydlg.hxx create mode 100644 sc/source/ui/inc/spelldialog.hxx create mode 100644 sc/source/ui/inc/spelleng.hxx create mode 100644 sc/source/ui/inc/spellparam.hxx create mode 100644 sc/source/ui/inc/strindlg.hxx create mode 100644 sc/source/ui/inc/styledlg.hxx create mode 100644 sc/source/ui/inc/subtdlg.hxx create mode 100644 sc/source/ui/inc/tabbgcolordlg.hxx create mode 100644 sc/source/ui/inc/tabcont.hxx create mode 100644 sc/source/ui/inc/tabopdlg.hxx create mode 100644 sc/source/ui/inc/tabpages.hxx create mode 100644 sc/source/ui/inc/tabsplit.hxx create mode 100644 sc/source/ui/inc/tabview.hxx create mode 100644 sc/source/ui/inc/tabvwsh.hxx create mode 100644 sc/source/ui/inc/target.hxx create mode 100644 sc/source/ui/inc/tbzoomsliderctrl.hxx create mode 100644 sc/source/ui/inc/textdlgs.hxx create mode 100644 sc/source/ui/inc/textimportoptions.hxx create mode 100644 sc/source/ui/inc/tpcalc.hxx create mode 100644 sc/source/ui/inc/tpcompatibility.hxx create mode 100644 sc/source/ui/inc/tpdefaults.hxx create mode 100644 sc/source/ui/inc/tpformula.hxx create mode 100644 sc/source/ui/inc/tphf.hxx create mode 100644 sc/source/ui/inc/tphfedit.hxx create mode 100644 sc/source/ui/inc/tpprint.hxx create mode 100644 sc/source/ui/inc/tpsort.hxx create mode 100644 sc/source/ui/inc/tpstat.hxx create mode 100644 sc/source/ui/inc/tpsubt.hxx create mode 100644 sc/source/ui/inc/tptable.hxx create mode 100644 sc/source/ui/inc/tpusrlst.hxx create mode 100644 sc/source/ui/inc/tpview.hxx create mode 100644 sc/source/ui/inc/transobj.hxx create mode 100644 sc/source/ui/inc/uiitems.hxx create mode 100644 sc/source/ui/inc/uiobject.hxx create mode 100644 sc/source/ui/inc/undo/UndoDeleteSparkline.hxx create mode 100644 sc/source/ui/inc/undo/UndoDeleteSparklineGroup.hxx create mode 100644 sc/source/ui/inc/undo/UndoEditSparkline.hxx create mode 100644 sc/source/ui/inc/undo/UndoEditSparklineGroup.hxx create mode 100644 sc/source/ui/inc/undo/UndoGroupSparklines.hxx create mode 100644 sc/source/ui/inc/undo/UndoInsertSparkline.hxx create mode 100644 sc/source/ui/inc/undo/UndoUngroupSparklines.hxx create mode 100644 sc/source/ui/inc/undobase.hxx create mode 100644 sc/source/ui/inc/undoblk.hxx create mode 100644 sc/source/ui/inc/undocell.hxx create mode 100644 sc/source/ui/inc/undoconvert.hxx create mode 100644 sc/source/ui/inc/undodat.hxx create mode 100644 sc/source/ui/inc/undodraw.hxx create mode 100644 sc/source/ui/inc/undoolk.hxx create mode 100644 sc/source/ui/inc/undosort.hxx create mode 100644 sc/source/ui/inc/undostyl.hxx create mode 100644 sc/source/ui/inc/undotab.hxx create mode 100644 sc/source/ui/inc/undoutil.hxx create mode 100644 sc/source/ui/inc/validate.hxx create mode 100644 sc/source/ui/inc/viewdata.hxx create mode 100644 sc/source/ui/inc/viewfunc.hxx create mode 100644 sc/source/ui/inc/viewutil.hxx create mode 100644 sc/source/ui/inc/warnbox.hxx create mode 100644 sc/source/ui/inc/xmlsourcedlg.hxx create mode 100644 sc/source/ui/miscdlgs/acredlin.cxx create mode 100644 sc/source/ui/miscdlgs/anyrefdg.cxx create mode 100644 sc/source/ui/miscdlgs/autofmt.cxx create mode 100644 sc/source/ui/miscdlgs/conflictsdlg.cxx create mode 100644 sc/source/ui/miscdlgs/crdlg.cxx create mode 100644 sc/source/ui/miscdlgs/crnrdlg.cxx create mode 100644 sc/source/ui/miscdlgs/datafdlg.cxx create mode 100644 sc/source/ui/miscdlgs/dataproviderdlg.cxx create mode 100644 sc/source/ui/miscdlgs/datastreamdlg.cxx create mode 100644 sc/source/ui/miscdlgs/datatableview.cxx create mode 100644 sc/source/ui/miscdlgs/delcldlg.cxx create mode 100644 sc/source/ui/miscdlgs/delcodlg.cxx create mode 100644 sc/source/ui/miscdlgs/filldlg.cxx create mode 100644 sc/source/ui/miscdlgs/gototabdlg.cxx create mode 100644 sc/source/ui/miscdlgs/groupdlg.cxx create mode 100644 sc/source/ui/miscdlgs/highred.cxx create mode 100644 sc/source/ui/miscdlgs/inscldlg.cxx create mode 100644 sc/source/ui/miscdlgs/inscodlg.cxx create mode 100644 sc/source/ui/miscdlgs/instbdlg.cxx create mode 100644 sc/source/ui/miscdlgs/lbseldlg.cxx create mode 100644 sc/source/ui/miscdlgs/linkarea.cxx create mode 100644 sc/source/ui/miscdlgs/mergecellsdialog.cxx create mode 100644 sc/source/ui/miscdlgs/mtrindlg.cxx create mode 100644 sc/source/ui/miscdlgs/mvtabdlg.cxx create mode 100644 sc/source/ui/miscdlgs/namecrea.cxx create mode 100644 sc/source/ui/miscdlgs/optsolver.cxx create mode 100644 sc/source/ui/miscdlgs/protectiondlg.cxx create mode 100644 sc/source/ui/miscdlgs/redcom.cxx create mode 100644 sc/source/ui/miscdlgs/retypepassdlg.cxx create mode 100644 sc/source/ui/miscdlgs/scuiautofmt.cxx create mode 100644 sc/source/ui/miscdlgs/sharedocdlg.cxx create mode 100644 sc/source/ui/miscdlgs/shtabdlg.cxx create mode 100644 sc/source/ui/miscdlgs/simpref.cxx create mode 100644 sc/source/ui/miscdlgs/solveroptions.cxx create mode 100644 sc/source/ui/miscdlgs/solverutil.cxx create mode 100644 sc/source/ui/miscdlgs/solvrdlg.cxx create mode 100644 sc/source/ui/miscdlgs/strindlg.cxx create mode 100644 sc/source/ui/miscdlgs/tabbgcolordlg.cxx create mode 100644 sc/source/ui/miscdlgs/tabopdlg.cxx create mode 100644 sc/source/ui/miscdlgs/textdlgs.cxx create mode 100644 sc/source/ui/miscdlgs/warnbox.cxx create mode 100644 sc/source/ui/namedlg/namedefdlg.cxx create mode 100644 sc/source/ui/namedlg/namedlg.cxx create mode 100644 sc/source/ui/namedlg/namemgrtable.cxx create mode 100644 sc/source/ui/namedlg/namepast.cxx create mode 100644 sc/source/ui/navipi/content.cxx create mode 100644 sc/source/ui/navipi/navcitem.cxx create mode 100644 sc/source/ui/navipi/navipi.cxx create mode 100644 sc/source/ui/navipi/scenwnd.cxx create mode 100644 sc/source/ui/optdlg/calcoptionsdlg.cxx create mode 100644 sc/source/ui/optdlg/calcoptionsdlg.hxx create mode 100644 sc/source/ui/optdlg/opredlin.cxx create mode 100644 sc/source/ui/optdlg/tpcalc.cxx create mode 100644 sc/source/ui/optdlg/tpcompatibility.cxx create mode 100644 sc/source/ui/optdlg/tpdefaults.cxx create mode 100644 sc/source/ui/optdlg/tpformula.cxx create mode 100644 sc/source/ui/optdlg/tpprint.cxx create mode 100644 sc/source/ui/optdlg/tpusrlst.cxx create mode 100644 sc/source/ui/optdlg/tpview.cxx create mode 100644 sc/source/ui/pagedlg/areasdlg.cxx create mode 100644 sc/source/ui/pagedlg/hfedtdlg.cxx create mode 100644 sc/source/ui/pagedlg/scuitphfedit.cxx create mode 100644 sc/source/ui/pagedlg/tphf.cxx create mode 100644 sc/source/ui/pagedlg/tphfedit.cxx create mode 100644 sc/source/ui/pagedlg/tptable.cxx create mode 100644 sc/source/ui/sidebar/AlignmentPropertyPanel.cxx create mode 100644 sc/source/ui/sidebar/AlignmentPropertyPanel.hxx create mode 100644 sc/source/ui/sidebar/CellAppearancePropertyPanel.cxx create mode 100644 sc/source/ui/sidebar/CellAppearancePropertyPanel.hxx create mode 100644 sc/source/ui/sidebar/CellBorderStyleControl.cxx create mode 100644 sc/source/ui/sidebar/CellBorderStyleControl.hxx create mode 100644 sc/source/ui/sidebar/CellLineStyleControl.cxx create mode 100644 sc/source/ui/sidebar/CellLineStyleControl.hxx create mode 100644 sc/source/ui/sidebar/CellLineStyleValueSet.cxx create mode 100644 sc/source/ui/sidebar/CellLineStyleValueSet.hxx create mode 100644 sc/source/ui/sidebar/NumberFormatControl.cxx create mode 100644 sc/source/ui/sidebar/NumberFormatPropertyPanel.cxx create mode 100644 sc/source/ui/sidebar/NumberFormatPropertyPanel.hxx create mode 100644 sc/source/ui/sidebar/ScPanelFactory.cxx create mode 100644 sc/source/ui/sidebar/ScPanelFactory.hxx create mode 100644 sc/source/ui/sparklines/SparklineAttributes.cxx create mode 100644 sc/source/ui/sparklines/SparklineData.cxx create mode 100644 sc/source/ui/sparklines/SparklineGroup.cxx create mode 100644 sc/source/ui/sparklines/SparklineList.cxx create mode 100644 sc/source/ui/styleui/styledlg.cxx create mode 100644 sc/source/ui/styleui/template.cur create mode 100644 sc/source/ui/uitest/uiobject.cxx create mode 100644 sc/source/ui/undo/UndoDeleteSparkline.cxx create mode 100644 sc/source/ui/undo/UndoDeleteSparklineGroup.cxx create mode 100644 sc/source/ui/undo/UndoEditSparkline.cxx create mode 100644 sc/source/ui/undo/UndoEditSparklineGroup.cxx create mode 100644 sc/source/ui/undo/UndoGroupSparklines.cxx create mode 100644 sc/source/ui/undo/UndoInsertSparkline.cxx create mode 100644 sc/source/ui/undo/UndoUngroupSparklines.cxx create mode 100644 sc/source/ui/undo/areasave.cxx create mode 100644 sc/source/ui/undo/refundo.cxx create mode 100644 sc/source/ui/undo/target.cxx create mode 100644 sc/source/ui/undo/undobase.cxx create mode 100644 sc/source/ui/undo/undoblk.cxx create mode 100644 sc/source/ui/undo/undoblk2.cxx create mode 100644 sc/source/ui/undo/undoblk3.cxx create mode 100644 sc/source/ui/undo/undocell.cxx create mode 100644 sc/source/ui/undo/undocell2.cxx create mode 100644 sc/source/ui/undo/undoconvert.cxx create mode 100644 sc/source/ui/undo/undodat.cxx create mode 100644 sc/source/ui/undo/undodraw.cxx create mode 100644 sc/source/ui/undo/undoolk.cxx create mode 100644 sc/source/ui/undo/undorangename.cxx create mode 100644 sc/source/ui/undo/undosort.cxx create mode 100644 sc/source/ui/undo/undostyl.cxx create mode 100644 sc/source/ui/undo/undotab.cxx create mode 100644 sc/source/ui/undo/undoutil.cxx create mode 100644 sc/source/ui/unoobj/ChartRangeSelectionListener.cxx create mode 100644 sc/source/ui/unoobj/ChartTools.cxx create mode 100644 sc/source/ui/unoobj/PivotTableDataProvider.cxx create mode 100644 sc/source/ui/unoobj/PivotTableDataSequence.cxx create mode 100644 sc/source/ui/unoobj/PivotTableDataSource.cxx create mode 100644 sc/source/ui/unoobj/TablePivotChart.cxx create mode 100644 sc/source/ui/unoobj/TablePivotCharts.cxx create mode 100644 sc/source/ui/unoobj/addruno.cxx create mode 100644 sc/source/ui/unoobj/afmtuno.cxx create mode 100644 sc/source/ui/unoobj/appluno.cxx create mode 100644 sc/source/ui/unoobj/celllistsource.cxx create mode 100644 sc/source/ui/unoobj/celllistsource.hxx create mode 100644 sc/source/ui/unoobj/cellsuno.cxx create mode 100644 sc/source/ui/unoobj/cellvaluebinding.cxx create mode 100644 sc/source/ui/unoobj/cellvaluebinding.hxx create mode 100644 sc/source/ui/unoobj/chart2uno.cxx create mode 100644 sc/source/ui/unoobj/chartuno.cxx create mode 100644 sc/source/ui/unoobj/condformatuno.cxx create mode 100644 sc/source/ui/unoobj/confuno.cxx create mode 100644 sc/source/ui/unoobj/convuno.cxx create mode 100644 sc/source/ui/unoobj/cursuno.cxx create mode 100644 sc/source/ui/unoobj/dapiuno.cxx create mode 100644 sc/source/ui/unoobj/datauno.cxx create mode 100644 sc/source/ui/unoobj/defltuno.cxx create mode 100644 sc/source/ui/unoobj/dispuno.cxx create mode 100644 sc/source/ui/unoobj/docuno.cxx create mode 100644 sc/source/ui/unoobj/drdefuno.cxx create mode 100644 sc/source/ui/unoobj/editsrc.cxx create mode 100644 sc/source/ui/unoobj/eventuno.cxx create mode 100644 sc/source/ui/unoobj/exceldetect.cxx create mode 100644 sc/source/ui/unoobj/exceldetect.hxx create mode 100644 sc/source/ui/unoobj/fielduno.cxx create mode 100644 sc/source/ui/unoobj/filtuno.cxx create mode 100644 sc/source/ui/unoobj/fmtuno.cxx create mode 100644 sc/source/ui/unoobj/forbiuno.cxx create mode 100644 sc/source/ui/unoobj/funcuno.cxx create mode 100644 sc/source/ui/unoobj/linkuno.cxx create mode 100644 sc/source/ui/unoobj/listenercalls.cxx create mode 100644 sc/source/ui/unoobj/miscuno.cxx create mode 100644 sc/source/ui/unoobj/nameuno.cxx create mode 100644 sc/source/ui/unoobj/notesuno.cxx create mode 100644 sc/source/ui/unoobj/optuno.cxx create mode 100644 sc/source/ui/unoobj/pageuno.cxx create mode 100644 sc/source/ui/unoobj/scdetect.cxx create mode 100644 sc/source/ui/unoobj/scdetect.hxx create mode 100644 sc/source/ui/unoobj/servuno.cxx create mode 100644 sc/source/ui/unoobj/shapeuno.cxx create mode 100644 sc/source/ui/unoobj/srchuno.cxx create mode 100644 sc/source/ui/unoobj/styleuno.cxx create mode 100644 sc/source/ui/unoobj/targuno.cxx create mode 100644 sc/source/ui/unoobj/textuno.cxx create mode 100644 sc/source/ui/unoobj/tokenuno.cxx create mode 100644 sc/source/ui/unoobj/unodoc.cxx create mode 100644 sc/source/ui/unoobj/unoreflist.cxx create mode 100644 sc/source/ui/unoobj/viewuno.cxx create mode 100644 sc/source/ui/unoobj/warnpassword.cxx create mode 100644 sc/source/ui/vba/excelvbahelper.cxx create mode 100644 sc/source/ui/vba/excelvbahelper.hxx create mode 100644 sc/source/ui/vba/helperdecl.hxx create mode 100644 sc/source/ui/vba/vbaapplication.cxx create mode 100644 sc/source/ui/vba/vbaapplication.hxx create mode 100644 sc/source/ui/vba/vbaassistant.cxx create mode 100644 sc/source/ui/vba/vbaassistant.hxx create mode 100644 sc/source/ui/vba/vbaaxes.cxx create mode 100644 sc/source/ui/vba/vbaaxes.hxx create mode 100644 sc/source/ui/vba/vbaaxis.cxx create mode 100644 sc/source/ui/vba/vbaaxis.hxx create mode 100644 sc/source/ui/vba/vbaaxistitle.cxx create mode 100644 sc/source/ui/vba/vbaaxistitle.hxx create mode 100644 sc/source/ui/vba/vbaborders.cxx create mode 100644 sc/source/ui/vba/vbaborders.hxx create mode 100644 sc/source/ui/vba/vbacharacters.cxx create mode 100644 sc/source/ui/vba/vbacharacters.hxx create mode 100644 sc/source/ui/vba/vbachart.cxx create mode 100644 sc/source/ui/vba/vbachart.hxx create mode 100644 sc/source/ui/vba/vbachartobject.cxx create mode 100644 sc/source/ui/vba/vbachartobject.hxx create mode 100644 sc/source/ui/vba/vbachartobjects.cxx create mode 100644 sc/source/ui/vba/vbachartobjects.hxx create mode 100644 sc/source/ui/vba/vbacharttitle.cxx create mode 100644 sc/source/ui/vba/vbacharttitle.hxx create mode 100644 sc/source/ui/vba/vbacomment.cxx create mode 100644 sc/source/ui/vba/vbacomment.hxx create mode 100644 sc/source/ui/vba/vbacomments.cxx create mode 100644 sc/source/ui/vba/vbacomments.hxx create mode 100644 sc/source/ui/vba/vbacondition.cxx create mode 100644 sc/source/ui/vba/vbacondition.hxx create mode 100644 sc/source/ui/vba/vbadialog.cxx create mode 100644 sc/source/ui/vba/vbadialog.hxx create mode 100644 sc/source/ui/vba/vbadialogs.cxx create mode 100644 sc/source/ui/vba/vbadialogs.hxx create mode 100644 sc/source/ui/vba/vbaeventshelper.cxx create mode 100644 sc/source/ui/vba/vbaeventshelper.hxx create mode 100644 sc/source/ui/vba/vbafiledialog.cxx create mode 100644 sc/source/ui/vba/vbafiledialog.hxx create mode 100644 sc/source/ui/vba/vbafiledialogitems.cxx create mode 100644 sc/source/ui/vba/vbafiledialogitems.hxx create mode 100644 sc/source/ui/vba/vbafont.cxx create mode 100644 sc/source/ui/vba/vbafont.hxx create mode 100644 sc/source/ui/vba/vbaformat.cxx create mode 100644 sc/source/ui/vba/vbaformat.hxx create mode 100644 sc/source/ui/vba/vbaformatcondition.cxx create mode 100644 sc/source/ui/vba/vbaformatcondition.hxx create mode 100644 sc/source/ui/vba/vbaformatconditions.cxx create mode 100644 sc/source/ui/vba/vbaformatconditions.hxx create mode 100644 sc/source/ui/vba/vbaglobals.cxx create mode 100644 sc/source/ui/vba/vbaglobals.hxx create mode 100644 sc/source/ui/vba/vbahyperlink.cxx create mode 100644 sc/source/ui/vba/vbahyperlink.hxx create mode 100644 sc/source/ui/vba/vbahyperlinks.cxx create mode 100644 sc/source/ui/vba/vbahyperlinks.hxx create mode 100644 sc/source/ui/vba/vbainterior.cxx create mode 100644 sc/source/ui/vba/vbainterior.hxx create mode 100644 sc/source/ui/vba/vbalineshape.cxx create mode 100644 sc/source/ui/vba/vbalineshape.hxx create mode 100644 sc/source/ui/vba/vbamenu.cxx create mode 100644 sc/source/ui/vba/vbamenu.hxx create mode 100644 sc/source/ui/vba/vbamenubar.cxx create mode 100644 sc/source/ui/vba/vbamenubar.hxx create mode 100644 sc/source/ui/vba/vbamenubars.cxx create mode 100644 sc/source/ui/vba/vbamenubars.hxx create mode 100644 sc/source/ui/vba/vbamenuitem.cxx create mode 100644 sc/source/ui/vba/vbamenuitem.hxx create mode 100644 sc/source/ui/vba/vbamenuitems.cxx create mode 100644 sc/source/ui/vba/vbamenuitems.hxx create mode 100644 sc/source/ui/vba/vbamenus.cxx create mode 100644 sc/source/ui/vba/vbamenus.hxx create mode 100644 sc/source/ui/vba/vbaname.cxx create mode 100644 sc/source/ui/vba/vbaname.hxx create mode 100644 sc/source/ui/vba/vbanames.cxx create mode 100644 sc/source/ui/vba/vbanames.hxx create mode 100644 sc/source/ui/vba/vbaoleobject.cxx create mode 100644 sc/source/ui/vba/vbaoleobject.hxx create mode 100644 sc/source/ui/vba/vbaoleobjects.cxx create mode 100644 sc/source/ui/vba/vbaoleobjects.hxx create mode 100644 sc/source/ui/vba/vbaoutline.cxx create mode 100644 sc/source/ui/vba/vbaoutline.hxx create mode 100644 sc/source/ui/vba/vbaovalshape.cxx create mode 100644 sc/source/ui/vba/vbaovalshape.hxx create mode 100644 sc/source/ui/vba/vbapagebreak.cxx create mode 100644 sc/source/ui/vba/vbapagebreak.hxx create mode 100644 sc/source/ui/vba/vbapagebreaks.cxx create mode 100644 sc/source/ui/vba/vbapagebreaks.hxx create mode 100644 sc/source/ui/vba/vbapagesetup.cxx create mode 100644 sc/source/ui/vba/vbapagesetup.hxx create mode 100644 sc/source/ui/vba/vbapalette.cxx create mode 100644 sc/source/ui/vba/vbapalette.hxx create mode 100644 sc/source/ui/vba/vbapane.cxx create mode 100644 sc/source/ui/vba/vbapane.hxx create mode 100644 sc/source/ui/vba/vbapivotcache.cxx create mode 100644 sc/source/ui/vba/vbapivotcache.hxx create mode 100644 sc/source/ui/vba/vbapivottable.cxx create mode 100644 sc/source/ui/vba/vbapivottable.hxx create mode 100644 sc/source/ui/vba/vbapivottables.cxx create mode 100644 sc/source/ui/vba/vbapivottables.hxx create mode 100644 sc/source/ui/vba/vbarange.cxx create mode 100644 sc/source/ui/vba/vbarange.hxx create mode 100644 sc/source/ui/vba/vbasheetobject.cxx create mode 100644 sc/source/ui/vba/vbasheetobject.hxx create mode 100644 sc/source/ui/vba/vbasheetobjects.cxx create mode 100644 sc/source/ui/vba/vbasheetobjects.hxx create mode 100644 sc/source/ui/vba/vbastyle.cxx create mode 100644 sc/source/ui/vba/vbastyle.hxx create mode 100644 sc/source/ui/vba/vbastyles.cxx create mode 100644 sc/source/ui/vba/vbastyles.hxx create mode 100644 sc/source/ui/vba/vbatextboxshape.cxx create mode 100644 sc/source/ui/vba/vbatextboxshape.hxx create mode 100644 sc/source/ui/vba/vbatextframe.cxx create mode 100644 sc/source/ui/vba/vbatextframe.hxx create mode 100644 sc/source/ui/vba/vbatitle.hxx create mode 100644 sc/source/ui/vba/vbavalidation.cxx create mode 100644 sc/source/ui/vba/vbavalidation.hxx create mode 100644 sc/source/ui/vba/vbawindow.cxx create mode 100644 sc/source/ui/vba/vbawindow.hxx create mode 100644 sc/source/ui/vba/vbawindows.cxx create mode 100644 sc/source/ui/vba/vbawindows.hxx create mode 100644 sc/source/ui/vba/vbaworkbook.cxx create mode 100644 sc/source/ui/vba/vbaworkbook.hxx create mode 100644 sc/source/ui/vba/vbaworkbooks.cxx create mode 100644 sc/source/ui/vba/vbaworkbooks.hxx create mode 100644 sc/source/ui/vba/vbaworksheet.cxx create mode 100644 sc/source/ui/vba/vbaworksheet.hxx create mode 100644 sc/source/ui/vba/vbaworksheets.cxx create mode 100644 sc/source/ui/vba/vbaworksheets.hxx create mode 100644 sc/source/ui/vba/vbawsfunction.cxx create mode 100644 sc/source/ui/vba/vbawsfunction.hxx create mode 100644 sc/source/ui/view/SparklineShell.cxx create mode 100644 sc/source/ui/view/auditsh.cxx create mode 100644 sc/source/ui/view/cellmergeoption.cxx create mode 100644 sc/source/ui/view/cellsh.cxx create mode 100644 sc/source/ui/view/cellsh1.cxx create mode 100644 sc/source/ui/view/cellsh2.cxx create mode 100644 sc/source/ui/view/cellsh3.cxx create mode 100644 sc/source/ui/view/cellsh4.cxx create mode 100644 sc/source/ui/view/cliputil.cxx create mode 100644 sc/source/ui/view/colrowba.cxx create mode 100644 sc/source/ui/view/dbfunc.cxx create mode 100644 sc/source/ui/view/dbfunc2.cxx create mode 100644 sc/source/ui/view/dbfunc3.cxx create mode 100644 sc/source/ui/view/dbfunc4.cxx create mode 100644 sc/source/ui/view/drawutil.cxx create mode 100644 sc/source/ui/view/drawvie3.cxx create mode 100644 sc/source/ui/view/drawvie4.cxx create mode 100644 sc/source/ui/view/drawview.cxx create mode 100644 sc/source/ui/view/editsh.cxx create mode 100644 sc/source/ui/view/formatsh.cxx create mode 100644 sc/source/ui/view/gridmerg.cxx create mode 100644 sc/source/ui/view/gridwin.cxx create mode 100644 sc/source/ui/view/gridwin2.cxx create mode 100644 sc/source/ui/view/gridwin3.cxx create mode 100644 sc/source/ui/view/gridwin4.cxx create mode 100644 sc/source/ui/view/gridwin5.cxx create mode 100644 sc/source/ui/view/gridwin_dbgutil.cxx create mode 100644 sc/source/ui/view/hdrcont.cxx create mode 100644 sc/source/ui/view/hintwin.cxx create mode 100644 sc/source/ui/view/imapwrap.cxx create mode 100644 sc/source/ui/view/imapwrap.hxx create mode 100644 sc/source/ui/view/invmerge.cxx create mode 100644 sc/source/ui/view/notemark.cxx create mode 100644 sc/source/ui/view/olinewin.cxx create mode 100644 sc/source/ui/view/output.cxx create mode 100644 sc/source/ui/view/output2.cxx create mode 100644 sc/source/ui/view/output3.cxx create mode 100644 sc/source/ui/view/overlayobject.cxx create mode 100644 sc/source/ui/view/pfuncache.cxx create mode 100644 sc/source/ui/view/pgbrksh.cxx create mode 100644 sc/source/ui/view/pivotsh.cxx create mode 100644 sc/source/ui/view/preview.cxx create mode 100644 sc/source/ui/view/prevloc.cxx create mode 100644 sc/source/ui/view/prevwsh.cxx create mode 100644 sc/source/ui/view/prevwsh2.cxx create mode 100644 sc/source/ui/view/printfun.cxx create mode 100644 sc/source/ui/view/reffact.cxx create mode 100644 sc/source/ui/view/scextopt.cxx create mode 100644 sc/source/ui/view/select.cxx create mode 100644 sc/source/ui/view/selectionstate.cxx create mode 100644 sc/source/ui/view/spellcheckcontext.cxx create mode 100644 sc/source/ui/view/spelldialog.cxx create mode 100644 sc/source/ui/view/spelleng.cxx create mode 100644 sc/source/ui/view/tabcont.cxx create mode 100644 sc/source/ui/view/tabsplit.cxx create mode 100644 sc/source/ui/view/tabview.cxx create mode 100644 sc/source/ui/view/tabview2.cxx create mode 100644 sc/source/ui/view/tabview3.cxx create mode 100644 sc/source/ui/view/tabview4.cxx create mode 100644 sc/source/ui/view/tabview5.cxx create mode 100644 sc/source/ui/view/tabvwsh.cxx create mode 100644 sc/source/ui/view/tabvwsh2.cxx create mode 100644 sc/source/ui/view/tabvwsh3.cxx create mode 100644 sc/source/ui/view/tabvwsh4.cxx create mode 100644 sc/source/ui/view/tabvwsh5.cxx create mode 100644 sc/source/ui/view/tabvwsh8.cxx create mode 100644 sc/source/ui/view/tabvwsh9.cxx create mode 100644 sc/source/ui/view/tabvwsha.cxx create mode 100644 sc/source/ui/view/tabvwshb.cxx create mode 100644 sc/source/ui/view/tabvwshc.cxx create mode 100644 sc/source/ui/view/tabvwshd.cxx create mode 100644 sc/source/ui/view/tabvwshe.cxx create mode 100644 sc/source/ui/view/tabvwshf.cxx create mode 100644 sc/source/ui/view/tabvwshg.cxx create mode 100644 sc/source/ui/view/tabvwshh.cxx create mode 100644 sc/source/ui/view/viewdata.cxx create mode 100644 sc/source/ui/view/viewfun2.cxx create mode 100644 sc/source/ui/view/viewfun3.cxx create mode 100644 sc/source/ui/view/viewfun4.cxx create mode 100644 sc/source/ui/view/viewfun5.cxx create mode 100644 sc/source/ui/view/viewfun6.cxx create mode 100644 sc/source/ui/view/viewfun7.cxx create mode 100644 sc/source/ui/view/viewfunc.cxx create mode 100644 sc/source/ui/view/viewutil.cxx create mode 100644 sc/source/ui/view/waitoff.cxx create mode 100644 sc/source/ui/xmlsource/xmlsourcedlg.cxx (limited to 'sc/source') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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( 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; iGetItemSet(), 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(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(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 pNewPattern; + if(pPattern) + { + pNewPattern.reset( new ScPatternAttr(*pPattern) ); + SCROW nPatternStartRow; + SCROW nPatternEndRow; + GetPatternRange( nPatternStartRow, nPatternEndRow, nTempStartRow ); + + nTempEndRow = std::min( 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( nPatternEndRow, nEndRow ); + if (const ScCondFormatItem* pItem = pPattern->GetItemSet().GetItemIfSet( ATTR_CONDITIONAL )) + { + auto pPatternAttr = std::make_unique( *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 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(*aCell.mpEditText), *pPattern); + + if (pDataArray) + { + std::unique_ptr 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(const_cast(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; nkRemove( *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(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 pNewPattern(new ScPatternAttr(*pOldPattern)); + pNewPattern->SetStyleSheet(const_cast(&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(dest)->SetColor(c); + } +} + +static void SetLine(const SvxBorderLine* dest, const SvxBorderLine* src) +{ + if (dest) + { + SvxBorderLine* pCast = const_cast(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 pNewPattern(new ScPatternAttr(*pOldPattern)); + SfxItemSet& rNewSet = pNewPattern->GetItemSet(); + SCROW nY1 = nStart; + SCROW nY2 = mvData[nPos].nEndRow; + + std::unique_ptr pNewBoxItem( pBoxItem ? pBoxItem->Clone() : nullptr); + std::unique_ptr pNewTLBRItem( pTLBRItem ? pTLBRItem->Clone() : nullptr); + std::unique_ptr 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( &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 && 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(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(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( nRow1, i ? mvData[i-1].nEndRow + 1: 0 ); + SCROW nRowEndCond = std::min( 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 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(*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(*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(*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( + 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(*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( + 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 ( nThisPosIsVisibleEqual(*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= 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(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= nStartRow && mvData[i].nEndRow <= sal::static_int_cast(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(*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(static_cast(nStartRow) + nDy), tools::Long(0)); + SCROW nDestEnd = std::min(static_cast(static_cast(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(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(nDestStart), static_cast(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(static_cast(nStartRow) + nDy), tools::Long(0)); + SCROW nDestEnd = std::min(static_cast(static_cast(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(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(nDestStart), static_cast(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 (nIndexGetMarkEnd( nStartRow, true ); + if (nMarkEnd>rEndRow) + rEndRow = nMarkEnd; + } + } + else + { + rEndRow = rDocument.MaxRow(); + if (pMarkArray) + { + SCROW nMarkEnd = pMarkArray->GetMarkEnd( nStartRow, false ); + if (nMarkEnd0) + 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 + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +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(rItem).nColMerge) + && (nRowMerge == static_cast(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(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(rItem).bProtection) + && (bHideFormula == static_cast(rItem).bHideFormula) + && (bHideCell == static_cast(rItem).bHideCell) + && (bHidePrint == static_cast(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 xContent = + new ScHeaderFooterContentObj(); + xContent->Init(pLeftArea.get(), pCenterArea.get(), pRightArea.get()); + + uno::Reference xCont(xContent); + + rVal <<= xCont; + return true; +} + +bool ScPageHFItem::PutValue( const uno::Any& rVal, sal_uInt8 /* nMemberId */ ) +{ + bool bRet = false; + uno::Reference xContent; + if ( rVal >>= xContent ) + { + if ( xContent.is() ) + { + rtl::Reference 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(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(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(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 + +#include +#include +#include +#include + +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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if DEBUG_AREA_BROADCASTER +#include +#include +#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& 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(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(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(nRow - rSD.nStartRow) / rSD.nSliceRow + + rSD.nCumulatedCol + + static_cast(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 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(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(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 ScBroadcastAreaSlotMachine::GetAllListeners( + const ScRange& rRange, sc::AreaOverlapType eType, sc::ListenerGroupType eGroup ) +{ + std::vector 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 +#include + +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 +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +CellType adjustCellType( CellType eOrig ) +{ + switch (eOrig) + { + case CELLTYPE_EDIT: + return CELLTYPE_STRING; + default: + ; + } + return eOrig; +} + +template +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 +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 +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 +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(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(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(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 +#include +#include +#include + +#include + +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 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& rVals ) +{ + mpImpl->maCells.resize(rVals.size()); + mpImpl->maCells.set(0, rVals.begin(), rVals.end()); + + // Set default text attributes. + std::vector aDefaults(rVals.size(), CellTextAttr()); + mpImpl->maCellTextAttrs.resize(rVals.size()); + mpImpl->maCellTextAttrs.set(0, aDefaults.begin(), aDefaults.end()); +} + +void CellValues::assign( const std::vector& rVals ) +{ + std::vector 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 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 CellValues::getNonEmptySpans() const +{ + std::vector 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(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 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 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(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> TableType; +typedef std::vector> 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()); + std::unique_ptr& rTab2 = m_Tables.back(); + for (size_t nCol = 0; nCol < nCols; ++nCol) + rTab2->push_back(std::make_unique()); + } + } + + 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& 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 TableValues::getNonEmptySpans( SCTAB nTab, SCCOL nCol ) const +{ + std::vector 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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(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 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 const& CopyFromClipContext::getSingleSparkline(size_t nColOffset) const +{ + assert(nColOffset < maSingleSparkline.size()); + return maSingleSparkline[nColOffset]; +} + +void CopyFromClipContext::setSingleSparkline(size_t nColOffset, std::shared_ptr 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 + + +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(rRange.aEnd.Row() - rRange.aStart.Row()) + : nNonFilteredRows - 1; + SCROW nRow1 = 0; + SCROW nRow2 = static_cast(rRange.aEnd.Col() - rRange.aStart.Col()); + nCol1 += static_cast(nRowDelta); + nCol2 += static_cast(nRowDelta); + nRow1 += static_cast(nColDelta); + nRow2 += static_cast(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(rRange1.aEnd.Col() - rRange1.aStart.Col()); + nCol1 += static_cast(nRowDelta); + nCol2 += static_cast(nRowDelta); + nRow1 += static_cast(nColDelta); + nRow2 += static_cast(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 +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +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& 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::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& 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>(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& ScColorFormat::getValues() const +{ + if(!mpCache) + { + mpCache.reset(new ScColorFormatCache); + std::vector& 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& rValues = getValues(); + if(rValues.empty()) + return 0; + return rValues[0]; +} + +double ScColorFormat::getMaxValue() const +{ + std::vector& 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((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& 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(fFloor); + double fDiff = fPercentile * (nSize-1) - fFloor; + std::vector::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& 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 ScColorScaleFormat::GetColor( const ScAddress& rAddr ) const +{ + ScRefCellValue rCell(*mpDoc, rAddr); + if(!rCell.hasNumeric()) + return std::optional(); + + // now we have for sure a value + double nVal = rCell.getValue(); + + if (maColorScales.size() < 2) + return std::optional(); + + double nMin = std::numeric_limits::max(); + double nMax = std::numeric_limits::min(); + calcMinMax(nMin, nMax); + + // this check is for safety + if(nMin >= nMax) + return std::optional(); + + 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(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& 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(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& rValues = getValues(); + return GetPercentile(rValues, fPercentile); + } + + default: + break; + } + + return mpFormatData->mpUpperLimit->GetValue(); +} + +std::unique_ptr 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 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 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 pInfo(new ScIconSetInfo); + + const SfxPoolItem& rPoolItem = mpDoc->GetPattern(rAddr)->GetItem(ATTR_FONT_HEIGHT); + tools::Long aFontHeight = static_cast(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& 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::iterator itr = rIconSetBitmapMap.find(sBitmap); + if (itr != rIconSetBitmapMap.end()) + return itr->second; + + BitmapEx aBitmap(sBitmap); + std::pair aPair(sBitmap, aBitmap); + std::pair::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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 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 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 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( &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 pNewPattern(new ScPatternAttr(*pPattern)); + pNewPattern->SetStyleSheet(const_cast(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 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 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 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 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 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 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 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 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(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 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 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 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(nSrcRow)); + ScPostIt* pNote = maCellNotes.get(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 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 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 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 pNewPattern(new ScPatternAttr( *pPattern )); + pNewPattern->SetStyleSheet( const_cast(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>& pObjects, SCROW nRowStart, SCROW nRowEnd) +{ + assert(static_cast(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& 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(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 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()); + } + +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& mrBounds; + +public: + UpdateRefGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector& 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& mrBounds; + +public: + UpdateRefExpandGroupBoundChecker(const sc::RefUpdateContext& rCxt, std::vector& 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& mrGroups; + +public: + explicit FormulaGroupPicker( std::vector& 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 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 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 ScColumn::GetFormulaGroupEntries() +{ + std::vector 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 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 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(p)->IsMultilineResult(); + } + + /** + * Callback for a block of other types. + */ + std::pair operator() (const sc::CellStoreType::value_type& node, size_t nOffset, size_t nDataSize) + { + typedef std::pair 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 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 maDirtyRows; +public: + void operator() (size_t nRow, ScFormulaCell* pCell) + { + if (pCell->GetDirty() && pCell->GetCode()->IsRecalcModeOnRefMove()) + maDirtyRows.push_back(nRow); + } + + const std::vector& 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// 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 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(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( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs ); + tools::Long nWidth; + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + nWidth = static_cast( 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( rDocument.GetRowHeight( nRow,nTab ) * + (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs ); + } + else + nWidth = static_cast( 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 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 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( rDocument.GetOriginalWidth(nCol,nTab) * fWidthFactor ); + SCCOL nColMerge = pMerge->GetColMerge(); + if (nColMerge > 1) + for (SCCOL nColAdd=1; nColAdd( rDocument.GetColWidth(nCol+nColAdd,nTab) * fWidthFactor ); + nDocWidth -= static_cast( pMargin->GetLeftMargin() * fWidthFactor ) + + static_cast( pMargin->GetRightMargin() * fWidthFactor ) + + 1; // output size is width-1 pixel (due to gridline) + if ( nIndent ) + nDocWidth -= static_cast( 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( aSize.Height() * nCosAbs + aSize.Width() * nSinAbs ); + tools::Long nWidth; + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + nWidth = static_cast( 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( rDocument.GetRowHeight( nRow,nTab ) * + (bInPrintTwips ? 1.0 : nPPT) * nCosAbs / nSinAbs ); + } + else + nWidth = static_cast( 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(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(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( pMargin->GetLeftMargin() * nPPTX ) + + static_cast( 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(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 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(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( + std::min(nWidth / nPPTX, std::numeric_limits::max() / 2.0)); + return nTwips; + } + else + return nOldWidth; +} + +static sal_uInt16 lcl_GetAttribHeight( const ScPatternAttr& rPattern, sal_uInt16 nFontHeightId ) +{ + const SvxFontHeightItem& rFontHeight = + static_cast(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( + std::min( + GetNeededSize( nRow, rCxt.getOutputDevice(), rCxt.getPPTX(), rCxt.getPPTY(), + rCxt.getZoomX(), rCxt.getZoomY(), false, aOptions, + &pPattern) / rCxt.getPPTY(), + double(std::numeric_limits::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 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 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; nParRemoveCharAttribs(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(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 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 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 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 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 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(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(aPos.first->position - 1); +} + +bool ScColumn::GetPrevDataPos(SCROW& rRow) const +{ + std::pair 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 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 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(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(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 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 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(maCells.size()) == GetDoc().GetMaxRowCount() + && "Size of the cell array is incorrect." ); + + assert(sal::static_int_cast(maCellTextAttrs.size()) == GetDoc().GetMaxRowCount() + && "Size of the cell text attribute array is incorrect."); + + assert(sal::static_int_cast(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(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(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(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(nRow); +} + +const SvtBroadcaster* ScColumn::GetBroadcaster(SCROW nRow) const +{ + return maBroadcasters.get(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( 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(nRow); +} + +void ScColumn::CreateSparklineCell(SCROW nRow, std::shared_ptr 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 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(*pGroup); // Copy the group + auto pNewSparkline = std::make_shared(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(nRow); +} + +const ScPostIt* ScColumn::GetCellNote(SCROW nRow) const +{ + return maCellNotes.get(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(const_cast(this)->GetCellNote( rBlockPos, nRow )); +} + +void ScColumn::SetCellNote(SCROW nRow, std::unique_ptr 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(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(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 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 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(*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 maNumVals; + std::vector 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 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 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 +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(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(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(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(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(nArrayLen, + std::numeric_limits::quiet_NaN())); + pNumArray = rCxt.m_NumArrays.back().get(); + } + + (*pNumArray)[nPos] = aRes.mfValue; + } + else + { + if (!pStrArray) + { + rCxt.m_StrArrays.push_back( + std::make_unique(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(it, itEnd)); + sc::FormulaGroupContext::NumArrayType& rArray = *rCxt.m_NumArrays.back(); + // allocate to the requested length. + rArray.resize(nRow2+1, std::numeric_limits::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(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(nRow2+1, + std::numeric_limits::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 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 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 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 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 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& 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 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 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 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +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& 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 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 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* 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& 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& 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& rNewSharedRows ) +{ + assert(rNewSharedRows.empty() || rNewSharedRows.size() == 2 || rNewSharedRows.size() == 4); + ScDocument& rDoc = GetDoc(); + if (rNewSharedRows.empty() || rDoc.IsDelayedFormulaGrouping()) + return; + + auto pPosSet = std::make_shared(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* 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(nRow)) + { + // Upper part will be split off. + pNewSharedRows->push_back(nTopRow); + pNewSharedRows->push_back(nRow - 1); + } + if (static_cast(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(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& 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& 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& 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& 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(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& 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 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 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> getFormulaRanges() + { + std::vector> 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::DeleteCells( + sc::ColumnBlockPosition& rBlockPos, SCROW nRow1, SCROW nRow2, InsertDeleteFlags nDelFlag ) +{ + std::unique_ptr xResult = std::make_unique(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 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 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 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 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 ScColumnData::CreateAttrIterator( SCROW nStartRow, SCROW nEndRow ) const +{ + return std::make_unique( 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 pEditText ) +{ + pEditText->NormalizeString(GetDoc().GetSharedStringPool()); + std::vector 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 pEditText ) +{ + pEditText->NormalizeString(GetDoc().GetSharedStringPool()); + std::vector 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 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 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 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 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& 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 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 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 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(aEntry); + std::optional 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(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 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& 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 EntriesType; + EntriesType maEntries; + +public: + + void operator() (size_t nRow, const ScFormulaCell* p) + { + ScFormulaCell* p2 = const_cast(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 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 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 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 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 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 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 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(p); + return p2->IsValue() ? p2->GetValue() : 0.0; + } + default: + ; + } + + return 0.0; +} + +const EditTextObject* ScColumn::GetEditText( SCROW nRow ) const +{ + std::pair 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 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(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 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(p)->GetErrCode(); +} + +bool ScColumn::HasStringData( SCROW nRow ) const +{ + std::pair 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(p)->IsValue(); + } + default: + ; + } + + return false; +} + +bool ScColumn::HasValueData( SCROW nRow ) const +{ + std::pair 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(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 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(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(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* mpGroupPos; + +public: + explicit GroupFormulaCells(std::vector* 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(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* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +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 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(*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 aTextAttrs(nDestSize, rSrcAttr); + + switch (rSrcCell.meType) + { + case CELLTYPE_VALUE: + { + std::vector 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 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 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 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 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(*pSparklineGroup); + + std::vector aSparklines(nDestSize, nullptr); + ScAddress aCurrentPosition = aDestPosition; + for (size_t i = 0; i < nDestSize; ++i) + { + auto pNewSparkline = std::make_shared(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& 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 aNewSharedRows; + DetachFormulaCells(aPos, rVals.size(), &aNewSharedRows); + + maCells.set(nRow, rVals.begin(), rVals.end()); + std::vector aDefaults(rVals.size()); + maCellTextAttrs.set(nRow, aDefaults.begin(), aDefaults.end()); + + CellStorageModified(); + + StartListeningUnshared( aNewSharedRows); + + std::vector 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 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 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 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 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 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& 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& 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 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 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& rRanges ) +{ + sc::ColumnBlockPosition aBlockPos; + InitBlockPosition(aBlockPos); + CloneFormulaCell(aBlockPos, rSrc, rAttr, rRanges); +} + +std::unique_ptr ScColumn::ReleaseNote( SCROW nRow ) +{ + if (!GetDoc().ValidRow(nRow)) + return nullptr; + + ScPostIt* p = nullptr; + maCellNotes.release(nRow, p); + return std::unique_ptr(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& mrNotes; + SCTAB mnTab; + SCCOL mnCol; + SCROW mnStartRow; + SCROW mnEndRow; +public: + NoteEntryCollector( std::vector& 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& 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& rNotes ) const +{ + std::pair aPos = maCellNotes.position(nStartRow); + sc::CellNoteStoreType::const_iterator it = aPos.first; + if (it == maCellNotes.end()) + // Invalid row number. + return; + + std::pair 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 aStartPos = + maCellNotes.position(nStartRow); + if (aStartPos.first == maCellNotes.end()) + // Invalid row number. + return false; + + std::pair 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 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 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 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 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 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(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> aThisColRowDrawObjects + = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), GetCol(), nRow1, nRow2); + std::map> aOtherColRowDrawObjects + = pDrawLayer->GetObjectsAnchoredToRange(GetTab(), rOther.GetCol(), nRow1, nRow2); + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + std::vector& rThisCellDrawObjects = aThisColRowDrawObjects[nRow]; + if (!rThisCellDrawObjects.empty()) + UpdateDrawObjectsForRow(rThisCellDrawObjects, rOther.GetCol(), nRow); + std::vector& 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 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& rBounds ) + { + rBounds.swap(maBounds); + } +}; + +} + +void ScColumn::SplitFormulaGroupByRelativeRef( const ScRange& rBoundRange ) +{ + if (rBoundRange.aStart.Row() >= GetDoc().MaxRow()) + // Nothing to split. + return; + + std::vector 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& mrListeners; +public: + explicit ListenerCollector( std::vector& 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& mrCells; +public: + explicit FormulaCellCollector( std::vector& rCells ) : mrCells(rCells) {} + + void operator() ( size_t /*nRow*/, ScFormulaCell* p ) + { + mrCells.push_back(p); + } +}; + +} + +void ScColumn::CollectListeners( std::vector& 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& 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 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* 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* 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 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 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(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(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 aValues(nDataSize); + for (auto& rValue : aValues) + { + rStrm.ReadDouble(rValue); + } + maCells.set(nStartRow, aValues.begin(), aValues.end()); + } + break; + case 2: + { + std::vector aStrings(nDataSize); + svl::SharedStringPool& rPool = rDocument.GetSharedStringPool(); + for (auto& rString : aStrings) + { + sal_Int32 nStrLength = 0; + rStrm.ReadInt32(nStrLength); + std::unique_ptr 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 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(nDataSize);) + { + sal_uInt64 nFormulaGroupSize = 0; + rStrm.ReadUInt64(nFormulaGroupSize); + sal_Int32 nStrLength = 0; + rStrm.ReadInt32(nStrLength); + std::unique_ptr 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 +#include +#include +#include + +#include + +ScColumnTextWidthIterator::ScColumnTextWidthIterator(const ScDocument& rDoc, ScColumn& rCol, SCROW nStartRow, SCROW nEndRow) : + mnEnd(static_cast(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(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(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(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 +#include + +namespace sc { + +void ColumnSet::set(SCTAB nTab, SCCOL nCol) +{ + TabsType::iterator itTab = maTabs.find(nTab); + if (itTab == maTabs.end()) + { + std::pair 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& rCols) const +{ + std::vector 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::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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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(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(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 &rRows) const +{ + std::vector 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(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 +#include + +template< typename A, typename D > +ScCompressedArray::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::Search( A nAccess ) const +{ + if (nAccess == 0) + return 0; + + tools::Long nLo = 0; + tools::Long nHi = static_cast(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(pData[i - 1].nEnd); + else + nStart = -1; + tools::Long nEnd = static_cast(pData[i].nEnd); + if (nEnd < static_cast(nAccess)) + nLo = ++i; + else + if (nStart >= static_cast(nAccess)) + nHi = --i; + else + bFound = true; + } + return (bFound ? static_cast(i) : (nAccess < 0 ? 0 : nCount-1)); +} + +template< typename A, typename D > +void ScCompressedArray::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 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(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::CopyFrom( const ScCompressedArray& 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::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::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::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::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::Iterator::operator++() +{ + ++mnRegion; + if (mnRegion > mrArray.pData[mnIndex].nEnd) + ++mnIndex; +} + +template< typename A, typename D > +typename ScCompressedArray::Iterator ScCompressedArray::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::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( (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::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( (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::CopyFromAnded( + const ScBitMaskCompressedArray& 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::GetLastAnyBitAccess( const D& rBitMask ) const +{ + A nEnd = ::std::numeric_limits::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 +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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& 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& pArr1, const std::unique_ptr& 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(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 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 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 aResult = + mpCache->maStrings.emplace(aStr, 1); + + if(!aResult.second) + aResult.first->second++; + } + else + { + std::pair 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(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(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(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 ScConditionEntry::CreateFlatCopiedTokenArray( sal_uInt16 nIndex ) const +{ + assert(nIndex <= 1); + std::unique_ptr 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(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(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(::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(eDay))); + Date aEnd(rActDate - (2 + static_cast(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(eDay))); + Date aEnd(rActDate + (5 - static_cast(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(eDay)), + rActDate + (12 - static_cast(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::Clone(ScDocument* pNewDoc) const +{ + // Real copy of the formula (for Ref Undo/between documents) + if (!pNewDoc) + pNewDoc = pDoc; + + std::unique_ptr 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(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& p1, const std::unique_ptr& 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(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(*rxEntry); + if (rEntry.IsCellValid(rCell, rPos)) + return rEntry.GetStyle(); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Date) + { + const ScCondDateFormatEntry& rEntry = static_cast(*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(*rxEntry); + if (rEntry.IsCellValid(rCell, rPos)) + aData.aStyleName = rEntry.GetStyle(); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Colorscale && !aData.mxColorScale) + { + const ScColorScaleFormat& rEntry = static_cast(*rxEntry); + aData.mxColorScale = rEntry.GetColor(rPos); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Databar && !aData.pDataBar) + { + const ScDataBarFormat& rEntry = static_cast(*rxEntry); + aData.pDataBar = rEntry.GetDataBarInfo(rPos); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Iconset && !aData.pIconSet) + { + const ScIconSetFormat& rEntry = static_cast(*rxEntry); + aData.pIconSet = rEntry.GetIconSetInfo(rPos); + } + else if(rxEntry->GetType() == ScFormatEntry::Type::Date && aData.aStyleName.isEmpty()) + { + const ScCondDateFormatEntry& rEntry = static_cast(*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(*rxEntry).CompileAll(); +} + +void ScConditionalFormat::CompileXML() +{ + for(auto& rxEntry : maEntries) + if(rxEntry->GetType() == ScFormatEntry::Type::Condition || + rxEntry->GetType() == ScFormatEntry::Type::ExtCondition) + static_cast(*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(rCxt.mnOldPos, rCxt.mnNewPos); + SCTAB nMaxTab = std::max(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(*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(*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(*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 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& 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 +#include + +#include +#include + +#include +#include +#include +#include + +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& 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(::tools::Time::hourPerDay) + + aTime.Minutes / static_cast(::tools::Time::minutePerDay) + + aTime.Seconds / static_cast(::tools::Time::secondPerDay) + + aTime.NanoSeconds / static_cast(::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(::tools::Time::hourPerDay) + + aStamp.Minutes / static_cast(::tools::Time::minutePerDay) + + aStamp.Seconds / static_cast(::tools::Time::secondPerDay) + + aStamp.NanoSeconds / static_cast(::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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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; (iGetEntry(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(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(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 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(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::quiet_NaN()) + , mnError(FormulaError::NONE), mbIsNumber(true) +{ +} + +ScDBQueryDataIterator::ScDBQueryDataIterator(ScDocument& rDocument, const ScInterpreterContext& rContext, std::unique_ptr pParam) : + mpParam (std::move(pParam)) +{ + switch (mpParam->GetType()) + { + case ScDBQueryParamBase::INTERNAL: + { + ScDBQueryParamInternal* p = static_cast(mpParam.get()); + mpData.reset(new DataAccessInternal(p, rDocument, rContext)); + } + break; + case ScDBQueryParamBase::MATRIX: + { + ScDBQueryParamMatrix* p = static_cast(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(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(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(r.maPos->position)); + } + + SCROW nRow = std::max(static_cast(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* 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& 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 + +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 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// 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 ); + + 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( *this ), + ScResId(STR_STYLENAME_STANDARD) ); + else + mvPoolDefaults[ ATTR_PATTERN - ATTR_STARTINDEX ] = + new ScPatternAttr( SfxItemSetFixed( *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(static_cast(rNew)).SetKey(mnCurrentMaxKey); + } + return rNew; +} + +void ScDocumentPool::StyleDeleted( const ScStyleSheet* pStyle ) +{ + for (const SfxPoolItem* pItem : GetItemSurrogates( ATTR_PATTERN )) + { + ScPatternAttr* pPattern = const_cast(dynamic_cast(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(dynamic_cast(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 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(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(*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(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(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(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(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_NULLVALS: + rText = ScResId(STR_SCATTR_PAGE_NULLVALS) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_FORMULAS: + rText = ScResId(STR_SCATTR_PAGE_FORMULAS) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_NOTES: + rText = ScResId(STR_SCATTR_PAGE_NOTES) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_GRID: + rText = ScResId(STR_SCATTR_PAGE_GRID) + aStrSep; + rText += static_cast(rItem).GetValue() ? aStrYes : aStrNo ; + break; + + case ATTR_PAGE_SCALETOPAGES: + { + sal_uInt16 nPagNo = static_cast(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(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(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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(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(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 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(pSourceDoc->maTabs.size()); i++) + if (pSourceDoc->maTabs[i]) + if (!pMarks || pMarks->GetTableSelect(i)) + { + OUString aString = pSourceDoc->maTabs[i]->GetName(); + if ( i < static_cast(maTabs.size()) ) + { + maTabs[i].reset( new ScTable(*this, i, aString) ); + + } + else + { + if( i > static_cast(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(maTabs.size())) + { + maTabs.resize(nTab+1); + } + maTabs[nTab].reset( new ScTable(*this, nTab, "baeh") ); + if (nTab < static_cast(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(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(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(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(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()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(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(nOldPos), static_cast(nNewPos) ); + + bValid = true; + } + } + return bValid; +} + +bool ScDocument::CopyTab( SCTAB nOldPos, SCTAB nNewPos, const ScMarkData* pOnlyMarked ) +{ + if (SC_TAB_APPEND == nNewPos || nNewPos >= static_cast(maTabs.size())) + nNewPos = static_cast(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(maTabs.size())) + { + nNewPos = static_cast(maTabs.size()); + maTabs.emplace_back(new ScTable(*this, nNewPos, aName)); + } + else + { + if (ValidTab(nNewPos) && (nNewPos < static_cast(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(nOldPos), static_cast(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(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(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(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& 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 pData ) +{ + pConsolidateDlgData = std::move(pData); +} + +void ScDocument::SetChangeViewSettings(const ScChangeViewSettings& rNew) +{ + if (pChangeViewSettings==nullptr) + pChangeViewSettings.reset( new ScChangeViewSettings ); + + *pChangeViewSettings=rNew; +} + +std::unique_ptr ScDocument::CreateFieldEditEngine() +{ + std::unique_ptr 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& 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(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(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(pAction)-> + GetFromRange().aStart.Tab() == cell.Col() ) + { + ScRange aRange = static_cast(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(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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace com::sun::star; + +namespace { + +void sortAndRemoveDuplicates(std::vector& rStrings, bool bCaseSens) +{ + if (bCaseSens) + { + std::sort(rStrings.begin(), rStrings.end(), ScTypedStrData::LessCaseSensitive()); + std::vector::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::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(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>& 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(new ScRangeName( *pName )) ); + } + } +} + +void ScDocument::GetRangeNameMap(std::map& aRangeNameMap) +{ + for (SCTAB i = 0; i < static_cast(maTabs.size()); ++i) + { + if (!maTabs[i]) + continue; + ScRangeName* p = maTabs[i]->GetRangeName(); + if (!p ) + { + p = new ScRangeName(); + SetRangeName(i, std::unique_ptr(p)); + } + OUString aTableName = maTabs[i]->GetName(); + aRangeNameMap.insert(std::pair(aTableName,p)); + } + if (!pRangeName) + { + pRangeName.reset(new ScRangeName()); + } + aRangeNameMap.insert(std::pair(STR_GLOBAL_RANGE_NAME, pRangeName.get())); +} + +ScRangeName* ScDocument::GetRangeName(SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(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 pNew) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return; + + return maTabs[nTab]->SetRangeName(std::move(pNew)); +} + +void ScDocument::SetRangeName( std::unique_ptr 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 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; iGetCount(); + 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 pNewChartListenerCollection, + bool bSetChartRangeLists ) +{ + std::unique_ptr 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(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetScenario(bFlag); +} + +bool ScDocument::IsScenario( SCTAB nTab ) const +{ + return ValidTab(nTab) && nTab < static_cast(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(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(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(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetTabBgColor(rColor); +} + +bool ScDocument::IsDefaultTabBgColor( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(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(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(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsScenario()) + rFlags = maTabs[nTab]->GetScenarioFlags(); +} + +bool ScDocument::IsLinked( SCTAB nTab ) const +{ + return ValidTab(nTab) && nTab < static_cast(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(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkMode(); + return ScLinkMode::NONE; +} + +OUString ScDocument::GetLinkDoc( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkDoc(); + return OUString(); +} + +OUString ScDocument::GetLinkFlt( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkFlt(); + return OUString(); +} + +OUString ScDocument::GetLinkOpt( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkOpt(); + return OUString(); +} + +OUString ScDocument::GetLinkTab( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetLinkTab(); + return OUString(); +} + +sal_uLong ScDocument::GetLinkRefreshDelay( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(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(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(maTabs.size()); + for (SCTAB i=0; iIsLinked() + && 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(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(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetSheetEvents(); + return nullptr; +} + +void ScDocument::SetSheetEvents( SCTAB nTab, std::unique_ptr pNew ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetSheetEvents( std::move(pNew) ); +} + +bool ScDocument::HasSheetEventScript( SCTAB nTab, ScSheetEventId nEvent, bool bWithVbaEvents ) const +{ + if (nTab < static_cast(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(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(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(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(maTabs.size()) && maTabs[nTab] && !maTabs[nTab]->GetCalcNotification()) + maTabs[nTab]->SetCalcNotification(true); +} + +void ScDocument::ResetCalcNotifications() +{ + SCTAB nSize = static_cast(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(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(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(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(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->TestRemoveSubTotals( rParam ); +} + +void ScDocument::RemoveSubTotals( SCTAB nTab, ScSubTotalParam& rParam ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->RemoveSubTotals( rParam ); +} + +bool ScDocument::DoSubTotals( SCTAB nTab, ScSubTotalParam& rParam ) +{ + return ValidTab(nTab) && nTab < static_cast(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(maTabs.size()); + for (SCTAB nTab=0; nTabmaTabs[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(maTabs.size()) + && nDestTab < static_cast(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(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(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(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->HasScenarioRange( rRange ); +} + +const ScRangeList* ScDocument::GetScenarioRanges( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetScenarioRanges(); + + return nullptr; +} + +bool ScDocument::IsActiveScenario( SCTAB nTab ) const +{ + return ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsActiveScenario( ); +} + +void ScDocument::SetActiveScenario( SCTAB nTab, bool bActive ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetActiveScenario( bActive ); +} + +bool ScDocument::TestCopyScenario( SCTAB nSrcTab, SCTAB nDestTab ) const +{ + if (ValidTab(nSrcTab) && nSrcTab < static_cast(maTabs.size()) + && nDestTab < static_cast(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& 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 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 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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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& 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(maTabs.size())) + return; + + if (!maTabs[nTab]) + return; + + std::set 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(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; iGetColWidth(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(nHeight) * nRows; + if (nAdd + rTwips >= nStopTwips) + { + sal_Int64 nDiff = nAdd + rTwips - nStopTwips; + nRows -= static_cast(nDiff / nHeight); + nAdd = static_cast(nHeight) * nRows; + // We're looking for a value that satisfies loop condition. + if (nAdd + rTwips >= nStopTwips) + { + --nRows; + nAdd -= nHeight; + } + bStop = true; + } + rTwips += static_cast(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(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 && nX1GetColWidth(nX2, bHiddenAsZero); + if (nSize+nAdd < nTwips && nX2 if(nSize+nAdd if(nSize+nAddisProtected(); +} + +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(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(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetProtection(); + + return nullptr; +} + +void ScDocument::SetTabProtection(SCTAB nTab, const ScTableProtection* pProtect) +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size())) + return; + + maTabs[nTab]->SetProtection(pProtect); +} + +void ScDocument::CopyTabProtection(SCTAB nTabSrc, SCTAB nTabDest) +{ + if (!ValidTab(nTabSrc) || nTabSrc >= static_cast(maTabs.size()) || nTabDest >= static_cast(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(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 pNewOptions ) +{ + pExtDocOptions = std::move(pNewOptions); +} + +void ScDocument::SetClipOptions(std::unique_ptr 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(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(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(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(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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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( maTabs.size() ) && maTabs[nFTab] && + nVTab < static_cast( 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( fHorMaxAngle / fHorStepAngle ); + bool bDoneHorMove = false; + + while ( !bDoneHorMove && !bHorMoveError && nHorIter++ < nHorMaxIter ) + { + double fHorAngle = fHorStepAngle * static_cast( 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(nCol2, MaxCol()); + nRow2 = std::min(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(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(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(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(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(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(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(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(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(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(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(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(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(floor( log10( fVal ) )); + else if ( fVal < 0.0 ) + nExp = static_cast(floor( log10( -fVal ) )); + nPrecision -= nExp; + short nInteger = static_cast(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(GetDocOptions().GetStdPrecision()); + // #i115512# no rounding for automatic decimals + if (nPrecision == static_cast(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 pNew, SCTAB nTab ) +{ + if(!pNew) + return 0; + + if(ValidTab(nTab) && nTab < static_cast(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 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(*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(*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(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetCondFormList(); + + return nullptr; +} + +void ScDocument::SetCondFormList( ScConditionalFormatList* pList, SCTAB nTab ) +{ + if(ValidTab(nTab) && nTab < static_cast(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(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 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(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((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((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(( nTempOther + nMaxCont + nUnknown )) ); + for (SCCOLROW i=nTempOther; i<=nMax && nBest>0; i++) // stop at 0 + { + if (bColumns) + nComp = ColDifferences( static_cast(nThisRow), nThisTab, rOtherDoc, static_cast(i), nOtherTab, nEndCol, pTranslate ); + else + nComp = RowDifferences( nThisRow, nThisTab, rOtherDoc, i, nOtherTab, static_cast(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(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 pOtherTabs(new SCTAB[nThisCount]); + SCTAB nThisTab; + + // compare tables with identical names + OUString aThisName; + OUString aOtherName; + for (nThisTab=0; nThisTabMAXTAB; 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 pTempRows(new SCCOLROW[nThisEndRow+1]); + std::unique_ptr pOtherRows(new SCCOLROW[nThisEndRow+1]); + std::unique_ptr 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(nOtherEndCol + 1); + // nThisEndCol ... 0 + for ( nThisCol = nThisEndCol+1; nThisCol > 0; ) + { + --nThisCol; + SCCOL nOtherCol = static_cast(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(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(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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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(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(maTabs.size()) && maTabs[nTab]) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(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(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(maTabs.size()); + for (sal_uInt16 nTab=0; nTabGetPage(nTab); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 && + static_cast(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( rRangesVector.size() ); + uno::Sequence< OUString > aRangeStrings(nCount); + auto aRangeStringsRange = asNonConstRange(aRangeStrings); + for( sal_Int32 nN=0; nNGetPageCount(); + for (sal_uInt16 nTab=0; nTab(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(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(maTabs.size()) && maTabs[nTab]; nTab++) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 && + static_cast(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 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 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(maTabs.size()) && maTabs[nTab]; nTab++) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( pObject->GetObjIdentifier() == SdrObjKind::OLE2 && + static_cast(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(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; nTabGetPage(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(maTabs.size()); nTab++) + { + if (!maTabs[nTab]) + continue; + + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(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(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(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 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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( nTab1, 0, nMaxTab); + nTab2 = std::clamp( 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(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(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(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& 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace com::sun::star; + +namespace { + +sal_uInt16 getScaleValue(SfxStyleSheetBase& rStyle, sal_uInt16 nWhich) +{ + return static_cast(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>(*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(nFlags) ) ); + pSet->Put( SfxBoolItem( SID_PRINTER_NOTFOUND_WARN, officecfg::Office::Common::Print::Warning::NotFound::get() ) ); + + mpPrinter = VclPtr::Create( std::move(pSet) ); + mpPrinter->SetMapMode(MapMode(MapUnit::Map100thMM)); + UpdateDrawPrinter(); + mpPrinter->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() ); + } + + return mpPrinter; +} + +void ScDocument::SetPrinter( VclPtr 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 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(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::Create(DeviceFormat::GRAYSCALE); +#else + mpVirtualDevice_100th_mm = VclPtr::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; iGetPageStyle() == 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; iGetPageStyle() == 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; iGetPageStyle() == 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(nR) != nRow2 - nRow1 + 1 || static_cast(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(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(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(maTabs.size()) || !maTabs[aScope.Tab()]) + aScope.setTab(0); + + ScTable* pTab = maTabs[aScope.Tab()].get(); + ScStyleSheet* pStyle = static_cast(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 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(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(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(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( 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( 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(pBase)) + if ( !bExport40 || pLink->GetMode() == SC_DDE_DEFAULT ) + ++nDdeCount; + } + + // Header + + ScMultipleWriteHeader aHdr( rStream ); + rStream.WriteUInt16( nDdeCount ); + + // Save links + + for (i=0; i(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; iInsertDDELink(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 aRefLinks; + for (sal_uInt16 i = 0; i < nCount; ++i) + { + ::sfx2::SvBaseLink* pBase = rLinks[i].get(); + ScExternalRefLink* pRefLink = dynamic_cast(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 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(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( 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( 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(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( 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(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(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(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(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 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( 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(GetAttr( nCol, nRow, nTab, nWhich ))->GetValue(); + } + + uno::Sequence 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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(nSrcPos)); + SdrPage* pNewPage = mpDrawLayer->GetPage(static_cast(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( *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(maTabs.size()); nTab++) + if (maTabs[nTab]) + nDrawPages = nTab + 1; // needed number of pages + + for (nTab=0; nTab(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(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(pObject)->IsChart(); + } + + return false; +} + +IMPL_LINK( ScDocument, GetUserDefinedColor, sal_uInt16, nColorIndex, Color* ) +{ + rtl::Reference xColorList; + if (mpDrawLayer) + xColorList = mpDrawLayer->GetColorList(); + else + { + ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); + if (!pColorList.is()) + pColorList = XColorList::CreateStdColorList(); + xColorList = pColorList; + } + return const_cast(&(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(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(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(nTab)); + OSL_ENSURE(pPage,"Page ?"); + if (!pPage) + return; + + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if (SdrGrafObj* pGrafObj = dynamic_cast(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(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(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(maTabs.size()) && maTabs[nTab]) + { + SdrPage* pPage = mpDrawLayer->GetPage(static_cast(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 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(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(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(dynamic_cast(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(dynamic_cast(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(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& ScDocument::GetForbiddenCharacters() const +{ + return xForbiddenCharacters; +} + +void ScDocument::SetForbiddenCharacters(const std::shared_ptr& 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(nAsianKerning); +} + +void ScDocument::SetAsianKerning(bool bNew) +{ + nAsianKerning = static_cast(bNew); + if ( mpEditEngine ) + mpEditEngine->SetKernAsianPunctuation( static_cast( nAsianKerning ) ); + if ( mpDrawLayer ) + mpDrawLayer->SetKernAsianPunctuation( static_cast( 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +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 getMarkedTableRange(const std::vector& rTables, const ScMarkData& rMark) +{ + SCTAB nTabStart = MAXTAB; + SCTAB nTabEnd = 0; + SCTAB nMax = static_cast(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(nTabStart,nTabEnd); +} + +void collectUIInformation(std::map&& 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 ScDefaultAttrSet; + +void ScDocument::MakeTable( SCTAB nTab,bool _bNeedsNameCheck ) +{ + if ( !(ValidTab(nTab) && ( nTab >= static_cast(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(maTabs.size())) + { + maTabs[nTab].reset( new ScTable(*this, nTab, aString) ); + } + else + { + while(nTab > static_cast(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(maTabs.size())) + if (maTabs[nTab]) + return true; + + return false; +} + +bool ScDocument::GetHashCode( SCTAB nTab, sal_Int64& rHashCode ) const +{ + if (ValidTab(nTab) && nTab < static_cast(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(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(maTabNames.size())) + return maTabNames[nTab]; + return OUString(); +} + +bool ScDocument::SetCodeName( SCTAB nTab, const OUString& rName ) +{ + if (ValidTab(nTab) && nTab < static_cast(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(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(maTabs.size()); i++) + if (maTabs[i]) + { + if (aUpperName == maTabs[i]->GetUpperName()) + { + rTab = i; + return true; + } + } + rTab = 0; + return false; +} + +std::vector ScDocument::GetAllTableNames() const +{ + std::vector 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(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetAnonymousDBData(); + return nullptr; +} + +SCTAB ScDocument::GetTableCount() const +{ + return static_cast(maTabs.size()); +} + +void ScDocument::SetAnonymousDBData(SCTAB nTab, std::unique_ptr pDBData) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetAnonymousDBData(std::move(pDBData)); +} + +void ScDocument::SetAnonymousDBData( std::unique_ptr 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(maTabs.size())+1; !bOk ; i++ ) + { + rName = aStrTable + OUString::number(static_cast(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(i)); + } + while (!ValidNewTabName(aName.toString()) && (i < MAXTAB+1)); + rName = aName.makeStringAndClear(); + } + } +} + +void ScDocument::CreateValidTabNames(std::vector& 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(maTabs.size())+1; + + for (SCTAB j = 0; j < nCount; ++j) + { + bool bOk = false; + while(!bOk) + { + rName = aStrTable; + rName.append(static_cast(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(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(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(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(this->GetDocumentShell()->GetModel()); + SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); + } + } + + return bValid; +} + +bool ScDocument::InsertTabs( SCTAB nPos, const std::vector& rNames, + bool bNamesValid ) +{ + SCTAB nNewSheets = static_cast(rNames.size()); + SCTAB nTabCount = static_cast(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(maTabs.size())) + { + if (maTabs[nTab]) + { + SCTAB nTabCount = static_cast(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(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(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(maTabs.size())) + { + if (maTabs[nTab]) + { + SCTAB nTabCount = static_cast(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(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(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(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 (maTabs.size())) + if (maTabs[nTab]) + maTabs[nTab]->SetVisible(bVisible); +} + +bool ScDocument::IsVisible( SCTAB nTab ) const +{ + if (ValidTab(nTab) && nTab < static_cast (maTabs.size())) + if (maTabs[nTab]) + return maTabs[nTab]->IsVisible(); + + return false; +} + +bool ScDocument::IsStreamValid( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast (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 (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 (maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->IsPendingRowHeights(); + + return false; +} + +void ScDocument::SetPendingRowHeights( SCTAB nTab, bool bSet ) +{ + if ( ValidTab(nTab) && nTab < static_cast (maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetPendingRowHeights( bSet ); +} + +void ScDocument::SetLayoutRTL( SCTAB nTab, bool bRTL, ScObjectHandling eObjectHandling) +{ + if ( !(ValidTab(nTab) && nTab < static_cast (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(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 (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 (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 (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 (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 (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 (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 (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 (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 (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(nEndRow - nStartRow + 1); + + bool bTest = true; + for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast(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(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(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(maTabs.size()) ); + ScRange aShiftedRange(nStartCol, nStartRow, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd); + sc::EndListeningContext aEndListenCxt(*this); + + std::vector 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(nSize), 0); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(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(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(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(maTabs.size()); i++) + if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i))) + maTabs[i]->UpdateDrawRef( URM_INSDEL, + nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab, + 0, static_cast(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(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(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(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(nSize), 0 ); + } + else + DelBroadcastAreasInRange( ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( nEndCol, MaxRow(), nTabRangeEnd ) ) ); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + sc::RefUpdateContext aCxt(*this); + const bool bLastRowIncluded = (static_cast(nStartRow + nSize) == GetMaxRowCount() && ValidRow(nStartRow)); + if ( ValidRow(nStartRow+nSize) || bLastRowIncluded ) + { + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + aCxt.meMode = URM_INSDEL; + aCxt.mnRowDelta = -static_cast(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(maTabs.size()) ) ); + } + + if (pUndoOutline) + *pUndoOutline = false; + + // Keep track of the positions of all formula groups that have been joined + // during row deletion. + std::vector aGroupPos; + + for ( i = nStartTab; i <= nEndTab && i < static_cast(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(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(nEndCol - nStartCol + 1); + + bool bTest = true; + for (SCTAB i=nStartTab; i<=nEndTab && bTest && i < static_cast(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(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(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(maTabs.size()) ); + do + { + UpdateBroadcastAreas( URM_INSDEL, ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), static_cast(nSize), 0, 0 ); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(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(maTabs.size()) ) ); + + for (i=nStartTab; i<=nEndTab && i < static_cast(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(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(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(maTabs.size()) ); + do + { + if ( ValidCol(sal::static_int_cast(nStartCol+nSize)) ) + { + DelBroadcastAreasInRange( ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( sal::static_int_cast(nStartCol+nSize-1), nEndRow, nTabRangeEnd ) ) ); + UpdateBroadcastAreas( URM_INSDEL, ScRange( + ScAddress( sal::static_int_cast(nStartCol+nSize), nStartRow, nTabRangeStart ), + ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), -static_cast(nSize), 0, 0 ); + } + else + DelBroadcastAreasInRange( ScRange( + ScAddress( nStartCol, nStartRow, nTabRangeStart ), + ScAddress( MaxCol(), nEndRow, nTabRangeEnd ) ) ); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + + sc::RefUpdateContext aCxt(*this); + const bool bLastColIncluded = (static_cast(nStartCol + nSize) == GetMaxColCount() && ValidCol(nStartCol)); + if ( ValidCol(sal::static_int_cast(nStartCol+nSize)) || bLastColIncluded ) + { + lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ); + aCxt.meMode = URM_INSDEL; + aCxt.mnColDelta = -static_cast(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(nStartCol+nSize), nStartRow, nTabRangeStart, + MaxCol(), nEndRow, nTabRangeEnd); + } + do + { + UpdateReference(aCxt, pRefUndoDoc, true, false); + } + while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, static_cast(maTabs.size()) ) ); + } + + if (pUndoOutline) + *pUndoOutline = false; + + for (i = nStartTab; i <= nEndTab && i < static_cast(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(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(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 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(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(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(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(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(maTabs.size())) + maTabs[nTab] = std::move(pTable); + else + maTabs.push_back(std::move(pTable)); + } + else + { + if (nTab < static_cast(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(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(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(maTabs.size()) && nTab2 < static_cast(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(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(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(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(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(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(maTabs.size()) ? maTabs[rSrcRange.aStart.Tab()].get() : nullptr; + ScTable* pDestTab = nDestTab < static_cast(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(maTabs.size()) && nTab < static_cast(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(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(aClipRange.aEnd.Row() - aClipRange.aStart.Row()), + static_cast(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(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(*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(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(maTabs.size()); i++) + { + if (maTabs[i] && rMark.GetTableSelect(i) ) + { + while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast(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(rClipTabs.size()); + } + } + if (!(rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS)) + return; + + nClipTab = 0; + for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < static_cast(maTabs.size()); i++) + { + if (maTabs[i] && rMark.GetTableSelect(i) ) + { + while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast(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(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( aRefCxt.maRegroupCols); + StartNeededListeners( pColSet); + } + + SetInsertingFromOtherDoc( bOldInserting); + } + } + else + { + aRefCxt.meMode = URM_COPY; + UpdateReference(aRefCxt, rCxt.getUndoDoc(), false); + } + + nClipTab = (nClipTab+nFollow+1) % static_cast(rClipTabs.size()); + i = sal::static_int_cast( 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(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(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( nThisEndX - aClipRange.aEnd.Col() ); + nThisEndY = sal::static_int_cast( 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( nXw + nDestAddX ); + nYw = sal::static_int_cast( 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 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(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(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 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(nCol1 - rRange.aStart.Col()); + SCROW nDy = static_cast(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(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(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(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(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(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(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(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 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& ScDocument::GetFormulaGroupContext() +{ + ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE); + if (!mpFormulaGroupCxt) + mpFormulaGroupCxt = std::make_shared(); + + 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(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(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(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(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(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(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(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(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(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(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(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(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(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(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetErrCode( rPos ); + return FormulaError::NONE; +} + +void ScDocument::ResetChanged( const ScRange& rRange ) +{ + SCTAB nTabSize = static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->ShowCol( nCol, bShow ); +} + +void ScDocument::ShowRow(SCROW nRow, SCTAB nTab, bool bShow) +{ + if ( ValidTab(nTab) && nTab < static_cast(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(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(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(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetRowFlags( nStartRow, nEndRow, nNewFlags ); +} + +CRFlags ScDocument::GetColFlags( SCCOL nCol, SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(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(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetRowFlags( nRow ); + OSL_FAIL("wrong table number"); + return CRFlags::NONE; +} + +void ScDocument::GetAllRowBreaks(set& rBreaks, SCTAB nTab, bool bPage, bool bManual) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return; + maTabs[nTab]->GetAllRowBreaks(rBreaks, bPage, bManual); +} + +void ScDocument::GetAllColBreaks(set& 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(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(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(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(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(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(maTabs.size()) || !maTabs[nTab] || !ValidCol(nCol)) + return; + + maTabs[nTab]->RemoveColBreak(nCol, bPage, bManual); +} + +Sequence ScDocument::GetRowBreakData(SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return Sequence(); + + return maTabs[nTab]->GetRowBreakData(); +} + +bool ScDocument::RowHidden(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(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(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(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(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(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(maTabs.size()) || !maTabs[nTab]) + return ::std::numeric_limits::max(); + + return maTabs[nTab]->FirstVisibleRow(nStartRow, nEndRow); +} + +SCROW ScDocument::LastVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return ::std::numeric_limits::max(); + + return maTabs[nTab]->LastVisibleRow(nStartRow, nEndRow); +} + +SCROW ScDocument::CountVisibleRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(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(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(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(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(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(maTabs.size()) || !maTabs[nTab]) + return ::std::numeric_limits::max(); + + return maTabs[nTab]->FirstNonFilteredRow(nStartRow, nEndRow); +} + +SCROW ScDocument::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(maTabs.size()) || !maTabs[nTab]) + return ::std::numeric_limits::max(); + + return maTabs[nTab]->LastNonFilteredRow(nStartRow, nEndRow); +} + +SCROW ScDocument::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const +{ + if (!ValidTab(nTab) || nTab >= static_cast(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(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(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetLastFlaggedRow(); + return 0; +} + +SCCOL ScDocument::GetLastChangedColFlagsWidth( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetLastChangedColFlagsWidth(); + return 0; +} + +SCROW ScDocument::GetLastChangedRowFlagsWidth( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetLastChangedRowFlagsWidth(); + return 0; +} + +SCCOL ScDocument::GetNextDifferentChangedColFlagsWidth( SCTAB nTab, SCCOL nStart) const +{ + if ( ValidTab(nTab) && nTab < static_cast(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(maTabs.size()) || !maTabs[nTab]) + return 0; + + const ScBitMaskCompressedArray* 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(nEndRow - nStartRow + 1); + aAttr.nFirst = nStartRow; + aSet.insert(aAttr); + } + else + { + aAttr.nCount = aItr->nCount + static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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( 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(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(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 pAttr ) +{ + if (ValidTab(nTab) && nTab < static_cast(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 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(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(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetPattern( rPos, rAttr ); +} + +std::unique_ptr ScDocument::CreateSelectionPattern( const ScMarkData& rMark, bool bDeep ) +{ + ScMergePatternState aState; + + if ( rMark.IsMultiMarked() ) // multi selection + { + SCTAB nMax = static_cast(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(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 pPattern(new ScPatternAttr( std::move(*aState.pItemSet) )); + if (aState.mbValidPatternId) + pPattern->SetKey(aState.mnPatternId); + + return pPattern; + } + else + return std::unique_ptr(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(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(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(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(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(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(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(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(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->LockTable(); + else + { + OSL_FAIL("wrong table number"); + } +} + +void ScDocument::UnlockTable(SCTAB nTab) +{ + if ( ValidTab(nTab) && nTab < static_cast(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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 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(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(maTabs.size()) && maTabs[nTab]) + { + sc::AutoCalcSwitch aACSwitch(*this, false); + + std::vector 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(&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(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(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(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(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(dynamic_cast(pItem)); + if (pPattern) + pPattern->UpdateStyleSheet(*this); + } + const_cast(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(dynamic_cast(pItem)); + if (pPattern) + pPattern->StyleToName(); + } + const_cast(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(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(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->PageStyleModified( rNewName ); +} + +void ScDocument::SetPageStyle( SCTAB nTab, const OUString& rName ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetPageStyle( rName ); +} + +OUString ScDocument::GetPageStyle( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + return maTabs[nTab]->GetPageStyle(); + + return OUString(); +} + +void ScDocument::SetPageSize( SCTAB nTab, const Size& rSize ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetPageSize( rSize ); +} + +Size ScDocument::GetPageSize( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(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(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->SetRepeatArea( nStartCol, nEndCol, nStartRow, nEndRow ); +} + +void ScDocument::InvalidatePageBreaks(SCTAB nTab) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->InvalidatePageBreaks(); +} + +void ScDocument::UpdatePageBreaks( SCTAB nTab, const ScRange* pUserArea ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->UpdatePageBreaks( pUserArea ); +} + +void ScDocument::RemoveManualBreaks( SCTAB nTab ) +{ + if ( ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab] ) + maTabs[nTab]->RemoveManualBreaks(); +} + +bool ScDocument::HasManualBreaks( SCTAB nTab ) const +{ + if ( ValidTab(nTab) && nTab < static_cast(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(maTabs.size()) && maTabs[nTab] && maTabs[nTab]->IsPrintEntireSheet(); +} + +sal_uInt16 ScDocument::GetPrintRangeCount( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(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(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetPrintRange(nPos); + + return nullptr; +} + +std::optional ScDocument::GetRepeatColRange( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetRepeatColRange(); + + return std::nullopt; +} + +std::optional ScDocument::GetRepeatRowRange( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + return maTabs[nTab]->GetRepeatRowRange(); + + return std::nullopt; +} + +void ScDocument::ClearPrintRanges( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->ClearPrintRanges(); +} + +void ScDocument::AddPrintRange( SCTAB nTab, const ScRange& rNew ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->AddPrintRange( rNew ); +} + +void ScDocument::SetPrintEntireSheet( SCTAB nTab ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetPrintEntireSheet(); +} + +void ScDocument::SetRepeatColRange( SCTAB nTab, std::optional oNew ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetRepeatColRange( std::move(oNew) ); +} + +void ScDocument::SetRepeatRowRange( SCTAB nTab, std::optional oNew ) +{ + if (ValidTab(nTab) && nTab < static_cast(maTabs.size()) && maTabs[nTab]) + maTabs[nTab]->SetRepeatRowRange( std::move(oNew) ); +} + +std::unique_ptr ScDocument::CreatePrintRangeSaver() const +{ + const SCTAB nCount = static_cast(maTabs.size()); + std::unique_ptr pNew(new ScPrintRangeSaver( nCount )); + for (SCTAB i=0; iFillPrintSaver( pNew->GetTabData(i) ); + return pNew; +} + +void ScDocument::RestorePrintRanges( const ScPrintRangeSaver& rSaver ) +{ + const SCTAB nCount = rSaver.GetTabCount(); + const SCTAB maxIndex = std::min(nCount, static_cast(maTabs.size())); + for (SCTAB i=0; iRestorePrintRanges( 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(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(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 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(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(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(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 xVBA( + mpShell->GetBasicContainer(), uno::UNO_QUERY); + + return xVBA.is() && xVBA->getVBACompatibilityMode(); + } + catch (const lang::NotInitializedException&) {} + + return false; +} + +// Sparklines +std::shared_ptr 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(); +} + +bool ScDocument::HasSparkline(ScAddress const & rPosition) +{ + return bool(GetSparkline(rPosition)); +} + +sc::Sparkline* ScDocument::CreateSparkline(ScAddress const& rPosition, std::shared_ptr 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 pSparklineGroup; + return GetSparklineGroupInRange(rRange, pSparklineGroup); +} + +bool ScDocument::GetSparklineGroupInRange(ScRange const& rRange, std::shared_ptr& rGroup) +{ + std::shared_ptr 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 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(); +} + +// 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(maTabs.size())) + return maTabs[nTab]->GetNote(nCol, nRow); + else + return nullptr; + +} + +void ScDocument::SetNote(const ScAddress& rPos, std::unique_ptr pNote) +{ + return SetNote(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pNote)); +} + +void ScDocument::SetNote(SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr pNote) +{ + if (ValidTab(nTab) && nTab < static_cast(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 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(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 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& rNotes ) const +{ + for (const auto & pTab : maTabs) + { + if (!pTab) + continue; + + pTab->GetAllNoteEntries(rNotes); + } +} + +void ScDocument::GetAllNoteEntries( SCTAB nTab, std::vector& rNotes ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return; + + return pTab->GetAllNoteEntries( rNotes ); +} + +void ScDocument::GetNotesInRange( const ScRangeList& rRangeList, std::vector& 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 pCache ) +{ + pAutoNameCache = std::move(pCache); +} + +thread_local ScDocumentThreadSpecific ScDocument::maThreadSpecific; + +ScRecursionHelper& ScDocument::GetRecursionHelper() +{ + if (!IsThreadedGroupCalcInProgress()) + { + if (!maNonThreaded.xRecursionHelper) + maNonThreaded.xRecursionHelper = std::make_unique(); + return *maNonThreaded.xRecursionHelper; + } + else + { + if (!maThreadSpecific.xRecursionHelper) + maThreadSpecific.xRecursionHelper = std::make_unique(); + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +// 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(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& 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 ScDocument::GetDocColors() +{ + std::set 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(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(*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>& 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 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& 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( -1, -1 ); + } + else + { + auto it = pDelayedStartListeningFormulaCells.find( column ); + if( it != pDelayedStartListeningFormulaCells.end()) + { + if( it->second.first != -1 ) + { + auto pPosSet = std::make_shared(*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* 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* 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& 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& 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 mpCxt; +public: + explicit StartNeededListenersHandler( ScDocument& rDoc ) : mpCxt(std::make_shared(rDoc)) {} + explicit StartNeededListenersHandler( ScDocument& rDoc, const std::shared_ptr& rpColSet ) : + mpCxt(std::make_shared(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& 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(*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& 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(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 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(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 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 ScDocument::QueryColumnsWithFormulaCells( SCTAB nTab ) const +{ + const ScTable* pTab = FetchTable(nTab); + if (!pTab) + return std::set{}; + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +struct ColAttr +{ + bool mbLatinNumFmtOnly; + + ColAttr() : mbLatinNumFmtOnly(false) {} +}; + +struct TabAttr +{ + std::vector maCols; +}; + +} + +struct ScDocumentImportImpl +{ + ScDocument& mrDoc; + sc::StartListeningContext maListenCxt; + std::vector maBlockPosSet; + SvtScriptType mnDefaultScriptNumeric; + bool mbFuzzing; + std::vector maTabAttrs; + std::unordered_map 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 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 pFC = + std::make_unique(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 pFC = + std::make_unique(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 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 pFC = + std::make_unique(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(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 aCopied(nFillSize, aRefCell.mfValue); + pBlockPos->miCellPos = rCells.set( + pBlockPos->miCellPos, rPos.Row()+1, aCopied.begin(), aCopied.end()); + break; + } + case CELLTYPE_STRING: + { + std::vector 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(rDocImpl.mrDoc.GetSheetLimits(), mrDocImpl.mnDefaultScriptNumeric)) + {} + + std::shared_ptr 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 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 +#include +#include +#include +#include + +#include + +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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if DUMP_PIVOT_TABLE +#include +#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 +#include +#include +#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 +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& 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::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::iterator itUniqueEnd = + std::unique(aBuckets.begin(), aBuckets.end(), EqualByOrderIndex()); + + // Copy the unique values into items. + std::vector::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 LabelSet; + +void normalizeAddLabel(const OUString& rLabel, std::vector& 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 normalizeLabels(const std::vector& rColData) +{ + std::vector 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 normalizeLabels(const ScDPCache::DBConnector& rDB, const sal_Int32 nLabelCount) +{ + std::vector 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 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 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; + std::queue maQueue; + std::mutex maMutex; + std::condition_variable maCond; + + size_t mnMaxQueue; + +public: + ThreadQueue( size_t nMaxQueue ) : mnMaxQueue(nMaxQueue) {} + + void push( std::function aFunc ) + { + std::unique_lock 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 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 aColData(mnColumnCount, InitColumnData(rDoc.GetSheetLimits())); + maFields.reserve(mnColumnCount); + for (SCCOL i = 0; i < mnColumnCount; ++i) + maFields.push_back(std::make_unique()); + + 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()); + + // Get column titles and types. + maLabelNames = normalizeLabels(rDB, mnColumnCount); + + std::vector 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 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(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 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(maFields.size()); + if (nDim < nSourceCount) + return maFields[nDim]->mpGroup.get(); + + nDim -= nSourceCount; + if (nDim < static_cast(maGroupFields.size())) + return maGroupFields[nDim].get(); + + return nullptr; +} + +OUString ScDPCache::GetDimensionName(std::vector::size_type nDim) const +{ + OSL_ENSURE(nDim < maLabelNames.size()-1 , "ScDPTableDataCache::GetDimensionName"); + OSL_ENSURE(maLabelNames.size() == static_cast (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(nDim); + size_t nItemId = static_cast(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(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()); + return static_cast(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(maFields.size()); + if (nDim < nSourceCount) + { + maFields.at(nDim)->mpGroup.reset(new GroupItems(rNumInfo, nGroupType)); + return; + } + + nDim -= nSourceCount; + if (nDim < static_cast(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(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(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& rIds) const +{ + if (nDim < 0) + return; + + tools::Long nSourceCount = static_cast(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(i + nOffset)); + + return; + } + + nDim -= nSourceCount; + if (nDim < static_cast(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(i)); + } +} + +namespace { + +struct ClearGroupItems +{ + void operator() (const std::unique_ptr& 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(maFields.size()); + if (nDim < nSourceCount) + { + if (!maFields.at(nDim)->mpGroup) + return nullptr; + + return &maFields[nDim]->mpGroup->maInfo; + } + + nDim -= nSourceCount; + if (nDim < static_cast(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(maFields.size()); + if (nDim < nSourceCount) + { + if (!maFields.at(nDim)->mpGroup) + return 0; + + return maFields[nDim]->mpGroup->mnGroupType; + } + + nDim -= nSourceCount; + if (nDim < static_cast(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::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 +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +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* 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* 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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 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 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& rCriteria, const std::unordered_set& 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& 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 emptyEntries{}; + return emptyEntries; + } + return maFieldEntries[nColumn]; +} + +void ScDPFilteredCache::filterTable(const vector& rCriteria, Sequence< Sequence >& rTabData, + const std::unordered_set& rRepeatIfEmptyDims) +{ + sal_Int32 nRowSize = getRowSize(); + SCCOL nColSize = getColSize(); + + if (!nRowSize) + // no data to filter. + return; + + // Row first, then column. + vector< Sequence > tableData; + tableData.reserve(nRowSize+1); + + // Header first. + Sequence 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 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(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& rCriteria, + const std::unordered_set& 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(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& 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 + +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 + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +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&& rValues, const ScDPNumGroupInfo& rInfo); + + virtual bool match(const ScDPItemData &rCellData) const override; + virtual std::vector getMatchValues() const override; +private: + std::vector maValues; + ScDPNumGroupInfo maNumInfo; +}; + +} + +ScDPGroupNumFilter::ScDPGroupNumFilter( std::vector&& 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 ScDPGroupNumFilter::getMatchValues() const +{ + return std::vector(); +} + +namespace { + +class ScDPGroupDateFilter : public ScDPFilteredCache::FilterBase +{ +public: + ScDPGroupDateFilter( + std::vector&& rValues, const Date& rNullDate, const ScDPNumGroupInfo& rNumInfo); + + virtual bool match(const ScDPItemData & rCellData) const override; + virtual std::vector getMatchValues() const override; + +private: + std::vector maValues; + Date maNullDate; + ScDPNumGroupInfo maNumInfo; +}; + +} + +ScDPGroupDateFilter::ScDPGroupDateFilter( + std::vector&& 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(approxFloor(rCellData.GetValue())); + switch (nGroupType) + { + case DataPilotFieldGroupBy::YEARS: + { + sal_Int32 year = static_cast(date.GetYear()); + if (year == nValue) + return true; + } + break; + case DataPilotFieldGroupBy::QUARTERS: + { + sal_Int32 qtr = 1 + (static_cast(date.GetMonth()) - 1) / 3; + if (qtr == nValue) + return true; + } + break; + case DataPilotFieldGroupBy::MONTHS: + { + sal_Int32 month = static_cast(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 ScDPGroupDateFilter::getMatchValues() const +{ + return std::vector(); +} + +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& 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& ScDPNumGroupDimension::GetNumEntries( + SCCOL nSourceDim, const ScDPCache* pCache) const +{ + if (!maMemberEntries.empty()) + return maMemberEntries; + + pCache->GetGroupDimMemberIds(nSourceDim, maMemberEntries); + return maMemberEntries; +} + +ScDPGroupTableData::ScDPGroupTableData( const shared_ptr& 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(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( 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( nSourceCount + aGroups.size() ) ); // data layout dimension? +} + +bool ScDPGroupTableData::IsDateDimension(sal_Int32 nDim) +{ + if ( nDim >= nSourceCount ) + { + if ( nDim == sal::static_int_cast( 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( 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; iDisposeData(); +} + +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& rCriteria) +{ + // Build dimension ID to object map for group dimensions. + typedef std::unordered_map GroupFieldMapType; + GroupFieldMapType aGroupFieldIds; + + for (const auto& rGroup : aGroups) + { + aGroupFieldIds.emplace(rGroup.GetGroupDim(), &rGroup); + } + + vector 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 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( + std::move(aMatchValues), pDoc->GetFormatTable()->GetNullDate(), *pNumInfo); + } + else + { + // This dimension is grouped by numeric ranges. + aCri.mpFilter = + std::make_shared(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( + 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* pGrpFilter = + static_cast(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&& rCriteria, std::unordered_set&& rCatDims) +{ + ModifyFilterCriteria(rCriteria); + pSourceData->FilterCacheTable(std::move(rCriteria), std::move(rCatDims)); +} + +void ScDPGroupTableData::GetDrillDownData(std::vector&& rCriteria, std::unordered_set&&rCatDims, Sequence< Sequence >& 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& rItems, const vector& 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(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 +#include + +#include +#include +#include + +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::infinity(); + meType = RangeStart; +} + +void ScDPItemData::SetRangeLast() +{ + DisposeString(); + mfValue = std::numeric_limits::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 + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +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 mxRowSet; + uno::Reference mxRow; + uno::Reference mxMetaData; + Date maNullDate; + +public: + DBConnector(ScDPCache& rCache, const uno::Reference& 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& xRowSet, const Date& rNullDate) : + mrCache(rCache), mxRowSet(xRowSet), maNullDate(rNullDate) +{ + Reference 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(::tools::Time::hourPerDay) + + aTime.Minutes / static_cast(::tools::Time::minutePerDay) + + aTime.Seconds / static_cast(::tools::Time::secondPerDay) + + aTime.NanoSeconds / static_cast(::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(::tools::Time::hourPerDay) + + aStamp.Minutes / static_cast(::tools::Time::minutePerDay) + + aStamp.Seconds / static_cast(::tools::Time::secondPerDay) + + aStamp.NanoSeconds / static_cast(::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& xSource ) +{ + sheet::DataPilotFieldOrientation nRet = sheet::DataPilotFieldOrientation_HIDDEN; + if ( xSource.is() ) + { + uno::Reference xDimNameAccess = xSource->getDimensions(); + const uno::Sequence aDimNames = xDimNameAccess->getElementNames(); + for (const OUString& rDimName : aDimNames) + { + uno::Reference 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 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& 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& 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& 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& 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 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(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(pDoc, *pSheetDesc, *pCache); + } + } + } + + // grouping (for cell or database data) + if (pData && pDimData) + { + auto pGroupData = std::make_shared(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 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(mpTableData.get()); + if (pData) + { + // Replace the existing group table data with the source data. + mpTableData = pData->GetSourceTableData(); + } + return; + } + + ScDPGroupTableData* pData = dynamic_cast(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& pSource = pData->GetSourceTableData(); + auto pGroupData = std::make_shared(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(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& rNames ) +{ + vector 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& rMembers ) +{ + Reference< sheet::XMembersAccess > xMembersNA; + if (!GetMembersNA( nDim, nHier, xMembersNA )) + return false; + + Reference xMembersIA( new ScNameToIndexAccess(xMembersNA) ); + sal_Int32 nCount = xMembersIA->getCount(); + vector aMembers; + aMembers.reserve(nCount); + + for (sal_Int32 i = 0; i < nCount; ++i) + { + Reference xMember; + try + { + xMember = Reference(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 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( aParam.nCol1 + nDiffX ); + aParam.nCol2 = sal::static_int_cast( aParam.nCol2 + nDiffX ); + aParam.nRow1 += nDiffY; //TODO: used? + aParam.nRow2 += nDiffY; //TODO: used? + SCSIZE nEC = aParam.GetEntryCount(); + for (SCSIZE i=0; iSetQueryParam(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& rFilters) +{ + CreateOutput(); + + vector aFilters; + if (!pOutput->GetDataResultPositionData(aFilters, rPos)) + return false; + + sal_Int32 n = static_cast(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 >& rTableData) +{ + CreateOutput(); + + Reference xDrillDownData(xSource, UNO_QUERY); + if (!xDrillDownData.is()) + return; + + Sequence filters; + if (!GetDataFieldPositionData(rPos, filters)) + return; + + rTableData = xDrillDownData->getDrillDownData(filters); +} + +bool ScDPObject::IsDimNameInUse(std::u16string_view rName) const +{ + if (!xSource.is()) + return false; + + Reference xDims = xSource->getDimensions(); + const Sequence aDimNames = xDims->getElementNames(); + for (const OUString& rDimName : aDimNames) + { + if (rDimName.equalsIgnoreAsciiCase(rName)) + return true; + + Reference 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 xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimName( xIntDim, uno::UNO_QUERY ); + uno::Reference 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 xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference xDimProp(xDims->getByIndex(nDim), uno::UNO_QUERY); + if ( xDimProp.is() ) + { + try + { + uno::Any aOrigAny = xDimProp->getPropertyValue( SC_UNO_DP_ORIGINAL ); + uno::Reference 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 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 & 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& rFilters) +{ + if (!mbEnableGetPivotData) + return std::numeric_limits::quiet_NaN(); + + CreateObjects(); + + std::vector aDataDims; + pSaveData->GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_DATA, aDataDims); + if (aDataDims.empty()) + return std::numeric_limits::quiet_NaN(); + + std::vector::iterator it = std::find_if( + aDataDims.begin(), aDataDims.end(), + FindByName(ScGlobal::getCharClass().uppercase(rDataFieldName))); + + if (it == aDataDims.end()) + return std::numeric_limits::quiet_NaN(); + + size_t nDataIndex = std::distance(aDataDims.begin(), it); + + uno::Reference xDPResults(xSource, uno::UNO_QUERY); + if (!xDPResults.is()) + return std::numeric_limits::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 aFilters(n); + auto aFiltersRange = asNonConstRange(aFilters); + for (size_t i = 0; i < n; ++i) + aFiltersRange[i] = rFilters[i]; + + uno::Sequence aRes = xDPResults->getFilteredResults(aFilters); + if (nDataIndex >= o3tl::make_unsigned(aRes.getLength())) + return std::numeric_limits::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= 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(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(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& rFilters, + std::vector& rFilterFuncs, std::u16string_view rFilterList ) +{ + // parse the string rFilterList into parameters for GetPivotData + + CreateObjects(); // create xSource if not already done + + std::vector aDataNames; // data fields (source name) + std::vector aGivenNames; // data fields (compound name) + std::vector aFieldNames; // column/row/data fields + std::vector< uno::Sequence > aFieldValueNames; + std::vector< uno::Sequence > aFieldValues; + + // get all the field and item names + + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xIntDims = new ScNameToIndexAccess( xDimsName ); + sal_Int32 nDimCount = xIntDims->getCount(); + for ( sal_Int32 nDim = 0; nDim xIntDim(xIntDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDim( xIntDim, uno::UNO_QUERY ); + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference 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 xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + sal_Int32 nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_USEDHIERARCHY ); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + + uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if ( xHierSupp.is() ) + { + uno::Reference xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); + sal_Int32 nLevCount = xLevels->getCount(); + for (sal_Int32 nLev=0; nLev xLevel(xLevels->getByIndex(nLev), + uno::UNO_QUERY); + uno::Reference xLevNam( xLevel, uno::UNO_QUERY ); + uno::Reference xLevSupp( xLevel, uno::UNO_QUERY ); + if ( xLevNam.is() && xLevSupp.is() ) + { + uno::Reference xMembers = xLevSupp->getMembers(); + + OUString aFieldName( xLevNam->getName() ); + // getElementNames() and getLocaleIndependentElementNames() + // must be consecutive calls to obtain strings in matching order. + uno::Sequence aMemberValueNames( xMembers->getElementNames() ); + uno::Sequence 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; nDataPosGetCacheTable().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& rItemNames = aFieldValueNames[nField]; + const uno::Sequence& 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 xDim; + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference 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 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 xHiers; + uno::Reference xHierSupp( xDim, uno::UNO_QUERY ); + if ( xHierSupp.is() ) + { + uno::Reference xHiersName = xHierSupp->getHierarchies(); + xHiers = new ScNameToIndexAccess( xHiersName ); + nHierCount = xHiers->getCount(); + } + uno::Reference 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 xLevels; + uno::Reference xLevSupp( xHier, uno::UNO_QUERY ); + if ( xLevSupp.is() ) + { + uno::Reference xLevsName = xLevSupp->getLevels(); + xLevels = new ScNameToIndexAccess( xLevsName ); + nLevCount = xLevels->getCount(); + } + uno::Reference 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 xMembers; + uno::Reference 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 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& xDimProp ) // PIVOT_FUNC mask +{ + uno::Reference xDimSupp( xDimProp, uno::UNO_QUERY ); + if ( xDimProp.is() && xDimSupp.is() ) + { + uno::Reference xHiers = new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty( xDimProp, + SC_UNO_DP_USEDHIERARCHY ); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + + uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if ( xHierSupp.is() ) + { + uno::Reference xLevels = new ScNameToIndexAccess( xHierSupp->getLevels() ); + uno::Reference xLevel(xLevels->getByIndex(0), uno::UNO_QUERY); + uno::Reference xLevProp( xLevel, uno::UNO_QUERY ); + if ( xLevProp.is() ) + { + uno::Any aSubAny; + try + { + aSubAny = xLevProp->getPropertyValue( SC_UNO_DP_SUBTOTAL2 ); + } + catch(uno::Exception&) + { + } + uno::Sequence 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& 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 aPos; + + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nDimCount = xDims->getCount(); + for (tools::Long nDim = 0; nDim < nDimCount; ++nDim) + { + // dimension properties + uno::Reference 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(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(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(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 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 xDimSupp( xDimProp, uno::UNO_QUERY ); + if (!xDimProp.is() || !xDimSupp.is()) + return; + + uno::Reference 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 xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if (!xHierSupp.is()) + return; + + uno::Reference xLevels = + new ScNameToIndexAccess( xHierSupp->getLevels() ); + + uno::Reference 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& xDims, sal_Int32 nDim, ScDPLabelData& rLabelData) +{ + uno::Reference xIntDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimName( xIntDim, uno::UNO_QUERY ); + uno::Reference 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(nDim); + rLabelData.mnDupCount = nDupCount; + rLabelData.mbDataLayout = bData; + rLabelData.mbIsValue = true; //TODO: check + + if (bData) + return; + + rLabelData.mnOriginalDim = static_cast(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 xDimsName = xSource->getDimensions(); + uno::Reference 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 xDimsName = xSource->getDimensions(); + uno::Reference 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(pNewLabel)); + } +} + +bool ScDPObject::GetHierarchiesNA( sal_Int32 nDim, uno::Reference< container::XNameAccess >& xHiers ) +{ + bool bRet = false; + uno::Reference xDimsName( GetSource()->getDimensions() ); + uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); + if( xIntDims.is() ) + { + uno::Reference 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 xDimsName( GetSource()->getDimensions() ); + uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); + uno::Reference 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 xDimsName( GetSource()->getDimensions() ); + uno::Reference xIntDims(new ScNameToIndexAccess( xDimsName )); + uno::Reference xDim(xIntDims->getByIndex( nDim ), uno::UNO_QUERY); + if (xDim.is()) + { + uno::Reference xHierSup(xDim, uno::UNO_QUERY); + if (xHierSup.is()) + { + uno::Reference xHiers(new ScNameToIndexAccess(xHierSup->getHierarchies())); + uno::Reference xLevSupp( xHiers->getByIndex(nHier), uno::UNO_QUERY ); + if ( xLevSupp.is() ) + { + uno::Reference xLevels(new ScNameToIndexAccess( xLevSupp->getLevels())); + if (xLevels.is()) + { + sal_Int32 nLevCount = xLevels->getCount(); + if (nLevCount > 0) + { + uno::Reference 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& xSource, tools::Long nDim ) +{ + OUString aName; + if ( xSource.is() ) + { + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nDimCount = xDims->getCount(); + if ( nDim < nDimCount ) + { + uno::Reference xDimName(xDims->getByIndex(nDim), uno::UNO_QUERY); + if (xDimName.is()) + { + try + { + aName = xDimName->getName(); + } + catch(uno::Exception&) + { + } + } + } + } + return aName; +} + +bool hasFieldColumn(const vector* 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& 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 nSubTotalFuncs; + nSubTotalFuncs.reserve(16); + sal_uInt16 nMask = 1; + for (sal_uInt16 nBit=0; nBit<16; nBit++) + { + if ( nFuncs & static_cast(nMask) ) + nSubTotalFuncs.push_back( ScDataPilotConversion::FirstFunc( static_cast(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 xManager = comphelper::getProcessServiceFactory(); + uno::Reference xEnAc( xManager, uno::UNO_QUERY ); + if ( xEnAc.is() ) + { + uno::Reference xEnum = xEnAc->createContentEnumeration( + SCDPSOURCE_SERVICE ); + if ( xEnum.is() && xEnum->hasMoreElements() ) + bFound = true; + } + + return bFound; +} + +std::vector ScDPObject::GetRegisteredSources() +{ + std::vector aVec; + + // use implementation names... + + uno::Reference xManager = comphelper::getProcessServiceFactory(); + uno::Reference xEnAc( xManager, uno::UNO_QUERY ); + if ( xEnAc.is() ) + { + uno::Reference xEnum = xEnAc->createContentEnumeration( + SCDPSOURCE_SERVICE ); + if ( xEnum.is() ) + { + while ( xEnum->hasMoreElements() ) + { + uno::Any aAddInAny = xEnum->nextElement(); +// if ( aAddInAny.getReflection()->getTypeClass() == TypeClass_INTERFACE ) + { + uno::Reference xIntFac; + aAddInAny >>= xIntFac; + if ( xIntFac.is() ) + { + uno::Reference xInfo( xIntFac, uno::UNO_QUERY ); + if ( xInfo.is() ) + { + OUString sName = xInfo->getImplementationName(); + aVec.push_back( sName ); + } + } + } + } + } + } + + return aVec; +} + +uno::Reference ScDPObject::CreateSource( const ScDPServiceDesc& rDesc ) +{ + OUString aImplName = rDesc.aServiceName; + uno::Reference xRet; + + uno::Reference xManager = comphelper::getProcessServiceFactory(); + uno::Reference xEnAc(xManager, uno::UNO_QUERY); + if (!xEnAc.is()) + return xRet; + + uno::Reference xEnum = + xEnAc->createContentEnumeration(SCDPSOURCE_SERVICE); + if (!xEnum.is()) + return xRet; + + while (xEnum->hasMoreElements() && !xRet.is()) + { + uno::Any aAddInAny = xEnum->nextElement(); + uno::Reference xIntFac; + aAddInAny >>= xIntFac; + if (!xIntFac.is()) + continue; + + uno::Reference 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 xInterface; + uno::Reference xCtx( + comphelper::getComponentContext(xManager)); + uno::Reference xCFac( xIntFac, uno::UNO_QUERY ); + if (xCFac.is()) + xInterface = xCFac->createInstanceWithContext(xCtx); + + if (!xInterface.is()) + { + uno::Reference xFac( xIntFac, uno::UNO_QUERY ); + if ( xFac.is() ) + xInterface = xFac->createInstance(); + } + + uno::Reference xInit( xInterface, uno::UNO_QUERY ); + if (xInit.is()) + { + // initialize + uno::Sequence 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& 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 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& 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 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& 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 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& 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 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 xRowSet = createRowSet(nSdbType, rDBName, rCommand); + if (!xRowSet.is()) + return nullptr; + + ::std::unique_ptr 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 ScDPCollection::DBCaches::createRowSet( + sal_Int32 nSdbType, const OUString& rDBName, const OUString& rCommand) +{ + uno::Reference xRowSet; + try + { + xRowSet.set(comphelper::getProcessServiceFactory()->createInstance( + SC_SERVICE_ROWSET), + UNO_QUERY); + + uno::Reference 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 xExecute( xRowSet, uno::UNO_QUERY ); + if ( xExecute.is() ) + { + uno::Reference 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 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& 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 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 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& rObj) const + { + return rObj->GetOutRange().aStart.Tab() == mnTab; + } +}; + +} + +TranslateId ScDPCollection::ReloadCache(const ScDPObject* pDPObj, o3tl::sorted_vector& 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& 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( + 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( + 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( + 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& 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(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(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 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 const & pCurrent) + { + return pCurrent.get() == pDPObject; + }; + + maTables.erase(std::remove_if(maTables.begin(), maTables.end(), funcRemoveCondition), maTables.end()); +} + +ScDPObject* ScDPCollection::InsertNewTable(std::unique_ptr 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& 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& 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& rRefs) const +{ + o3tl::sorted_vector 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(&rObj)); + } + + rRefs.swap(aRefs); +} + +void ScDPCollection::GetAllTables(std::u16string_view rSrcName, o3tl::sorted_vector& rRefs) const +{ + o3tl::sorted_vector 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(&rObj)); + } + + rRefs.swap(aRefs); +} + +void ScDPCollection::GetAllTables( + sal_Int32 nSdbType, std::u16string_view rDBName, std::u16string_view rCommand, + o3tl::sorted_vector& rRefs) const +{ + o3tl::sorted_vector 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(&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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 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 &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 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(mnRows.size()) ); + + std::sort( mnCols.begin(), mnCols.end()); + std::sort( mnRows.begin(), mnRows.end()); + + for( SCCOL nCol = 0; nCol < static_cast(mnCols.size())-1; nCol ++ ) + { + if ( !bAllRows ) + { + if ( nCol < static_cast(mnCols.size())-2) + { + for ( SCROW i = nCol%2; i < static_cast(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(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( pStlPool->Find( aStyleName, SfxStyleFamily::Para ) ); + if (!pStyle) + { + // create new style (was in ScPivot::SetStyle) + + pStyle = static_cast( &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& rFormats, sal_Int32& rCount, + const uno::Reference& xLevRes, + const uno::Reference& xDims ) +{ + if ( rFormats ) + return; // already set + + // xLevRes is from the data layout dimension + //TODO: use result sequence from ScDPOutLevelData! + + uno::Sequence 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 aDataNames; + std::vector aDataFormats; + sal_Int32 nDimCount = xDims->getCount(); + sal_Int32 nDim = 0; + for ( ; nDim < nDimCount ; nDim++) + { + uno::Reference xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference 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& xDims ) +{ + tools::Long nDimCount = xDims->getCount(); + for (tools::Long nDim=0; nDim 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& 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 getVisiblePageMembersAsResults( const uno::Reference& xLevel ) +{ + if (!xLevel.is()) + return uno::Sequence(); + + uno::Reference xMSupplier(xLevel, UNO_QUERY); + if (!xMSupplier.is()) + return uno::Sequence(); + + uno::Reference xNA = xMSupplier->getMembers(); + if (!xNA.is()) + return uno::Sequence(); + + std::vector aRes; + const uno::Sequence aNames = xNA->getElementNames(); + for (const OUString& rName : aNames) + { + xNA->getByName(rName); + + uno::Reference 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::quiet_NaN()); + } + } + + if (o3tl::make_unsigned(aNames.getLength()) == aRes.size()) + // All members are visible. Return empty result. + return uno::Sequence(); + + return ScUnoHelpFunctions::VectorToSequence(aRes); +} + +} + +ScDPOutput::ScDPOutput( ScDocument* pD, const uno::Reference& 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 xResult( xSource, uno::UNO_QUERY ); + if ( xSource.is() && xResult.is() ) + { + // get dimension results: + + uno::Reference xDims = + new ScNameToIndexAccess( xSource->getDimensions() ); + tools::Long nDimCount = xDims->getCount(); + for (tools::Long nDim=0; nDim xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference 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 xHiers = + new ScNameToIndexAccess( xDimSupp->getHierarchies() ); + tools::Long nHierarchy = ScUnoHelpFunctions::GetLongProperty( + xDimProp, + SC_UNO_DP_USEDHIERARCHY ); + if ( nHierarchy >= xHiers->getCount() ) + nHierarchy = 0; + + uno::Reference xHierSupp(xHiers->getByIndex(nHierarchy), + uno::UNO_QUERY); + if ( xHierSupp.is() ) + { + uno::Reference xLevels = + new ScNameToIndexAccess( xHierSupp->getLevels() ); + tools::Long nLevCount = xLevels->getCount(); + for (tools::Long nLev=0; nLev xLevel(xLevels->getByIndex(nLev), + uno::UNO_QUERY); + uno::Reference xLevNam( xLevel, uno::UNO_QUERY ); + uno::Reference xLevRes( + xLevel, uno::UNO_QUERY ); + if ( xLevNam.is() && xLevRes.is() ) + { + OUString aName = xLevNam->getName(); + Reference 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 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 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 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 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(nLevel), nCol,nDataStartRow-1 ); + + lcl_SetStyleById( pDoc,nTab, nCol,nMemberStartRow+static_cast(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(nLevel),nRow, nDataStartCol-1,nRow ); + lcl_SetStyleById( pDoc,nTab, nMemberStartCol+static_cast(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* 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(pRowFields.size()) + nColCount - 1 > pDoc->MaxCol() || + aStartPos.Row() + nPageSize + nHeaderSize + static_cast(pColFields.size()) + nRowCount > pDoc->MaxRow()) + { + bSizeOverflow = true; + } + + nTabStartCol = aStartPos.Col(); + nTabStartRow = aStartPos.Row() + static_cast(nPageSize); // below page fields + nMemberStartCol = nTabStartCol; + nMemberStartRow = nTabStartRow + static_cast(nHeaderSize); + nDataStartCol = nMemberStartCol + static_cast(pRowFields.size()); + nDataStartRow = nMemberStartRow + static_cast(pColFields.size()); + if ( nColCount > 0 ) + nTabEndCol = nDataStartCol + static_cast(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(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* 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& 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(nField); //TODO: check for overflow + FieldCell(nHdrCol, nTabStartRow, nTab, pColFields[nField], true); + + SCROW nRowPos = nMemberStartRow + static_cast(nField); //TODO: check for overflow + const uno::Sequence 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(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(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 vbSetBorder; + vbSetBorder.resize( nTabEndRow - nDataStartRow + 1, false ); + for (size_t nField=0; nField(nField); //TODO: check for overflow + SCROW nHdrRow = nDataStartRow - 1; + FieldCell(nHdrCol, nHdrRow, nTab, pRowFields[nField], true); + + SCCOL nColPos = nMemberStartCol + static_cast(nField); //TODO: check for overflow + const uno::Sequence 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(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(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(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(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& 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& rDataNames, std::vector& rGivenNames, + sheet::DataPilotFieldOrientation& rDataOrient, + const uno::Reference& xSource ) +{ + rDataLayoutIndex = -1; // invalid + rGrandTotalCols = 0; + rGrandTotalRows = 0; + rDataOrient = sheet::DataPilotFieldOrientation_HIDDEN; + + uno::Reference 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 xDims = new ScNameToIndexAccess( xSource->getDimensions() ); + tools::Long nDimCount = xDims->getCount(); + for (tools::Long nDim=0; nDim xDim(xDims->getByIndex(nDim), uno::UNO_QUERY); + uno::Reference 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 aFilters; + GetDataResultPositionData(aFilters, rPos); + + DataPilotTableResultData aResData; + aResData.FieldFilters = comphelper::containerToSequence(aFilters); + aResData.DataFieldIndex = 0; + Reference 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 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(pColFields[nField].mnDim); + aHeaderData.Hierarchy = static_cast(pColFields[nField].mnHier); + aHeaderData.Level = static_cast(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 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(pRowFields[nField].mnDim); + aHeaderData.Hierarchy = static_cast(pRowFields[nField].mnHier); + aHeaderData.Level = static_cast(pRowFields[nField].mnLevel); + + rPosData.PositionData <<= aHeaderData; + return; + } + } +} + +bool ScDPOutput::GetDataResultPositionData(vector& rFilters, const ScAddress& rPos) +{ + // Check to make sure there is at least one data field. + Reference 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 aDataNames; + std::vector 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(nTabEndCol - nGrandTotalCols)); + bool bFilterByRow = (nRow <= static_cast(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 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 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& xDim ) +{ + uno::Reference xDimProp( xDim, uno::UNO_QUERY ); + uno::Reference 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) 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) 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) 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 +#include + +#include + +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& rAddrs) const +{ + sal_uInt32 nColumnFields, nRowFields; + adjustFieldsForDataLayout(nColumnFields, nRowFields); + + vector 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(mnPageFields-1); + nCurRow = nRowEnd + 2; + } + else if (mbShowFilter) + nCurRow += 2; + + SCROW nRow = nCurRow; + SCTAB nTab = maOutRange.aStart.Tab(); + SCCOL nColStart = static_cast(maOutRange.aStart.Col() + nRowFields); + if(mbCompactMode) + nColStart = static_cast(maOutRange.aStart.Col() + 1); // We have only one row in compact mode + SCCOL nColEnd = nColStart + static_cast(nColumnFields-1); + + for (SCCOL nCol = nColStart; nCol <= nColEnd; ++nCol) + aAddrs.emplace_back(nCol, nRow, nTab); + rAddrs.swap(aAddrs); +} + +void ScDPOutputGeometry::getRowFieldPositions(vector& rAddrs) const +{ + sal_uInt32 nColumnFields, nRowFields; + adjustFieldsForDataLayout(nColumnFields, nRowFields); + + vector 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(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& rAddrs) const +{ + vector 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(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(mnPageFields-1); + nCurRow = nRowEnd + 2; + } + else if (mbShowFilter) + nCurRow += 2; + + if (nColumnFields) + nCurRow += static_cast(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::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(mnPageFields-1); + if (rPos.Col() == nCol && nRowStart <= rPos.Row() && rPos.Row() <= nRowEnd) + { + size_t nPos = static_cast(rPos.Row() - nRowStart); + return std::pair(Page, nPos); + } + + nCurRow = nRowEnd + 2; + } + else if (mbShowFilter) + nCurRow += 2; + + if (nColumnFields) + { + SCROW nRow = nCurRow; + SCCOL nColStart = static_cast(maOutRange.aStart.Col() + nRowFields); + SCCOL nColEnd = nColStart + static_cast(nColumnFields-1); + if (rPos.Row() == nRow && nColStart <= rPos.Col() && rPos.Col() <= nColEnd) + { + size_t nPos = static_cast(rPos.Col() - nColStart); + return std::pair(Column, nPos); + } + + nCurRow += static_cast(nColumnFields); + } + else if (mbHeaderLayout) + ++nCurRow; + + if (nRowFields) + { + SCCOL nColStart = maOutRange.aStart.Col(); + SCCOL nColEnd = nColStart + static_cast(nRowFields-1); + if (rPos.Row() == nCurRow && nColStart <= rPos.Col() && rPos.Col() <= nColEnd) + { + size_t nPos = static_cast(rPos.Col() - nColStart); + return std::pair(Row, nPos); + } + } + + return std::pair(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 +#include + +#include +#include +#include + +#include +#include + +#include + +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& 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(); + std::pair 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 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::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& 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::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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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& 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 & ScDPSaveMember::GetLayoutName() const +{ + return mpLayoutName; +} + +void ScDPSaveMember::RemoveLayoutName() +{ + mpLayoutName.reset(); +} + +void ScDPSaveMember::WriteToSource( const uno::Reference& xMember, sal_Int32 nPosition ) +{ + uno::Reference 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(nVisibleMode) ); + + if ( nShowDetailsMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xMembProp, + SC_UNO_DP_SHOWDETAILS, static_cast(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 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 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 && 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 & 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 & pLayoutName = pMem->GetLayoutName(); + return pLayoutName && rName.equalsIgnoreAsciiCase(*pLayoutName); + }); +} + +void ScDPSaveDimension::SetLayoutName(const OUString& rName) +{ + mpLayoutName = rName; +} + +const std::optional & 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(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& xDim ) +{ + uno::Reference 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(nFunction); + xDimProp->setPropertyValue( SC_UNO_DP_FUNCTION2, uno::Any(eFunc) ); + + if ( nUsedHierarchy >= 0 ) + { + xDimProp->setPropertyValue( SC_UNO_DP_USEDHIERARCHY, uno::Any(static_cast(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 & 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 xHiers; + uno::Reference xHierSupp( xDim, uno::UNO_QUERY ); + if ( xHierSupp.is() ) + { + uno::Reference xHiersName = xHierSupp->getHierarchies(); + xHiers = new ScNameToIndexAccess( xHiersName ); + nHierCount = xHiers->getCount(); + } + + bool bHasHiddenMember = false; + + for (tools::Long nHier=0; nHier xLevels; + uno::Reference xLevSupp(xHiers->getByIndex(nHier), uno::UNO_QUERY); + if ( xLevSupp.is() ) + { + uno::Reference xLevelsName = xLevSupp->getLevels(); + xLevels = new ScNameToIndexAccess( xLevelsName ); + nLevCount = xLevels->getCount(); + } + + for (tools::Long nLev=0; nLev xLevel(xLevels->getByIndex(nLev), uno::UNO_QUERY); + uno::Reference xLevProp( xLevel, uno::UNO_QUERY ); + OSL_ENSURE( xLevProp.is(), "no properties at level" ); + if ( xLevProp.is() ) + { + if ( !bSubTotalDefault ) + { + uno::Sequence aSeq(maSubTotalFuncs.size()); + for(size_t i = 0; i < maSubTotalFuncs.size(); ++i) + aSeq.getArray()[i] = static_cast(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(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 xMembSupp( xLevel, uno::UNO_QUERY ); + if ( xMembSupp.is() ) + { + uno::Reference 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 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& 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(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(*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 & 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 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& rDims) const +{ + std::vector 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(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& 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& 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& rxDim) { return pDim == rxDim.get(); }); + if (it != m_DimList.end()) + { + // Tell vector 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& rxDim) { + if (rxDim->GetOrientation() == nOrient ) + --nNew; + return nNew <= 0; + }); + + m_DimList.insert(iterInsert, std::unique_ptr(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& xSource ) +{ + uno::Reference xDimsName = xSource->getDimensions(); + uno::Reference xIntDims = new ScNameToIndexAccess( xDimsName ); + tools::Long nIntCount = xIntDims->getCount(); + for (tools::Long nIntDim=0; nIntDim 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& xSource ) +{ + if (!xSource.is()) + return; + + // source options must be first! + + uno::Reference 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(nIgnoreEmptyMode) ); + if ( nRepeatEmptyMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xSourceProp, + SC_UNO_DP_REPEATEMPTY, static_cast(nRepeatEmptyMode) ); + } + catch(uno::Exception&) + { + // no error + } + + const std::optional & 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 xDimsName = xSource->getDimensions(); + uno::Reference 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 xIntDim(xIntDims->getByIndex(nIntDim), + uno::UNO_QUERY); + if ( bData ) + { + uno::Reference 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 xDimName( xIntDim, uno::UNO_QUERY ); + if (xDimName.is() && xDimName->getName() == aCoreName) + bFound = true; + } + + if (bFound) + { + if (rxDim->GetDupFlag()) + { + uno::Reference xCloneable(xIntDim, uno::UNO_QUERY); + SAL_WARN_IF(!xCloneable.is(), "sc.core", "cannot clone dimension"); + if (xCloneable.is()) + { + uno::Reference xNew = xCloneable->createClone(); + uno::Reference 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(nColumnGrandMode) ); + if ( nRowGrandMode != SC_DPSAVEMODE_DONTKNOW ) + lcl_SetBoolProperty( xSourceProp, + SC_UNO_DP_ROWGRAND, static_cast(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* 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 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& 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 pNewMember(new ScDPSaveMember(aMemName)); + pNewMember->SetIsVisible(true); + iter->AddMember(std::move(pNewMember)); + } + } + + mbDimensionMembersBuilt = true; +} + +void ScDPSaveData::SyncAllDimensionMembers(ScDPTableData* pData) +{ + typedef std::unordered_map 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& 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(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 +#include +#include +#include +#include +#include + +#include + +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(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&& rCriteria, std::unordered_set&& rCatDims) +{ + CreateCacheTable(); + aCacheTable.filterByPageDimension( + rCriteria, (IsRepeatIfEmpty() ? std::move(rCatDims) : std::unordered_set())); +} + +void ScDatabaseDPData::GetDrillDownData(std::vector&& rCriteria, std::unordered_set&& rCatDims, Sequence< Sequence >& rData) +{ + CreateCacheTable(); + sal_Int32 nRowSize = aCacheTable.getRowSize(); + if (!nRowSize) + return; + + aCacheTable.filterTable( + rCriteria, rData, IsRepeatIfEmpty() ? std::move(rCatDims) : std::unordered_set()); +} + +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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +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(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( 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&& rCriteria, std::unordered_set&& rCatDims) +{ + CreateCacheTable(); + aCacheTable.filterByPageDimension( + rCriteria, (IsRepeatIfEmpty() ? rCatDims : std::unordered_set())); +} + +void ScSheetDPData::GetDrillDownData(std::vector&& rCriteria, std::unordered_set&& rCatDims, Sequence< Sequence >& rData) +{ + CreateCacheTable(); + sal_Int32 nRowSize = aCacheTable.getRowSize(); + if (!nRowSize) + return; + + aCacheTable.filterTable( + rCriteria, rData, IsRepeatIfEmpty() ? rCatDims : std::unordered_set()); +} + +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 +#include +#include +#include + +#include +#include + + +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(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(nDim), static_cast(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 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& rDims, vector& 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(nDim), static_cast(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& nMembers = GetCacheTable().getFieldEntries( nDim ); + + return GetCacheTable().getCache().GetItemDataById( static_cast(nDim), static_cast(nMembers[nIndex]) ); +} + +const ScDPItemData* ScDPTableData::GetMemberById( sal_Int32 nDim, sal_Int32 nId) +{ + return GetCacheTable().getCache().GetItemDataById(nDim, static_cast(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 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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>& 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& mrFilters; +public: + explicit FilterStack(std::vector& 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(maWelford.getCount())); + break; + + case SUBTOTAL_FUNC_STD: + case SUBTOTAL_FUNC_VAR: + bError = ( nCount < 2 ); // need at least 2 values + assert(bError || nCount == static_cast(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(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(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; nPosGetChild(); // 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 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(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& rFunctions, std::vector& rRefs, + std::vector& rRefOrient, std::vector& 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 & 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(maDimMembers.size()) && maDimMembers[nDim]) + return *maDimMembers[nDim]; + + if (nDim >= static_cast(maDimMembers.size())) + maDimMembers.resize(nDim+1); + + std::unique_ptr 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::const_iterator itr = aMembers.begin(); + vector aChildMembers(++itr, aMembers.end()); + return pChildDim->IsValidEntry(aChildMembers); + } + else + return true; +} + +void ScDPResultMember::InitFrom( const vector& ppDim, const vector& 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& 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 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& 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) + // 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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& 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().swap( maColHeaders); + ::std::vector().swap( maRowHeaders); + ::std::vector().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[nColCount]); + for (SCSIZE i=0; i[nColCount] ); + for (SCSIZE i=0; i[nColCount] ); + for (SCSIZE i=0; i[nRowCount] ); + for (SCSIZE i=0; i(nCols); + nRowCount = static_cast(nRows); +} + +void ScConsData::GetSize( SCCOL& rCols, SCROW& rRows ) const +{ + rCols = static_cast(nColCount); + rRows = static_cast(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; iGetString(nCol1, nRow, nTab); + if (!aTitle.isEmpty()) + { + bool bFound = false; + for (SCSIZE i=0; i= sal::static_int_cast(nCol1 + nColCount) && !bColByName ) + { + OSL_FAIL("range too big"); + nCol2 = sal::static_int_cast( nCol1 + nColCount - 1 ); + } + if ( nRow2 >= sal::static_int_cast(nRow1 + nRowCount) && !bRowByName ) + { + OSL_FAIL("range too big"); + nRow2 = sal::static_int_cast( 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 pDestCols; + std::unique_ptr 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(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(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(nStartCol+i), nRow, nTab, maColHeaders[i] ); + if (bRowByName) + for (SCSIZE j=0; j(nStartRow+j), nTab, maRowHeaders[j] ); + + nCol = nStartCol; + nRow = nStartRow; + + // data + + if ( ppFunctionData && ppUsed ) // insert values directly + { + for (nArrX=0; nArrX(nCol+nArrX), + sal::static_int_cast(nRow+nArrY), nTab, FormulaError::NoValue ); + else + rDestDoc.SetValue( sal::static_int_cast(nCol+nArrX), + sal::static_int_cast(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(nCol+nArrX), + sal::static_int_cast(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(nCol+nArrX), + sal::static_int_cast(nRow+nArrY+nNeeded), nTab ); + + ScRange aRange(sal::static_int_cast(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 +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +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& left, const std::unique_ptr& 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(nCol1) - static_cast(nStartCol); + tools::Long nDifY = static_cast(nRow1) - static_cast(nStartRow); + + tools::Long nSortDif = bByRow ? nDifX : nDifY; + tools::Long nSortEnd = bByRow ? static_cast(nCol2) : static_cast(nRow2); + + for (sal_uInt16 i=0; iGetSortKeyCount(); 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(nCol2) - static_cast(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; inField[i] = sal::static_int_cast( 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 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 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 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(::std::max( nCol1 + std::min(nDx, 0) - nOldCol1, 0)); + size_t nTail = static_cast(::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().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& 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 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::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(&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 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 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 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 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 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 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 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 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 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 pData) +{ + auto p = pData.get(); + if (!pData->GetIndex()) + pData->SetIndex(mrParent.nEntryIndex++); + + pair 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 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(pData); +} + +void ScDBCollection::AnonDBs::insert(ScDBData* p) +{ + m_DBs.push_back(std::unique_ptr(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(*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(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(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 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 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 pDataCopy = std::make_unique(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 ScDBCollection::GetAllDBsFromTab(SCTAB nTab) +{ + std::vector 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(comphelper::string::getTokenCount(aLinkStr, '\n')); + std::u16string_view aLine = o3tl::getToken(aLinkStr, 0, '\n' ); + if (!aLine.empty()) + nCols = static_cast(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(nR), '\n' ); + for (SCSIZE nC=0; nC(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 +#include + +#include +#include +#include +#include + +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(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 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 aNames = GetPropertyNames(); + Sequence 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(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 aNames = GetPropertyNames(); + Sequence 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(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(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 +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 ), + aArrowSet( pModel->GetItemPool(), svl::Items ), + aToTabSet( pModel->GetItemPool(), svl::Items ), + aFromTabSet( pModel->GetItemPool(), svl::Items ), + aCircleSet( pModel->GetItemPool(), svl::Items ), + 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 ) +{ + 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(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(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( *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( *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(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( *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( *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(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( *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(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + if (!nObjCount) + return; + + size_t nDelCount = 0; + std::unique_ptr 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(*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(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + if (!nObjCount) + return; + + size_t nDelCount = 0; + std::unique_ptr 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( *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(nTab)); + OSL_ENSURE(pPage, "Page ?"); + + pPage->RecalcObjOrdNums(); + + const size_t nObjCount = pPage->GetObjCount(); + size_t nDelCount = 0; + if (nObjCount) + { + std::unique_ptr 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(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(*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(nTab)); + OSL_ENSURE(pPage,"Page ?"); + + pPage->RecalcObjOrdNums(); + + size_t nDelCount = 0; + const size_t nObjCount = pPage->GetObjCount(); + if (nObjCount) + { + std::unique_ptr 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( 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( *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& 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& rRefTokens) +{ + vector 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( pObject) != nullptr && dynamic_cast( 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(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( 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(pObject) != nullptr) + { + if (bValidStart) + { + // cell position is returned in rPosition + rPosition = pData->maStart; + eType = SC_DETOBJ_CIRCLE; + } + } + else if (dynamic_cast(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 +#include + +#include +#include + +#include +#include +#include + +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(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 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 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 aNames; + Sequence 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(nIntVal) ); + break; + case SCCALCOPT_ITER_MINCHG: + if (pValues[nProp] >>= fDoubleVal) SetIterEps( fDoubleVal ); + break; + case SCCALCOPT_DATE_DAY: + if (pValues[nProp] >>= nIntVal) nDateDay = static_cast(nIntVal); + break; + case SCCALCOPT_DATE_MONTH: + if (pValues[nProp] >>= nIntVal) nDateMonth = static_cast(nIntVal); + break; + case SCCALCOPT_DATE_YEAR: + if (pValues[nProp] >>= nIntVal) nDateYear = static_cast(nIntVal); + break; + case SCCALCOPT_DECIMALS: + if (pValues[nProp] >>= nIntVal) SetStdPrecision( static_cast(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 aNames = GetCalcPropertyNames(); + Sequence 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(GetIterCount()); + break; + case SCCALCOPT_ITER_MINCHG: + pValues[nProp] <<= GetIterEps(); + break; + case SCCALCOPT_DATE_DAY: + pValues[nProp] <<= static_cast(nDateDay); + break; + case SCCALCOPT_DATE_MONTH: + pValues[nProp] <<= static_cast(nDateMonth); + break; + case SCCALCOPT_DATE_YEAR: + pValues[nProp] <<= static_cast(nDateYear); + break; + case SCCALCOPT_DECIMALS: + pValues[nProp] <<= static_cast(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 aNames = GetLayoutPropertyNames(); + Sequence 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(TwipsToEvenHMM( GetTabDistance() )); + break; + } + } + aLayoutItem.PutProperties(aNames, aValues); +} + +void ScDocCfg::SetOptions( const ScDocOptions& rNew ) +{ + *static_cast(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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +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(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 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 ScDBInternalRange::createQueryParam(const ScDBRangeBase* pQueryRef) const +{ + unique_ptr 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(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(nC); + mnRows = static_cast(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 ScDBExternalRange::createQueryParam(const ScDBRangeBase* pQueryRef) const +{ + unique_ptr 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(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 + +ScEditDataArray::ScEditDataArray() +{ +} + +ScEditDataArray::~ScEditDataArray() +{ +} + +void ScEditDataArray::AddItem(SCTAB nTab, SCCOL nCol, SCROW nRow, + std::unique_ptr pOldData, std::unique_ptr 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 pOldData, std::unique_ptr 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 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(pDoc)->GetEditEngine(); + rEE.SetText( rEditText); + return GetMultilineString( rEE); + } + else + { + EditEngine& rEE = ScGlobal::GetStaticFieldEditEngine(); + rEE.SetText( rEditText); + return GetMultilineString( rEE); + } +} + +std::unique_ptr 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 ScEditUtil::Clone( const EditTextObject& rObj, ScDocument& rDestDoc ) +{ + std::unique_ptr 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* ppTextColor ) +{ + OUString aRet; + switch (rFieldData.GetClassId()) + { + case text::textfield::Type::URL: + { + const SvxURLField& rField = static_cast(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(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(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(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(pMargin->GetLeftMargin() * nPPTX); + nRightMargin = bInPrintTwips ? pMargin->GetRightMargin() : static_cast(pMargin->GetRightMargin() * nPPTX); + nTopMargin = bInPrintTwips ? pMargin->GetTopMargin() : static_cast(pMargin->GetTopMargin() * nPPTY); + nBottomMargin = bInPrintTwips ? pMargin->GetBottomMargin() : static_cast(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( nCellX * nPPTX ); + if ( pMerge->GetColMerge() > 1 ) + { + SCCOL nCountX = pMerge->GetColMerge(); + for (SCCOL i=1; iGetColWidth(nCol+i,nTab); + nCellX += (bInPrintTwips ? nColWidth : static_cast( nColWidth * nPPTX )); + } + } + tools::Long nCellY = pDoc->GetRowHeight(nRow,nTab); + if (!bInPrintTwips) + nCellY = static_cast( 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(nDifX * nPPTX); + nRightMargin = static_cast(nRightMargin * nPPTX); + nTopMargin = static_cast(nTopMargin * nPPTY); + nBottomMargin = static_cast(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 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 pCharItems; + bool bUpdateMode = SetUpdateLayout( false ); + sal_Int32 nParCount = GetParagraphCount(); + for (sal_Int32 nPar=0; nParGet(nWhich) ) + { + if (!pCharItems) + pCharItems.emplace( GetEmptyItemSet() ); + pCharItems->Put( *pParaItem ); + } + } + } + + if ( pCharItems ) + { + std::vector 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( 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( 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& /* rTxtColor */, std::optional& /* 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(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& rTxtColor, std::optional& /* 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(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 +#include +#include + +#include +#include + +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 aNames { "MS_Excel/ColScale", // SCFILTOPT_COLSCALE + "MS_Excel/RowScale", // SCFILTOPT_ROWSCALE + "Lotus123/WK3" }; // SCFILTOPT_WK3 + Sequence 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& /* 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 + +#include +#include +#include +#include +#include +#include + +#include +#if HAVE_FEATURE_OPENCL +#include +#endif +#include + +#include +#include +#include +#include + +#if HAVE_FEATURE_OPENCL +# include +#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 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(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(nArrayLen, + std::numeric_limits::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& rPlatforms) +{ + const std::vector& 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(-1); + size_t aPlatformId = static_cast(-1); + + openclwrapper::getOpenCLDeviceInfo(aDeviceId, aPlatformId); + rDeviceId = aDeviceId; + rPlatformId = aPlatformId; +} + +void FormulaGroupInterpreter::enableOpenCL_UnitTestsOnly() +{ + std::shared_ptr 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 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace sc { + +namespace { + +std::unique_ptr 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(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 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(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& 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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(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 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 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 aNames = GetPropertyNames(); + UpdateFromProperties( aNames ); + EnableNotification( aNames ); +} + +void ScFormulaCfg::UpdateFromProperties( const Sequence& aNames ) +{ + Sequence 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 aNames = GetPropertyNames(); + Sequence aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + Sequence 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(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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#include +#include + +#include + +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(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(p) : nullptr); + if (pMatResult) + { + const ScMatrixFormulaCellToken* pNewMatFormula = + dynamic_cast(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(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( + static_cast( + mpToken)->GetUpperLeftToken().get()); + if (p) + return p->IsDisplayedAsString(); + } + break; + case formula::svHybridCell: + { + const ScHybridCellToken* p = static_cast(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(this)->meMultiline = MULTILINE_TRUE; + else + const_cast(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(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(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(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(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(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(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(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(mpToken) : nullptr); +} + +ScMatrixFormulaCellToken* ScFormulaResult::GetMatrixFormulaCellTokenNonConst() +{ + return const_cast( 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 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& 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::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(&rHint) ) + { + notifyBulkChange(*pBulkHint); + } + else if (rHint.GetId() == SfxHintId::ScDataChanged || rHint.GetId() == SfxHintId::ScTableOpDirty) + { + const ScHint& rScHint = static_cast(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(rQuery); + rQR.add(aRange); + } + break; + default: + ; + } +} + +void FormulaGroupAreaListener::notifyBulkChange( const BulkDataHint& rHint ) +{ + const ColumnSpanSet* pSpans = rHint.getSpans(); + if (!pSpans) + return; + + ScDocument& rDoc = const_cast(rHint).getDoc(); + + CollectCellAction aAction(*this); + pSpans->executeColumnAction(rDoc, aAction); + + std::vector 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& 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& 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(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(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(this)->mnGroupLen = static_cast(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 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 + +// 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 +#include +#include + +#include +#include + +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 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 aNames = GetPropertyNames(); + EnableNotification(aNames); + ReadCfg(); +} + +void ScInputCfg::ReadCfg() +{ + const Sequence aNames = GetPropertyNames(); + const Sequence 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(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 aNames = GetPropertyNames(); + Sequence aValues(aNames.getLength()); + Any* pValues = aValues.getArray(); + + pValues[SCINPUTOPT_MOVEDIR] <<= static_cast(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& /* aPropertyNames */ ) +{ + ReadCfg(); +} + +void ScInputCfg::SetOptions( const ScInputOptions& rNew ) +{ + *static_cast(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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +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 pJumpMat( std::make_shared( + 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::max(), + nErrorRow = ::std::numeric_limits::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 pJumpMat( std::make_shared( + 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 pJumpMat( std::make_shared( + 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(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(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(aAdr.Col()) + nC; + sal_uLong nRow = static_cast(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(nCol) ); + aAdr.SetRow( static_cast(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; jValidColRowOrReplicated(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(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(num1) & static_cast(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(num1) | static_cast(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(num1) ^ static_cast(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& 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(0, static_cast(nC)); + nRows = std::max(0, static_cast(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(nCols), static_cast(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(i), static_cast(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(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(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(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(nCol2 - nCol1 + 1), + static_cast(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(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(fVal); + OUString aStr(&cEncodedChar, 1, osl_getThreadTextEncoding(), convertFlags); + PushString(aStr); + } +} + +/* #i70213# fullwidth/halfwidth conversion provided by + * Takashi Nakamoto + * 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& MatOpFunc, bool bDoMatOp ) +{ + const ScRefListToken* p = dynamic_cast(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::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::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::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::max()) + { + // Update vector element with current value. + MatOpFunc( nRefArrayPos, nMin); + + // Reset. + nMin = std::numeric_limits::max(); + nVal = 0.0; + nRefArrayPos = std::numeric_limits::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::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::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::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::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::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::max()) + { + // Update vector element with current value. + MatOpFunc( nRefArrayPos, nMax); + + // Reset. + nMax = std::numeric_limits::lowest(); + nVal = 0.0; + nRefArrayPos = std::numeric_limits::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::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::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 mvValues; + KahanSum mfSum; + ArrayRefListValue() = default; + double get() const { return mfSum.get(); } + }; + std::vector vArrayValues; + + std::vector 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(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().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::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::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::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::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(nTab2 - nTab1 + 1) * + static_cast(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(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1) * + static_cast(aAbs.aEnd.Col() - aAbs.aStart.Col() + 1); + } + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + } + PushDouble(static_cast(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(nTab2 - nTab1 + 1) * + static_cast(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(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1) * + static_cast(aAbs.aEnd.Row() - aAbs.aStart.Row() + 1); + } + break; + default: + PopError(); + SetError(FormulaError::IllegalParameter); + } + } + PushDouble(static_cast(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(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(aAbs.aEnd.Tab() - aAbs.aStart.Tab() + 1); + } + break; + default: + PopError(); + SetError( FormulaError::IllegalParameter ); + } + } + } + PushDouble( static_cast(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(nCols), 1, /*bEmpty*/true ); + if (pResMat) + { + for (SCCOL i=0; i < nCols; ++i) + pResMat->PutDouble( nVal + i, static_cast(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(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( 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(nCol2-nCol1+1), 1, /*bEmpty*/true); + if (pResMat) + { + for (SCCOL i = nCol1; i <= nCol2; i++) + pResMat->PutDouble(static_cast(i+1), + static_cast(i-nCol1), 0); + PushMatrix(pResMat); + return; + } + } + else + nVal = static_cast(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(nRows), /*bEmpty*/true); + if (pResMat) + { + for (SCROW i=0; i < nRows; i++) + pResMat->PutDouble( nVal + i, 0, static_cast(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(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( 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(nRow2-nRow1+1), /*bEmpty*/true); + if (pResMat) + { + for (SCROW i = nRow1; i <= nRow2; i++) + pResMat->PutDouble(static_cast(i+1), 0, + static_cast(i-nRow1)); + PushMatrix(pResMat); + return; + } + } + else + nVal = static_cast(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(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(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(aRange.aEnd.Row() - aRange.aStart.Row() + 1) * + static_cast(aRange.aEnd.Col() - aRange.aStart.Col() + 1) * + static_cast(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(nC - 1); + nRow2 = static_cast(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(nC - 1); + nMaxRow = static_cast(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(nC - 1); + nRow2 = static_cast(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& 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> 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::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(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(nC - 1); + nRow2 = static_cast(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 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 aResValues; + pResultMatrix->GetDoubleArray(aResValues); + if (vConditions.size() != aResValues.size()) + { + PushError( FormulaError::IllegalParameter); + return; + } + + std::vector::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::max()) + { + // Apply condition result to reference list array result position. + std::vector& 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().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::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(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(nC - 1); + nMainRow2 = static_cast(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::max() : 0)); + const bool bAppliedArray = (!bRefArrayMain && nRefArrayMainPos == 0); + + if (nRefArrayMainPos == 0) + xResMat = GetNewMat( 1, nRefArrayRows, /*bEmpty*/true ); + + if (pMainMatrix) + { + std::vector 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::const_iterator itRes = vConditions.begin(), itResEnd = vConditions.end(); + std::vector::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::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::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::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::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::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 vIndex; + if (bOmitErrorValues) + { + std::vector 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().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(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(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(nResRow1 + nDelta); + if (nTempRow > mrDoc.MaxRow()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nResCol1); + aAdr.SetRow(nTempRow); + } + else + { + SCCOL nTempCol = static_cast(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(nR-nRow1) : static_cast(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(nResRow1 + nDelta); + if (nTempRow > mrDoc.MaxRow()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nResCol1); + aAdr.SetRow(nTempRow); + } + else + { + SCCOL nTempCol = static_cast(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(nRow1 + nDelta); + if (nTempRow > mrDoc.MaxRow()) + { + PushDouble(0); + return; + } + aAdr.SetCol(nCol2); + aAdr.SetRow(nTempRow); + } + else + { + SCCOL nTempCol = static_cast(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(fIndex); + SCCOL nSpIndex = static_cast(fIndex); + + if (!pMat) + { + nZIndex += nRow1; // value row + nSpIndex = sal::static_int_cast( 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(nSpIndex); + SCSIZE nY = nDelta; + if ( bHLookup ) + { + nX = nDelta; + nY = static_cast(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 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 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 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(nVal)); + else + { + FormulaError nErr = FormulaError::NONE; + nField = pDBRef->findFieldColumn(aStr.getString(), &nErr); + SetError(nErr); + } + + if (!mrDoc.ValidCol(nField)) + return nullptr; + + unique_ptr 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(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 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 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(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 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 values; + KahanSum vSum = 0.0; + KahanSum fSum = 0.0; + + rValCount = 0.0; + bool bMissingField = false; + unique_ptr 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 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(GetInt16()); + SCROW nRow = static_cast(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(static_cast(nCol1) + nColPlus); + nRow1 = static_cast(static_cast(nRow1) + nRowPlus); + if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1)) + PushIllegalArgument(); + else + PushSingleRef(nCol1, nRow1, nTab1); + } + else + { + nCol1 = static_cast(static_cast(nCol1)+nColPlus); + nRow1 = static_cast(static_cast(nRow1)+nRowPlus); + nCol2 = static_cast(static_cast(nCol1)+nColNew-1); + nRow2 = static_cast(static_cast(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(static_cast(nCol1) + nColPlus); + nRow1 = static_cast(static_cast(nRow1) + nRowPlus); + if (!mrDoc.ValidCol(nCol1) || !mrDoc.ValidRow(nRow1)) + PushIllegalArgument(); + else + PushExternalSingleRef(nFileId, aTabName, nCol1, nRow1, nTab1); + } + else + { + nCol1 = static_cast(static_cast(nCol1)+nColPlus); + nRow1 = static_cast(static_cast(nRow1)+nRowPlus); + nCol2 = static_cast(static_cast(nCol1)+nColNew-1); + nRow2 = static_cast(static_cast(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(static_cast(nCol1)+nColPlus); + nRow1 = static_cast(static_cast(nRow1)+nRowPlus); + nCol2 = static_cast(static_cast(nCol1)+nColNew-1); + nRow2 = static_cast(static_cast(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(static_cast(nCol1)+nColPlus); + nRow1 = static_cast(static_cast(nRow1)+nRowPlus); + nCol2 = static_cast(static_cast(nCol1)+nColNew-1); + nRow2 = static_cast(static_cast(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(GetInt16()); + else + nCol = 0; + if (nParamCount >= 2) + nRow = static_cast(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(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(nCol), + static_cast(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(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(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(nCol-1), + static_cast(nRow-1))) + PushDouble( pMat->GetDouble( + static_cast(nCol-1), + static_cast(nRow-1))); + else + PushString( pMat->GetString( + static_cast(nCol-1), + static_cast(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(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(fDec) != pFormatter->GetFormatPrecision( nIndex ) ) + { + OUString sFormatString = pFormatter->GenerateFormat( + nIndex, + ScGlobal::eLnge, + true, // with thousands separator + false, // not red + static_cast(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(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(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(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(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(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(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(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(aMatch.getBuffer()), aMatch.length()); + PushString( aResult); + return; + } + + const icu::UnicodeString aIcuReplacement( + false, reinterpret_cast(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(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 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(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(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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +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(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(nDiff)); +} + +void ScInterpreter::ScGetActTime() +{ + nFuncFmtType = SvNumFormatType::DATETIME; + DateTime aActTime( DateTime::SYSTEM ); + tools::Long nDiff = aActTime - pFormatter->GetNullDate(); + double fTime = aActTime.GetHour() / static_cast(::tools::Time::hourPerDay) + + aActTime.GetMin() / static_cast(::tools::Time::minutePerDay) + + aActTime.GetSec() / static_cast(::tools::Time::secondPerDay) + + aActTime.GetNanoSec() / static_cast(::tools::Time::nanoSecPerDay); + PushDouble( static_cast(nDiff) + fTime ); +} + +void ScInterpreter::ScGetYear() +{ + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetInt32()); + PushDouble( static_cast(aDate.GetYear()) ); +} + +void ScInterpreter::ScGetMonth() +{ + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetInt32()); + PushDouble( static_cast(aDate.GetMonth()) ); +} + +void ScInterpreter::ScGetDay() +{ + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetInt32()); + PushDouble(static_cast(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(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(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( 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(aDate.GetWeekOfYear( eFirstDayOfWeek, nMinimumNumberOfDaysInWeek )) ); +} + +void ScInterpreter::ScGetIsoWeekOfYear() +{ + if ( MustHaveParamCount( GetByte(), 1 ) ) + { + Date aDate = pFormatter->GetNullDate(); + aDate.AddDays( GetInt32()); + PushInt( static_cast(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( O % 31 + 1 ); + sal_Int16 nMonth = sal::static_int_cast( 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(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 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( bReverse ? -nCnt : nCnt ) ); + } +} + +void ScInterpreter::ScWorkday_MS() +{ + sal_uInt8 nParamCount = GetByte(); + if ( !MustHaveParamCount( nParamCount, 2, 4 ) ) + return; + + nFuncFmtType = SvNumFormatType::DATE; + vector 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( 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( 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(nSign) * + ( static_cast(aDate2.GetDay()) + static_cast(aDate2.GetMonth()) * 30.0 + + static_cast(aDate2.GetYear()) * 360.0 + - static_cast(aDate1.GetDay()) - static_cast(aDate1.GetMonth()) * 30.0 + - static_cast(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( 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(::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(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(i), fFactor); + fSln = fSalvageValue/ (fLife1 - static_cast(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(fIntStart); + sal_uLong nLoopEnd = static_cast(fIntEnd); + + if (bNoSwitch) + { + for (sal_uLong i = nLoopStart + 1; i <= nLoopEnd; i++) + { + double fTerm = ScGetDDB(fCost, fSalvage, fLife, static_cast(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(fFlag); + sal_uLong nStart = static_cast(fStart); + sal_uLong nEnd = static_cast(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(i-2), fPmt, fPv, true) - fPmt; + else + fIpmt += ScGetFV(fRate, static_cast(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(fFlag); + double fPmt = ScGetPMT(fRate, fNper, fPv, 0.0, bPayInAdvance); + KahanSum fPpmt = 0.0; + sal_uLong nStart = static_cast(fStart); + sal_uLong nEnd = static_cast(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(i-2), fPmt, fPv, true) - fPmt) * fRate; + else + fPpmt += fPmt - ScGetFV(fRate, static_cast(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 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(*x1), const_cast(*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(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; iGetLinks()[i].get(); + if (ScDdeLink* pLink = dynamic_cast(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(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(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( 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(fVal); + sal_uLong nBase = static_cast(fBase); + while ( nVal && p > pBuf ) + { + *--p = pDigits[ nVal % nBase ]; + nVal /= nBase; + } + fVal = static_cast(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(fBase) - 1; + else + nDig = static_cast(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(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(fVal); + sal_uInt16 nMode = static_cast(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( nVal + pValues[ nIndex ] ); + nVal = sal::static_int_cast( 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( nValue + nDigit1 ); + nValidRest %= (nDigit1 * (bIsDec1 ? 5 : 2)); + bValid = (nValidRest >= nDigit1); + if( bValid ) + nValidRest = sal::static_int_cast( nValidRest - nDigit1 ); + nCharIndex++; + } + else if( nDigit1 * 2 != nDigit2 ) + { + sal_uInt16 nDiff = nDigit2 - nDigit1; + nValue = sal::static_int_cast( 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(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 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 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 +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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::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::epsilon(); + + OSL_ENSURE(fAx 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(::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 + +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::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::max()); + const double fLogDblMin = log( ::std::numeric_limits::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(k)-1; i >= 1; i--) + nVal *= n-static_cast(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::min()) + { + fFactor = pow(p, n); + if (fFactor <= ::std::numeric_limits::min()) + return GetBetaDistPDF(p, x+1.0, n-x+1.0)/(n+1.0); + else + { + sal_uInt32 max = static_cast(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(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( 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(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::min()) + PushDouble(lcl_GetBinomDistRange(n,xs,xe,fFactor,p,q)); + else + { + fFactor = pow(p, n); + if (fFactor > ::std::numeric_limits::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 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::min()) + { + fFactor = pow(p, n); + if (fFactor <= ::std::numeric_limits::min()) + PushDouble(GetBetaDist(q,n-x,x+1.0)); + else + { + if (fFactor > fMachEps) + { + double fSum = 1.0 - fFactor; + sal_uInt32 max = static_cast (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::min()) + { + KahanSum fSum = fFactor; + sal_uInt32 max = static_cast (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 (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::min()) + { + KahanSum fSum = 1.0 - fFactor; + sal_uInt32 max = static_cast (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 (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(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( x ); + for (int i = 1; i <= nEnd; i++) + { + fSummand = (fSummand * lambda)/static_cast(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 cnNumer, cnDenom; + + size_t nEstContainerSize = static_cast( 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(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(nC1*nR1 - 1); + if (fDF == 0.0) + { + PushNoValue(); + return; + } + } + else + fDF = static_cast(nC1-1)*static_cast(nR1-1); + PushDouble(GetChiDist(fChi.get(), fDF)); +} + +void ScInterpreter::ScKurt() +{ + KahanSum fSum; + double fCount; + std::vector 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& 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 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 & 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::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 aArray; + GetNumberSequenceArray( nParamCount, aArray, false ); + PushDouble( GetMedian( aArray)); +} + +double ScInterpreter::GetPercentile( vector & rArray, double fPercentile ) +{ + size_t nSize = rArray.size(); + if (nSize == 1) + return rArray[0]; + else + { + size_t nIndex = static_cast(::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::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 & 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( nSize1 - 1 ) ) + { + SetError( FormulaError::IllegalParameter ); + return 0.0; + } + + size_t nIndex = static_cast(::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::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 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 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 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 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 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::max())) + return static_cast(0); + return static_cast(f); + }); + + vector 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::iterator iPos = aSortArray.begin() + (bSmall ? k-1 : nSize-k); + ::std::nth_element( aSortArray.begin(), iPos, aSortArray.end()); + PushDouble( *iPos); + } + } + else + { + std::set 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 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 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 & 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( 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( i + 1 ) / static_cast( 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( nOldCount - 1 ) + fFract, nSize - 1 ); + else + fRes = ( static_cast(nOldCount) + fFract ) / static_cast( 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 aSortArray; + GetSortArray( 1, aSortArray, nullptr, false, false ); + SCSIZE nSize = aSortArray.size(); + if (nSize == 0 || nGlobalError != FormulaError::NONE) + PushNoValue(); + else + { + sal_uLong nIndex = static_cast(::rtl::math::approxFloor(alpha*static_cast(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(nSize-2*nIndex)); + } +} + +std::vector ScInterpreter::GetTopNumberArray( SCSIZE& rCol, SCSIZE& rRow ) +{ + std::vector 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& 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& rSortArray, vector* 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& rSortArray, vector* 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& rSortArray, vector* pIndexOrder ) +{ + tools::Long n = static_cast(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 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::min() + || (!_bStexy && fSumSqrDeltaY < ::std::numeric_limits::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::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 mfWReal; + std::vector mfWImag; + SCSIZE mnN; + bool mbInverse; +}; + +} + +void ScTwiddleFactors::Compute() +{ + mfWReal.resize(mnN); + mfWImag.resize(mnN); + + double nW = (mbInverse ? 2 : -2)*M_PI/static_cast(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(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(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& 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& mrArray; + std::vector& mfWReal; + std::vector& 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& rCmplxArray, bool bScaleOnlyReal) +{ + const SCSIZE nPoints = rCmplxArray.size()/2; + const double fScale = 1.0/static_cast(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& 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& 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& mrArray; + const SCSIZE mnPoints; + double mfMinMag; + bool mbReal:1; + bool mbInverse:1; + bool mbPolar:1; + bool mbDisableNormalize:1; +}; + +} + +void ScComplexBluesteinFFT::Compute() +{ + std::vector aRealScalars(mnPoints); + std::vector aImagScalars(mnPoints); + double fW = (mbInverse ? 2 : -2)*M_PI/static_cast(mnPoints); + for (SCSIZE nIdx = 0; nIdx < mnPoints; ++nIdx) + { + double fAngle = 0.5*fW*static_cast(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 aASignal(nExtendedLength*2); // complex valued + std::vector 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& rInArray, std::vector& rOutArray, bool bInverse, + bool bPolar, double fMinMag) : + mrInArray(rInArray), + mrOutArray(rOutArray), + mfMinMag(fMinMag), + mbInverse(bInverse), + mbPolar(bPolar) + {} + + void Compute(); + +private: + std::vector& mrInArray; + std::vector& 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 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&); + +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& rMatGenFunc); + +private: + ScMatrixRef& mpInputMat; + double mfMinMag; + bool mbReal:1; + bool mbInverse:1; + bool mbPolar:1; +}; + +} + +ScMatrixRef ScFFT::Compute(const std::function& rMatGenFunc) +{ + std::vector aArray; + mpInputMat->GetDoubleArray(aArray); + SCSIZE nPoints = mbReal ? aArray.size() : (aArray.size()/2); + if (nPoints == 1) + { + std::vector 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 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 aFunc = [this](SCSIZE nCol, SCSIZE nRow, std::vector& 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 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using namespace formula; +using ::std::unique_ptr; + +#define ADDIN_MAXSTRLEN 256 + +thread_local std::unique_ptr 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(pCellArr); + *p++ = static_cast(nCol1); + *p++ = static_cast(nRow1); + *p++ = static_cast(nTab1); + *p++ = static_cast(nCol2); + *p++ = static_cast(nRow2); + *p++ = static_cast(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(nCol); + *p++ = static_cast(nRow); + *p++ = static_cast(nTab); + *p++ = static_cast(nErr); + memcpy( p, &nVal, sizeof(double)); + nPos += 8 + sizeof(double); + p = reinterpret_cast( 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(pCellArr); + *p++ = static_cast(nCol1); + *p++ = static_cast(nRow1); + *p++ = static_cast(nTab1); + *p++ = static_cast(nCol2); + *p++ = static_cast(nRow2); + *p++ = static_cast(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(aTmp.getLength()); + sal_uInt16 nLen = ( nStrLen + 2 ) & ~1; + + if ((static_cast(nPos) + (5 * sizeof(sal_uInt16)) + nLen) > MAXARRSIZE) + return false; + *p++ = static_cast(nCol); + *p++ = static_cast(nRow); + *p++ = static_cast(nTab); + *p++ = static_cast(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( 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(pCellArr); + *p++ = static_cast(nCol1); + *p++ = static_cast(nRow1); + *p++ = static_cast(nTab1); + *p++ = static_cast(nCol2); + *p++ = static_cast(nRow2); + *p++ = static_cast(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(nCol); + *p++ = static_cast(nRow); + *p++ = static_cast(nTab); + *p++ = static_cast(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(aTmp.getLength()); + sal_uInt16 nLen = ( nStrLen + 2 ) & ~1; + if ( (static_cast(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( 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(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* 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(nCol2 - nCol1 + 1); + if ( nJumpRows < o3tl::make_unsigned(nRow2 - nRow1 + 1) ) + nJumpRows = static_cast(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( 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(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(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(nFmt) ); + return p; + } + } + + // Allocate a new token + auto p = new FormulaTypedDoubleToken( fVal, static_cast(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( 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( rRange.aStart.Col() + nC ) ); + rAdr.SetRow( sal::static_int_cast( 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(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(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(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 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 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( + 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 aInner( &nVal, 1 ); + uno::Sequence< uno::Sequence > 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 aInner( &fVal, 1 ); + uno::Sequence< uno::Sequence > 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 aInner( &aString, 1 ); + uno::Sequence< uno::Sequence > 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 aInner( &aElem, 1 ); + uno::Sequence< uno::Sequence > 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 xObj = + ScCellRangeObj::CreateRangeFromDoc( mrDoc, aRange ); + if (xObj.is()) + aParam <<= xObj; + else + SetError(FormulaError::IllegalParameter); + } + break; + case svDoubleRef: + { + ScRange aRange; + PopDoubleRef( aRange ); + uno::Reference 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 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& 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 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(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(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(nR)); + refArray->AddDim(1, static_cast(nC)); + for( SCSIZE nMatRow = 0; nMatRow < nR; nMatRow++ ) + { + sal_Int32 nIdx[ 2 ]; + nIdx[ 0 ] = static_cast(nMatRow+1); + for( SCSIZE nMatCol = 0; nMatCol < nC; nMatCol++ ) + { + nIdx[ 1 ] = static_cast(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(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(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(nRe - nRs + 1); + pDimArray->GetDim(2, nCs, nCe); + nC = static_cast(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(j); + for ( SCSIZE i=0; i < nC; i++ ) + { + nIdx[ nColIdx ] = nCs + static_cast(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::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 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(eOp) + << " params: " << static_cast(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 +#include +#include + +#ifdef DEBUG_SC_LUP_DECOMPOSITION +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //Application:: + +#include + +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& 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(nCol2 - nCol1 + 1); + SCSIZE nMatRows = static_cast(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((*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(nThisCol - nCol1); + const SCSIZE nMatRow = static_cast(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(GetUInt32()); + SCSIZE nC = static_cast(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(nR) && + nRow2 - nRow1 >= static_cast(nC) && + nTab1 == nTab2) + { + ScAddress aAdr( sal::static_int_cast( nCol1 + nR ), + sal::static_int_cast( 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(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; iGetDouble(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 +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( *pMat1, *pMat2, this); + } + else + { + pResMat = lcl_MatrixCalculation( *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( *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( *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( *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 aResArray; + pMatLast->GetDoubleArray(aResArray); + + while (nParamCount--) + { + pMat = GetMatrix( nParamCount, nInRefList); + if (!pMat) + { + PushIllegalParameter(); + return; + } + pMat->GetDimensions(nC, nR); + if (nC != nCLast || nR != nRLast) + { + PushNoValue(); + return; + } + + pMat->MergeDoubleArrayMultiply(aResArray); + } + + KahanSum fSum = 0.0; + for( double fPosArray : aResArray ) + { + FormulaError nErr = GetDoubleErrorValue(fPosArray); + if (nErr == FormulaError::NONE) + fSum += fPosArray; + else if (nErr != FormulaError::ElementNaN) + { + // Propagate the first error encountered, ignore "this is not a number" elements. + PushError(nErr); + return; + } + } + + PushDouble(fSum.get()); +} + +void ScInterpreter::ScSumX2MY2() +{ + CalculateSumX2MY2SumX2DY2(false); +} +void ScInterpreter::CalculateSumX2MY2SumX2DY2(bool _bSumX2DY2) +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + ScMatrixRef pMat1 = nullptr; + ScMatrixRef pMat2 = nullptr; + SCSIZE i, j; + pMat2 = GetMatrix(); + pMat1 = GetMatrix(); + if (!pMat2 || !pMat1) + { + PushIllegalParameter(); + return; + } + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat2->GetDimensions(nC2, nR2); + pMat1->GetDimensions(nC1, nR1); + if (nC1 != nC2 || nR1 != nR2) + { + PushNoValue(); + return; + } + double fVal; + KahanSum fSum = 0.0; + for (i = 0; i < nC1; i++) + for (j = 0; j < nR1; j++) + if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) + { + fVal = pMat1->GetDouble(i,j); + fSum += fVal * fVal; + fVal = pMat2->GetDouble(i,j); + if ( _bSumX2DY2 ) + fSum += fVal * fVal; + else + fSum -= fVal * fVal; + } + PushDouble(fSum.get()); +} + +void ScInterpreter::ScSumX2DY2() +{ + CalculateSumX2MY2SumX2DY2(true); +} + +void ScInterpreter::ScSumXMY2() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + ScMatrixRef pMat2 = GetMatrix(); + ScMatrixRef pMat1 = GetMatrix(); + if (!pMat2 || !pMat1) + { + PushIllegalParameter(); + return; + } + SCSIZE nC1, nC2; + SCSIZE nR1, nR2; + pMat2->GetDimensions(nC2, nR2); + pMat1->GetDimensions(nC1, nR1); + if (nC1 != nC2 || nR1 != nR2) + { + PushNoValue(); + return; + } // if (nC1 != nC2 || nR1 != nR2) + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this); + if (!pResMat) + { + PushNoValue(); + } + else + { + PushDouble(pResMat->SumSquare(false).maAccumulator.get()); + } +} + +void ScInterpreter::ScFrequency() +{ + if ( !MustHaveParamCount( GetByte(), 2 ) ) + return; + + vector aBinArray; + vector aBinIndexOrder; + + GetSortArray( 1, aBinArray, &aBinIndexOrder, false, false ); + SCSIZE nBinSize = aBinArray.size(); + if (nGlobalError != FormulaError::NONE) + { + PushNoValue(); + return; + } + + vector 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(nCount), aBinIndexOrder[j]); + } + pResMat->PutDouble(static_cast(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). + +// 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; iGetDouble(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; rowGetDouble(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; colGetDouble(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; rowGetDouble(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; colGetDouble(col,nR)); + if (fNorm < fVal) + fNorm = fVal; + } + return fNorm; +} + +// Special version for use within QR decomposition. +// 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; rowGetDouble(nCa,row) * pMatB->GetDouble(nCb,row); + return fResult.get(); +} + +// 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; colGetDouble(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 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; cPutDouble( 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 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; rPutDouble( + 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; colGetDouble(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 ; iGetDouble(i); + return fSum.get()/static_cast(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(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(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; iGetDouble(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(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 && (NPutError( 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; iPutDouble( ::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; iPutDouble( ::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( 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(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(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( 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; iPutDouble(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(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(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( 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; iPutDouble(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(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(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 && (NClone(); // 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; iPutDouble( ::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; iPutDouble( ::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(aPos.Col() - aAdr.Col()); + SCSIZE nR = static_cast(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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace formula; + +double const fHalfMachEps = 0.5 * ::std::numeric_limits::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::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::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 (fAlpha0.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(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::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(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::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::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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +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 pContext( + xmlNewParserCtxt(), xmlFreeParserCtxt ); + + std::shared_ptr pDoc( xmlParseMemory( pXML, aOString.getLength() ), + xmlFreeDoc ); + + if(!pDoc) + { + PushError( FormulaError::NoValue ); + return; + } + + std::shared_ptr pXPathCtx( xmlXPathNewContext(pDoc.get()), + xmlXPathFreeContext ); + + std::shared_ptr 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(pNodeSet->nodeTab[nNode]); + xmlNodePtr cur = reinterpret_cast(ns->next); + std::shared_ptr pChar2(xmlNodeGetContent(cur), xmlFree); + aResult = OStringToOUString(std::string_view(reinterpret_cast(pChar2.get())), RTL_TEXTENCODING_UTF8); + } + else + { + xmlNodePtr cur = pNodeSet->nodeTab[nNode]; + std::shared_ptr pChar2(xmlNodeGetContent(cur), xmlFree); + aResult = OStringToOUString(std::string_view(reinterpret_cast(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(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; iGetLinks()[i].get(); + if (ScWebServiceLink* pLink = dynamic_cast(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( c ) ) || c == '-' || c == '_' ) + aUrlBuf.append( c ); + else + { + aUrlBuf.append( '%' ); + OString convertedChar = OString::number( static_cast( 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 mpBase; // calculated base value array + std::unique_ptr mpTrend; // calculated trend factor array + std::unique_ptr mpPerIdx; // calculated periodical deviation array, not used with eds + std::unique_ptr 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::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::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 +#include +#include + +#include +#include +#include +#include +#include + +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 +#include +#include + +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(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(nCol) * nRows + nRow]. + GetJump(rBool, rStart, rNext, rStop); +} + +void ScJumpMatrix::SetAllJumps(double fBool, short nStart, short nNext, short nStop) +{ + sal_uInt64 n = static_cast(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 +#include +#include + +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 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 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 +#include +#include +#include + +#include + +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& 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(&rHint); + if ((p && (p->GetId() == SfxHintId::ScDataChanged)) || dynamic_cast(&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 +#include +#include +#include + +#include +#include + +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(f); + if (i % 2 != 0 && rtl::math::approxEqual(1 / static_cast(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 + +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 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 +#include + +//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 +#include +#include + +#include +#include +#include +#include + +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 + +// 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 +#include +#include +#include + +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& rLink ) +{ + aCommitLink = rLink; +} + +void ScLinkConfigItem::SetNotifyLink( const Link& rLink ) +{ + aNotifyLink = rLink; +} + +void ScLinkConfigItem::Notify( const css::uno::Sequence& /* 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 + +#include + +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(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 +#include +#include +#include +#include +#include +#include +#include +#include + +#if DEBUG_SC_PARCLASSDOC +// the documentation thingy +#include +#include +#include +#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(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( 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( 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(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; igetSymbol(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 +#include +#include + +#include +#include + +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(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 ScPrintCfg::GetPropertyNames() +{ + return {"Page/EmptyPages", // SCPRINTOPT_EMPTYPAGES + "Other/AllSheets", // SCPRINTOPT_ALLSHEETS + "Page/ForceBreaks"}; // SCPRINTOPT_FORCEBREAKS; +} + +ScPrintCfg::ScPrintCfg() : + ConfigItem( CFGPATH_PRINT ) +{ + Sequence aNames = GetPropertyNames(); + EnableNotification(aNames); + ReadCfg(); +} + +void ScPrintCfg::ReadCfg() +{ + const Sequence aNames = GetPropertyNames(); + const Sequence 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 aNames = GetPropertyNames(); + Sequence 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(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 +#include + +#include +#include + +// 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 oCol, std::optional 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& rRangeVec = rPsTab.GetPrintRanges(); + + rPrintRanges.put("sheet", static_cast(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 +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SC_PROGRESS_CXX +#include +#include +#include +#include + +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 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 + +#include + +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include + +#include + +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 ScQueryParamBase::FindAllEntriesByField(SCCOLROW nField) +{ + std::vector 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( nCol1 + nDifX ); + nRow1 = sal::static_int_cast( nRow1 + nDifY ); + nCol2 = sal::static_int_cast( nCol2 + nDifX ); + nRow2 = sal::static_int_cast( nRow2 + nDifY ); + nTab = sal::static_int_cast( nTab + nDifZ ); + size_t n = m_Entries.size(); + for (size_t i=0; iGetDimensions(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 +#include +#include +#include +#include +#include + +#include +#include +#include + +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; + 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; + // 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(&rHint); + if ((p && (p->GetId() == SfxHintId::ScDataChanged)) + || dynamic_cast(&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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using ::std::vector; +using ::std::find_if; +using ::std::for_each; +using ::formula::FormulaGrammar; + +namespace { + +template +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 +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::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::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 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(nColStart, rRange.aStart.Col()); + SCCOL nNewRangeEndCol = std::min(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 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(nRowStart, rRange.aStart.Row()); + SCROW nNewRangeEndRow = std::min(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 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 +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& 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 +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& 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& 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 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(rAdr)); + return itr == maRanges.end() ? nullptr : &*itr; +} + +ScRange* ScRangeList::Find( const ScAddress& rAdr ) +{ + auto itr = find_if( + maRanges.begin(), maRanges.end(), FindEnclosingRange(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(rRange)); +} + +bool ScRangeList::Contains( const ScRange& rRange ) const +{ + return std::any_of(maRanges.begin(), maRanges.end(), FindEnclosingRange(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(nColStart1, nColStart2), std::max(nRowStart1, nRowStart2), + std::max(nTabStart1, nTabStart2), std::min(nColEnd1, nColEnd2), + std::min(nRowEnd1, nRowEnd2), std::min(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::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::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 ScRangePairList::CreateNameSortedArray( ScDocument& rDoc ) const +{ + std::vector 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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(-nMinCol), static_cast(-nMinRow), static_cast(-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; iIsReference(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( 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( 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( 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(ScGlobal::GetCollator().compareString( + (*static_cast(p1))->GetName(), + (*static_cast(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> 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(*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(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 r = + m_Data.insert(std::make_pair(aName, std::unique_ptr(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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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(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 > aRowSeq( nRowCount ); + uno::Sequence* pRowAry = aRowSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence aColSeq( nColCount ); + sal_Int32* pColAry = aColSeq.getArray(); + for (sal_Int32 nCol = 0; nCol < nColCount; nCol++) + pColAry[nCol] = lcl_DoubleToLong( rDoc.GetValue( + ScAddress( static_cast(nStartCol+nCol), static_cast(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 > aRowSeq( static_cast(nRowCount) ); + uno::Sequence* pRowAry = aRowSeq.getArray(); + for (SCSIZE nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence aColSeq( static_cast(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 > aRowSeq( nRowCount ); + uno::Sequence* pRowAry = aRowSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence aColSeq( nColCount ); + double* pColAry = aColSeq.getArray(); + for (sal_Int32 nCol = 0; nCol < nColCount; nCol++) + pColAry[nCol] = rDoc.GetValue( + ScAddress( static_cast(nStartCol+nCol), static_cast(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 > aRowSeq( static_cast(nRowCount) ); + uno::Sequence* pRowAry = aRowSeq.getArray(); + for (SCSIZE nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence aColSeq( static_cast(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 > aRowSeq( nRowCount ); + uno::Sequence* pRowAry = aRowSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence aColSeq( nColCount ); + OUString* pColAry = aColSeq.getArray(); + for (sal_Int32 nCol = 0; nCol < nColCount; nCol++) + { + FormulaError nErrCode = rDoc.GetStringForFormula( + ScAddress(static_cast(nStartCol+nCol), static_cast(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 > aRowSeq( static_cast(nRowCount) ); + uno::Sequence* pRowAry = aRowSeq.getArray(); + for (SCSIZE nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence aColSeq( static_cast(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 > aRowSeq( nRowCount ); + uno::Sequence* pRowAry = aRowSeq.getArray(); + for (sal_Int32 nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence aColSeq( nColCount ); + uno::Any* pColAry = aColSeq.getArray(); + for (sal_Int32 nCol = 0; nCol < nColCount; nCol++) + { + uno::Any& rElement = pColAry[nCol]; + + ScAddress aPos( static_cast(nStartCol+nCol), static_cast(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 > aRowSeq( static_cast(nRowCount) ); + uno::Sequence* pRowAry = aRowSeq.getArray(); + for (SCSIZE nRow = 0; nRow < nRowCount; nRow++) + { + uno::Sequence aColSeq( static_cast(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& a, const uno::Sequence& b) { + return a.getLength() < b.getLength(); }); + nMaxColCount = pRow->getLength(); + } + if ( nMaxColCount && nRowCount ) + { + const uno::Sequence* pRowArr = aSequence.getConstArray(); + OUString aUStr; + xMatrix = new ScMatrix( + static_cast(nMaxColCount), + static_cast(nRowCount), 0.0); + SCSIZE nCols, nRows; + xMatrix->GetDimensions( nCols, nRows); + if (nCols != static_cast(nMaxColCount) || nRows != static_cast(nRowCount)) + { + OSL_FAIL( "ScSequenceToMatrix::CreateMixedMatrix: matrix exceeded max size, returning NULL matrix"); + return nullptr; + } + for (sal_Int32 nRow=0; nRowPutBoolean( fVal != 0.0, + static_cast(nCol), + static_cast(nRow) ); + else + xMatrix->PutDouble( fVal, + static_cast(nCol), + static_cast(nRow) ); + } + else + { + // Try string, else use empty as last resort. + + if ( pColArr[nCol] >>= aUStr ) + { + xMatrix->PutString( + svl::SharedString(aUStr), static_cast(nCol), static_cast(nRow)); + } + else + xMatrix->PutEmpty( + static_cast(nCol), + static_cast(nRow) ); + } + } + for (sal_Int32 nCol=nColCount; nColPutEmpty( + static_cast(nCol), + static_cast(nRow) ); + } + } + } + } + return xMatrix; +} + +bool ScByteSequenceToString::GetString( OUString& rString, const uno::Any& rAny, + sal_uInt16 nEncoding ) +{ + uno::Sequence aSeq; + if ( rAny >>= aSeq ) + { + rString = OUString( reinterpret_cast(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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* 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(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; ifindByUpperName(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(rAddress.Column), static_cast(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(rRange.StartColumn), static_cast(rRange.StartRow), rRange.Sheet, + static_cast(rRange.EndColumn), static_cast(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(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 +#include + +#include + +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 +#include + +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* 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 + +#include + +#include +#include + +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(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(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 + +#include + +#include +#include +#include +#include + +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 + +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 +#include + +void ScRefreshTimerControl::SetAllowRefresh( bool b ) +{ + if ( b && nBlockRefresh ) + --nBlockRefresh; + else if ( !b && nBlockRefresh < sal_uInt16(~0) ) + ++nBlockRefresh; +} + +ScRefreshTimerProtector::ScRefreshTimerProtector( std::unique_ptr 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 const * pp ) +{ + ppControl = pp; +} + +void ScRefreshTimer::SetRefreshHandler( const Link& 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 +#include +#include +#include +#include + +#include +#include +#include + +#include + +using namespace formula; + +using ::std::vector; + +void ScRefTokenHelper::compileRangeRepresentation( + vector& 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 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& 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& pTokens, const ScRangeList& rRanges) +{ + vector 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& 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& 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 + 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& 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& 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 +#include +#include +#include + +#include + +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( 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( 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( rRef + nDelta ); + return true; + } + + if ( nDelta > 0 ) // move backward + { + if ( rRef >= nStart && rRef <= nEnd + nDelta ) + { + if ( rRef <= nEnd ) + rRef = sal::static_int_cast( 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( 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( 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( 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( n2 + nD ); + return; + } + // at the beginning + n1 = sal::static_int_cast( 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(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( nNewTab + nCount ); + while (nNewTab>=nCount) nNewTab = sal::static_int_cast( 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(static_cast(rDest.Col()) + + static_cast(nRelY)); + rRow = static_cast(static_cast(rDest.Row()) + + static_cast(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(rRef.aEnd.Col() + nGrowX)); + eRet = UR_UPDATED; + } + if ( bUpdateY ) + { + rRef.aEnd.SetRow(sal::static_int_cast(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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#if DEBUG_MATRIX +#include +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 element_block_func; +}; + +} + +typedef mdds::multi_type_matrix 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 +class CompareMatrixElemFunc +{ + static Comp maComp; + + std::vector 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 +Comp CompareMatrixElemFunc::maComp; + +} + +/* TODO: it would be good if mdds had get/set additionally to + * get/set, 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& 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& rArray, bool bEmptyAsZero ) const; + void MergeDoubleArrayMultiply( std::vector& rArray ) const; + + template + void ApplyOperation(T aOp, ScMatrixImpl& rMat); + + void ExecuteOperation(const std::pair& rStartPos, + const std::pair& rEndPos, const ScMatrix::DoubleOpFunction& aDoubleFunc, + const ScMatrix::BoolOpFunction& aBoolFunc, const ScMatrix::StringOpFunction& aStringFunc, + const ScMatrix::EmptyOpFunction& aEmptyFunc) const; + + template + ScMatrix::IterateResultMultiple ApplyCollectOperation(const std::vector& 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& 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(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 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 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 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 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 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 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 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 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 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 +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(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(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(maMat); +} + +namespace { + +template +class WalkElementBlocks +{ + Op maOp; + ScMatrix::IterateResult maRes; + bool mbTextAsZero:1; + bool mbIgnoreErrorValues:1; +public: + WalkElementBlocks(bool bTextAsZero, bool bIgnoreErrorValues) : + maRes(Op::InitVal, 0), + mbTextAsZero(bTextAsZero), mbIgnoreErrorValues(bIgnoreErrorValues) + {} + + const ScMatrix::IterateResult& 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 +class WalkElementBlocksMultipleValues +{ + const std::vector* mpOp; + ScMatrix::IterateResultMultiple maRes; +public: + WalkElementBlocksMultipleValues(const std::vector& 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& 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::max(); + +template +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::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::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::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::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 +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 +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(eOp)); + return CreateDoubleError( FormulaError::UnknownState); +} + +class CompareMatrixFunc +{ + sc::Compare& mrComp; + size_t mnMatPos; + sc::CompareOptions* mpOptions; + std::vector 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& 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 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& getValues() const + { + return maResValues; + } +}; + +class ToDoubleArray +{ + std::vector maArray; + std::vector::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& rOther) + { + maArray.swap(rOther); + } +}; + +struct ArrayMul +{ + double operator() (const double& lhs, const double& rhs) const + { + return lhs * rhs; + } +}; + +template +class MergeDoubleArrayFunc +{ + std::vector::iterator miPos; + double mfNaN; +public: + MergeDoubleArrayFunc(std::vector& 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 +ScMatrix::IterateResult GetValueWithCount(bool bTextAsZero, bool bIgnoreErrorValues, const MatrixImplType& maMat) +{ + WalkElementBlocks aFunc(bTextAsZero, bIgnoreErrorValues); + aFunc = maMat.walk(aFunc); + return aFunc.getResult(); +} + +} + +ScMatrix::KahanIterateResult ScMatrixImpl::Sum(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return GetValueWithCount(bTextAsZero, bIgnoreErrorValues, maMat); +} + +ScMatrix::KahanIterateResult ScMatrixImpl::SumSquare(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return GetValueWithCount(bTextAsZero, bIgnoreErrorValues, maMat); +} + +ScMatrix::DoubleIterateResult ScMatrixImpl::Product(bool bTextAsZero, bool bIgnoreErrorValues) const +{ + return GetValueWithCount(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 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 aFunc(rStr, maMat.size(), nCol1, nCol2); + aFunc = maMat.walk(aFunc); + return aFunc.getMatching(); +} + +double ScMatrixImpl::GetMaxValue( bool bTextAsZero, bool bIgnoreErrorValues ) const +{ + CalcMaxMinValue aFunc(bTextAsZero, bIgnoreErrorValues); + aFunc = maMat.walk(aFunc); + return aFunc.getValue(); +} + +double ScMatrixImpl::GetMinValue( bool bTextAsZero, bool bIgnoreErrorValues ) const +{ + CalcMaxMinValue aFunc(bTextAsZero, bIgnoreErrorValues); + aFunc = maMat.walk(aFunc); + return aFunc.getValue(); +} + +double ScMatrixImpl::GetGcd() const +{ + CalcGcdLcm aFunc; + aFunc = maMat.walk(aFunc); + return aFunc.getResult(); +} + +double ScMatrixImpl::GetLcm() const +{ + CalcGcdLcm 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& 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& 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& 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& rArray ) const +{ + MatrixImplType::size_pair_type aSize = maMat.size(); + size_t nSize = aSize.row*aSize.column; + if (nSize != rArray.size()) + return; + + MergeDoubleArrayFunc aFunc(rArray); + maMat.walk(std::move(aFunc)); +} + +namespace { + +template +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 +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 begin() + { + return wrapped_iterator(m_itBegin, maOp); + } + + wrapped_iterator end() + { + return wrapped_iterator(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 +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 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 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 aFunc(it, itEnd, *mpOp); + pos = mrMat.set(pos, aFunc.begin(), aFunc.end()); + } + break; + case mdds::mtm::element_empty: + { + if (mpOp->useFunctionForEmpty()) + { + std::vector aVec(node.size); + MatrixIteratorWrapper, 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 +void ScMatrixImpl::ApplyOperation(T aOp, ScMatrixImpl& rMat) +{ + MatrixOpWrapper aFunc(rMat.maMat, aOp); + maMat.walk(aFunc); +} + +template +ScMatrix::IterateResultMultiple ScMatrixImpl::ApplyCollectOperation(const std::vector& aOp) +{ + WalkElementBlocksMultipleValues 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& rStartPos, + const std::pair& 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 aString(nMaxCol * nMaxRow); + std::vector aValid(nMaxCol * nMaxRow, true); + std::vector nErrors(nMaxCol * nMaxRow,FormulaError::NONE); + + size_t nRowOffset = 0; + size_t nColOffset = 0; + std::function 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 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 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 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(0, 0), + std::pair(std::min(nR1, nMaxRow) - 1, std::min(nC1, nMaxCol) - 1), + aDoubleFunc, aBoolFunc, aStringFunc, aEmptyFunc); + } + } + } + else + xMat1->ExecuteOperation( + std::pair(0, 0), + std::pair(nMaxRow - 1, nMaxCol - 1), + aDoubleFunc, aBoolFunc, aStringFunc, aEmptyFunc); + + std::vector aSharedString(nMaxCol*nMaxRow); + + std::function 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 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 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 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(0, 0), + std::pair(std::min(nR2, nMaxRow) - 1, std::min(nC2, nMaxCol) - 1), + aDoubleFunc2, aBoolFunc2, aStringFunc2, aEmptyFunc2); + } + } + } + else + xMat2->ExecuteOperation( + std::pair(0, 0), + std::pair(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& 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& rArray, bool bEmptyAsZero ) const +{ + pImpl->GetDoubleArray(rArray, bEmptyAsZero); +} + +void ScMatrix::MergeDoubleArrayMultiply( std::vector& 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 +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(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 aOp(not_, pImpl->GetErrorInterpreter()); + pImpl->ApplyOperation(aOp, *rMat.pImpl); +} + +void ScMatrix::NegOp( const ScMatrix& rMat) +{ + auto neg_ = [](double a, double){return -a;}; + matop::MatOp 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 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 aOp(sub_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } + else + { + auto sub_ = [](double a, double b){return a - b;}; + matop::MatOp 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 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 aOp(div_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } + else + { + auto div_ = [](double a, double b){return sc::div(a, b);}; + matop::MatOp 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 aOp(pow_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } + else + { + auto pow_ = [](double a, double b){return sc::power(a, b);}; + matop::MatOp aOp(pow_, pImpl->GetErrorInterpreter(), fVal); + pImpl->ApplyOperation(aOp, *rMat.pImpl); + } +} + +void ScMatrix::ExecuteOperation(const std::pair& rStartPos, + const std::pair& 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& aOp) +{ + return pImpl->ApplyCollectOperation(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 +#include +#include + +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 +#include +#include +#include +#include +#include +#include + +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& rBounds) +{ + if (rBounds.empty()) + return false; + + // Sort and remove duplicates. + std::sort(rBounds.begin(), rBounds.end()); + std::vector::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::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(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& 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 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 + +#include + +#include + +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& 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& 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 +#include +#include + +#include +#include +#include + +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(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 + +#include + +#include +#include +#include + +#include +#include +#include + +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 +#include +#include + +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 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::std::vector; + +#include +#include +#include +#include +#include +#include +#include +#include + +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(rAPI.RelativeColumn)); + else + rRef.SetAbsCol(static_cast(rAPI.Column)); + + if (rRef.IsRowRel()) + rRef.SetRelRow(static_cast(rAPI.RelativeRow)); + else + rRef.SetAbsRow(static_cast(rAPI.Row)); + + if (rRef.IsTabRel()) + rRef.SetRelTab(static_cast(rAPI.RelativeSheet)); + else + rRef.SetAbsTab(static_cast(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(rAPI.RelativeColumn)); + else + rRef.SetAbsCol(static_cast(rAPI.Column)); + + if (rRef.IsRowRel()) + rRef.SetRelRow(static_cast(rAPI.RelativeRow)); + else + rRef.SetAbsRow(static_cast(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(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(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(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(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(&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(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(&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 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(xMatrix.get()); +} + +bool ScMatrixCellResultToken::operator==( const FormulaToken& r ) const +{ + return FormulaToken::operator==( r ) && + xUpperLeft == static_cast(r).xUpperLeft && + xMatrix == static_cast(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(&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(&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(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(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(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::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::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::get() ) ) + { + sheet::NameToken aTokenData; + rToken.Data >>= aTokenData; + if ( eOpCode == ocName ) + { + SAL_WARN_IF( aTokenData.Sheet < -1 || std::numeric_limits::max() < aTokenData.Sheet, + "sc.core", + "ScTokenArray::AddFormulaToken - NameToken.Sheet out of limits: " << aTokenData.Sheet); + sal_Int16 nSheet = static_cast(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::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(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 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(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(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(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(nVal); + } + break; + case svDouble: + { + // Constant value. + double fVal = p->GetDouble(); + nHash += std::hash()(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(eOp); + } + } + else + // Use the opcode value in all the other cases. + nHash += static_cast(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(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 ScTokenArray::Clone() const +{ + std::unique_ptr 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(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(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(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(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(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(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(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; jGetType() ) + { + 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(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& 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& 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& 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& 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& 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(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 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(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 +#include +#include +#include +#include +#include +#include + +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(i); + std::vector 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 +#include + +#include +#include + +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 + +#include +#include +#include + +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 aNodeNames = aConfigItem.GetNodeNames( "" ); + + tools::Long nNodeCount = aNodeNames.getLength(); + if ( !nNodeCount ) + return; + + Sequence 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 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>= 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 +#include + +#include +#include +#include +#include +#include + +#include + +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(nLen); + while (nStart > 0) + { + if (xCal[--nStart].ID == rCalendar.StartOfWeek) + break; + } + sal_Int16 nLast = sal::static_int_cast( (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( aDayShort )); + if ( !HasEntry( aDayLong ) ) + maData.push_back( std::make_unique( 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( aMonthShort )); + if ( !HasEntry( aMonthLong ) ) + maData.push_back( std::make_unique( aMonthLong )); + } + } +} + +ScUserList::ScUserList(const ScUserList& rOther) +{ + for (const std::unique_ptr& rData : rOther.maData) + maData.push_back(std::make_unique(*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& rData : rOther.maData) + maData.push_back(std::make_unique(*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& lhs, const std::unique_ptr& 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 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(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 + +#include +#include + +#include +#include +#include +#include + +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 ScViewOptions::CreateGridItem() const +{ + std::unique_ptr 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(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 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 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 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 aNames = GetLayoutPropertyNames(); + Sequence 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(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(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(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 aNames = GetLayoutPropertyNames(); + Sequence 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 aNames = GetDisplayPropertyNames(); + Sequence 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(GetObjMode( VOBJ_TYPE_OLE )); + break; + case SCDISPLAYOPT_CHART: + pValues[nProp] <<= static_cast(GetObjMode( VOBJ_TYPE_CHART )); + break; + case SCDISPLAYOPT_DRAWING: + pValues[nProp] <<= static_cast(GetObjMode( VOBJ_TYPE_DRAW )); + break; + } + } + aDisplayItem.PutProperties(aNames, aValues); +} + +IMPL_LINK_NOARG(ScViewCfg, GridCommitHdl, ScLinkConfigItem&, void) +{ + const ScGridOptions& rGrid = GetGridOptions(); + + Sequence aNames = GetGridPropertyNames(); + Sequence 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(rGrid.GetFieldDrawX()); + break; + case SCGRIDOPT_RESOLU_Y: + pValues[nProp] <<= static_cast(rGrid.GetFieldDrawY()); + break; + case SCGRIDOPT_SUBDIV_X: + pValues[nProp] <<= static_cast(rGrid.GetFieldDivisionX()); + break; + case SCGRIDOPT_SUBDIV_Y: + pValues[nProp] <<= static_cast(rGrid.GetFieldDivisionY()); + break; + case SCGRIDOPT_OPTION_X: + pValues[nProp] <<= static_cast(rGrid.GetFieldSnapX()); + break; + case SCGRIDOPT_OPTION_Y: + pValues[nProp] <<= static_cast(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(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 +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +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 xFileAccess + = css::ucb::SimpleFileAccess::create(comphelper::getProcessComponentContext()); + if (!xFileAccess.is()) + return ERROR_GENERAL; + + css::uno::Reference 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 buffer(BUF_LEN); + OStringBuffer aBuffer(64000); + + sal_Int32 nRead = 0; + while ((nRead = xStream->readBytes(buffer, BUF_LEN)) == BUF_LEN) + aBuffer.append(reinterpret_cast(buffer.getConstArray()), nRead); + + if (nRead > 0) + aBuffer.append(reinterpret_cast(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 +#include +#include +#include +#include +#include + +#include +#include + +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(eSysLanguage) ).WriteUInt16( static_cast(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: */ diff --git a/sc/source/filter/dif/difexp.cxx b/sc/source/filter/dif/difexp.cxx new file mode 100644 index 000000000..89bafd754 --- /dev/null +++ b/sc/source/filter/dif/difexp.cxx @@ -0,0 +1,279 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void ScFormatFilterPluginImpl::ScExportDif( SvStream& rStream, ScDocument* pDoc, + const ScAddress& rOutPos, const rtl_TextEncoding eNach ) +{ + SCCOL nEndCol; + SCROW nEndRow; + pDoc->GetTableArea( rOutPos.Tab(), nEndCol, nEndRow ); + ScAddress aEnd( nEndCol, nEndRow, rOutPos.Tab() ); + ScAddress aStart( rOutPos ); + + aStart.PutInOrder( aEnd ); + + ScExportDif( rStream, pDoc, ScRange( aStart, aEnd ), eNach ); +} + +void ScFormatFilterPluginImpl::ScExportDif( SvStream& rOut, ScDocument* pDoc, + const ScRange&rRange, const rtl_TextEncoding eCharSet ) +{ + OSL_ENSURE( rRange.aStart <= rRange.aEnd, "*ScExportDif(): Range not sorted!" ); + OSL_ENSURE( rRange.aStart.Tab() == rRange.aEnd.Tab(), + "ScExportDif(): only one table please!" ); + + const rtl_TextEncoding eStreamCharSet = rOut.GetStreamCharSet(); + if ( eStreamCharSet != eCharSet ) + rOut.SetStreamCharSet( eCharSet ); + + sal_Unicode cStrDelim('"'); + OString aStrDelimEncoded; // only used if not Unicode + OUString aStrDelimDecoded; // only used if context encoding + bool bContextOrNotAsciiEncoding; + if ( eCharSet == RTL_TEXTENCODING_UNICODE ) + { + rOut.StartWritingUnicodeText(); + bContextOrNotAsciiEncoding = false; + } + else + { + aStrDelimEncoded = OString(&cStrDelim, 1, eCharSet); + rtl_TextEncodingInfo aInfo; + aInfo.StructSize = sizeof(aInfo); + if ( rtl_getTextEncodingInfo( eCharSet, &aInfo ) ) + { + bContextOrNotAsciiEncoding = + (((aInfo.Flags & RTL_TEXTENCODING_INFO_CONTEXT) != 0) || + ((aInfo.Flags & RTL_TEXTENCODING_INFO_ASCII) == 0)); + if ( bContextOrNotAsciiEncoding ) + aStrDelimDecoded = OStringToOUString(aStrDelimEncoded, eCharSet); + } + else + bContextOrNotAsciiEncoding = false; + } + + const char p2DoubleQuotes_LF[] = "\"\"\n"; + const char pSpecDataType_LF[] = "-1,0\n"; + const char pEmptyData[] = "1,0\n\"\"\n"; + const char pStringData[] = "1,0\n"; + const char pNumData[] = "0,"; + const char pNumDataERROR[] = "0,0\nERROR\n"; + + OUStringBuffer aOS; + OUString aString; + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCCOL nNumCols = nEndCol - rRange.aStart.Col() + 1; + SCROW nNumRows = nEndRow - rRange.aStart.Row() + 1; + SCTAB nTab = rRange.aStart.Tab(); + + ScProgress aPrgrsBar( pDoc->GetDocumentShell(), ScResId( STR_LOAD_DOC ), nNumRows, true ); + + aPrgrsBar.SetState( 0 ); + + // TABLE + OSL_ENSURE( pDoc->HasTable( nTab ), "*ScExportDif(): Table not existent!" ); + + aOS.append(pKeyTABLE); + aOS.append("\n0,1\n\""); + + pDoc->GetName( nTab, aString ); + aOS.append(aString); + aOS.append("\"\n"); + rOut.WriteUnicodeOrByteText(aOS); + aOS.setLength(0); + + // VECTORS + aOS.append(pKeyVECTORS); + aOS.append("\n0,"); + aOS.append(static_cast(nNumCols)); + aOS.append('\n'); + aOS.append(p2DoubleQuotes_LF); + rOut.WriteUnicodeOrByteText(aOS); + aOS.setLength(0); + + // TUPLES + aOS.append(pKeyTUPLES); + aOS.append("\n0,"); + aOS.append(static_cast(nNumRows)); + aOS.append('\n'); + aOS.append(p2DoubleQuotes_LF); + rOut.WriteUnicodeOrByteText(aOS); + aOS.setLength(0); + + // DATA + aOS.append(pKeyDATA); + aOS.append("\n0,0\n"); + aOS.append(p2DoubleQuotes_LF); + rOut.WriteUnicodeOrByteText(aOS); + aOS.setLength(0); + + SCCOL nColCnt; + SCROW nRowCnt; + + for( nRowCnt = rRange.aStart.Row() ; nRowCnt <= nEndRow ; nRowCnt++ ) + { + assert( aOS.isEmpty() && "aOS should be empty"); + aOS.append(pSpecDataType_LF); + aOS.append(pKeyBOT); + aOS.append('\n'); + rOut.WriteUnicodeOrByteText(aOS); + aOS.setLength(0); + for( nColCnt = rRange.aStart.Col() ; nColCnt <= nEndCol ; nColCnt++ ) + { + assert( aOS.isEmpty() && "aOS should be empty"); + bool bWriteStringData = false; + ScRefCellValue aCell(*pDoc, ScAddress(nColCnt, nRowCnt, nTab)); + + switch (aCell.meType) + { + case CELLTYPE_NONE: + aOS.append(pEmptyData); + break; + case CELLTYPE_VALUE: + aOS.append(pNumData); + aString = pDoc->GetInputString( nColCnt, nRowCnt, nTab ); + aOS.append(aString); + aOS.append("\nV\n"); + break; + case CELLTYPE_EDIT: + case CELLTYPE_STRING: + aString = aCell.getString(pDoc); + bWriteStringData = true; + break; + case CELLTYPE_FORMULA: + if (aCell.mpFormula->GetErrCode() != FormulaError::NONE) + aOS.append(pNumDataERROR); + else if (aCell.mpFormula->IsValue()) + { + aOS.append(pNumData); + aString = pDoc->GetInputString( nColCnt, nRowCnt, nTab ); + aOS.append(aString); + aOS.append("\nV\n"); + } + else + { + aString = aCell.mpFormula->GetString().getString(); + bWriteStringData = true; + } + + break; + default:; + } + + if ( !bWriteStringData ) + { + rOut.WriteUnicodeOrByteText(aOS); + aOS.setLength(0); + } + else + { + // for an explanation why this complicated, see + // sc/source/ui/docsh.cxx:ScDocShell::AsciiSave() + // In fact we should create a common method if this would be + // needed just one more time... + assert( aOS.isEmpty() && "aOS should be empty"); + OUString aTmpStr = aString; + aOS.append(pStringData); + rOut.WriteUnicodeOrByteText(aOS, eCharSet); + aOS.setLength(0); + if ( eCharSet == RTL_TEXTENCODING_UNICODE ) + { + sal_Int32 nPos = aTmpStr.indexOf( cStrDelim ); + while ( nPos != -1 ) + { + aTmpStr = aTmpStr.replaceAt( nPos, 0, rtl::OUStringChar(cStrDelim) ); + nPos = aTmpStr.indexOf( cStrDelim, nPos+2 ); + } + rOut.WriteUniOrByteChar( cStrDelim, eCharSet ); + write_uInt16s_FromOUString(rOut, aTmpStr); + rOut.WriteUniOrByteChar( cStrDelim, eCharSet ); + } + else if ( bContextOrNotAsciiEncoding ) + { + // to byte encoding + OString aStrEnc = OUStringToOString(aTmpStr, eCharSet); + // back to Unicode + OUString aStrDec = OStringToOUString(aStrEnc, eCharSet); + // search on re-decoded string + sal_Int32 nPos = aStrDec.indexOf(aStrDelimDecoded); + while (nPos >= 0) + { + OUStringBuffer aBuf(aStrDec); + aBuf.insert(nPos, aStrDelimDecoded); + aStrDec = aBuf.makeStringAndClear(); + nPos = aStrDec.indexOf( + aStrDelimDecoded, nPos+1+aStrDelimDecoded.getLength()); + } + // write byte re-encoded + rOut.WriteUniOrByteChar( cStrDelim, eCharSet ); + rOut.WriteUnicodeOrByteText( aStrDec, eCharSet ); + rOut.WriteUniOrByteChar( cStrDelim, eCharSet ); + } + else + { + OString aStrEnc = OUStringToOString(aTmpStr, eCharSet); + // search on encoded string + sal_Int32 nPos = aStrEnc.indexOf(aStrDelimEncoded); + while (nPos >= 0) + { + OStringBuffer aBuf(aStrEnc); + aBuf.insert(nPos, aStrDelimEncoded); + aStrEnc = aBuf.makeStringAndClear(); + nPos = aStrEnc.indexOf( + aStrDelimEncoded, nPos+1+aStrDelimEncoded.getLength()); + } + // write byte encoded + rOut.WriteBytes(aStrDelimEncoded.getStr(), aStrDelimEncoded.getLength()); + rOut.WriteBytes(aStrEnc.getStr(), aStrEnc.getLength()); + rOut.WriteBytes(aStrDelimEncoded.getStr(), aStrDelimEncoded.getLength()); + } + rOut.WriteUniOrByteChar( '\n', eCharSet ); + } + } + aPrgrsBar.SetState( nRowCnt ); + } + + assert( aOS.isEmpty() && "aOS should be empty"); + aOS.append(pSpecDataType_LF); + aOS.append(pKeyEOD); + aOS.append('\n'); + rOut.WriteUnicodeOrByteText(aOS); + aOS.setLength(0); + + // restore original value + rOut.SetStreamCharSet( eStreamCharSet ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/dif/difimp.cxx b/sc/source/filter/dif/difimp.cxx new file mode 100644 index 000000000..fd88cdf4c --- /dev/null +++ b/sc/source/filter/dif/difimp.cxx @@ -0,0 +1,674 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const std::u16string_view pKeyTABLE = u"TABLE"; +const std::u16string_view pKeyVECTORS = u"VECTORS"; +const std::u16string_view pKeyTUPLES = u"TUPLES"; +const std::u16string_view pKeyDATA = u"DATA"; +const std::u16string_view pKeyBOT = u"BOT"; +const std::u16string_view pKeyEOD = u"EOD"; + +ErrCode ScFormatFilterPluginImpl::ScImportDif(SvStream& rIn, ScDocument* pDoc, const ScAddress& rInsPos, + const rtl_TextEncoding eVon ) +{ + DifParser aDifParser( rIn, *pDoc, eVon ); + + SCTAB nBaseTab = rInsPos.Tab(); + + TOPIC eTopic = T_UNKNOWN; + bool bSyntErrWarn = false; + bool bOverflowWarn = false; + + OUStringBuffer& rData = aDifParser.m_aData; + + rIn.Seek( 0 ); + + ScfStreamProgressBar aPrgrsBar( rIn, pDoc->GetDocumentShell() ); + + while( eTopic != T_DATA && eTopic != T_END ) + { + eTopic = aDifParser.GetNextTopic(); + + aPrgrsBar.Progress(); + + const bool bData = !rData.isEmpty(); + + switch( eTopic ) + { + case T_TABLE: + { + if( aDifParser.nVector != 0 || aDifParser.nVal != 1 ) + bSyntErrWarn = true; + if( bData ) + pDoc->RenameTab(nBaseTab, rData.toString()); + } + break; + case T_VECTORS: + { + if( aDifParser.nVector != 0 ) + bSyntErrWarn = true; + } + break; + case T_TUPLES: + { + if( aDifParser.nVector != 0 ) + bSyntErrWarn = true; + } + break; + case T_DATA: + { + if( aDifParser.nVector != 0 || aDifParser.nVal != 0 ) + bSyntErrWarn = true; + } + break; + case T_LABEL: + case T_COMMENT: + case T_SIZE: + case T_PERIODICITY: + case T_MAJORSTART: + case T_MINORSTART: + case T_TRUELENGTH: + case T_UINITS: + case T_DISPLAYUNITS: + case T_END: + case T_UNKNOWN: + break; + default: + OSL_FAIL( "ScImportDif - missing enum" ); + } + + } + + if( eTopic == T_DATA ) + { // data starts here + SCCOL nBaseCol = rInsPos.Col(); + + SCCOL nColCnt = SCCOL_MAX; + SCROW nRowCnt = rInsPos.Row(); + DifAttrCache aAttrCache; + + DATASET eCurrent = D_UNKNOWN; + + ScSetStringParam aStrParam; // used to set string value without number detection. + aStrParam.setTextInput(); + + while( eCurrent != D_EOD ) + { + eCurrent = aDifParser.GetNextDataset(); + + aPrgrsBar.Progress(); + ScAddress aPos(nColCnt, nRowCnt, nBaseTab); + const OUString aData = rData.makeStringAndClear(); + + switch( eCurrent ) + { + case D_BOT: + if( nColCnt < SCCOL_MAX ) + nRowCnt++; + nColCnt = nBaseCol; + break; + case D_EOD: + break; + case D_NUMERIC: // Number cell + if( nColCnt == SCCOL_MAX ) + nColCnt = nBaseCol; + + if( pDoc->ValidCol(nColCnt) && pDoc->ValidRow(nRowCnt) ) + { + pDoc->EnsureTable(nBaseTab); + + if( DifParser::IsV( aData.getStr() ) ) + { + pDoc->SetValue(aPos, aDifParser.fVal); + aAttrCache.SetNumFormat( pDoc, nColCnt, nRowCnt, + aDifParser.nNumFormat ); + } + else if( aData == "TRUE" || aData == "FALSE" ) + { + pDoc->SetValue(aPos, aDifParser.fVal); + aAttrCache.SetNumFormat( pDoc, nColCnt, nRowCnt, + aDifParser.nNumFormat ); + } + else if( aData == "NA" || aData == "ERROR" ) + { + pDoc->SetString(aPos, aData, &aStrParam); + } + else + { + OUString aTmp = "#IND:" + aData + "?"; + pDoc->SetString(aPos, aTmp, &aStrParam); + } + } + else + bOverflowWarn = true; + + nColCnt++; + break; + case D_STRING: // Text cell + if( nColCnt == SCCOL_MAX ) + nColCnt = nBaseCol; + + if( pDoc->ValidCol(nColCnt) && pDoc->ValidRow(nRowCnt) ) + { + if (!aData.isEmpty()) + { + pDoc->EnsureTable(nBaseTab); + pDoc->SetTextCell(aPos, aData); + } + } + else + bOverflowWarn = true; + + nColCnt++; + break; + case D_UNKNOWN: + break; + case D_SYNT_ERROR: + break; + default: + OSL_FAIL( "ScImportDif - missing enum" ); + } + } + + aAttrCache.Apply( *pDoc, nBaseTab ); + } + else + return SCERR_IMPORT_FORMAT; + + if( bSyntErrWarn ) + + // FIXME: Add proper warning! + return SCWARN_IMPORT_RANGE_OVERFLOW; + + else if( bOverflowWarn ) + return SCWARN_IMPORT_RANGE_OVERFLOW; + else + return ERRCODE_NONE; +} + +DifParser::DifParser( SvStream& rNewIn, const ScDocument& rDoc, rtl_TextEncoding eCharSet ) + : fVal(0.0) + , nVector(0) + , nVal(0) + , nNumFormat(0) + , pNumFormatter(rDoc.GetFormatTable()) + , rIn(rNewIn) +{ + if ( rIn.GetStreamCharSet() != eCharSet ) + { + OSL_FAIL( "CharSet passed overrides and modifies StreamCharSet" ); + rIn.SetStreamCharSet( eCharSet ); + } + rIn.StartReadingUnicodeText( eCharSet ); +} + +TOPIC DifParser::GetNextTopic() +{ + enum STATE { S_VectorVal, S_Data, S_END, S_START, S_UNKNOWN, S_ERROR_L2 }; + + static const std::u16string_view ppKeys[] = + { + pKeyTABLE, // 0 + pKeyVECTORS, + pKeyTUPLES, + pKeyDATA, + u"LABEL", + u"COMMENT", // 5 + u"SIZE", + u"PERIODICITY", + u"MAJORSTART", + u"MINORSTART", + u"TRUELENGTH", // 10 + u"UINITS", + u"DISPLAYUNITS", + u"" // 13 + }; + + static const TOPIC pTopics[] = + { + T_TABLE, // 0 + T_VECTORS, + T_TUPLES, + T_DATA, + T_LABEL, + T_COMMENT, // 5 + T_SIZE, + T_PERIODICITY, + T_MAJORSTART, + T_MINORSTART, + T_TRUELENGTH, // 10 + T_UINITS, + T_DISPLAYUNITS, + T_UNKNOWN // 13 + }; + + STATE eS = S_START; + OUString aLine; + + nVector = 0; + nVal = 0; + TOPIC eRet = T_UNKNOWN; + + while( eS != S_END ) + { + if( !ReadNextLine( aLine ) ) + { + eS = S_END; + eRet = T_END; + } + + switch( eS ) + { + case S_START: + { + const std::u16string_view* pRef; + sal_uInt16 nCnt = 0; + bool bSearch = true; + + pRef = &ppKeys[ nCnt ]; + + while( bSearch ) + { + if( aLine == *pRef ) + { + eRet = pTopics[ nCnt ]; + bSearch = false; + } + else + { + nCnt++; + pRef = &ppKeys[ nCnt ]; + if( pRef->empty() ) + bSearch = false; + } + } + + if( !pRef->empty() ) + eS = S_VectorVal; + else + eS = S_UNKNOWN; + } + break; + case S_VectorVal: + { + const sal_Unicode* pCur = aLine.getStr(); + + pCur = ScanIntVal( pCur, nVector ); + + if( pCur && *pCur == ',' ) + { + pCur++; + ScanIntVal( pCur, nVal ); + eS = S_Data; + } + else + eS = S_ERROR_L2; + } + break; + case S_Data: + OSL_ENSURE( aLine.getLength() >= 2, + "+GetNextTopic(): is too short!" ); + if( aLine.getLength() > 2 ) + m_aData.append(aLine.subView(1, aLine.getLength() - 2)); + else + m_aData.truncate(); + eS = S_END; + break; + case S_END: + OSL_FAIL( "DifParser::GetNextTopic - unexpected state" ); + break; + case S_UNKNOWN: + // skip 2 lines + ReadNextLine( aLine ); + [[fallthrough]]; + case S_ERROR_L2: // error happened in line 2 + // skip 1 line + ReadNextLine( aLine ); + eS = S_END; + break; + default: + OSL_FAIL( "DifParser::GetNextTopic - missing enum" ); + } + } + + return eRet; +} + +static void lcl_DeEscapeQuotesDif(OUStringBuffer& rString) +{ + // Special handling for DIF import: Escaped (duplicated) quotes are resolved. + // Single quote characters are left in place because older versions didn't + // escape quotes in strings (and Excel doesn't when using the clipboard). + // The quotes around the string are removed before this function is called. + + rString = rString.makeStringAndClear().replaceAll("\"\"", "\""); +} + +// Determine if passed in string is numeric data and set fVal/nNumFormat if so +DATASET DifParser::GetNumberDataset( const sal_Unicode* pPossibleNumericData ) +{ + DATASET eRet = D_SYNT_ERROR; + + OSL_ENSURE( pNumFormatter, "-DifParser::GetNumberDataset(): No Formatter, more fun!" ); + OUString aTestVal( pPossibleNumericData ); + sal_uInt32 nFormat = 0; + double fTmpVal; + if( pNumFormatter->IsNumberFormat( aTestVal, nFormat, fTmpVal ) ) + { + fVal = fTmpVal; + nNumFormat = nFormat; + eRet = D_NUMERIC; + } + else + eRet = D_SYNT_ERROR; + + return eRet; +} + +bool DifParser::ReadNextLine( OUString& rStr ) +{ + if( aLookAheadLine.isEmpty() ) + { + return rIn.ReadUniOrByteStringLine( rStr, rIn.GetStreamCharSet() ); + } + else + { + rStr = aLookAheadLine; + aLookAheadLine.clear(); + return true; + } +} + +// Look ahead in the stream to determine if the next line is the first line of +// a valid data record structure +bool DifParser::LookAhead() +{ + const sal_Unicode* pCurrentBuffer; + bool bValidStructure = false; + + OSL_ENSURE( aLookAheadLine.isEmpty(), "*DifParser::LookAhead(): LookAhead called twice in a row" ); + rIn.ReadUniOrByteStringLine( aLookAheadLine, rIn.GetStreamCharSet() ); + + pCurrentBuffer = aLookAheadLine.getStr(); + + switch( *pCurrentBuffer ) + { + case '-': // Special Datatype + pCurrentBuffer++; + + if( Is1_0( pCurrentBuffer ) ) + { + bValidStructure = true; + } + break; + case '0': // Numeric Data + pCurrentBuffer++; + if( *pCurrentBuffer == ',' ) + { + pCurrentBuffer++; + bValidStructure = ( GetNumberDataset(pCurrentBuffer) != D_SYNT_ERROR ); + } + break; + case '1': // String Data + if( Is1_0( aLookAheadLine.getStr() ) ) + { + bValidStructure = true; + } + break; + } + return bValidStructure; +} + +DATASET DifParser::GetNextDataset() +{ + DATASET eRet = D_UNKNOWN; + OUString aLine; + const sal_Unicode* pCurrentBuffer; + + ReadNextLine( aLine ); + + pCurrentBuffer = aLine.getStr(); + + switch( *pCurrentBuffer ) + { + case '-': // Special Datatype + pCurrentBuffer++; + + if( Is1_0( pCurrentBuffer ) ) + { + ReadNextLine( aLine ); + if( IsBOT( aLine.getStr() ) ) + eRet = D_BOT; + else if( IsEOD( aLine.getStr() ) ) + eRet = D_EOD; + } + break; + case '0': // Numeric Data + pCurrentBuffer++; // value in fVal, 2. line in m_aData + if( *pCurrentBuffer == ',' ) + { + pCurrentBuffer++; + eRet = GetNumberDataset(pCurrentBuffer); + OUString aTmpLine; + ReadNextLine( aTmpLine ); + if ( eRet == D_SYNT_ERROR ) + { // for broken records write "#ERR: data" to cell + m_aData = OUString::Concat("#ERR: ") + pCurrentBuffer + " (" + aTmpLine + ")"; + eRet = D_STRING; + } + else + { + m_aData = aTmpLine; + } + } + break; + case '1': // String Data + if( Is1_0( aLine.getStr() ) ) + { + ReadNextLine( aLine ); + sal_Int32 nLineLength = aLine.getLength(); + const sal_Unicode* pLine = aLine.getStr(); + + if( nLineLength >= 1 && *pLine == '"' ) + { + // Quotes are not always escaped (duplicated), see lcl_DeEscapeQuotesDif + // A look ahead into the next line is needed in order to deal with + // multiline strings containing quotes + if( LookAhead() ) + { + // Single line string + if( nLineLength >= 2 && pLine[nLineLength - 1] == '"' ) + { + m_aData = aLine.subView( 1, nLineLength - 2 ); + lcl_DeEscapeQuotesDif(m_aData); + eRet = D_STRING; + } + } + else + { + // Multiline string + m_aData = aLine.subView( 1 ); + bool bContinue = true; + while ( bContinue ) + { + m_aData.append("\n"); + bContinue = !rIn.eof() && ReadNextLine( aLine ); + if( bContinue ) + { + nLineLength = aLine.getLength(); + if( nLineLength >= 1 ) + { + pLine = aLine.getStr(); + bContinue = !LookAhead(); + if( bContinue ) + { + m_aData.append(aLine); + } + else if( pLine[nLineLength - 1] == '"' ) + { + m_aData.append(aLine.subView(0, nLineLength -1)); + lcl_DeEscapeQuotesDif(m_aData); + eRet = D_STRING; + } + } + } + } + } + } + } + break; + } + + if( eRet == D_UNKNOWN ) + ReadNextLine( aLine ); + + if( rIn.eof() ) + eRet = D_EOD; + + return eRet; +} + +const sal_Unicode* DifParser::ScanIntVal( const sal_Unicode* pStart, sal_uInt32& rRet ) +{ + // eat leading whitespace, not specified, but seen in the wild + while (*pStart == ' ' || *pStart == '\t') + ++pStart; + + sal_Unicode cCurrent = *pStart; + + if( IsNumber( cCurrent ) ) + rRet = static_cast( cCurrent - '0' ); + else + return nullptr; + + pStart++; + cCurrent = *pStart; + + while( IsNumber( cCurrent ) && rRet < ( 0xFFFFFFFF / 10 ) ) + { + rRet *= 10; + rRet += static_cast( cCurrent - '0' ); + + pStart++; + cCurrent = *pStart; + } + + return pStart; +} + +DifColumn::DifColumn () + : mpCurrent(nullptr) +{ +} + +void DifColumn::SetNumFormat( const ScDocument* pDoc, SCROW nRow, const sal_uInt32 nNumFormat ) +{ + OSL_ENSURE( pDoc->ValidRow(nRow), "*DifColumn::SetNumFormat(): Row too big!" ); + + if( nNumFormat > 0 ) + { + if(mpCurrent) + { + OSL_ENSURE( nRow > 0, + "*DifColumn::SetNumFormat(): more cannot be zero!" ); + OSL_ENSURE( nRow > mpCurrent->nEnd, + "*DifColumn::SetNumFormat(): start from scratch?" ); + + if( mpCurrent->nNumFormat == nNumFormat && mpCurrent->nEnd == nRow - 1 ) + mpCurrent->nEnd = nRow; + else + NewEntry( nRow, nNumFormat ); + } + else + NewEntry(nRow,nNumFormat ); + } + else + mpCurrent = nullptr; +} + +void DifColumn::NewEntry( const SCROW nPos, const sal_uInt32 nNumFormat ) +{ + maEntries.emplace_back(); + mpCurrent = &maEntries.back(); + mpCurrent->nStart = mpCurrent->nEnd = nPos; + mpCurrent->nNumFormat = nNumFormat; + +} + +void DifColumn::Apply( ScDocument& rDoc, const SCCOL nCol, const SCTAB nTab ) +{ + ScPatternAttr aAttr( rDoc.GetPool() ); + SfxItemSet &rItemSet = aAttr.GetItemSet(); + + for (const auto& rEntry : maEntries) + { + OSL_ENSURE( rEntry.nNumFormat > 0, + "+DifColumn::Apply(): Number format must not be 0!" ); + + rItemSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, rEntry.nNumFormat ) ); + + rDoc.ApplyPatternAreaTab( nCol, rEntry.nStart, nCol, rEntry.nEnd, nTab, aAttr ); + + rItemSet.ClearItem(); + } +} + +DifAttrCache::DifAttrCache() +{ +} + +DifAttrCache::~DifAttrCache() +{ +} + +void DifAttrCache::SetNumFormat( const ScDocument* pDoc, const SCCOL nCol, const SCROW nRow, const sal_uInt32 nNumFormat ) +{ + OSL_ENSURE( pDoc->ValidCol(nCol), "-DifAttrCache::SetNumFormat(): Col too big!" ); + + if( !maColMap.count(nCol) ) + maColMap[ nCol ].reset( new DifColumn ); + + maColMap[ nCol ]->SetNumFormat( pDoc, nRow, nNumFormat ); +} + +void DifAttrCache::Apply( ScDocument& rDoc, SCTAB nTab ) +{ + for( SCCOL nCol : rDoc.GetWritableColumnsRange(nTab, 0, rDoc.MaxCol()) ) + { + if( maColMap.count(nCol) ) + maColMap[ nCol ]->Apply( rDoc, nCol, nTab ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/colrowst.cxx b/sc/source/filter/excel/colrowst.cxx new file mode 100644 index 000000000..e194b7309 --- /dev/null +++ b/sc/source/filter/excel/colrowst.cxx @@ -0,0 +1,359 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include + +XclImpColRowSettings::XclImpColRowSettings( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maColWidths(0, rRoot.GetDoc().GetSheetLimits().GetMaxColCount(), 0), + maColFlags(0, rRoot.GetDoc().GetSheetLimits().GetMaxColCount(), ExcColRowFlags::NONE), + maRowHeights(0, rRoot.GetDoc().GetSheetLimits().GetMaxRowCount(), 0), + maRowFlags(0, rRoot.GetDoc().GetSheetLimits().GetMaxRowCount(), ExcColRowFlags::NONE), + maHiddenRows(0, rRoot.GetDoc().GetSheetLimits().GetMaxRowCount(), false), + mnLastScRow( -1 ), + mnDefWidth( STD_COL_WIDTH ), + mnDefHeight( ScGlobal::nStdRowHeight ), + mnDefRowFlags( EXC_DEFROW_DEFAULTFLAGS ), + mbHasStdWidthRec( false ), + mbHasDefHeight( false ), + mbDirty( true ) +{ +} + +XclImpColRowSettings::~XclImpColRowSettings() +{ +} + +void XclImpColRowSettings::SetDefWidth( sal_uInt16 nDefWidth, bool bStdWidthRec ) +{ + if( bStdWidthRec ) + { + // STANDARDWIDTH record overrides DEFCOLWIDTH record + mnDefWidth = nDefWidth; + mbHasStdWidthRec = true; + } + else if( !mbHasStdWidthRec ) + { + // use DEFCOLWIDTH record only, if no STANDARDWIDTH record exists + mnDefWidth = nDefWidth; + } +} + +void XclImpColRowSettings::SetWidthRange( SCCOL nCol1, SCCOL nCol2, sal_uInt16 nWidth ) +{ + ScDocument& rDoc = GetDoc(); + nCol2 = ::std::min( nCol2, rDoc.MaxCol() ); + if (nCol2 == 256) + // In BIFF8, the column range is 0-255, and the use of 256 probably + // means the range should extend to the max column if the loading app + // support columns beyond 255. + nCol2 = rDoc.MaxCol(); + + nCol1 = ::std::min( nCol1, nCol2 ); + maColWidths.insert_back(nCol1, nCol2+1, nWidth); + + // We need to apply flag values individually since all flag values are aggregated for each column. + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + ApplyColFlag(nCol, ExcColRowFlags::Used); +} + +void XclImpColRowSettings::HideCol( SCCOL nCol ) +{ + if (!GetDoc().ValidCol(nCol)) + return; + + ApplyColFlag(nCol, ExcColRowFlags::Hidden); +} + +void XclImpColRowSettings::HideColRange( SCCOL nCol1, SCCOL nCol2 ) +{ + nCol2 = ::std::min( nCol2, GetDoc().MaxCol() ); + nCol1 = ::std::min( nCol1, nCol2 ); + + for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol) + ApplyColFlag(nCol, ExcColRowFlags::Hidden); +} + +void XclImpColRowSettings::SetDefHeight( sal_uInt16 nDefHeight, sal_uInt16 nFlags ) +{ + mnDefHeight = nDefHeight; + mnDefRowFlags = nFlags; + if( mnDefHeight == 0 ) + { + mnDefHeight = ScGlobal::nStdRowHeight; + ::set_flag( mnDefRowFlags, EXC_DEFROW_HIDDEN ); + } + mbHasDefHeight = true; +} + +void XclImpColRowSettings::SetHeight( SCROW nScRow, sal_uInt16 nHeight ) +{ + if (!GetDoc().ValidRow(nScRow)) + return; + + sal_uInt16 nRawHeight = nHeight & EXC_ROW_HEIGHTMASK; + bool bDefHeight = ::get_flag( nHeight, EXC_ROW_FLAGDEFHEIGHT ) || (nRawHeight == 0); + maRowHeights.insert_back(nScRow, nScRow+1, nRawHeight); + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + if (!maRowFlags.search(nScRow, nFlagVal).second) + return; + + ::set_flag(nFlagVal, ExcColRowFlags::Used); + ::set_flag(nFlagVal, ExcColRowFlags::Default, bDefHeight); + + maRowFlags.insert_back(nScRow, nScRow+1, nFlagVal); + + if (nScRow > mnLastScRow) + mnLastScRow = nScRow; +} + +void XclImpColRowSettings::SetRowSettings( SCROW nScRow, sal_uInt16 nHeight, sal_uInt16 nFlags ) +{ + if (!GetDoc().ValidRow(nScRow)) + return; + + SetHeight(nScRow, nHeight); + + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + if (!maRowFlags.search(nScRow, nFlagVal).second) + return; + + if (::get_flag(nFlags, EXC_ROW_UNSYNCED)) + ::set_flag(nFlagVal, ExcColRowFlags::Man); + + maRowFlags.insert_back(nScRow, nScRow+1, nFlagVal); + + if (::get_flag(nFlags, EXC_ROW_HIDDEN)) + maHiddenRows.insert_back(nScRow, nScRow+1, true); +} + +void XclImpColRowSettings::SetManualRowHeight( SCROW nScRow ) +{ + if (!GetDoc().ValidRow(nScRow)) + return; + + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + if (!maRowFlags.search(nScRow, nFlagVal).second) + return; + + nFlagVal |= ExcColRowFlags::Man; + maRowFlags.insert_back(nScRow, nScRow+1, nFlagVal); +} + +void XclImpColRowSettings::SetDefaultXF( SCCOL nCol1, SCCOL nCol2, sal_uInt16 nXFIndex ) +{ + /* assign the default column formatting here to ensure that + explicit cell formatting is not overwritten. */ + OSL_ENSURE( (nCol1 <= nCol2) && GetDoc().ValidCol( nCol2 ), "XclImpColRowSettings::SetDefaultXF - invalid column index" ); + nCol2 = ::std::min( nCol2, GetDoc().MaxCol() ); + nCol1 = ::std::min( nCol1, nCol2 ); + XclImpXFRangeBuffer& rXFRangeBuffer = GetXFRangeBuffer(); + for( SCCOL nCol = nCol1; nCol <= nCol2; ++nCol ) + rXFRangeBuffer.SetColumnDefXF( nCol, nXFIndex ); +} + +void XclImpColRowSettings::Convert( SCTAB nScTab ) +{ + if( !mbDirty ) + return; + + ScDocument& rDoc = GetDoc(); + + // column widths ---------------------------------------------------------- + + maColWidths.build_tree(); + for (SCCOL nCol = 0; nCol <= rDoc.MaxCol(); ++nCol) + { + sal_uInt16 nWidth = mnDefWidth; + if (GetColFlag(nCol, ExcColRowFlags::Used)) + { + sal_uInt16 nTmp; + if (maColWidths.search_tree(nCol, nTmp).second) + nWidth = nTmp; + } + + /* Hidden columns: remember hidden state, but do not set hidden state + in document here. Needed for #i11776#, no HIDDEN flags in the + document, until filters and outlines are inserted. */ + if( nWidth == 0 ) + { + ApplyColFlag(nCol, ExcColRowFlags::Hidden); + nWidth = mnDefWidth; + } + rDoc.SetColWidthOnly( nCol, nScTab, nWidth ); + } + + // row heights ------------------------------------------------------------ + + // #i54252# set default row height + rDoc.SetRowHeightOnly( 0, rDoc.MaxRow(), nScTab, mnDefHeight ); + if( ::get_flag( mnDefRowFlags, EXC_DEFROW_UNSYNCED ) ) + // first access to row flags, do not ask for old flags + rDoc.SetRowFlags( 0, rDoc.MaxRow(), nScTab, CRFlags::ManualSize ); + + maRowHeights.build_tree(); + if (!maRowHeights.is_tree_valid()) + return; + + SCROW nPrevRow = -1; + ExcColRowFlags nPrevFlags = ExcColRowFlags::NONE; + for (const auto& [nRow, nFlags] : maRowFlags) + { + if (nPrevRow >= 0) + { + sal_uInt16 nHeight = 0; + + if (nPrevFlags & ExcColRowFlags::Used) + { + if (nPrevFlags & ExcColRowFlags::Default) + { + nHeight = mnDefHeight; + rDoc.SetRowHeightOnly(nPrevRow, nRow-1, nScTab, nHeight); + } + else + { + for (SCROW i = nPrevRow; i <= nRow - 1; ++i) + { + SCROW nLast; + if (!maRowHeights.search_tree(i, nHeight, nullptr, &nLast).second) + { + // search failed for some reason + return; + } + + if (nLast > nRow) + nLast = nRow; + + rDoc.SetRowHeightOnly(i, nLast-1, nScTab, nHeight); + i = nLast-1; + } + } + + if (nPrevFlags & ExcColRowFlags::Man) + rDoc.SetManualHeight(nPrevRow, nRow-1, nScTab, true); + } + else + { + nHeight = mnDefHeight; + rDoc.SetRowHeightOnly(nPrevRow, nRow-1, nScTab, nHeight); + } + } + + nPrevRow = nRow; + nPrevFlags = nFlags; + } + + mbDirty = false; +} + +void XclImpColRowSettings::ConvertHiddenFlags( SCTAB nScTab ) +{ + ScDocument& rDoc = GetDoc(); + + // hide the columns + for( SCCOL nCol : rDoc.GetColumnsRange(nScTab, 0, rDoc.MaxCol()) ) + if (GetColFlag(nCol, ExcColRowFlags::Hidden)) + rDoc.ShowCol( nCol, nScTab, false ); + + // #i38093# rows hidden by filter need extra flag + SCROW nFirstFilterScRow = SCROW_MAX; + SCROW nLastFilterScRow = SCROW_MAX; + if( GetBiff() == EXC_BIFF8 ) + { + const XclImpAutoFilterData* pFilter = GetFilterManager().GetByTab( nScTab ); + // #i70026# use IsFiltered() to set the CRFlags::Filtered flag for active filters only + if( pFilter && pFilter->IsActive() && pFilter->IsFiltered() ) + { + nFirstFilterScRow = pFilter->StartRow(); + nLastFilterScRow = pFilter->EndRow(); + } + } + + // In case the excel row limit is lower than calc's, use the visibility of + // the last row and extend it to calc's last row. + SCROW nLastXLRow = GetRoot().GetXclMaxPos().Row(); + if (nLastXLRow < rDoc.MaxRow()) + { + bool bHidden = false; + if (!maHiddenRows.search(nLastXLRow, bHidden).second) + return; + + maHiddenRows.insert_back(nLastXLRow, GetDoc().GetSheetLimits().GetMaxRowCount(), bHidden); + } + + SCROW nPrevRow = -1; + bool bPrevHidden = false; + for (const auto& [nRow, bHidden] : maHiddenRows) + { + if (nPrevRow >= 0) + { + if (bPrevHidden) + { + rDoc.SetRowHidden(nPrevRow, nRow-1, nScTab, true); + // #i38093# rows hidden by filter need extra flag + if (nFirstFilterScRow <= nPrevRow && nPrevRow <= nLastFilterScRow) + { + SCROW nLast = ::std::min(nRow-1, nLastFilterScRow); + rDoc.SetRowFiltered(nPrevRow, nLast, nScTab, true); + } + } + } + + nPrevRow = nRow; + bPrevHidden = bHidden; + } + + // #i47438# if default row format is hidden, hide remaining rows + if( ::get_flag( mnDefRowFlags, EXC_DEFROW_HIDDEN ) && (mnLastScRow < rDoc.MaxRow()) ) + rDoc.ShowRows( mnLastScRow + 1, rDoc.MaxRow(), nScTab, false ); +} + +void XclImpColRowSettings::ApplyColFlag(SCCOL nCol, ExcColRowFlags nNewVal) +{ + // Get the original flag value. + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + std::pair r = maColFlags.search(nCol, nFlagVal); + if (!r.second) + // Search failed. + return; + + ::set_flag(nFlagVal, nNewVal); + + // Re-insert the flag value. + maColFlags.insert(r.first, nCol, nCol+1, nFlagVal); +} + +bool XclImpColRowSettings::GetColFlag(SCCOL nCol, ExcColRowFlags nMask) const +{ + ExcColRowFlags nFlagVal = ExcColRowFlags::NONE; + if (!maColFlags.search(nCol, nFlagVal).second) + return false; + // Search failed. + + return bool(nFlagVal & nMask); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excdoc.cxx b/sc/source/filter/excel/excdoc.cxx new file mode 100644 index 000000000..c01dde329 --- /dev/null +++ b/sc/source/filter/excel/excdoc.cxx @@ -0,0 +1,905 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace oox; + +static OUString lcl_GetVbaTabName( SCTAB n ) +{ + OUString aRet = "__VBA__" + OUString::number( static_cast(n) ); + return aRet; +} + +static void lcl_AddBookviews( XclExpRecordList<>& aRecList, const ExcTable& self ) +{ + aRecList.AppendNewRecord( new XclExpXmlStartElementRecord( XML_bookViews ) ); + aRecList.AppendNewRecord( new XclExpWindow1( self.GetRoot() ) ); + aRecList.AppendNewRecord( new XclExpXmlEndElementRecord( XML_bookViews ) ); +} + +static void lcl_AddCalcPr( XclExpRecordList<>& aRecList, const ExcTable& self ) +{ + ScDocument& rDoc = self.GetDoc(); + + aRecList.AppendNewRecord( new XclExpXmlStartSingleElementRecord( XML_calcPr ) ); + // OOXTODO: calcCompleted, calcId, calcMode, calcOnSave, + // concurrentCalc, concurrentManualCount, + // forceFullCalc, fullCalcOnLoad, fullPrecision + aRecList.AppendNewRecord( new XclCalccount( rDoc ) ); + aRecList.AppendNewRecord( new XclRefmode( rDoc ) ); + aRecList.AppendNewRecord( new XclIteration( rDoc ) ); + aRecList.AppendNewRecord( new XclDelta( rDoc ) ); + aRecList.AppendNewRecord( new XclExpBoolRecord(oox::xls::BIFF_ID_SAVERECALC, true) ); + aRecList.AppendNewRecord( new XclExpXmlEndSingleElementRecord() ); // XML_calcPr +} + +static void lcl_AddWorkbookProtection( XclExpRecordList<>& aRecList, const ExcTable& self ) +{ + aRecList.AppendNewRecord( new XclExpXmlStartSingleElementRecord( XML_workbookProtection ) ); + + const ScDocProtection* pProtect = self.GetDoc().GetDocProtection(); + if (pProtect && pProtect->isProtected()) + { + aRecList.AppendNewRecord( new XclExpWindowProtection(pProtect->isOptionEnabled(ScDocProtection::WINDOWS)) ); + aRecList.AppendNewRecord( new XclExpProtection(pProtect->isOptionEnabled(ScDocProtection::STRUCTURE)) ); + aRecList.AppendNewRecord( new XclExpPassHash(pProtect->getPasswordHash(PASSHASH_XL)) ); + } + + aRecList.AppendNewRecord( new XclExpXmlEndSingleElementRecord() ); // XML_workbookProtection +} + +static void lcl_AddScenariosAndFilters( XclExpRecordList<>& aRecList, const XclExpRoot& rRoot, SCTAB nScTab ) +{ + // Scenarios + aRecList.AppendNewRecord( new ExcEScenarioManager( rRoot, nScTab ) ); + // filter + aRecList.AppendRecord( rRoot.GetFilterManager().CreateRecord( nScTab ) ); +} + +ExcTable::ExcTable( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnScTab( SCTAB_GLOBAL ), + nExcTab( EXC_NOTAB ), + mxNoteList( new XclExpNoteList ) +{ +} + +ExcTable::ExcTable( const XclExpRoot& rRoot, SCTAB nScTab ) : + XclExpRoot( rRoot ), + mnScTab( nScTab ), + nExcTab( rRoot.GetTabInfo().GetXclTab( nScTab ) ), + mxNoteList( new XclExpNoteList ) +{ +} + +ExcTable::~ExcTable() +{ +} + +void ExcTable::Add( XclExpRecordBase* pRec ) +{ + OSL_ENSURE( pRec, "-ExcTable::Add(): pRec is NULL!" ); + aRecList.AppendNewRecord( pRec ); +} + +void ExcTable::FillAsHeaderBinary( ExcBoundsheetList& rBoundsheetList ) +{ + InitializeGlobals(); + + RootData& rR = GetOldRoot(); + ScDocument& rDoc = GetDoc(); + XclExpTabInfo& rTabInfo = GetTabInfo(); + + if ( GetBiff() <= EXC_BIFF5 ) + Add( new ExcBofW ); + else + Add( new ExcBofW8 ); + + sal_uInt16 nExcTabCount = rTabInfo.GetXclTabCount(); + sal_uInt16 nCodenames = static_cast< sal_uInt16 >( GetExtDocOptions().GetCodeNameCount() ); + + SfxObjectShell* pShell = GetDocShell(); + sal_uInt16 nWriteProtHash = pShell ? pShell->GetModifyPasswordHash() : 0; + bool bRecommendReadOnly = pShell && pShell->IsLoadReadonly(); + + if( (nWriteProtHash > 0) || bRecommendReadOnly ) + Add( new XclExpEmptyRecord( EXC_ID_WRITEPROT ) ); + + // TODO: correct codepage for BIFF5? + sal_uInt16 nCodePage = XclTools::GetXclCodePage( (GetBiff() <= EXC_BIFF5) ? RTL_TEXTENCODING_MS_1252 : RTL_TEXTENCODING_UNICODE ); + + if( GetBiff() <= EXC_BIFF5 ) + { + Add( new XclExpEmptyRecord( EXC_ID_INTERFACEHDR ) ); + Add( new XclExpUInt16Record( EXC_ID_MMS, 0 ) ); + Add( new XclExpEmptyRecord( EXC_ID_TOOLBARHDR ) ); + Add( new XclExpEmptyRecord( EXC_ID_TOOLBAREND ) ); + Add( new XclExpEmptyRecord( EXC_ID_INTERFACEEND ) ); + Add( new ExcDummy_00 ); + } + else + { + if( IsDocumentEncrypted() ) + Add( new XclExpFileEncryption( GetRoot() ) ); + Add( new XclExpInterfaceHdr( nCodePage ) ); + Add( new XclExpUInt16Record( EXC_ID_MMS, 0 ) ); + Add( new XclExpInterfaceEnd ); + Add( new XclExpWriteAccess ); + } + + Add( new XclExpFileSharing( GetRoot(), nWriteProtHash, bRecommendReadOnly ) ); + Add( new XclExpUInt16Record( EXC_ID_CODEPAGE, nCodePage ) ); + + if( GetBiff() == EXC_BIFF8 ) + { + Add( new XclExpBoolRecord( EXC_ID_DSF, false ) ); + Add( new XclExpEmptyRecord( EXC_ID_XL9FILE ) ); + rR.pTabId = new XclExpChTrTabId( std::max( nExcTabCount, nCodenames ) ); + Add( rR.pTabId ); + if( HasVbaStorage() ) + { + Add( new XclObproj ); + const OUString& rCodeName = GetExtDocOptions().GetDocSettings().maGlobCodeName; + if( !rCodeName.isEmpty() ) + Add( new XclCodename( rCodeName ) ); + } + } + + Add( new XclExpUInt16Record( EXC_ID_FNGROUPCOUNT, 14 ) ); + + if ( GetBiff() <= EXC_BIFF5 ) + { + // global link table: EXTERNCOUNT, EXTERNSHEET, NAME + aRecList.AppendRecord( CreateRecord( EXC_ID_EXTERNSHEET ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_NAME ) ); + } + + // document protection options + lcl_AddWorkbookProtection( aRecList, *this ); + + if( GetBiff() == EXC_BIFF8 ) + { + Add( new XclExpProt4Rev ); + Add( new XclExpProt4RevPass ); + } + + lcl_AddBookviews( aRecList, *this ); + + Add( new XclExpXmlStartSingleElementRecord( XML_workbookPr ) ); + if ( GetBiff() == EXC_BIFF8 && GetOutput() != EXC_OUTPUT_BINARY ) + { + Add( new XclExpBoolRecord(0x0040, false, XML_backupFile ) ); // BACKUP + Add( new XclExpBoolRecord(0x008D, false, XML_showObjects ) ); // HIDEOBJ + } + + if ( GetBiff() == EXC_BIFF8 ) + { + Add( new XclExpBoolRecord(0x0040, false) ); // BACKUP + Add( new XclExpBoolRecord(0x008D, false) ); // HIDEOBJ + } + + if( GetBiff() <= EXC_BIFF5 ) + { + Add( new ExcDummy_040 ); + Add( new Exc1904( rDoc ) ); + Add( new ExcDummy_041 ); + } + else + { + // BIFF8 + Add( new Exc1904( rDoc ) ); + Add( new XclExpBoolRecord( 0x000E, !rDoc.GetDocOptions().IsCalcAsShown() ) ); + Add( new XclExpBoolRecord(0x01B7, false) ); // REFRESHALL + Add( new XclExpBoolRecord(0x00DA, false) ); // BOOKBOOL + } + + // Formatting: FONT, FORMAT, XF, STYLE, PALETTE + aRecList.AppendRecord( CreateRecord( EXC_ID_FONTLIST ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_FORMATLIST ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_XFLIST ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_PALETTE ) ); + + SCTAB nC; + SCTAB nScTabCount = rTabInfo.GetScTabCount(); + if( GetBiff() <= EXC_BIFF5 ) + { + // Bundlesheet + for( nC = 0 ; nC < nScTabCount ; nC++ ) + if( rTabInfo.IsExportTab( nC ) ) + { + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet( rR, nC ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + } + else + { + // Pivot Cache + GetPivotTableManager().CreatePivotTables(); + aRecList.AppendRecord( GetPivotTableManager().CreatePivotCachesRecord() ); + + // Change tracking + if( rDoc.GetChangeTrack() ) + { + rR.pUserBViewList = new XclExpUserBViewList( *rDoc.GetChangeTrack() ); + Add( rR.pUserBViewList ); + } + + // Natural Language Formulas Flag + aRecList.AppendNewRecord( new XclExpBoolRecord( EXC_ID_USESELFS, GetDoc().GetDocOptions().IsLookUpColRowNames() ) ); + + // Bundlesheet + for( nC = 0 ; nC < nScTabCount ; nC++ ) + if( rTabInfo.IsExportTab( nC ) ) + { + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet8( rR, nC ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + + OUString aTmpString; + for( SCTAB nAdd = 0; nC < static_cast(nCodenames) ; nC++, nAdd++ ) + { + aTmpString = lcl_GetVbaTabName( nAdd ); + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet8( aTmpString ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + + // COUNTRY - in BIFF8 in workbook globals + Add( new XclExpCountry( GetRoot() ) ); + + // link table: SUPBOOK, XCT, CRN, EXTERNNAME, EXTERNSHEET, NAME + aRecList.AppendRecord( CreateRecord( EXC_ID_EXTERNSHEET ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_NAME ) ); + + Add( new XclExpRecalcId ); + + // MSODRAWINGGROUP per-document data + aRecList.AppendRecord( GetObjectManager().CreateDrawingGroup() ); + // Shared string table: SST, EXTSST + aRecList.AppendRecord( CreateRecord( EXC_ID_SST ) ); + + Add( new XclExpBookExt ); + } + + Add( new ExcEof ); +} + +void ExcTable::FillAsHeaderXml( ExcBoundsheetList& rBoundsheetList ) +{ + InitializeGlobals(); + + RootData& rR = GetOldRoot(); + ScDocument& rDoc = GetDoc(); + XclExpTabInfo& rTabInfo = GetTabInfo(); + + sal_uInt16 nExcTabCount = rTabInfo.GetXclTabCount(); + sal_uInt16 nCodenames = static_cast< sal_uInt16 >( GetExtDocOptions().GetCodeNameCount() ); + + rR.pTabId = new XclExpChTrTabId( std::max( nExcTabCount, nCodenames ) ); + Add( rR.pTabId ); + + Add( new XclExpXmlStartSingleElementRecord( XML_workbookPr ) ); + Add( new XclExpBoolRecord(0x0040, false, XML_backupFile ) ); // BACKUP + Add( new XclExpBoolRecord(0x008D, false, XML_showObjects ) ); // HIDEOBJ + + Add( new Exc1904( rDoc ) ); + // OOXTODO: The following /workbook/workbookPr attributes are mapped + // to various BIFF records that are not currently supported: + // + // XML_allowRefreshQuery: QSISTAG 802h: fEnableRefresh + // XML_autoCompressPictures: COMPRESSPICTURES 89Bh: fAutoCompressPictures + // XML_checkCompatibility: COMPAT12 88Ch: fNoCompatChk + // XML_codeName: "Calc" + // XML_defaultThemeVersion: ??? + // XML_filterPrivacy: BOOKEXT 863h: fFilterPrivacy + // XML_hidePivotFieldList: BOOKBOOL DAh: fHidePivotTableFList + // XML_promptedSolutions: BOOKEXT 863h: fBuggedUserAboutSolution + // XML_publishItems: NAMEPUBLISH 893h: fPublished + // XML_saveExternalLinkValues: BOOKBOOL DAh: fNoSavSupp + // XML_showBorderUnselectedTables: BOOKBOOL DAh: fHideBorderUnsels + // XML_showInkAnnotation: BOOKEXT 863h: fShowInkAnnotation + // XML_showPivotChart: PIVOTCHARTBITS 859h: fGXHide?? + // XML_updateLinks: BOOKBOOL DAh: grbitUpdateLinks + Add( new XclExpXmlEndSingleElementRecord() ); // XML_workbookPr + + // Formatting: FONT, FORMAT, XF, STYLE, PALETTE + aRecList.AppendNewRecord( new XclExpXmlStyleSheet( *this ) ); + + // Change tracking + if( rDoc.GetChangeTrack() ) + { + rR.pUserBViewList = new XclExpUserBViewList( *rDoc.GetChangeTrack() ); + Add( rR.pUserBViewList ); + } + + lcl_AddWorkbookProtection( aRecList, *this ); + lcl_AddBookviews( aRecList, *this ); + + // Bundlesheet + SCTAB nC; + SCTAB nScTabCount = rTabInfo.GetScTabCount(); + aRecList.AppendNewRecord( new XclExpXmlStartElementRecord( XML_sheets ) ); + for( nC = 0 ; nC < nScTabCount ; nC++ ) + if( rTabInfo.IsExportTab( nC ) ) + { + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet8( rR, nC ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + aRecList.AppendNewRecord( new XclExpXmlEndElementRecord( XML_sheets ) ); + + OUString aTmpString; + for( SCTAB nAdd = 0; nC < static_cast(nCodenames) ; nC++, nAdd++ ) + { + aTmpString = lcl_GetVbaTabName( nAdd ); + ExcBoundsheetList::RecordRefType xBoundsheet = new ExcBundlesheet8( aTmpString ); + aRecList.AppendRecord( xBoundsheet ); + rBoundsheetList.AppendRecord( xBoundsheet ); + } + + // link table: SUPBOOK, XCT, CRN, EXTERNNAME, EXTERNSHEET, NAME + aRecList.AppendRecord( CreateRecord( EXC_ID_EXTERNSHEET ) ); + aRecList.AppendRecord( CreateRecord( EXC_ID_NAME ) ); + + lcl_AddCalcPr( aRecList, *this ); + + // MSODRAWINGGROUP per-document data + aRecList.AppendRecord( GetObjectManager().CreateDrawingGroup() ); + // Shared string table: SST, EXTSST + aRecList.AppendRecord( CreateRecord( EXC_ID_SST ) ); +} + +void ExcTable::FillAsTableBinary( SCTAB nCodeNameIdx ) +{ + InitializeTable( mnScTab ); + + RootData& rR = GetOldRoot(); + XclBiff eBiff = GetBiff(); + ScDocument& rDoc = GetDoc(); + + OSL_ENSURE( (mnScTab >= 0) && (mnScTab <= MAXTAB), "-ExcTable::Table(): mnScTab - no ordinary table!" ); + OSL_ENSURE( nExcTab <= o3tl::make_unsigned(MAXTAB), "-ExcTable::Table(): nExcTab - no ordinary table!" ); + + // create a new OBJ list for this sheet (may be used by notes, autofilter, data validation) + if( eBiff == EXC_BIFF8 ) + GetObjectManager().StartSheet(); + + // cell table: DEFROWHEIGHT, DEFCOLWIDTH, COLINFO, DIMENSIONS, ROW, cell records + mxCellTable = new XclExpCellTable( GetRoot() ); + + //export cell notes + std::vector aNotes; + rDoc.GetAllNoteEntries(aNotes); + for (const auto& rNote : aNotes) + { + if (rNote.maPos.Tab() != mnScTab) + continue; + + mxNoteList->AppendNewRecord(new XclExpNote(GetRoot(), rNote.maPos, rNote.mpNote, u"")); + } + + // WSBOOL needs data from page settings, create it here, add it later + rtl::Reference xPageSett = new XclExpPageSettings( GetRoot() ); + bool bFitToPages = xPageSett->GetPageData().mbFitToPages; + + if( eBiff <= EXC_BIFF5 ) + { + Add( new ExcBof ); + Add( new ExcDummy_02a ); + } + else + { + Add( new ExcBof8 ); + lcl_AddCalcPr( aRecList, *this ); + } + + // GUTS (count & size of outline icons) + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_GUTS ) ); + // DEFROWHEIGHT, created by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID2_DEFROWHEIGHT ) ); + + // COUNTRY - in BIFF5/7 in every worksheet + if( eBiff <= EXC_BIFF5 ) + Add( new XclExpCountry( GetRoot() ) ); + + Add( new XclExpWsbool( bFitToPages ) ); + + // page settings (SETUP and various other records) + aRecList.AppendRecord( xPageSett ); + + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(mnScTab); + if (pTabProtect && pTabProtect->isProtected()) + { + Add( new XclExpProtection(true) ); + Add( new XclExpBoolRecord(oox::xls::BIFF_ID_SCENPROTECT, pTabProtect->isOptionEnabled(ScTableProtection::SCENARIOS)) ); + if (pTabProtect->isOptionEnabled(ScTableProtection::OBJECTS)) + Add( new XclExpBoolRecord(oox::xls::BIFF_ID_OBJECTPROTECT, true )); + Add( new XclExpPassHash(pTabProtect->getPasswordHash(PASSHASH_XL)) ); + } + + // local link table: EXTERNCOUNT, EXTERNSHEET + if( eBiff <= EXC_BIFF5 ) + aRecList.AppendRecord( CreateRecord( EXC_ID_EXTERNSHEET ) ); + + if ( eBiff == EXC_BIFF8 ) + lcl_AddScenariosAndFilters( aRecList, GetRoot(), mnScTab ); + + // cell table: DEFCOLWIDTH, COLINFO, DIMENSIONS, ROW, cell records + aRecList.AppendRecord( mxCellTable ); + + // MERGEDCELLS record, generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_MERGEDCELLS ) ); + // label ranges + if( eBiff == EXC_BIFF8 ) + Add( new XclExpLabelranges( GetRoot() ) ); + // data validation (DVAL and list of DV records), generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_DVAL ) ); + + if( eBiff == EXC_BIFF8 ) + { + // all MSODRAWING and OBJ stuff of this sheet goes here + aRecList.AppendRecord( GetObjectManager().ProcessDrawing( GetSdrPage( mnScTab ) ) ); + // pivot tables + aRecList.AppendRecord( GetPivotTableManager().CreatePivotTablesRecord( mnScTab ) ); + } + + // list of NOTE records, generated by the cell table + aRecList.AppendRecord( mxNoteList ); + + // sheet view settings: WINDOW2, SCL, PANE, SELECTION + aRecList.AppendNewRecord( new XclExpTabViewSettings( GetRoot(), mnScTab ) ); + + if( eBiff == EXC_BIFF8 ) + { + // sheet protection options + Add( new XclExpSheetProtectOptions( GetRoot(), mnScTab ) ); + + // enhanced protections if there are + if (pTabProtect) + { + const ::std::vector& rProts( pTabProtect->getEnhancedProtection()); + for (const auto& rProt : rProts) + { + Add( new XclExpSheetEnhancedProtection( GetRoot(), rProt)); + } + } + + // web queries + Add( new XclExpWebQueryBuffer( GetRoot() ) ); + + // conditional formats + Add( new XclExpCondFormatBuffer( GetRoot(), XclExtLstRef() ) ); + + if( HasVbaStorage() ) + if( nCodeNameIdx < GetExtDocOptions().GetCodeNameCount() ) + Add( new XclCodename( GetExtDocOptions().GetCodeName( nCodeNameIdx ) ) ); + } + + // list of HLINK records, generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_HLINK ) ); + + // change tracking + if( rR.pUserBViewList ) + { + XclExpUserBViewList::const_iterator iter; + for ( iter = rR.pUserBViewList->cbegin(); iter != rR.pUserBViewList->cend(); ++iter) + { + Add( new XclExpUsersViewBegin( (*iter).GetGUID(), nExcTab ) ); + Add( new XclExpUsersViewEnd ); + } + } + + // EOF + Add( new ExcEof ); +} + +void ExcTable::FillAsTableXml() +{ + InitializeTable( mnScTab ); + + ScDocument& rDoc = GetDoc(); + + OSL_ENSURE( (mnScTab >= 0) && (mnScTab <= MAXTAB), "-ExcTable::Table(): mnScTab - no ordinary table!" ); + OSL_ENSURE( nExcTab <= o3tl::make_unsigned(MAXTAB), "-ExcTable::Table(): nExcTab - no ordinary table!" ); + + // create a new OBJ list for this sheet (may be used by notes, autofilter, data validation) + GetObjectManager().StartSheet(); + + // cell table: DEFROWHEIGHT, DEFCOLWIDTH, COLINFO, DIMENSIONS, ROW, cell records + mxCellTable = new XclExpCellTable( GetRoot() ); + + //export cell notes + std::vector aNotes; + rDoc.GetAllNoteEntries(aNotes); + for (const auto& rNote : aNotes) + { + if (rNote.maPos.Tab() != mnScTab) + continue; + + mxNoteList->AppendNewRecord(new XclExpNote(GetRoot(), rNote.maPos, rNote.mpNote, u"")); + } + + // WSBOOL needs data from page settings, create it here, add it later + rtl::Reference xPageSett = new XclExpPageSettings( GetRoot() ); + XclExtLstRef xExtLst = new XclExtLst( GetRoot() ); + bool bFitToPages = xPageSett->GetPageData().mbFitToPages; + + Color aTabColor = GetRoot().GetDoc().GetTabBgColor(mnScTab); + Add(new XclExpXmlSheetPr(bFitToPages, mnScTab, aTabColor, &GetFilterManager())); + + // GUTS (count & size of outline icons) + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_GUTS ) ); + // DEFROWHEIGHT, created by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID2_DEFROWHEIGHT ) ); + + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID3_DIMENSIONS ) ); + + // sheet view settings: WINDOW2, SCL, PANE, SELECTION + aRecList.AppendNewRecord( new XclExpTabViewSettings( GetRoot(), mnScTab ) ); + + // cell table: DEFCOLWIDTH, COLINFO, DIMENSIONS, ROW, cell records + aRecList.AppendRecord( mxCellTable ); + + // list of NOTE records, generated by the cell table + // not in the worksheet file + if( mxNoteList != nullptr && !mxNoteList->IsEmpty() ) + aRecList.AppendNewRecord( new XclExpComments( mnScTab, *mxNoteList ) ); + + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(mnScTab); + if (pTabProtect && pTabProtect->isProtected()) + Add( new XclExpSheetProtection(true, mnScTab) ); + + lcl_AddScenariosAndFilters( aRecList, GetRoot(), mnScTab ); + + // MERGEDCELLS record, generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_MERGEDCELLS ) ); + + // conditional formats + Add( new XclExpCondFormatBuffer( GetRoot(), xExtLst ) ); + + Add(new xcl::exp::SparklineBuffer(GetRoot(), xExtLst)); + + // data validation (DVAL and list of DV records), generated by the cell table + aRecList.AppendRecord( mxCellTable->CreateRecord( EXC_ID_DVAL ) ); + + // list of HLINK records, generated by the cell table + XclExpRecordRef xHyperlinks = mxCellTable->CreateRecord( EXC_ID_HLINK ); + XclExpHyperlinkList* pHyperlinkList = dynamic_cast(xHyperlinks.get()); + if( pHyperlinkList != nullptr && !pHyperlinkList->IsEmpty() ) + { + aRecList.AppendNewRecord( new XclExpXmlStartElementRecord( XML_hyperlinks ) ); + aRecList.AppendRecord( xHyperlinks ); + aRecList.AppendNewRecord( new XclExpXmlEndElementRecord( XML_hyperlinks ) ); + } + + aRecList.AppendRecord( xPageSett ); + + // all MSODRAWING and OBJ stuff of this sheet goes here + aRecList.AppendRecord( GetObjectManager().ProcessDrawing( GetSdrPage( mnScTab ) ) ); + + XclExpImgData* pImgData = xPageSett->getGraphicExport(); + if (pImgData) + aRecList.AppendRecord(pImgData); + + // after and before + aRecList.AppendRecord( GetTablesManager().GetTablesBySheet( mnScTab)); + + aRecList.AppendRecord( xExtLst ); +} + +void ExcTable::FillAsEmptyTable( SCTAB nCodeNameIdx ) +{ + InitializeTable( mnScTab ); + + if( !(HasVbaStorage() && (nCodeNameIdx < GetExtDocOptions().GetCodeNameCount())) ) + return; + + if( GetBiff() <= EXC_BIFF5 ) + { + Add( new ExcBof ); + } + else + { + Add( new ExcBof8 ); + Add( new XclCodename( GetExtDocOptions().GetCodeName( nCodeNameIdx ) ) ); + } + // sheet view settings: WINDOW2, SCL, PANE, SELECTION + aRecList.AppendNewRecord( new XclExpTabViewSettings( GetRoot(), mnScTab ) ); + Add( new ExcEof ); +} + +void ExcTable::Write( XclExpStream& rStrm ) +{ + SetCurrScTab( mnScTab ); + if( mxCellTable ) + mxCellTable->Finalize(true); + aRecList.Save( rStrm ); +} + +void ExcTable::WriteXml( XclExpXmlStream& rStrm ) +{ + if (!GetTabInfo().IsExportTab(mnScTab)) + { + // header export. + SetCurrScTab(mnScTab); + if (mxCellTable) + mxCellTable->Finalize(false); + aRecList.SaveXml(rStrm); + + return; + } + + // worksheet export + OUString sSheetName = XclXmlUtils::GetStreamName( "xl/", "worksheets/sheet", mnScTab+1 ); + + sax_fastparser::FSHelperPtr pWorksheet = rStrm.GetStreamForPath( sSheetName ); + + rStrm.PushStream( pWorksheet ); + + pWorksheet->startElement( XML_worksheet, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + FSNS(XML_xmlns, XML_xdr), "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", // rStrm.getNamespaceURL(OOX_NS(xm)).toUtf8() -> "http://schemas.microsoft.com/office/excel/2006/main", + FSNS(XML_xmlns, XML_x14), rStrm.getNamespaceURL(OOX_NS(xls14Lst)).toUtf8(), + FSNS(XML_xmlns, XML_xr2), rStrm.getNamespaceURL(OOX_NS(xr2)).toUtf8(), + FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce)).toUtf8()); + + SetCurrScTab( mnScTab ); + if (mxCellTable) + mxCellTable->Finalize(false); + aRecList.SaveXml( rStrm ); + + XclExpXmlPivotTables* pPT = GetXmlPivotTableManager().GetTablesBySheet(mnScTab); + if (pPT) + pPT->SaveXml(rStrm); + + rStrm.GetCurrentStream()->endElement( XML_worksheet ); + rStrm.PopStream(); +} + +ExcDocument::ExcDocument( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + aHeader( rRoot ) +{ +} + +ExcDocument::~ExcDocument() +{ + maTableList.RemoveAllRecords(); // for the following assertion! +} + +void ExcDocument::ReadDoc() +{ + InitializeConvert(); + + if (GetOutput() == EXC_OUTPUT_BINARY) + aHeader.FillAsHeaderBinary(maBoundsheetList); + else + { + aHeader.FillAsHeaderXml(maBoundsheetList); + GetXmlPivotTableManager().Initialize(); + GetTablesManager().Initialize(); // Move outside conditions if we wanted to support BIFF. + } + + SCTAB nScTab = 0, nScTabCount = GetTabInfo().GetScTabCount(); + SCTAB nCodeNameIdx = 0, nCodeNameCount = GetExtDocOptions().GetCodeNameCount(); + + for( ; nScTab < nScTabCount; ++nScTab ) + { + if( GetTabInfo().IsExportTab( nScTab ) ) + { + ExcTableList::RecordRefType xTab = new ExcTable( GetRoot(), nScTab ); + maTableList.AppendRecord( xTab ); + if (GetOutput() == EXC_OUTPUT_BINARY) + xTab->FillAsTableBinary(nCodeNameIdx); + else + xTab->FillAsTableXml(); + + ++nCodeNameIdx; + } + } + for( ; nCodeNameIdx < nCodeNameCount; ++nScTab, ++nCodeNameIdx ) + { + ExcTableList::RecordRefType xTab = new ExcTable( GetRoot(), nScTab ); + maTableList.AppendRecord( xTab ); + xTab->FillAsEmptyTable( nCodeNameIdx ); + } + + if ( GetBiff() == EXC_BIFF8 ) + { + // complete temporary Escher stream + GetObjectManager().EndDocument(); + + // change tracking + if ( GetDoc().GetChangeTrack() ) + m_xExpChangeTrack.reset(new XclExpChangeTrack( GetRoot() )); + } +} + +void ExcDocument::Write( SvStream& rSvStrm ) +{ + if( !maTableList.IsEmpty() ) + { + InitializeSave(); + + XclExpStream aXclStrm( rSvStrm, GetRoot() ); + + aHeader.Write( aXclStrm ); + + OSL_ENSURE( maTableList.GetSize() == maBoundsheetList.GetSize(), + "ExcDocument::Write - different number of sheets and BOUNDSHEET records" ); + + for( size_t nTab = 0, nTabCount = maTableList.GetSize(); nTab < nTabCount; ++nTab ) + { + // set current stream position in BOUNDSHEET record + ExcBoundsheetRef xBoundsheet = maBoundsheetList.GetRecord( nTab ); + if( xBoundsheet ) + xBoundsheet->SetStreamPos( aXclStrm.GetSvStreamPos() ); + // write the table + maTableList.GetRecord( nTab )->Write( aXclStrm ); + } + + // write the table stream positions into the BOUNDSHEET records + for( size_t nBSheet = 0, nBSheetCount = maBoundsheetList.GetSize(); nBSheet < nBSheetCount; ++nBSheet ) + maBoundsheetList.GetRecord( nBSheet )->UpdateStreamPos( aXclStrm ); + } + if( m_xExpChangeTrack ) + m_xExpChangeTrack->Write(); +} + +void ExcDocument::WriteXml( XclExpXmlStream& rStrm ) +{ + SfxObjectShell* pDocShell = GetDocShell(); + + using namespace ::com::sun::star; + uno::Reference xDPS( pDocShell->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference xDocProps = xDPS->getDocumentProperties(); + + OUString sUserName = GetUserName(); + sal_uInt32 nWriteProtHash = pDocShell->GetModifyPasswordHash(); + bool bHasPasswordHash = nWriteProtHash && !sUserName.isEmpty(); + const uno::Sequence aInfo = pDocShell->GetModifyPasswordInfo(); + OUString sAlgorithm, sSalt, sHash; + sal_Int32 nCount = 0; + for (const auto& prop : aInfo) + { + if (prop.Name == "algorithm-name") + prop.Value >>= sAlgorithm; + else if (prop.Name == "salt") + prop.Value >>= sSalt; + else if (prop.Name == "iteration-count") + prop.Value >>= nCount; + else if (prop.Name == "hash") + prop.Value >>= sHash; + } + bool bHasPasswordInfo + = sAlgorithm != "PBKDF2" && !sSalt.isEmpty() && !sHash.isEmpty() && !sUserName.isEmpty(); + rStrm.exportDocumentProperties(xDocProps, pDocShell->IsSecurityOptOpenReadOnly() + && !bHasPasswordHash && !bHasPasswordInfo); + rStrm.exportCustomFragments(); + + sax_fastparser::FSHelperPtr& rWorkbook = rStrm.GetCurrentStream(); + rWorkbook->startElement( XML_workbook, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8() ); + rWorkbook->singleElement( XML_fileVersion, + XML_appName, "Calc" + // OOXTODO: XML_codeName + // OOXTODO: XML_lastEdited + // OOXTODO: XML_lowestEdited + // OOXTODO: XML_rupBuild + ); + + if (bHasPasswordHash) + rWorkbook->singleElement(XML_fileSharing, + XML_userName, sUserName, + XML_reservationPassword, OString::number(nWriteProtHash, 16).getStr()); + else if (bHasPasswordInfo) + rWorkbook->singleElement(XML_fileSharing, + XML_userName, sUserName, + XML_algorithmName, sAlgorithm.toUtf8().getStr(), + XML_hashValue, sHash.toUtf8().getStr(), + XML_saltValue, sSalt.toUtf8().getStr(), + XML_spinCount, OString::number(nCount).getStr()); + + if( !maTableList.IsEmpty() ) + { + InitializeSave(); + + aHeader.WriteXml( rStrm ); + + for( size_t nTab = 0, nTabCount = maTableList.GetSize(); nTab < nTabCount; ++nTab ) + { + // write the table + maTableList.GetRecord( nTab )->WriteXml( rStrm ); + } + } + + if( m_xExpChangeTrack ) + m_xExpChangeTrack->WriteXml( rStrm ); + + XclExpXmlPivotCaches& rCaches = GetXmlPivotTableManager().GetCaches(); + if (rCaches.HasCaches()) + rCaches.SaveXml(rStrm); + + const ScCalcConfig& rCalcConfig = GetDoc().GetCalcConfig(); + formula::FormulaGrammar::AddressConvention eConv = rCalcConfig.meStringRefAddressSyntax; + + // don't save "unspecified" string ref syntax ... query formula grammar + // and save that instead + if( eConv == formula::FormulaGrammar::CONV_UNSPECIFIED) + { + eConv = GetDoc().GetAddressConvention(); + } + + // write if it has been read|imported or explicitly changed + // or if ref syntax isn't what would be native for our file format + // i.e. ExcelA1 in this case + if ( rCalcConfig.mbHasStringRefSyntax || + (eConv != formula::FormulaGrammar::CONV_XL_A1) ) + { + XclExtLstRef xExtLst = new XclExtLst( GetRoot() ); + xExtLst->AddRecord( new XclExpExtCalcPr( GetRoot(), eConv ) ); + xExtLst->SaveXml(rStrm); + } + + rWorkbook->endElement( XML_workbook ); + rWorkbook.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excel.cxx b/sc/source/filter/excel/excel.cxx new file mode 100644 index 000000000..edc60721a --- /dev/null +++ b/sc/source/filter/excel/excel.cxx @@ -0,0 +1,494 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace css; + +static void lcl_getListOfStreams(SotStorage * pStorage, comphelper::SequenceAsHashMap& aStreamsData, const OUString& sPrefix) +{ + SvStorageInfoList aElements; + pStorage->FillInfoList(&aElements); + for (const auto & aElement : aElements) + { + OUString sStreamFullName = sPrefix.getLength() ? sPrefix + "/" + aElement.GetName() : aElement.GetName(); + if (aElement.IsStorage()) + { + tools::SvRef xSubStorage = pStorage->OpenSotStorage(aElement.GetName(), StreamMode::STD_READ | StreamMode::SHARE_DENYALL); + lcl_getListOfStreams(xSubStorage.get(), aStreamsData, sStreamFullName); + } + else + { + // Read stream + tools::SvRef rStream = pStorage->OpenSotStream(aElement.GetName(), StreamMode::READ | StreamMode::SHARE_DENYALL); + if (rStream.is()) + { + sal_Int32 nStreamSize = rStream->GetSize(); + uno::Sequence< sal_Int8 > oData; + oData.realloc(nStreamSize); + sal_Int32 nReadBytes = rStream->ReadBytes(oData.getArray(), nStreamSize); + if (nStreamSize == nReadBytes) + aStreamsData[sStreamFullName] <<= oData; + } + } + } +} + +static tools::SvRef lcl_DRMDecrypt(const SfxMedium& rMedium, const tools::SvRef& rStorage, std::shared_ptr& rNewStorageStrm) +{ + tools::SvRef aNewStorage; + + // We have DRM encrypted storage. We should try to decrypt it first, if we can + uno::Sequence< uno::Any > aArguments; + uno::Reference xComponentContext(comphelper::getProcessComponentContext()); + uno::Reference< packages::XPackageEncryption > xPackageEncryption( + xComponentContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.oox.crypto.DRMDataSpace", aArguments, xComponentContext), uno::UNO_QUERY); + + if (!xPackageEncryption.is()) + { + // We do not know how to decrypt this + return aNewStorage; + } + + comphelper::SequenceAsHashMap aStreamsData; + lcl_getListOfStreams(rStorage.get(), aStreamsData, ""); + + try { + uno::Sequence aStreams = aStreamsData.getAsConstNamedValueList(); + if (!xPackageEncryption->readEncryptionInfo(aStreams)) + { + // We failed with decryption + return aNewStorage; + } + + tools::SvRef rContentStream = rStorage->OpenSotStream("\011DRMContent", StreamMode::READ | StreamMode::SHARE_DENYALL); + if (!rContentStream.is()) + { + return aNewStorage; + } + + rNewStorageStrm = std::make_shared(); + + uno::Reference xInputStream(new utl::OSeekableInputStreamWrapper(rContentStream.get(), false)); + uno::Reference xDecryptedStream(new utl::OSeekableOutputStreamWrapper(*rNewStorageStrm)); + + if (!xPackageEncryption->decrypt(xInputStream, xDecryptedStream)) + { + // We failed with decryption + return aNewStorage; + } + + rNewStorageStrm->Seek(0); + + // Further reading is done from new document + aNewStorage = new SotStorage(*rNewStorageStrm); + + // Set the media descriptor data + uno::Sequence aEncryptionData = xPackageEncryption->createEncryptionData(""); + rMedium.GetItemSet()->Put(SfxUnoAnyItem(SID_ENCRYPTIONDATA, uno::Any(aEncryptionData))); + } + catch (const std::exception&) + { + return aNewStorage; + } + + return aNewStorage; +} + +ErrCode ScFormatFilterPluginImpl::ScImportExcel( SfxMedium& rMedium, ScDocument* pDocument, const EXCIMPFORMAT eFormat ) +{ + // check the passed Calc document + OSL_ENSURE( pDocument, "::ScImportExcel - no document" ); + if( !pDocument ) return SCERR_IMPORT_INTERNAL; // should not happen + + /* Import all BIFF versions regardless on eFormat, needed for import of + external cells (file type detection returns Excel4.0). */ + if( (eFormat != EIF_AUTO) && (eFormat != EIF_BIFF_LE4) && (eFormat != EIF_BIFF5) && (eFormat != EIF_BIFF8) ) + { + OSL_FAIL( "::ScImportExcel - wrong file format specification" ); + return SCERR_IMPORT_FORMAT; + } + + // check the input stream from medium + SvStream* pMedStrm = rMedium.GetInStream(); + OSL_ENSURE( pMedStrm, "::ScImportExcel - medium without input stream" ); + if( !pMedStrm ) return SCERR_IMPORT_OPEN; // should not happen + + SvStream* pBookStrm = nullptr; // The "Book"/"Workbook" stream containing main data. + XclBiff eBiff = EXC_BIFF_UNKNOWN; // The BIFF version of the main stream. + + // try to open an OLE storage + tools::SvRef xRootStrg; + tools::SvRef xStrgStrm; + std::shared_ptr aNewStorageStrm; + if( SotStorage::IsStorageFile( pMedStrm ) ) + { + xRootStrg = new SotStorage( pMedStrm, false ); + if( xRootStrg->GetError() ) + xRootStrg = nullptr; + } + + // try to open "Book" or "Workbook" stream in OLE storage + if( xRootStrg.is() ) + { + // Check if there is DRM encryption in storage + tools::SvRef xDRMStrm = ScfTools::OpenStorageStreamRead(xRootStrg, "\011DRMContent"); + if (xDRMStrm.is()) + { + xRootStrg = lcl_DRMDecrypt(rMedium, xRootStrg, aNewStorageStrm); + } + + // try to open the "Book" stream + tools::SvRef xBookStrm = ScfTools::OpenStorageStreamRead( xRootStrg, EXC_STREAM_BOOK ); + XclBiff eBookBiff = xBookStrm.is() ? XclImpStream::DetectBiffVersion( *xBookStrm ) : EXC_BIFF_UNKNOWN; + + // try to open the "Workbook" stream + tools::SvRef xWorkbookStrm = ScfTools::OpenStorageStreamRead( xRootStrg, EXC_STREAM_WORKBOOK ); + XclBiff eWorkbookBiff = xWorkbookStrm.is() ? XclImpStream::DetectBiffVersion( *xWorkbookStrm ) : EXC_BIFF_UNKNOWN; + + // decide which stream to use + if( (eWorkbookBiff != EXC_BIFF_UNKNOWN) && ((eBookBiff == EXC_BIFF_UNKNOWN) || (eWorkbookBiff > eBookBiff)) ) + { + /* Only "Workbook" stream exists; or both streams exist, + and "Workbook" has higher BIFF version than "Book" stream. */ + xStrgStrm = xWorkbookStrm; + eBiff = eWorkbookBiff; + } + else if( eBookBiff != EXC_BIFF_UNKNOWN ) + { + /* Only "Book" stream exists; or both streams exist, + and "Book" has higher BIFF version than "Workbook" stream. */ + xStrgStrm = xBookStrm; + eBiff = eBookBiff; + } + + pBookStrm = xStrgStrm.get(); + } + + // no "Book" or "Workbook" stream found, try plain input stream from medium (even for BIFF5+) + if( !pBookStrm ) + { + eBiff = XclImpStream::DetectBiffVersion( *pMedStrm ); + if( eBiff != EXC_BIFF_UNKNOWN ) + pBookStrm = pMedStrm; + } + + // try to import the file + ErrCode eRet = SCERR_IMPORT_UNKNOWN_BIFF; + if( pBookStrm ) + { + pBookStrm->SetBufferSize( 0x8000 ); // still needed? + + XclImpRootData aImpData( + eBiff, rMedium, xRootStrg, *pDocument, + utl_getWinTextEncodingFromLangStr(utl_getLocaleForGlobalDefaultEncoding())); + std::unique_ptr< ImportExcel > xFilter; + switch( eBiff ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + xFilter.reset( new ImportExcel( aImpData, *pBookStrm ) ); + break; + case EXC_BIFF8: + xFilter.reset( new ImportExcel8( aImpData, *pBookStrm ) ); + break; + default: DBG_ERROR_BIFF(); + } + + eRet = xFilter ? xFilter->Read() : SCERR_IMPORT_INTERNAL; + } + + return eRet; +} + +static ErrCode lcl_ExportExcelBiff( SfxMedium& rMedium, ScDocument *pDocument, + SvStream* pMedStrm, bool bBiff8, rtl_TextEncoding eNach ) +{ + uno::Reference< packages::XPackageEncryption > xPackageEncryption; + uno::Sequence< beans::NamedValue > aEncryptionData; + const SfxUnoAnyItem* pEncryptionDataItem = SfxItemSet::GetItem(rMedium.GetItemSet(), SID_ENCRYPTIONDATA, false); + SvStream* pOriginalMediaStrm = pMedStrm; + std::shared_ptr pMediaStrm; + if (pEncryptionDataItem && (pEncryptionDataItem->GetValue() >>= aEncryptionData)) + { + ::comphelper::SequenceAsHashMap aHashData(aEncryptionData); + OUString sCryptoType = aHashData.getUnpackedValueOrDefault("CryptoType", OUString()); + + if (sCryptoType.getLength()) + { + uno::Reference xComponentContext(comphelper::getProcessComponentContext()); + uno::Sequence aArguments{ + uno::Any(beans::NamedValue("Binary", uno::Any(true))) }; + xPackageEncryption.set( + xComponentContext->getServiceManager()->createInstanceWithArgumentsAndContext( + "com.sun.star.comp.oox.crypto." + sCryptoType, aArguments, xComponentContext), uno::UNO_QUERY); + + if (xPackageEncryption.is()) + { + // We have an encryptor. Export document into memory stream and encrypt it later + pMediaStrm = std::make_shared(); + pMedStrm = pMediaStrm.get(); + + // Temp removal of EncryptionData to avoid password protection triggering + rMedium.GetItemSet()->ClearItem(SID_ENCRYPTIONDATA); + } + } + } + + // try to open an OLE storage + tools::SvRef xRootStrg = new SotStorage( pMedStrm, false ); + if( xRootStrg->GetError() ) return SCERR_IMPORT_OPEN; + + // create BIFF dependent strings + OUString aStrmName, aClipName, aClassName; + if( bBiff8 ) + { + aStrmName = EXC_STREAM_WORKBOOK; + aClipName = "Biff8"; + aClassName = "Microsoft Excel 97-Tabelle"; + } + else + { + aStrmName = EXC_STREAM_BOOK; + aClipName = "Biff5"; + aClassName = "Microsoft Excel 5.0-Tabelle"; + } + + // open the "Book"/"Workbook" stream + tools::SvRef xStrgStrm = ScfTools::OpenStorageStreamWrite( xRootStrg, aStrmName ); + if( !xStrgStrm.is() || xStrgStrm->GetError() ) return SCERR_IMPORT_OPEN; + + xStrgStrm->SetBufferSize( 0x8000 ); // still needed? + + ErrCode eRet = SCERR_IMPORT_UNKNOWN_BIFF; + XclExpRootData aExpData( bBiff8 ? EXC_BIFF8 : EXC_BIFF5, rMedium, xRootStrg, *pDocument, eNach ); + if ( bBiff8 ) + { + ExportBiff8 aFilter( aExpData, *xStrgStrm ); + eRet = aFilter.Write(); + } + else + { + ExportBiff5 aFilter( aExpData, *xStrgStrm ); + eRet = aFilter.Write(); + } + + if( eRet == SCWARN_IMPORT_RANGE_OVERFLOW ) + eRet = SCWARN_EXPORT_MAXROW; + + SvGlobalName aGlobName(MSO_EXCEL5_CLASSID); + SotClipboardFormatId nClip = SotExchange::RegisterFormatName( aClipName ); + xRootStrg->SetClass( aGlobName, nClip, aClassName ); + + xStrgStrm->Commit(); + xRootStrg->Commit(); + + if (xPackageEncryption.is()) + { + // Perform DRM encryption + pMedStrm->Seek(0); + + xPackageEncryption->setupEncryption(aEncryptionData); + + uno::Reference xInputStream(new utl::OSeekableInputStreamWrapper(pMedStrm, false)); + uno::Sequence aStreams = xPackageEncryption->encrypt(xInputStream); + + tools::SvRef xEncryptedRootStrg = new SotStorage(pOriginalMediaStrm, false); + for (const beans::NamedValue & aStreamData : std::as_const(aStreams)) + { + // To avoid long paths split and open substorages recursively + // Splitting paths manually, since comphelper::string::split is trimming special characters like \0x01, \0x09 + tools::SvRef pStorage = xEncryptedRootStrg.get(); + OUString sFileName; + sal_Int32 idx = 0; + do + { + OUString sPathElem = aStreamData.Name.getToken(0, L'/', idx); + if (!sPathElem.isEmpty()) + { + if (idx < 0) + { + sFileName = sPathElem; + } + else + { + pStorage = pStorage->OpenSotStorage(sPathElem); + } + } + } while (pStorage && idx >= 0); + + if (!pStorage) + { + eRet = ERRCODE_IO_GENERAL; + break; + } + + tools::SvRef pStream = pStorage->OpenSotStream(sFileName); + if (!pStream) + { + eRet = ERRCODE_IO_GENERAL; + break; + } + uno::Sequence aStreamContent; + aStreamData.Value >>= aStreamContent; + size_t nBytesWritten = pStream->WriteBytes(aStreamContent.getConstArray(), aStreamContent.getLength()); + if (nBytesWritten != static_cast(aStreamContent.getLength())) + { + eRet = ERRCODE_IO_CANTWRITE; + break; + } + } + xEncryptedRootStrg->Commit(); + + // Restore encryption data + rMedium.GetItemSet()->Put(SfxUnoAnyItem(SID_ENCRYPTIONDATA, uno::Any(aEncryptionData))); + } + + return eRet; +} + +ErrCode ScFormatFilterPluginImpl::ScExportExcel5( SfxMedium& rMedium, ScDocument *pDocument, + ExportFormatExcel eFormat, rtl_TextEncoding eNach ) +{ + if( eFormat != ExpBiff5 && eFormat != ExpBiff8 ) + return SCERR_IMPORT_NI; + + // check the passed Calc document + OSL_ENSURE( pDocument, "::ScExportExcel5 - no document" ); + if( !pDocument ) return SCERR_IMPORT_INTERNAL; // should not happen + + // check the output stream from medium + SvStream* pMedStrm = rMedium.GetOutStream(); + OSL_ENSURE( pMedStrm, "::ScExportExcel5 - medium without output stream" ); + if( !pMedStrm ) return SCERR_IMPORT_OPEN; // should not happen + + ErrCode eRet = lcl_ExportExcelBiff(rMedium, pDocument, pMedStrm, eFormat == ExpBiff8, eNach); + return eRet; +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportCalcRTF(SvStream &rStream) +{ + ScDLL::Init(); + ScDocument aDocument; + ScDocOptions aDocOpt = aDocument.GetDocOptions(); + aDocOpt.SetLookUpColRowNames(false); + aDocument.SetDocOptions(aDocOpt); + aDocument.MakeTable(0); + aDocument.EnableExecuteLink(false); + aDocument.SetInsertingFromOtherDoc(true); + ScRange aRange; + + bool bRet; + + try + { + bRet = ScFormatFilter::Get().ScImportRTF(rStream, OUString(), &aDocument, aRange) == ERRCODE_NONE; + } + catch (const std::range_error&) + { + return false; + } + + return bRet; +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportXLS(SvStream& rStream) +{ + ScDLL::Init(); + SfxMedium aMedium; + css::uno::Reference xStm(new utl::OInputStreamWrapper(rStream)); + aMedium.GetItemSet()->Put(SfxUnoAnyItem(SID_INPUTSTREAM, css::uno::Any(xStm))); + + ScDocShellRef xDocShell = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | + SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS | + SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + + xDocShell->DoInitNew(); + + ScDocument& rDoc = xDocShell->GetDocument(); + + ScDocOptions aDocOpt = rDoc.GetDocOptions(); + aDocOpt.SetLookUpColRowNames(false); + rDoc.SetDocOptions(aDocOpt); + rDoc.MakeTable(0); + rDoc.EnableExecuteLink(false); + rDoc.SetInsertingFromOtherDoc(true); + rDoc.InitDrawLayer(xDocShell.get()); + bool bRet(false); + try + { + bRet = ScFormatFilter::Get().ScImportExcel(aMedium, &rDoc, EIF_AUTO) == ERRCODE_NONE; + } + catch (const css::ucb::ContentCreationException&) + { + } + catch (const std::out_of_range&) + { + } + xDocShell->DoClose(); + xDocShell.clear(); + return bRet; +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportDIF(SvStream &rStream) +{ + ScDLL::Init(); + ScDocument aDocument; + ScDocOptions aDocOpt = aDocument.GetDocOptions(); + aDocOpt.SetLookUpColRowNames(false); + aDocument.SetDocOptions(aDocOpt); + aDocument.MakeTable(0); + aDocument.EnableExecuteLink(false); + aDocument.SetInsertingFromOtherDoc(true); + return ScFormatFilter::Get().ScImportDif(rStream, &aDocument, ScAddress(0, 0, 0), RTL_TEXTENCODING_IBM_850) == ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excform.cxx b/sc/source/filter/excel/excform.cxx new file mode 100644 index 000000000..d217c9622 --- /dev/null +++ b/sc/source/filter/excel/excform.cxx @@ -0,0 +1,1911 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using ::std::vector; + +const sal_uInt16 ExcelToSc::nRowMask = 0x3FFF; + +void ImportExcel::Formula25() +{ + XclAddress aXclPos; + sal_uInt16 nXF = 0, nFormLen; + double fCurVal; + bool bShrFmla; + + aIn >> aXclPos; + + if( GetBiff() == EXC_BIFF2 ) + {// BIFF2 + aIn.Ignore( 3 ); + + fCurVal = aIn.ReadDouble(); + aIn.Ignore( 1 ); + nFormLen = aIn.ReaduInt8(); + bShrFmla = false; + } + else + {// BIFF5 + sal_uInt8 nFlag0; + nXF = aIn.ReaduInt16(); + fCurVal = aIn.ReadDouble(); + nFlag0 = aIn.ReaduInt8(); + aIn.Ignore( 5 ); + + nFormLen = aIn.ReaduInt16(); + + bShrFmla = nFlag0 & 0x08; // shared or not shared + } + + Formula( aXclPos, nXF, nFormLen, fCurVal, bShrFmla ); +} + +void ImportExcel::Formula3() +{ + Formula4(); +} + +void ImportExcel::Formula4() +{ + XclAddress aXclPos; + + aIn >> aXclPos; + sal_uInt16 nXF = aIn.ReaduInt16(); + double fCurVal = aIn.ReadDouble(); + aIn.Ignore( 2 ); + sal_uInt16 nFormLen = aIn.ReaduInt16(); + + Formula( aXclPos, nXF, nFormLen, fCurVal, false ); +} + +void ImportExcel::Formula( + const XclAddress& rXclPos, sal_uInt16 nXF, sal_uInt16 nFormLen, double fCurVal, bool bShrFmla) +{ + if (!nFormLen) + return; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if (!GetAddressConverter().ConvertAddress(aScPos, rXclPos, GetCurrScTab(), true)) + // Conversion failed. + return; + + // Formula will be read next, length in nFormLen + std::unique_ptr pResult; + + pFormConv->Reset( aScPos ); + ScDocumentImport& rDoc = GetDocImport(); + + if (bShrFmla) + { + // This is a shared formula. Get the token array from the shared formula pool. + SCCOL nSharedCol; + SCROW nSharedRow; + if (ExcelToSc::ReadSharedFormulaPosition(maStrm, nSharedCol, nSharedRow)) + { + ScAddress aRefPos(nSharedCol, nSharedRow, GetCurrScTab()); + const ScTokenArray* pSharedCode = pFormConv->GetSharedFormula(aRefPos); + if (pSharedCode) + { + ScFormulaCell* pCell; + pCell = new ScFormulaCell(rD, aScPos, pSharedCode->Clone()); + pCell->GetCode()->WrapReference(aScPos, EXC_MAXCOL8, EXC_MAXROW8); + rDoc.getDoc().EnsureTable(aScPos.Tab()); + rDoc.setFormulaCell(aScPos, pCell); + pCell->SetNeedNumberFormat(false); + if (std::isfinite(fCurVal)) + pCell->SetResultDouble(fCurVal); + + GetXFRangeBuffer().SetXF(aScPos, nXF); + SetLastFormula(aScPos.Col(), aScPos.Row(), fCurVal, nXF, pCell); + } + else + { + // Shared formula not found even though it's clearly a shared formula. + // The cell will be created in the following shared formula + // record. + SetLastFormula(aScPos.Col(), aScPos.Row(), fCurVal, nXF, nullptr); + } + return; + } + } + + ConvErr eErr = pFormConv->Convert( pResult, maStrm, nFormLen, true ); + + ScFormulaCell* pCell = nullptr; + + if (pResult) + { + pCell = new ScFormulaCell(rDoc.getDoc(), aScPos, std::move(pResult)); + pCell->GetCode()->WrapReference(aScPos, EXC_MAXCOL8, EXC_MAXROW8); + rDoc.getDoc().CheckLinkFormulaNeedingCheck( *pCell->GetCode()); + rDoc.getDoc().EnsureTable(aScPos.Tab()); + rDoc.setFormulaCell(aScPos, pCell); + SetLastFormula(aScPos.Col(), aScPos.Row(), fCurVal, nXF, pCell); + } + else + { + pCell = rDoc.getDoc().GetFormulaCell(aScPos); + if (pCell) + pCell->AddRecalcMode( ScRecalcMode::ONLOAD_ONCE ); + } + + if (pCell) + { + pCell->SetNeedNumberFormat(false); + if( eErr != ConvErr::OK ) + ExcelToSc::SetError( *pCell, eErr ); + + if (std::isfinite(fCurVal)) + pCell->SetResultDouble(fCurVal); + } + + GetXFRangeBuffer().SetXF(aScPos, nXF); +} + +ExcelToSc::ExcelToSc( XclImpRoot& rRoot ) : + ExcelConverterBase(rRoot.GetDocImport().getDoc().GetSharedStringPool()), + XclImpRoot( rRoot ), + maFuncProv( rRoot ), + meBiff( rRoot.GetBiff() ) +{ +} + +ExcelToSc::~ExcelToSc() +{ +} + +std::unique_ptr ExcelToSc::GetDummy() +{ + aPool.Store( "Dummy()" ); + aPool >> aStack; + return aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); +} + +// if bAllowArrays is false stream seeks to first byte after +// otherwise it will seek to the first byte after the additional content (eg +// inline arrays) following +ConvErr ExcelToSc::Convert( std::unique_ptr& pResult, XclImpStream& aIn, std::size_t nFormulaLen, bool bAllowArrays, const FORMULA_TYPE eFT ) +{ + RootData& rR = GetOldRoot(); + sal_uInt8 nOp, nLen; + bool bError = false; + bool bArrayFormula = false; + TokenId nBuf0; + const bool bRangeName = eFT == FT_RangeName; + const bool bSharedFormula = eFT == FT_SharedFormula; + const bool bConditional = eFT == FT_CondFormat; + const bool bRNorSF = bRangeName || bSharedFormula || bConditional; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + ExtensionTypeVec aExtensions; + + if( nFormulaLen == 0 ) + { + aPool.Store( "-/-" ); + aPool >> aStack; + pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + return ConvErr::OK; + } + + std::size_t nEndPos = aIn.GetRecPos() + nFormulaLen; + + while( (aIn.GetRecPos() < nEndPos) && !bError ) + { + nOp = aIn.ReaduInt8(); + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) // book page: + { // SDK4 SDK5 + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + case 0x02: // Data Table [325 277] + { + sal_uInt16 nUINT16 = 3; + + if( meBiff != EXC_BIFF2 ) + nUINT16++; + + aIn.Ignore( nUINT16 ); + + bArrayFormula = true; + break; + } + case 0x03: // Addition [312 264] + aStack >> nBuf0; + aPool << aStack << ocAdd << nBuf0; + aPool >> aStack; + break; + case 0x04: // Subtraction [313 264] + // SECOND-TOP minus TOP + aStack >> nBuf0; + aPool << aStack << ocSub << nBuf0; + aPool >> aStack; + break; + case 0x05: // Multiplication [313 264] + aStack >> nBuf0; + aPool << aStack << ocMul << nBuf0; + aPool >> aStack; + break; + case 0x06: // Division [313 264] + // divide TOP by SECOND-TOP + aStack >> nBuf0; + aPool << aStack << ocDiv << nBuf0; + aPool >> aStack; + break; + case 0x07: // Exponentiation [313 265] + // raise SECOND-TOP to power of TOP + aStack >> nBuf0; + aPool << aStack << ocPow << nBuf0; + aPool >> aStack; + break; + case 0x08: // Concatenation [313 265] + // append TOP to SECOND-TOP + aStack >> nBuf0; + aPool << aStack << ocAmpersand << nBuf0; + aPool >> aStack; + break; + case 0x09: // Less Than [313 265] + // SECOND-TOP < TOP + aStack >> nBuf0; + aPool << aStack << ocLess << nBuf0; + aPool >> aStack; + break; + case 0x0A: // Less Than or Equal [313 265] + // SECOND-TOP <= TOP + aStack >> nBuf0; + aPool << aStack << ocLessEqual << nBuf0; + aPool >> aStack; + break; + case 0x0B: // Equal [313 265] + // SECOND-TOP == TOP + aStack >> nBuf0; + aPool << aStack << ocEqual << nBuf0; + aPool >> aStack; + break; + case 0x0C: // Greater Than or Equal [313 265] + // SECOND-TOP >= TOP + aStack >> nBuf0; + aPool << aStack << ocGreaterEqual << nBuf0; + aPool >> aStack; + break; + case 0x0D: // Greater Than [313 265] + // SECOND-TOP > TOP + aStack >> nBuf0; + aPool << aStack << ocGreater << nBuf0; + aPool >> aStack; + break; + case 0x0E: // Not Equal [313 265] + // SECOND-TOP != TOP + aStack >> nBuf0; + aPool << aStack << ocNotEqual << nBuf0; + aPool >> aStack; + break; + case 0x0F: // Intersection [314 265] + aStack >> nBuf0; + aPool << aStack << ocIntersect << nBuf0; + aPool >> aStack; + break; + case 0x10: // Union [314 265] + // ocSep instead of 'ocUnion' + aStack >> nBuf0; + aPool << aStack << ocSep << nBuf0; + // doesn't fit exactly, but is more Excel-like + aPool >> aStack; + break; + case 0x11: // Range [314 265] + aStack >> nBuf0; + aPool << aStack << ocRange << nBuf0; + aPool >> aStack; + break; + case 0x12: // Unary Plus [312 264] + aPool << ocAdd << aStack; + aPool >> aStack; + break; + case 0x13: // Unary Minus [312 264] + aPool << ocNegSub << aStack; + aPool >> aStack; + break; + case 0x14: // Percent Sign [312 264] + aPool << aStack << ocPercentSign; + aPool >> aStack; + break; + case 0x15: // Parenthesis [326 278] + aPool << ocOpen << aStack << ocClose; + aPool >> aStack; + break; + case 0x16: // Missing Argument [314 266] + aPool << ocMissing; + aPool >> aStack; + GetTracer().TraceFormulaMissingArg(); + break; + case 0x17: // String Constant [314 266] + { + nLen = aIn.ReaduInt8(); + OUString aString = aIn.ReadRawByteString( nLen ); + + aStack << aPool.Store( aString ); + break; + } + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData(0), nFactor(0); + + sal_uInt8 nOpt = aIn.ReaduInt8(); + + if( meBiff == EXC_BIFF2 ) + { + nData = aIn.ReaduInt8(); + nFactor = 1; + } + else + { + nData = aIn.ReaduInt16(); + nFactor = 2; + } + + if( nOpt & 0x04 ) + { + // nFactor -> skip bytes or words AttrChoose + ++nData; + aIn.Ignore(static_cast(nData) * nFactor); + } + else if( nOpt & 0x10 ) // AttrSum + DoMulArgs( ocSum, 1 ); + } + break; + case 0x1A: // External Reference [330 ] + switch( meBiff ) + { + case EXC_BIFF2: aIn.Ignore( 7 ); break; + case EXC_BIFF3: + case EXC_BIFF4: aIn.Ignore( 10 ); break; + case EXC_BIFF5: + SAL_INFO( "sc", "-ExcelToSc::Convert(): 0x1A does not exist in Biff5!" ); + [[fallthrough]]; + default: + SAL_INFO( "sc", "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x1B: // End External Reference [330 ] + switch( meBiff ) + { + case EXC_BIFF2: aIn.Ignore( 3 ); break; + case EXC_BIFF3: + case EXC_BIFF4: aIn.Ignore( 4 ); break; + case EXC_BIFF5: + SAL_INFO( "sc", "-ExcelToSc::Convert(): 0x1B does not exist in Biff5!" ); + [[fallthrough]]; + default: + SAL_INFO( "sc", "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x1C: // Error Value [314 266] + { + sal_uInt8 nByte = aIn.ReaduInt8(); + DefTokenId eOc; + switch( nByte ) + { + case EXC_ERR_NULL: + case EXC_ERR_DIV0: + case EXC_ERR_VALUE: + case EXC_ERR_REF: + case EXC_ERR_NAME: + case EXC_ERR_NUM: eOc = ocStop; break; + case EXC_ERR_NA: eOc = ocNotAvail; break; + default: eOc = ocNoName; + } + aPool << eOc; + if( eOc != ocStop ) + aPool << ocOpen << ocClose; + aPool >> aStack; + break; + } + case 0x1D: // Boolean [315 266] + { + sal_uInt8 nByte = aIn.ReaduInt8(); + if( nByte == 0 ) + aPool << ocFalse << ocOpen << ocClose; + else + aPool << ocTrue << ocOpen << ocClose; + aPool >> aStack; + break; + } + case 0x1E: // Integer [315 266] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + aStack << aPool.Store( static_cast(nUINT16) ); + break; + } + case 0x1F: // Number [315 266] + { + double fDouble = aIn.ReadDouble(); + aStack << aPool.Store( fDouble ); + break; + } + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + { + aIn.Ignore( (meBiff == EXC_BIFF2) ? 6 : 7 ); + if( bAllowArrays ) + { + aStack << aPool.StoreMatrix(); + aExtensions.push_back( EXTENSION_ARRAY ); + } + else + { + aPool << ocBad; + aPool >> aStack; + } + break; + } + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + { + sal_uInt16 nXclFunc; + if( meBiff <= EXC_BIFF3 ) + nXclFunc = aIn.ReaduInt8(); + else + nXclFunc = aIn.ReaduInt16(); + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclFunc( nXclFunc ) ) + DoMulArgs( pFuncInfo->meOpCode, pFuncInfo->mnMaxParamCount ); + else + DoMulArgs( ocNoName, 0 ); + } + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + { + sal_uInt16 nXclFunc; + sal_uInt8 nParamCount; + nParamCount = aIn.ReaduInt8(); + nParamCount &= 0x7F; + if( meBiff <= EXC_BIFF3 ) + nXclFunc = aIn.ReaduInt8(); + else + nXclFunc = aIn.ReaduInt16(); + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclFunc( nXclFunc ) ) + DoMulArgs( pFuncInfo->meOpCode, nParamCount ); + else + DoMulArgs( ocNoName, 0 ); + } + break; + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + switch( meBiff ) + { + case EXC_BIFF2: aIn.Ignore( 5 ); break; + case EXC_BIFF3: + case EXC_BIFF4: aIn.Ignore( 8 ); break; + case EXC_BIFF5: aIn.Ignore( 12 ); break; + default: + OSL_FAIL( + "-ExcelToSc::Convert(): A little oblivious?" ); + } + const XclImpName* pName = GetNameManager().GetName( nUINT16 ); + if(pName && !pName->GetScRangeData()) + aStack << aPool.Store( ocMacro, pName->GetXclName() ); + else + aStack << aPool.StoreName(nUINT16, -1); + } + break; + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + sal_uInt8 nByte = aIn.ReaduInt8(); + aSRD.SetAbsCol(static_cast(nByte)); + aSRD.SetAbsRow(nUINT16 & 0x3FFF); + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel( nUINT16, nByte, aSRD, bRangeName ); + + switch ( nOp ) + { + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + // no information which part is deleted, set both + aSRD.SetColDeleted( true ); + aSRD.SetRowDeleted( true ); + } + + aStack << aPool.Store( aSRD ); + break; + } + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + { + sal_uInt16 nRowFirst, nRowLast; + sal_uInt8 nColFirst, nColLast; + ScSingleRefData& rSRef1 = aCRD.Ref1; + ScSingleRefData& rSRef2 = aCRD.Ref2; + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + rSRef1.SetRelTab(0); + rSRef2.SetRelTab(0); + rSRef1.SetFlag3D( bRangeName ); + rSRef2.SetFlag3D( bRangeName ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + switch ( nOp ) + { + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + // no information which part is deleted, set all + rSRef1.SetColDeleted( true ); + rSRef1.SetRowDeleted( true ); + rSRef2.SetColDeleted( true ); + rSRef2.SetRowDeleted( true ); + } + + aStack << aPool.Store( aCRD ); + } + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + aExtensions.push_back( EXTENSION_MEMAREA ); + [[fallthrough]]; + + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + aIn.Ignore( (meBiff == EXC_BIFF2) ? 4 : 6 ); + break; + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + sal_uInt8 nByte = aIn.ReaduInt8(); // >> Attribute, Row >> Col + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel( nUINT16, nByte, aSRD, bRNorSF ); + + aStack << aPool.Store( aSRD ); + break; + } + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + { // Area Reference Within a Shared Formula[ 274] + sal_uInt16 nRowFirst, nRowLast; + sal_uInt8 nColFirst, nColLast; + + aCRD.Ref1.SetRelTab(0); + aCRD.Ref2.SetRelTab(0); + aCRD.Ref1.SetFlag3D( bRangeName ); + aCRD.Ref2.SetFlag3D( bRangeName ); + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8( ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRNorSF ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRNorSF ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + aStack << aPool.Store( aCRD ); + } + break; + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + aIn.Ignore( (meBiff == EXC_BIFF2) ? 1 : 2 ); + break; + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + { + OUString aString = "COMM_EQU_FUNC"; + sal_uInt8 nByte = aIn.ReaduInt8(); + aString += OUString::number( nByte ); + nByte = aIn.ReaduInt8(); + aStack << aPool.Store( aString ); + DoMulArgs( ocPush, nByte + 1 ); + break; + } + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + { + sal_Int16 nINT16 = aIn.ReadInt16(); + aIn.Ignore( 8 ); + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + if( nINT16 >= 0 ) + { + aPool << ocBad; + aPool >> aStack; + } + else + aStack << aPool.StoreName( nUINT16, -1 ); + aIn.Ignore( 12 ); + break; + } + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + { + sal_uInt16 nTabFirst, nTabLast, nRow; + sal_Int16 nExtSheet; + sal_uInt8 nCol; + + nExtSheet = aIn.ReadInt16(); + aIn.Ignore( 8 ); + nTabFirst = aIn.ReaduInt16(); + nTabLast = aIn.ReaduInt16(); + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt8(); + + if( nExtSheet >= 0 ) + { // from external + if( rR.pExtSheetBuff->GetScTabIndex( nExtSheet, nTabLast ) ) + { + nTabFirst = nTabLast; + nExtSheet = 0; // found + } + else + { + aPool << ocBad; + aPool >> aStack; + nExtSheet = 1; // don't create a SingleRef + } + } + + if( nExtSheet <= 0 ) + { // in current Workbook + aSRD.SetAbsTab(nTabFirst); + aSRD.SetFlag3D(true); + + ExcRelToScRel( nRow, nCol, aSRD, bRangeName ); + + switch ( nOp ) + { + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + // no information which part is deleted, set both + aSRD.SetColDeleted( true ); + aSRD.SetRowDeleted( true ); + } + if ( !ValidTab(static_cast(nTabFirst)) ) + aSRD.SetTabDeleted( true ); + + if( nTabLast != nTabFirst ) + { + aCRD.Ref1 = aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(nTabLast); + aCRD.Ref2.SetTabDeleted( !ValidTab(static_cast(nTabLast)) ); + aStack << aPool.Store( aCRD ); + } + else + aStack << aPool.Store( aSRD ); + } + } + + break; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + { + sal_uInt16 nTabFirst, nTabLast, nRowFirst, nRowLast; + sal_Int16 nExtSheet; + sal_uInt8 nColFirst, nColLast; + + nExtSheet = aIn.ReadInt16(); + aIn.Ignore( 8 ); + nTabFirst = aIn.ReaduInt16(); + nTabLast = aIn.ReaduInt16(); + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + if( nExtSheet >= 0 ) + // from external + { + if( rR.pExtSheetBuff->GetScTabIndex( nExtSheet, nTabLast ) ) + { + nTabFirst = nTabLast; + nExtSheet = 0; // found + } + else + { + aPool << ocBad; + aPool >> aStack; + nExtSheet = 1; // don't create a CompleteRef + } + } + + if( nExtSheet <= 0 ) + {// in current Workbook + // first part of range + ScSingleRefData& rR1 = aCRD.Ref1; + ScSingleRefData& rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nTabFirst); + rR2.SetAbsTab(nTabLast); + rR1.SetFlag3D(true); + rR2.SetFlag3D( nTabFirst != nTabLast ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + switch ( nOp ) + { + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + // no information which part is deleted, set all + rR1.SetColDeleted( true ); + rR1.SetRowDeleted( true ); + rR2.SetColDeleted( true ); + rR2.SetRowDeleted( true ); + } + if ( !ValidTab(static_cast(nTabFirst)) ) + rR1.SetTabDeleted( true ); + if ( !ValidTab(static_cast(nTabLast)) ) + rR2.SetTabDeleted( true ); + + aStack << aPool.Store( aCRD ); + }//END in current Workbook + } + break; + default: bError = true; + } + bError |= !aIn.IsValid(); + } + + ConvErr eRet; + + if( bError ) + { + aPool << ocBad; + aPool >> aStack; + pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::Ni; + } + else if( aIn.GetRecPos() != nEndPos ) + { + aPool << ocBad; + aPool >> aStack; + pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::Count; + } + else if( bArrayFormula ) + { + pResult = nullptr; + eRet = ConvErr::OK; + } + else + { + pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::OK; + } + + aIn.Seek( nEndPos ); + + if( eRet == ConvErr::OK ) + ReadExtensions( aExtensions, aIn ); + + return eRet; +} + +// stream seeks to first byte after +ConvErr ExcelToSc::Convert( ScRangeListTabs& rRangeList, XclImpStream& aIn, std::size_t nFormulaLen, + SCTAB nTab, const FORMULA_TYPE eFT ) +{ + RootData& rR = GetOldRoot(); + sal_uInt8 nOp, nLen; + bool bError = false; + const bool bRangeName = eFT == FT_RangeName; + const bool bSharedFormula = eFT == FT_SharedFormula; + const bool bRNorSF = bRangeName || bSharedFormula; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + aCRD.Ref1.SetAbsTab(aEingPos.Tab()); + aCRD.Ref2.SetAbsTab(aEingPos.Tab()); + + if( nFormulaLen == 0 ) + return ConvErr::OK; + + std::size_t nEndPos = aIn.GetRecPos() + nFormulaLen; + + while( (aIn.GetRecPos() < nEndPos) && !bError ) + { + nOp = aIn.ReaduInt8(); + std::size_t nIgnore = 0; + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) // book page: + { // SDK4 SDK5 + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + nIgnore = (meBiff == EXC_BIFF2) ? 3 : 4; + break; + case 0x02: // Data Table [325 277] + nIgnore = (meBiff == EXC_BIFF2) ? 3 : 4; + break; + case 0x03: // Addition [312 264] + case 0x04: // Subtraction [313 264] + case 0x05: // Multiplication [313 264] + case 0x06: // Division [313 264] + case 0x07: // Exponetiation [313 265] + case 0x08: // Concatenation [313 265] + case 0x09: // Less Than [313 265] + case 0x0A: // Less Than or Equal [313 265] + case 0x0B: // Equal [313 265] + case 0x0C: // Greater Than or Equal [313 265] + case 0x0D: // Greater Than [313 265] + case 0x0E: // Not Equal [313 265] + case 0x0F: // Intersection [314 265] + case 0x10: // Union [314 265] + case 0x11: // Range [314 265] + case 0x12: // Unary Plus [312 264] + case 0x13: // Unary Minus [312 264] + case 0x14: // Percent Sign [312 264] + case 0x15: // Parenthesis [326 278] + case 0x16: // Missing Argument [314 266] + break; + case 0x17: // String Constant [314 266] + nLen = aIn.ReaduInt8(); + nIgnore = nLen; + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData(0), nFactor(0); + + sal_uInt8 nOpt = aIn.ReaduInt8(); + + if( meBiff == EXC_BIFF2 ) + { + nData = aIn.ReaduInt8(); + nFactor = 1; + } + else + { + nData = aIn.ReaduInt16(); + nFactor = 2; + } + + if( nOpt & 0x04 ) + { + // nFactor -> skip bytes or words AttrChoose + ++nData; + aIn.Ignore(static_cast(nData) * nFactor); + } + } + break; + case 0x1A: // External Reference [330 ] + switch( meBiff ) + { + case EXC_BIFF2: nIgnore = 7; break; + case EXC_BIFF3: + case EXC_BIFF4: nIgnore = 10; break; + case EXC_BIFF5: SAL_INFO( "sc", "-ExcelToSc::Convert(): 0x1A does not exist in Biff5!" ); + [[fallthrough]]; + default: SAL_INFO( "sc", "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x1B: // End External Reference [330 ] + switch( meBiff ) + { + case EXC_BIFF2: nIgnore = 3; break; + case EXC_BIFF3: + case EXC_BIFF4: nIgnore = 4; break; + case EXC_BIFF5: SAL_INFO( "sc", "-ExcelToSc::Convert(): 0x1B does not exist in Biff5!" ); + [[fallthrough]]; + default: SAL_INFO( "sc", "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x1C: // Error Value [314 266] + case 0x1D: // Boolean [315 266] + nIgnore = 1; + break; + case 0x1E: // Integer [315 266] + nIgnore = 2; + break; + case 0x1F: // Number [315 266] + nIgnore = 8; + break; + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + nIgnore = (meBiff == EXC_BIFF2) ? 6 : 7; + break; + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + nIgnore = (meBiff <= EXC_BIFF3) ? 1 : 2; + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + nIgnore = (meBiff <= EXC_BIFF3) ? 2 : 3; + break; + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + switch( meBiff ) + { + case EXC_BIFF2: nIgnore = 7; break; + case EXC_BIFF3: + case EXC_BIFF4: nIgnore = 10; break; + case EXC_BIFF5: nIgnore = 14; break; + default: OSL_FAIL( "-ExcelToSc::Convert(): A little oblivious?" ); + } + break; + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + sal_uInt8 nByte = aIn.ReaduInt8(); + aSRD.SetAbsCol(static_cast(nByte)); + aSRD.SetAbsRow(nUINT16 & 0x3FFF); + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel( nUINT16, nByte, aSRD, bRangeName ); + + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + break; + } + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + { + sal_uInt16 nRowFirst, nRowLast; + sal_uInt8 nColFirst, nColLast; + ScSingleRefData &rSRef1 = aCRD.Ref1; + ScSingleRefData &rSRef2 = aCRD.Ref2; + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + rSRef1.SetRelTab(0); + rSRef2.SetRelTab(0); + rSRef1.SetFlag3D( bRangeName ); + rSRef2.SetFlag3D( bRangeName ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + nIgnore = (meBiff == EXC_BIFF2) ? 4 : 6; + break; + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + nIgnore = 3; + break; + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + nIgnore = 6; + break; + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + sal_uInt8 nByte = aIn.ReaduInt8(); // >> Attribute, Row >> Col + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel( nUINT16, nByte, aSRD, bRNorSF ); + + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + break; + } + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + { // Area Reference Within a Shared Formula[ 274] + sal_uInt16 nRowFirst, nRowLast; + sal_uInt8 nColFirst, nColLast; + + aCRD.Ref1.SetRelTab(0); + aCRD.Ref2.SetRelTab(0); + aCRD.Ref1.SetFlag3D( bRangeName ); + aCRD.Ref2.SetFlag3D( bRangeName ); + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRNorSF ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRNorSF ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + nIgnore = (meBiff == EXC_BIFF2) ? 1 : 2; + break; + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + nIgnore = 2; + break; + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + nIgnore = 24; + break; + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + { + sal_uInt16 nTabFirst, nTabLast, nRow; + sal_Int16 nExtSheet; + sal_uInt8 nCol; + + nExtSheet = aIn.ReadInt16(); + aIn.Ignore( 8 ); + nTabFirst = aIn.ReaduInt16(); + nTabLast = aIn.ReaduInt16(); + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt8(); + + if( nExtSheet >= 0 ) + // from external + { + if( rR.pExtSheetBuff->GetScTabIndex( nExtSheet, nTabLast ) ) + { + nTabFirst = nTabLast; + nExtSheet = 0; // found + } + else + { + aPool << ocBad; + aPool >> aStack; + nExtSheet = 1; // don't create a SingleRef + } + } + + if( nExtSheet <= 0 ) + {// in current Workbook + bool b3D = ( static_cast(nTabFirst) != aEingPos.Tab() ) || bRangeName; + aSRD.SetAbsTab(nTabFirst); + aSRD.SetFlag3D( b3D ); + + ExcRelToScRel( nRow, nCol, aSRD, bRangeName ); + + if( nTabLast != nTabFirst ) + { + aCRD.Ref1 = aSRD; + aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(static_cast(nTabLast)); + b3D = ( static_cast(nTabLast) != aEingPos.Tab() ); + aCRD.Ref2.SetFlag3D( b3D ); + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + else + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + } + + break; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + { + sal_uInt16 nTabFirst, nTabLast, nRowFirst, nRowLast; + sal_Int16 nExtSheet; + sal_uInt8 nColFirst, nColLast; + + nExtSheet = aIn.ReadInt16(); + aIn.Ignore( 8 ); + nTabFirst = aIn.ReaduInt16(); + nTabLast = aIn.ReaduInt16(); + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + + if( nExtSheet >= 0 ) + // from external + { + if( rR.pExtSheetBuff->GetScTabIndex( nExtSheet, nTabLast ) ) + { + nTabFirst = nTabLast; + nExtSheet = 0; // found + } + else + { + aPool << ocBad; + aPool >> aStack; + nExtSheet = 1; // don't create a CompleteRef + } + } + + if( nExtSheet <= 0 ) + {// in current Workbook + // first part of range + ScSingleRefData &rR1 = aCRD.Ref1; + ScSingleRefData &rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nTabFirst); + rR2.SetAbsTab(nTabLast); + rR1.SetFlag3D( ( static_cast(nTabFirst) != aEingPos.Tab() ) || bRangeName ); + rR2.SetFlag3D( ( static_cast(nTabLast) != aEingPos.Tab() ) || bRangeName ); + + ExcRelToScRel( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + }//END in current Workbook + } + break; + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + nIgnore = 17; + break; + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + nIgnore = 20; + break; + default: bError = true; + } + bError |= !aIn.IsValid(); + + aIn.Ignore( nIgnore ); + } + + ConvErr eRet; + + if( bError ) + eRet = ConvErr::Ni; + else if( aIn.GetRecPos() != nEndPos ) + eRet = ConvErr::Count; + else + eRet = ConvErr::OK; + + aIn.Seek( nEndPos ); + return eRet; +} + +void ExcelToSc::ConvertExternName( std::unique_ptr& /*rpArray*/, XclImpStream& /*rStrm*/, std::size_t /*nFormulaLen*/, + const OUString& /*rUrl*/, const vector& /*rTabNames*/ ) +{ +} + +void ExcelToSc::GetAbsRefs( ScRangeList& rRangeList, XclImpStream& rStrm, std::size_t nLen ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF5 ); + if( GetBiff() != EXC_BIFF5 ) + return; + + sal_uInt8 nOp; + sal_uInt16 nRow1, nRow2; + sal_uInt8 nCol1, nCol2; + SCTAB nTab1, nTab2; + sal_uInt16 nTabFirst, nTabLast; + sal_Int16 nRefIdx; + + std::size_t nSeek; + std::size_t nEndPos = rStrm.GetRecPos() + nLen; + + while( rStrm.IsValid() && (rStrm.GetRecPos() < nEndPos) ) + { + nOp = rStrm.ReaduInt8(); + nSeek = 0; + + switch( nOp ) + { + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + nRow1 = rStrm.ReaduInt16(); + nCol1 = rStrm.ReaduInt8(); + + nRow2 = nRow1; + nCol2 = nCol1; + nTab1 = nTab2 = GetCurrScTab(); + goto _common; + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + // Area Reference Within a Shared Formula[ 274] + nRow1 = rStrm.ReaduInt16(); + nRow2 = rStrm.ReaduInt16(); + nCol1 = rStrm.ReaduInt8(); + nCol2 = rStrm.ReaduInt8(); + + nTab1 = nTab2 = GetCurrScTab(); + goto _common; + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + nRefIdx = rStrm.ReadInt16(); + rStrm.Ignore( 8 ); + nTabFirst = rStrm.ReaduInt16(); + nTabLast = rStrm.ReaduInt16(); + nRow1 = rStrm.ReaduInt16(); + nCol1 = rStrm.ReaduInt8(); + + nRow2 = nRow1; + nCol2 = nCol1; + + goto _3d_common; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + nRefIdx = rStrm.ReadInt16(); + rStrm.Ignore( 8 ); + nTabFirst = rStrm.ReaduInt16(); + nTabLast = rStrm.ReaduInt16(); + nRow1 = rStrm.ReaduInt16(); + nRow2 = rStrm.ReaduInt16(); + nCol1 = rStrm.ReaduInt8(); + nCol2 = rStrm.ReaduInt8(); + + _3d_common: + nTab1 = static_cast< SCTAB >( nTabFirst ); + nTab2 = static_cast< SCTAB >( nTabLast ); + + // skip references to deleted sheets + if( (nRefIdx >= 0) || !ValidTab( nTab1 ) || (nTab1 != nTab2) ) + break; + + goto _common; + _common: + // do not check abs/rel flags, linked controls have set them! + { + ScRange aScRange; + nRow1 &= 0x3FFF; + nRow2 &= 0x3FFF; + if( GetAddressConverter().ConvertRange( aScRange, XclRange( nCol1, nRow1, nCol2, nRow2 ), nTab1, nTab2, true ) ) + rRangeList.push_back( aScRange ); + } + break; + + case 0x03: // Addition [312 264] + case 0x04: // Subtraction [313 264] + case 0x05: // Multiplication [313 264] + case 0x06: // Division [313 264] + case 0x07: // Exponetiation [313 265] + case 0x08: // Concatenation [313 265] + case 0x09: // Less Than [313 265] + case 0x0A: // Less Than or Equal [313 265] + case 0x0B: // Equal [313 265] + case 0x0C: // Greater Than or Equal [313 265] + case 0x0D: // Greater Than [313 265] + case 0x0E: // Not Equal [313 265] + case 0x0F: // Intersection [314 265] + case 0x10: // Union [314 265] + case 0x11: // Range [314 265] + case 0x12: // Unary Plus [312 264] + case 0x13: // Unary Minus [312 264] + case 0x14: // Percent Sign [312 264] + case 0x15: // Parenthesis [326 278] + case 0x16: // Missing Argument [314 266] + break; + case 0x1C: // Error Value [314 266] + case 0x1D: // Boolean [315 266] + nSeek = 1; + break; + case 0x1E: // Integer [315 266] + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + nSeek = 2; + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + nSeek = 3; + break; + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + case 0x02: // Data Table [325 277] + nSeek = 4; + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + nSeek = 6; + break; + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + nSeek = 7; + break; + case 0x1F: // Number [315 266] + nSeek = 8; + break; + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + nSeek = 14; + break; + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + nSeek = 17; + break; + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + nSeek = 20; + break; + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + nSeek = 24; + break; + case 0x17: // String Constant [314 266] + nSeek = rStrm.ReaduInt8(); + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt8 nOpt; + sal_uInt16 nData; + nOpt = rStrm.ReaduInt8(); + nData = rStrm.ReaduInt16(); + if( nOpt & 0x04 ) + nSeek = nData * 2 + 2; + } + break; + } + + rStrm.Ignore( nSeek ); + } + rStrm.Seek( nEndPos ); +} + +void ExcelToSc::DoMulArgs( DefTokenId eId, sal_uInt8 nCnt ) +{ + TokenId eParam[ 256 ]; + sal_Int32 nPass; + + if( eId == ocCeil || eId == ocFloor ) + { + aStack << aPool.Store( 1.0 ); // default, because not present in Excel + nCnt++; + } + + for( nPass = 0; aStack.HasMoreTokens() && (nPass < nCnt); nPass++ ) + aStack >> eParam[ nPass ]; + // #i70925# reduce parameter count, if no more tokens available on token stack + if( nPass < nCnt ) + nCnt = static_cast< sal_uInt8 >( nPass ); + + if( nCnt > 0 && eId == ocExternal ) + { + TokenId n = eParam[ nCnt - 1 ]; +//##### ADJUST STUPIDITY FOR BASIC-FUNCS! + if( const OUString* pExt = aPool.GetExternal( n ) ) + { + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclMacroName( *pExt ) ) + aPool << pFuncInfo->meOpCode; + else + aPool << n; + nCnt--; + } + else + aPool << eId; + } + else + aPool << eId; + + aPool << ocOpen; + + if( nCnt > 0 ) + { + // attention: 0 = last parameter, nCnt-1 = first parameter + sal_Int16 nSkipEnd = -1; // skip all parameters <= nSkipEnd + + sal_Int16 nLast = nCnt - 1; + + // functions for which parameters have to be skipped + if( eId == ocPercentrank && nCnt == 3 ) + nSkipEnd = 0; // skip last parameter if necessary + + // Joost special cases + else if( eId == ocIf ) + { + sal_uInt16 nNullParam = 0; + for( nPass = 0 ; nPass < nCnt ; nPass++ ) + { + if( aPool.IsSingleOp( eParam[ nPass ], ocMissing ) ) + { + if( !nNullParam ) + nNullParam = static_cast(aPool.Store( 0.0 )); + eParam[ nPass ] = nNullParam; + } + } + } + + // [Parameter{;Parameter}] + if( nLast > nSkipEnd ) + { + // nSkipEnd is either 0 or -1 => nLast >= 0 + aPool << eParam[ nLast ]; + for( nPass = nLast - 1 ; nPass > nSkipEnd ; nPass-- ) + { + // nPass > nSkipEnd => nPass >= 0 + aPool << ocSep << eParam[nPass]; + } + } + } + aPool << ocClose; + + aPool >> aStack; +} + +void ExcelToSc::ExcRelToScRel( sal_uInt16 nRow, sal_uInt8 nCol, ScSingleRefData &rSRD, const bool bName ) +{ + if( bName ) + { + // C O L + if( nRow & 0x4000 ) + rSRD.SetRelCol(nCol); + else + rSRD.SetAbsCol(nCol); + + // R O W + if( nRow & 0x8000 ) + {// rel Row + if( nRow & 0x2000 ) // Bit 13 set? + // Row negative + rSRD.SetRelRow(nRow | 0xC000); + else + // Row positive + rSRD.SetRelRow(nRow & nRowMask); + } + else + {// abs Row + rSRD.SetAbsRow(nRow & nRowMask); + } + + // T A B + // abs needed if rel in shared formula for ScCompiler UpdateNameReference + if ( rSRD.IsTabRel() && !rSRD.IsFlag3D() ) + rSRD.SetAbsTab(GetCurrScTab()); + } + else + { + bool bColRel = (nRow & 0x4000) > 0; + bool bRowRel = (nRow & 0x8000) > 0; + + if (bColRel) + rSRD.SetRelCol(nCol - aEingPos.Col()); + else + rSRD.SetAbsCol(nCol); + + rSRD.SetAbsRow(nRow & nRowMask); + if (bRowRel) + rSRD.SetRelRow(rSRD.Row() - aEingPos.Row()); + + // T A B + // #i10184# abs needed if rel in shared formula for ScCompiler UpdateNameReference + if ( rSRD.IsTabRel() && !rSRD.IsFlag3D() ) + rSRD.SetAbsTab(GetCurrScTab() + rSRD.Tab()); + } +} + +std::unique_ptr ExcelToSc::GetBoolErr( XclBoolError eType ) +{ + FormulaError nError; + aPool.Reset(); + aStack.Reset(); + + DefTokenId eOc; + + switch( eType ) + { + case xlErrNull: eOc = ocStop; nError = FormulaError::NoCode; break; + case xlErrDiv0: eOc = ocStop; nError = FormulaError::DivisionByZero; break; + case xlErrValue: eOc = ocStop; nError = FormulaError::NoValue; break; + case xlErrRef: eOc = ocStop; nError = FormulaError::NoRef; break; + case xlErrName: eOc = ocStop; nError = FormulaError::NoName; break; + case xlErrNum: eOc = ocStop; nError = FormulaError::IllegalFPOperation; break; + case xlErrNA: eOc = ocNotAvail; nError = FormulaError::NotAvailable; break; + case xlErrTrue: eOc = ocTrue; nError = FormulaError::NONE; break; + case xlErrFalse: eOc = ocFalse; nError = FormulaError::NONE; break; + case xlErrUnknown: eOc = ocStop; nError = FormulaError::UnknownState; break; + default: + OSL_FAIL( "ExcelToSc::GetBoolErr - wrong enum!" ); + eOc = ocNoName; + nError = FormulaError::UnknownState; + } + + aPool << eOc; + if( eOc != ocStop ) + aPool << ocOpen << ocClose; + + aPool >> aStack; + + std::unique_ptr pResult = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + if( nError != FormulaError::NONE ) + pResult->SetCodeError( nError ); + + pResult->SetExclusiveRecalcModeNormal(); + + return pResult; +} + +bool ExcelToSc::ReadSharedFormulaPosition( XclImpStream& rStrm, SCCOL& rCol, SCROW& rRow ) +{ + rStrm.PushPosition(); + + sal_uInt8 nOp; + nOp = rStrm.ReaduInt8(); + + if (nOp != 0x01) // must be PtgExp token. + { + rStrm.PopPosition(); + return false; + } + + sal_uInt16 nRow, nCol; + nRow = rStrm.ReaduInt16(); + nCol = rStrm.ReaduInt16(); + rStrm.PopPosition(); + rCol = nCol; + rRow = nRow; + return true; +} + +const ScTokenArray* ExcelToSc::GetSharedFormula( const ScAddress& rRefPos ) const +{ + return GetOldRoot().pShrfmlaBuff->Find(rRefPos); +} + +void ExcelToSc::SetError( ScFormulaCell &rCell, const ConvErr eErr ) +{ + FormulaError nInd; + + switch( eErr ) + { + case ConvErr::Ni: nInd = FormulaError::UnknownToken; break; + case ConvErr::Count: nInd = FormulaError::CodeOverflow; break; + default: nInd = FormulaError::NoCode; // I had no better idea + } + + rCell.SetErrCode( nInd ); +} + +void ExcelToSc::SetComplCol( ScComplexRefData &rCRD ) +{ + ScSingleRefData &rSRD = rCRD.Ref2; + ScDocument& rDoc = GetDocImport().getDoc(); + if( rSRD.IsColRel() ) + rSRD.SetRelCol(rDoc.MaxCol() - aEingPos.Col()); + else + rSRD.SetAbsCol(rDoc.MaxCol()); +} + +void ExcelToSc::SetComplRow( ScComplexRefData &rCRD ) +{ + ScSingleRefData &rSRD = rCRD.Ref2; + ScDocument& rDoc = GetDocImport().getDoc(); + if( rSRD.IsRowRel() ) + rSRD.SetRelRow(rDoc.MaxRow() - aEingPos.Row()); + else + rSRD.SetAbsRow(rDoc.MaxRow()); +} + +void ExcelToSc::ReadExtensionArray( unsigned int n, XclImpStream& aIn ) +{ + sal_uInt8 nByte = aIn.ReaduInt8(); + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + + SCSIZE nC, nCols; + SCSIZE nR, nRows; + if( GetBiff() == EXC_BIFF8 ) + { + nCols = nByte + 1; + nRows = nUINT16 + 1; + } + else + { + nCols = nByte ? nByte : 256; + nRows = nUINT16; + } + + ScMatrix* pMatrix = aPool.GetMatrix( n ); + + if( nullptr != pMatrix ) + { + pMatrix->Resize(nCols, nRows); + pMatrix->GetDimensions( nC, nR); + if( nC != nCols || nR != nRows ) + { + OSL_FAIL( "ExcelToSc::ReadExtensionArray - matrix size mismatch" ); + pMatrix = nullptr; + } + } + else + { + OSL_FAIL( "ExcelToSc::ReadExtensionArray - missing matrix" ); + } + + //assuming worst case scenario of unknown types + const size_t nMinRecordSize = 1; + const size_t nMaxRows = aIn.GetRecLeft() / (nMinRecordSize * nCols); + if (nRows > nMaxRows) + { + SAL_WARN("sc", "Parsing error: " << nMaxRows << + " max possible rows, but " << nRows << " claimed, truncating"); + nRows = nMaxRows; + } + + svl::SharedStringPool& rPool = GetDoc().GetSharedStringPool(); + for( nR = 0 ; nR < nRows; nR++ ) + { + for( nC = 0 ; nC < nCols; nC++ ) + { + nByte = aIn.ReaduInt8(); + switch( nByte ) + { + case EXC_CACHEDVAL_EMPTY: + aIn.Ignore( 8 ); + if( nullptr != pMatrix ) + { + pMatrix->PutEmpty( nC, nR ); + } + break; + + case EXC_CACHEDVAL_DOUBLE: + { + double fDouble = aIn.ReadDouble(); + if( nullptr != pMatrix ) + { + pMatrix->PutDouble( fDouble, nC, nR ); + } + break; + } + case EXC_CACHEDVAL_STRING: + { + OUString aString; + if( GetBiff() == EXC_BIFF8 ) + { + nUINT16 = aIn.ReaduInt16(); + aString = aIn.ReadUniString( nUINT16 ); + } + else + { + nByte = aIn.ReaduInt8(); + aString = aIn.ReadRawByteString( nByte ); + } + if( nullptr != pMatrix ) + { + pMatrix->PutString(rPool.intern(aString), nC, nR); + } + break; + } + case EXC_CACHEDVAL_BOOL: + nByte = aIn.ReaduInt8(); + aIn.Ignore( 7 ); + if( nullptr != pMatrix ) + { + pMatrix->PutBoolean( nByte != 0, nC, nR ); + } + break; + + case EXC_CACHEDVAL_ERROR: + nByte = aIn.ReaduInt8(); + aIn.Ignore( 7 ); + if( nullptr != pMatrix ) + { + pMatrix->PutError( XclTools::GetScErrorCode( nByte ), nC, nR ); + } + break; + } + } + } +} + +void ExcelToSc::ReadExtensionNlr( XclImpStream& aIn ) +{ + sal_uInt32 nFlags; + nFlags = aIn.ReaduInt32(); + + sal_uInt32 nCount = nFlags & EXC_TOK_NLR_ADDMASK; + aIn.Ignore( nCount * 4 ); // Drop the cell positions +} + +void ExcelToSc::ReadExtensionMemArea( XclImpStream& aIn ) +{ + sal_uInt16 nCount = aIn.ReaduInt16(); + + aIn.Ignore( static_cast(nCount) * ((GetBiff() == EXC_BIFF8) ? 8 : 6) ); // drop the ranges +} + +void ExcelToSc::ReadExtensions( const ExtensionTypeVec& rExtensions, + XclImpStream& aIn ) +{ + unsigned int nArray = 0; + + for(int eType : rExtensions) + { + switch( eType ) + { + case EXTENSION_ARRAY: + ReadExtensionArray( nArray++, aIn ); + break; + + case EXTENSION_NLR: + ReadExtensionNlr( aIn ); + break; + + case EXTENSION_MEMAREA: + ReadExtensionMemArea( aIn ); + break; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excform8.cxx b/sc/source/filter/excel/excform8.cxx new file mode 100644 index 000000000..62e184204 --- /dev/null +++ b/sc/source/filter/excel/excform8.cxx @@ -0,0 +1,1671 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +using ::std::vector; + +namespace { + +/** + * Extract a file path from OLE link path. An OLE link path is expected to + * be in the following format: + * + * Excel.Sheet.8 \3 [file path] + */ +bool extractFilePath(const OUString& rUrl, OUString& rPath) +{ + const char* prefix = "Excel.Sheet.8\3"; + size_t nPrefixLen = ::std::strlen(prefix); + + sal_Int32 n = rUrl.getLength(); + if (n <= static_cast(nPrefixLen)) + // needs to have the specified prefix. + return false; + + OUStringBuffer aBuf; + const sal_Unicode* p = rUrl.getStr(); + for (size_t i = 0; i < o3tl::make_unsigned(n); ++i, ++p) + { + if (i < nPrefixLen) + { + sal_Unicode pc = static_cast(*prefix++); + if (pc != *p) + return false; + + continue; + } + aBuf.append(*p); + } + + rPath = aBuf.makeStringAndClear(); + return true; +} + +} + +ExcelToSc8::ExternalTabInfo::ExternalTabInfo() : + mnFileId(0), mbExternal(false) +{ +} + +ExcelToSc8::ExcelToSc8( XclImpRoot& rRoot ) : + ExcelToSc( rRoot ), + rLinkMan( rRoot.GetLinkManager() ) +{ +} + +ExcelToSc8::~ExcelToSc8() +{ +} + +bool ExcelToSc8::GetExternalFileIdFromXti( sal_uInt16 nIxti, sal_uInt16& rFileId ) const +{ + const OUString* pFileUrl = rLinkMan.GetSupbookUrl(nIxti); + if (!pFileUrl || pFileUrl->isEmpty() || !GetDocShell()) + return false; + + OUString aFileUrl = ScGlobal::GetAbsDocName(*pFileUrl, GetDocShell()); + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + rFileId = pRefMgr->getExternalFileId(aFileUrl); + + return true; +} + +bool ExcelToSc8::Read3DTabReference( sal_uInt16 nIxti, SCTAB& rFirstTab, SCTAB& rLastTab, ExternalTabInfo& rExtInfo ) +{ + rFirstTab = rLastTab = 0; + rExtInfo.mbExternal = !rLinkMan.IsSelfRef(nIxti); + bool bSuccess = rLinkMan.GetScTabRange(rFirstTab, rLastTab, nIxti); + if (!bSuccess) + return false; + + if (!rExtInfo.mbExternal) + // This is internal reference. Stop here. + return true; + + rExtInfo.maTabName = rLinkMan.GetSupbookTabName(nIxti, rFirstTab); + return GetExternalFileIdFromXti(nIxti, rExtInfo.mnFileId); +} + +bool ExcelToSc8::HandleOleLink(sal_uInt16 nXtiIndex, const XclImpExtName& rExtName, ExternalTabInfo& rExtInfo) +{ + const OUString* pUrl = rLinkMan.GetSupbookUrl(nXtiIndex); + if (!pUrl) + return false; + + OUString aPath; + if (!extractFilePath(*pUrl, aPath)) + // file path extraction failed. + return false; + + OUString aFileUrl = ScGlobal::GetAbsDocName(aPath, GetDocShell()); + return rExtName.CreateOleData(GetDoc(), aFileUrl, rExtInfo.mnFileId, rExtInfo.maTabName, rExtInfo.maRange); +} + +// if bAllowArrays is false stream seeks to first byte after +// otherwise it will seek to the first byte past additional content after +ConvErr ExcelToSc8::Convert( std::unique_ptr& rpTokArray, XclImpStream& aIn, std::size_t nFormulaLen, bool bAllowArrays, const FORMULA_TYPE eFT ) +{ + bool bError = false; + bool bArrayFormula = false; + TokenId nBuf0; + const bool bCondFormat = eFT == FT_CondFormat; + const bool bRangeName = eFT == FT_RangeName; + const bool bRangeNameOrCond = bRangeName || bCondFormat; + const bool bSharedFormula = eFT == FT_SharedFormula; + const bool bRNorSF = bRangeNameOrCond || bSharedFormula; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + ExtensionTypeVec aExtensions; + + if( nFormulaLen == 0 ) + { + aPool.Store( "-/-" ); + aPool >> aStack; + rpTokArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + return ConvErr::OK; + } + + std::size_t nEndPos = aIn.GetRecPos() + nFormulaLen; + + while( (aIn.GetRecPos() < nEndPos) && !bError ) + { + sal_uInt8 nOp = aIn.ReaduInt8(); + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) // book page: + { // SDK4 SDK5 + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + case 0x02: // Data Table [325 277] + aIn.Ignore( 4 ); + + bArrayFormula = true; + break; + case 0x03: // Addition [312 264] + aStack >> nBuf0; + aPool << aStack << ocAdd << nBuf0; + aPool >> aStack; + break; + case 0x04: // Subtraction [313 264] + // SECOND-TOP minus TOP + aStack >> nBuf0; + aPool << aStack << ocSub << nBuf0; + aPool >> aStack; + break; + case 0x05: // Multiplication [313 264] + aStack >> nBuf0; + aPool << aStack << ocMul << nBuf0; + aPool >> aStack; + break; + case 0x06: // Division [313 264] + // divide TOP by SECOND-TOP + aStack >> nBuf0; + aPool << aStack << ocDiv << nBuf0; + aPool >> aStack; + break; + case 0x07: // Exponentiation [313 265] + // raise SECOND-TOP to power of TOP + aStack >> nBuf0; + aPool << aStack << ocPow << nBuf0; + aPool >> aStack; + break; + case 0x08: // Concatenation [313 265] + // append TOP to SECOND-TOP + aStack >> nBuf0; + aPool << aStack << ocAmpersand << nBuf0; + aPool >> aStack; + break; + case 0x09: // Less Than [313 265] + // SECOND-TOP < TOP + aStack >> nBuf0; + aPool << aStack << ocLess << nBuf0; + aPool >> aStack; + break; + case 0x0A: // Less Than or Equal [313 265] + // SECOND-TOP <= TOP + aStack >> nBuf0; + aPool << aStack << ocLessEqual << nBuf0; + aPool >> aStack; + break; + case 0x0B: // Equal [313 265] + // SECOND-TOP == TOP + aStack >> nBuf0; + aPool << aStack << ocEqual << nBuf0; + aPool >> aStack; + break; + case 0x0C: // Greater Than or Equal [313 265] + // SECOND-TOP >= TOP + aStack >> nBuf0; + aPool << aStack << ocGreaterEqual << nBuf0; + aPool >> aStack; + break; + case 0x0D: // Greater Than [313 265] + // SECOND-TOP > TOP + aStack >> nBuf0; + aPool << aStack << ocGreater << nBuf0; + aPool >> aStack; + break; + case 0x0E: // Not Equal [313 265] + // SECOND-TOP != TOP + aStack >> nBuf0; + aPool << aStack << ocNotEqual << nBuf0; + aPool >> aStack; + break; + case 0x0F: // Intersection [314 265] + aStack >> nBuf0; + aPool << aStack << ocIntersect << nBuf0; + aPool >> aStack; + break; + case 0x10: // Union [314 265] + // ocSep instead of 'ocUnion' + aStack >> nBuf0; + aPool << aStack << ocSep << nBuf0; + // doesn't fit exactly, but is more Excel-like + aPool >> aStack; + break; + case 0x11: // Range [314 265] + aStack >> nBuf0; + aPool << aStack << ocRange << nBuf0; + aPool >> aStack; + break; + case 0x12: // Unary Plus [312 264] + aPool << ocAdd << aStack; + aPool >> aStack; + break; + case 0x13: // Unary Minus [312 264] + aPool << ocNegSub << aStack; + aPool >> aStack; + break; + case 0x14: // Percent Sign [312 264] + aPool << aStack << ocPercentSign; + aPool >> aStack; + break; + case 0x15: // Parenthesis [326 278] + aPool << ocOpen << aStack << ocClose; + aPool >> aStack; + break; + case 0x16: // Missing Argument [314 266] + aPool << ocMissing; + aPool >> aStack; + GetTracer().TraceFormulaMissingArg(); + break; + case 0x17: // String Constant [314 266] + { + sal_uInt8 nLen = aIn.ReaduInt8(); // Why? + OUString aString = aIn.ReadUniString( nLen ); // reads Grbit even if nLen==0 + + aStack << aPool.Store( aString ); + break; + } + case 0x18: // natural language formula + { + sal_uInt8 nEptg; + sal_uInt16 nCol, nRow; + nEptg = aIn.ReaduInt8(); + switch( nEptg ) + { // name size ext type + case 0x01: // Lel 4 - err + aIn.Ignore( 4 ); + aPool << ocBad; + aPool >> aStack; + break; + case 0x02: // Rw 4 - ref + case 0x03: // Col 4 - ref + case 0x06: // RwV 4 - val + case 0x07: // ColV 4 - val + { + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + ScAddress aAddr(static_cast(nCol & 0xFF), static_cast(nRow), aEingPos.Tab()); + aSRD.InitAddress(aAddr); + + if( nEptg == 0x02 || nEptg == 0x06 ) + aSRD.SetRowRel(true); + else + aSRD.SetColRel(true); + + aSRD.SetAddress(GetDocImport().getDoc().GetSheetLimits(), aAddr, aEingPos); + + aStack << aPool.StoreNlf( aSRD ); + + break; + } + case 0x0A: // Radical 13 - ref + { + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + aIn.Ignore( 9 ); + ScAddress aAddr(static_cast(nCol & 0xFF), static_cast(nRow), aEingPos.Tab()); + aSRD.InitAddress(aAddr); + aSRD.SetColRel(true); + aSRD.SetAddress(GetDocImport().getDoc().GetSheetLimits(), aAddr, aEingPos); + + aStack << aPool.StoreNlf( aSRD ); + + break; + } + case 0x0B: // RadicalS 13 x ref + aIn.Ignore( 13 ); + aExtensions.push_back( EXTENSION_NLR ); + aPool << ocBad; + aPool >> aStack; + break; + case 0x0C: // RwS 4 x ref + case 0x0D: // ColS 4 x ref + case 0x0E: // RwSV 4 x val + case 0x0F: // ColSV 4 x val + aIn.Ignore( 4 ); + aExtensions.push_back( EXTENSION_NLR ); + aPool << ocBad; + aPool >> aStack; + break; + case 0x10: // RadicalLel 4 - err + case 0x1D: // SxName 4 - val + aIn.Ignore( 4 ); + aPool << ocBad; + aPool >> aStack; + break; + default: + aPool << ocBad; + aPool >> aStack; + } + } + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData(0), nFactor(0); + + sal_uInt8 nOpt = aIn.ReaduInt8(); + nData = aIn.ReaduInt16(); + nFactor = 2; + + if( nOpt & 0x04 ) + { + // nFactor -> skip bytes or words AttrChoose + nData++; + aIn.Ignore(static_cast(nData) * nFactor); + } + else if( nOpt & 0x10 ) // AttrSum + DoMulArgs( ocSum, 1 ); + break; + } + case 0x1C: // Error Value [314 266] + { + sal_uInt8 nByte = aIn.ReaduInt8(); + + DefTokenId eOc; + switch( nByte ) + { + case EXC_ERR_NULL: + case EXC_ERR_DIV0: + case EXC_ERR_VALUE: + case EXC_ERR_REF: + case EXC_ERR_NAME: + case EXC_ERR_NUM: eOc = ocStop; break; + case EXC_ERR_NA: eOc = ocNotAvail; break; + default: eOc = ocNoName; + } + aPool << eOc; + if( eOc != ocStop ) + aPool << ocOpen << ocClose; + aPool >> aStack; + + break; + } + case 0x1D: // Boolean [315 266] + { + sal_uInt8 nByte = aIn.ReaduInt8(); + if( nByte == 0 ) + aPool << ocFalse << ocOpen << ocClose; + else + aPool << ocTrue << ocOpen << ocClose; + aPool >> aStack; + break; + } + case 0x1E: // Integer [315 266] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + aStack << aPool.Store( static_cast(nUINT16) ); + break; + } + case 0x1F: // Number [315 266] + { + double fDouble = aIn.ReadDouble(); + aStack << aPool.Store( fDouble ); + break; + } + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + { + aIn.Ignore( 7 ); + if( bAllowArrays ) + { + aStack << aPool.StoreMatrix(); + aExtensions.push_back( EXTENSION_ARRAY ); + } + else + { + aPool << ocBad; + aPool >> aStack; + } + break; + } + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + { + sal_uInt16 nXclFunc; + nXclFunc = aIn.ReaduInt16(); + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclFunc( nXclFunc ) ) + DoMulArgs( pFuncInfo->meOpCode, pFuncInfo->mnMaxParamCount ); + else + DoMulArgs( ocNoName, 0 ); + break; + } + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + { + sal_uInt16 nXclFunc; + sal_uInt8 nParamCount; + nParamCount = aIn.ReaduInt8(); + nXclFunc = aIn.ReaduInt16(); + nParamCount &= 0x7F; + if( const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromXclFunc( nXclFunc ) ) + DoMulArgs( pFuncInfo->meOpCode, nParamCount ); + else + DoMulArgs( ocNoName, 0 ); + break; + } + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + { + sal_uInt16 nUINT16 = aIn.ReaduInt16(); + aIn.Ignore( 2 ); + const XclImpName* pName = GetNameManager().GetName( nUINT16 ); + if (pName) + { + if (pName->IsMacro()) + // user-defined macro name. + aStack << aPool.Store(ocMacro, pName->GetXclName()); + else + aStack << aPool.StoreName(nUINT16, pName->IsGlobal() ? -1 : pName->GetScTab()); + } + break; + } + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + { + sal_uInt16 nCol, nRow; + + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel8( nRow, nCol, aSRD, bRangeNameOrCond ); + + switch ( nOp ) + { + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + // no information which part is deleted, set both + aSRD.SetColDeleted( true ); + aSRD.SetRowDeleted( true ); + } + + aStack << aPool.Store( aSRD ); + break; + } + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + { + sal_uInt16 nRowFirst, nRowLast; + sal_uInt16 nColFirst, nColLast; + ScSingleRefData &rSRef1 = aCRD.Ref1; + ScSingleRefData &rSRef2 = aCRD.Ref2; + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt16(); + nColLast = aIn.ReaduInt16(); + + rSRef1.SetRelTab(0); + rSRef2.SetRelTab(0); + rSRef1.SetFlag3D( bRangeName ); + rSRef2.SetFlag3D( bRangeName ); + + ExcRelToScRel8( nRowFirst, nColFirst, aCRD.Ref1, bRangeNameOrCond ); + ExcRelToScRel8( nRowLast, nColLast, aCRD.Ref2, bRangeNameOrCond ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + switch ( nOp ) + { + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + // no information which part is deleted, set all + rSRef1.SetColDeleted( true ); + rSRef1.SetRowDeleted( true ); + rSRef2.SetColDeleted( true ); + rSRef2.SetRowDeleted( true ); + } + + aStack << aPool.Store( aCRD ); + break; + } + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + aExtensions.push_back( EXTENSION_MEMAREA ); + aIn.Ignore( 6 ); // There isn't any more + break; + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + aIn.Ignore( 6 ); // There isn't any more + break; + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + aIn.Ignore( 6 ); // There isn't any more + break; + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + aIn.Ignore( 2 ); // There isn't any more + break; + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + { + sal_uInt16 nRow, nCol; + + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel8( nRow, nCol, aSRD, bRNorSF ); + + aStack << aPool.Store( aSRD ); + break; + } + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + { // Area Reference Within a Shared Formula[ 274] + sal_uInt16 nRowFirst, nRowLast; + sal_uInt16 nColFirst, nColLast; + + aCRD.Ref1.SetRelTab(0); + aCRD.Ref2.SetRelTab(0); + aCRD.Ref1.SetFlag3D( bRangeName ); + aCRD.Ref2.SetFlag3D( bRangeName ); + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt16(); + nColLast = aIn.ReaduInt16(); + + ExcRelToScRel8( nRowFirst, nColFirst, aCRD.Ref1, bRNorSF ); + ExcRelToScRel8( nRowLast, nColLast, aCRD.Ref2, bRNorSF ); + + bool bColRel = aCRD.Ref1.IsColRel() || aCRD.Ref2.IsColRel(); + bool bRowRel = aCRD.Ref1.IsRowRel() || aCRD.Ref2.IsRowRel(); + + if( !bColRel && IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( !bRowRel && IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + aStack << aPool.Store( aCRD ); + break; + } + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + aIn.Ignore( 2 ); // There isn't any more + break; + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + aIn.Ignore( 2 ); // There isn't any more + break; + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + { + OUString aString = "COMM_EQU_FUNC"; + sal_uInt8 nByte = aIn.ReaduInt8(); + aString += OUString::number( nByte ); + nByte = aIn.ReaduInt8(); + aStack << aPool.Store( aString ); + DoMulArgs( ocPush, nByte + 1 ); + break; + } + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + { + sal_uInt16 nXtiIndex, nNameIdx; + nXtiIndex = aIn.ReaduInt16(); + nNameIdx = aIn.ReaduInt16(); + aIn.Ignore( 2 ); + + if( rLinkMan.IsSelfRef( nXtiIndex ) ) + { + // internal defined name with explicit sheet, i.e.: =Sheet1!AnyName + const XclImpName* pName = GetNameManager().GetName( nNameIdx ); + if (pName) + { + if (pName->GetScRangeData()) + aStack << aPool.StoreName( nNameIdx, pName->IsGlobal() ? -1 : pName->GetScTab()); + else + aStack << aPool.Store(ocMacro, pName->GetXclName()); + } + } + else if( const XclImpExtName* pExtName = rLinkMan.GetExternName( nXtiIndex, nNameIdx ) ) + { + switch( pExtName->GetType() ) + { + case xlExtName: + { + /* FIXME: enable this code for #i4385# once + * external name reference can be stored in ODF, + * which remains to be done for #i3740#. Until then + * create a #NAME? token. */ +#if 1 + sal_uInt16 nFileId; + if (!GetExternalFileIdFromXti(nXtiIndex, nFileId) || !pExtName->HasFormulaTokens()) + { + aStack << aPool.Store(ocNoName, pExtName->GetName()); + break; + } + + aStack << aPool.StoreExtName(nFileId, pExtName->GetName()); + pExtName->CreateExtNameData(GetDoc(), nFileId); +#else + aStack << aPool.Store( ocNoName, pExtName->GetName() ); +#endif + } + break; + + case xlExtAddIn: + { + aStack << aPool.Store( ocExternal, pExtName->GetName() ); + } + break; + + case xlExtDDE: + { + OUString aApplic, aTopic; + if( rLinkMan.GetLinkData( aApplic, aTopic, nXtiIndex ) ) + { + TokenId nPar1 = aPool.Store( aApplic ); + TokenId nPar2 = aPool.Store( aTopic ); + nBuf0 = aPool.Store( pExtName->GetName() ); + aPool << ocDde << ocOpen << nPar1 << ocSep << nPar2 << ocSep + << nBuf0 << ocClose; + aPool >> aStack; + pExtName->CreateDdeData( GetDoc(), aApplic, aTopic ); + GetDoc().SetLinkFormulaNeedingCheck(true); + } + } + break; + + case xlExtEuroConvert: + { + aStack << aPool.Store( ocEuroConvert, OUString() ); + } + break; + case xlExtOLE: + { + ExternalTabInfo aExtInfo; + if (HandleOleLink(nXtiIndex, *pExtName, aExtInfo)) + { + if (aExtInfo.maRange.aStart == aExtInfo.maRange.aEnd) + { + // single cell + aSRD.InitAddress(aExtInfo.maRange.aStart); + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aSRD); + } + else + { + // range + aCRD.InitRange(aExtInfo.maRange); + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aCRD); + } + } + else + aStack << aPool.Store(ocNoName, pExtName->GetName()); + } + break; + default: + { + aPool << ocBad; + aPool >> aStack; + } + } + } + else + { + aPool << ocBad; + aPool >> aStack; + } + break; + } + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + { + sal_uInt16 nIxti, nRw, nGrbitCol; + SCTAB nTabFirst, nTabLast; + + nIxti = aIn.ReaduInt16(); + nRw = aIn.ReaduInt16(); + nGrbitCol = aIn.ReaduInt16(); + + ExternalTabInfo aExtInfo; + if (!Read3DTabReference(nIxti, nTabFirst, nTabLast, aExtInfo)) + { + aPool << ocBad; + aPool >> aStack; + break; + } + + aSRD.SetAbsTab(nTabFirst); + aSRD.SetFlag3D(true); + + ExcRelToScRel8( nRw, nGrbitCol, aSRD, bRangeNameOrCond ); + + switch ( nOp ) + { + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + // no information which part is deleted, set both + aSRD.SetColDeleted( true ); + aSRD.SetRowDeleted( true ); + } + + if (aExtInfo.mbExternal) + { + // nTabFirst and nTabLast are the indices of the referenced + // sheets in the SUPBOOK record, hence do not represent + // the actual indices of the original sheets since the + // SUPBOOK record only stores referenced sheets and skips + // the ones that are not referenced. + + if (nTabLast != nTabFirst) + { + aCRD.Ref1 = aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(nTabLast); + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aCRD); + } + else + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aSRD); + } + else + { + if ( !ValidTab(nTabFirst)) + aSRD.SetTabDeleted( true ); + + if( nTabLast != nTabFirst ) + { + aCRD.Ref1 = aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(nTabLast); + aCRD.Ref2.SetTabDeleted( !ValidTab(nTabLast) ); + aStack << aPool.Store( aCRD ); + } + else + aStack << aPool.Store( aSRD ); + } + break; + } + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + { + sal_uInt16 nIxti, nRw1, nGrbitCol1, nRw2, nGrbitCol2; + SCTAB nTabFirst, nTabLast; + nIxti = aIn.ReaduInt16(); + nRw1 = aIn.ReaduInt16(); + nRw2 = aIn.ReaduInt16(); + nGrbitCol1 = aIn.ReaduInt16(); + nGrbitCol2 = aIn.ReaduInt16(); + + ExternalTabInfo aExtInfo; + if (!Read3DTabReference(nIxti, nTabFirst, nTabLast, aExtInfo)) + { + aPool << ocBad; + aPool >> aStack; + break; + } + ScSingleRefData &rR1 = aCRD.Ref1; + ScSingleRefData &rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nTabFirst); + rR2.SetAbsTab(nTabLast); + rR1.SetFlag3D(true); + rR2.SetFlag3D( nTabFirst != nTabLast ); + + ExcRelToScRel8( nRw1, nGrbitCol1, aCRD.Ref1, bRangeNameOrCond ); + ExcRelToScRel8( nRw2, nGrbitCol2, aCRD.Ref2, bRangeNameOrCond ); + + if( IsComplColRange( nGrbitCol1, nGrbitCol2 ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRw1, nRw2 ) ) + SetComplRow( aCRD ); + + switch ( nOp ) + { + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + // no information which part is deleted, set all + rR1.SetColDeleted( true ); + rR1.SetRowDeleted( true ); + rR2.SetColDeleted( true ); + rR2.SetRowDeleted( true ); + } + + if (aExtInfo.mbExternal) + { + aStack << aPool.StoreExtRef(aExtInfo.mnFileId, aExtInfo.maTabName, aCRD); + } + else + { + if ( !ValidTab(nTabFirst) ) + rR1.SetTabDeleted( true ); + if ( !ValidTab(nTabLast) ) + rR2.SetTabDeleted( true ); + + aStack << aPool.Store( aCRD ); + } + break; + } + default: + bError = true; + } + bError |= !aIn.IsValid(); + } + + ConvErr eRet; + + if( bError ) + { + aPool << ocBad; + aPool >> aStack; + rpTokArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::Ni; + } + else if( aIn.GetRecPos() != nEndPos ) + { + aPool << ocBad; + aPool >> aStack; + rpTokArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::Count; + } + else if( bArrayFormula ) + { + rpTokArray = nullptr; + eRet = ConvErr::OK; + } + else + { + rpTokArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + eRet = ConvErr::OK; + } + + aIn.Seek( nEndPos ); + + if( eRet == ConvErr::OK) + ReadExtensions( aExtensions, aIn ); + + return eRet; +} + +// stream seeks to first byte after +ConvErr ExcelToSc8::Convert( ScRangeListTabs& rRangeList, XclImpStream& aIn, std::size_t nFormulaLen, + SCTAB nTab, const FORMULA_TYPE eFT ) +{ + sal_uInt8 nOp, nLen; + bool bError = false; + const bool bCondFormat = eFT == FT_CondFormat; + const bool bRangeName = eFT == FT_RangeName || bCondFormat; + const bool bSharedFormula = eFT == FT_SharedFormula; + const bool bRNorSF = bRangeName || bSharedFormula; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + + if( nFormulaLen == 0 ) + return ConvErr::OK; + + std::size_t nEndPos = aIn.GetRecPos() + nFormulaLen; + + while( (aIn.GetRecPos() < nEndPos) && !bError ) + { + nOp = aIn.ReaduInt8(); + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) // book page: + { // SDK4 SDK5 + case 0x01: // Array Formula [325 ] + // Array Formula or Shared Formula [ 277] + aIn.Ignore( 4 ); + break; + case 0x02: // Data Table [325 277] + aIn.Ignore( 4 ); + break; + case 0x03: // Addition [312 264] + case 0x04: // Subtraction [313 264] + case 0x05: // Multiplication [313 264] + case 0x06: // Division [313 264] + case 0x07: // Exponetiation [313 265] + case 0x08: // Concatenation [313 265] + case 0x09: // Less Than [313 265] + case 0x0A: // Less Than or Equal [313 265] + case 0x0B: // Equal [313 265] + case 0x0C: // Greater Than or Equal [313 265] + case 0x0D: // Greater Than [313 265] + case 0x0E: // Not Equal [313 265] + case 0x0F: // Intersection [314 265] + case 0x10: // Union [314 265] + case 0x11: // Range [314 265] + case 0x12: // Unary Plus [312 264] + case 0x13: // Unary Minus [312 264] + case 0x14: // Percent Sign [312 264] + case 0x15: // Parenthesis [326 278] + case 0x16: // Missing Argument [314 266] + break; + case 0x17: // String Constant [314 266] + nLen = aIn.ReaduInt8(); // Why? + + aIn.IgnoreUniString( nLen ); // reads Grbit even if nLen==0 + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData(0), nFactor(0); + + sal_uInt8 nOpt = aIn.ReaduInt8(); + nData = aIn.ReaduInt16(); + nFactor = 2; + + if( nOpt & 0x04 ) + { + // nFactor -> skip bytes or words AttrChoose + ++nData; + aIn.Ignore(static_cast(nData) * nFactor); + } + } + break; + case 0x1C: // Error Value [314 266] + case 0x1D: // Boolean [315 266] + aIn.Ignore( 1 ); + break; + case 0x1E: // Integer [315 266] + aIn.Ignore( 2 ); + break; + case 0x1F: // Number [315 266] + aIn.Ignore( 8 ); + break; + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + aIn.Ignore( 7 ); + break; + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + aIn.Ignore( 2 ); + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + aIn.Ignore( 3 ); + break; + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + aIn.Ignore( 4 ); + break; + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + { + sal_uInt16 nCol, nRow; + + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName && !bCondFormat ); + + ExcRelToScRel8( nRow, nCol, aSRD, bRangeName ); + + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + { + sal_uInt16 nRowFirst, nRowLast; + sal_uInt16 nColFirst, nColLast; + ScSingleRefData &rSRef1 = aCRD.Ref1; + ScSingleRefData &rSRef2 = aCRD.Ref2; + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt16(); + nColLast = aIn.ReaduInt16(); + + rSRef1.SetRelTab(0); + rSRef2.SetRelTab(0); + rSRef1.SetFlag3D( bRangeName && !bCondFormat ); + rSRef2.SetFlag3D( bRangeName && !bCondFormat ); + + ExcRelToScRel8( nRowFirst, nColFirst, aCRD.Ref1, bRangeName ); + ExcRelToScRel8( nRowLast, nColLast, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + aIn.Ignore( 6 ); // There isn't any more + break; + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + aIn.Ignore( 2 ); // There isn't any more + break; + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + aIn.Ignore( 3 ); + break; + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + aIn.Ignore( 6 ); + break; + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + { + sal_uInt16 nRow, nCol; + + nRow = aIn.ReaduInt16(); + nCol = aIn.ReaduInt16(); + + aSRD.SetRelTab(0); + aSRD.SetFlag3D( bRangeName ); + + ExcRelToScRel8( nRow, nCol, aSRD, bRNorSF ); + + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + { // Area Reference Within a Shared Formula[ 274] + sal_uInt16 nRowFirst, nRowLast; + sal_uInt16 nColFirst, nColLast; + + aCRD.Ref1.SetRelTab(0); + aCRD.Ref2.SetRelTab(0); + aCRD.Ref1.SetFlag3D( bRangeName ); + aCRD.Ref2.SetFlag3D( bRangeName ); + + nRowFirst = aIn.ReaduInt16(); + nRowLast = aIn.ReaduInt16(); + nColFirst = aIn.ReaduInt16( ); + nColLast = aIn.ReaduInt16(); + + ExcRelToScRel8( nRowFirst, nColFirst, aCRD.Ref1, bRNorSF ); + ExcRelToScRel8( nRowLast, nColLast, aCRD.Ref2, bRNorSF ); + + if( IsComplColRange( nColFirst, nColLast ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRowFirst, nRowLast ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + break; + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + aIn.Ignore( 2 ); + break; + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + aIn.Ignore( 24 ); + break; + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + { + sal_uInt16 nIxti, nRw, nGrbitCol; + + nIxti = aIn.ReaduInt16(); + nRw = aIn.ReaduInt16(); + nGrbitCol = aIn.ReaduInt16(); + + SCTAB nFirstScTab, nLastScTab; + if( rLinkMan.GetScTabRange( nFirstScTab, nLastScTab, nIxti ) ) + { + aSRD.SetAbsTab(nFirstScTab); + aSRD.SetFlag3D(true); + + ExcRelToScRel8( nRw, nGrbitCol, aSRD, bRangeName ); + + if( nFirstScTab != nLastScTab ) + { + aCRD.Ref1 = aSRD; + aCRD.Ref2 = aSRD; + aCRD.Ref2.SetAbsTab(nLastScTab); + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + else + rRangeList.Append(aSRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + } + break; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + { + sal_uInt16 nIxti, nRw1, nGrbitCol1, nRw2, nGrbitCol2; + + nIxti = aIn.ReaduInt16(); + nRw1 = aIn.ReaduInt16(); + nRw2 = aIn.ReaduInt16(); + nGrbitCol1 = aIn.ReaduInt16(); + nGrbitCol2 = aIn.ReaduInt16(); + + SCTAB nFirstScTab, nLastScTab; + if( rLinkMan.GetScTabRange( nFirstScTab, nLastScTab, nIxti ) ) + { + ScSingleRefData &rR1 = aCRD.Ref1; + ScSingleRefData &rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nFirstScTab); + rR2.SetAbsTab(nLastScTab); + rR1.SetFlag3D(true); + rR2.SetFlag3D( nFirstScTab != nLastScTab ); + + ExcRelToScRel8( nRw1, nGrbitCol1, aCRD.Ref1, bRangeName ); + ExcRelToScRel8( nRw2, nGrbitCol2, aCRD.Ref2, bRangeName ); + + if( IsComplColRange( nGrbitCol1, nGrbitCol2 ) ) + SetComplCol( aCRD ); + else if( IsComplRowRange( nRw1, nRw2 ) ) + SetComplRow( aCRD ); + + rRangeList.Append(aCRD.toAbs(GetDocImport().getDoc(), aEingPos), nTab); + } + } + break; + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + aIn.Ignore( 6 ); + break; + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + aIn.Ignore( 10 ); + break; + default: + bError = true; + } + bError |= !aIn.IsValid(); + } + + ConvErr eRet; + + if( bError ) + eRet = ConvErr::Ni; + else if( aIn.GetRecPos() != nEndPos ) + eRet = ConvErr::Count; + else + eRet = ConvErr::OK; + + aIn.Seek( nEndPos ); + return eRet; +} + +void ExcelToSc8::ConvertExternName( std::unique_ptr& rpArray, XclImpStream& rStrm, std::size_t nFormulaLen, + const OUString& rUrl, const vector& rTabNames ) +{ + if( !GetDocShell() ) + return; + + OUString aFileUrl = ScGlobal::GetAbsDocName(rUrl, GetDocShell()); + + sal_uInt8 nOp, nByte; + bool bError = false; + + ScSingleRefData aSRD; + ScComplexRefData aCRD; + + if (nFormulaLen == 0) + { + aPool.Store("-/-"); + aPool >> aStack; + rpArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + return; + } + + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aFileUrl); + sal_uInt16 nTabCount = static_cast< sal_uInt16 >( rTabNames.size() ); + + std::size_t nEndPos = rStrm.GetRecPos() + nFormulaLen; + + while( (rStrm.GetRecPos() < nEndPos) && !bError ) + { + nOp = rStrm.ReaduInt8(); + + // always reset flags + aSRD.InitFlags(); + aCRD.InitFlags(); + + switch( nOp ) + { + case 0x1C: // Error Value + { + nByte = rStrm.ReaduInt8(); + DefTokenId eOc; + switch( nByte ) + { + case EXC_ERR_NULL: + case EXC_ERR_DIV0: + case EXC_ERR_VALUE: + case EXC_ERR_REF: + case EXC_ERR_NAME: + case EXC_ERR_NUM: eOc = ocStop; break; + case EXC_ERR_NA: eOc = ocNotAvail; break; + default: eOc = ocNoName; + } + aPool << eOc; + if( eOc != ocStop ) + aPool << ocOpen << ocClose; + aPool >> aStack; + } + break; + case 0x3A: + { + // cell reference in external range name + sal_uInt16 nExtTab1, nExtTab2, nRow, nGrbitCol; + nExtTab1 = rStrm.ReaduInt16(); + nExtTab2 = rStrm.ReaduInt16(); + nRow = rStrm.ReaduInt16(); + nGrbitCol = rStrm.ReaduInt16(); + if (nExtTab1 >= nTabCount || nExtTab2 >= nTabCount) + { + bError = true; + break; + } + + aSRD.SetAbsTab(nExtTab1); + aSRD.SetFlag3D(true); + ExcRelToScRel8(nRow, nGrbitCol, aSRD, true); + aCRD.Ref1 = aCRD.Ref2 = aSRD; + OUString aTabName = rTabNames[nExtTab1]; + + if (nExtTab1 == nExtTab2) + { + // single cell reference + aStack << aPool.StoreExtRef(nFileId, aTabName, aSRD); + } + else + { + // area reference + aCRD.Ref2.SetAbsTab(nExtTab2); + aStack << aPool.StoreExtRef(nFileId, aTabName, aCRD); + } + } + break; + case 0x3B: + { + // area reference + sal_uInt16 nExtTab1, nExtTab2, nRow1, nRow2, nGrbitCol1, nGrbitCol2; + nExtTab1 = rStrm.ReaduInt16(); + nExtTab2 = rStrm.ReaduInt16(); + nRow1 = rStrm.ReaduInt16(); + nRow2 = rStrm.ReaduInt16(); + nGrbitCol1 = rStrm.ReaduInt16(); + nGrbitCol2 = rStrm.ReaduInt16(); + + if (nExtTab1 >= nTabCount || nExtTab2 >= nTabCount) + { + bError = true; + break; + } + + ScSingleRefData& rR1 = aCRD.Ref1; + ScSingleRefData& rR2 = aCRD.Ref2; + + rR1.SetAbsTab(nExtTab1); + rR1.SetFlag3D(true); + ExcRelToScRel8(nRow1, nGrbitCol1, rR1, true); + + rR2.SetAbsTab(nExtTab2); + rR2.SetFlag3D(true); + ExcRelToScRel8(nRow2, nGrbitCol2, rR2, true); + + OUString aTabName = rTabNames[nExtTab1]; + aStack << aPool.StoreExtRef(nFileId, aTabName, aCRD); + } + break; + default: + bError = true; + } + bError |= !rStrm.IsValid(); + } + + if( bError ) + { + aPool << ocBad; + aPool >> aStack; + rpArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + } + else if( rStrm.GetRecPos() != nEndPos ) + { + aPool << ocBad; + aPool >> aStack; + rpArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + } + else + { + rpArray = aPool.GetTokenArray( GetDocImport().getDoc(), aStack.Get()); + } + + rStrm.Seek(nEndPos); +} + +void ExcelToSc8::ExcRelToScRel8( sal_uInt16 nRow, sal_uInt16 nC, ScSingleRefData &rSRD, const bool bName ) +{ + const bool bColRel = ( nC & 0x4000 ) != 0; + const bool bRowRel = ( nC & 0x8000 ) != 0; + const sal_uInt8 nCol = static_cast(nC); + + if( bName ) + { + // C O L + if( bColRel ) + { + SCCOL nRelCol = static_cast(nC); + sal_Int16 nDiff = aEingPos.Col() + nRelCol; + if ( nDiff < 0) + { + // relative column references wrap around + nRelCol = static_cast(256 + static_cast(nRelCol)); + } + rSRD.SetRelCol(nRelCol); + } + else + rSRD.SetAbsCol(static_cast(nCol)); + + // R O W + if( bRowRel ) + { + SCROW nRelRow = static_cast(nRow); + sal_Int32 nDiff = aEingPos.Row() + nRelRow; + if (nDiff < 0) + { + // relative row references wrap around + nRelRow = 65536 + nRelRow; + } + rSRD.SetRelRow(nRelRow); + } + else + rSRD.SetAbsRow(std::min( static_cast(nRow), GetDoc().MaxRow())); + } + else + { + // C O L + if ( bColRel ) + rSRD.SetRelCol(static_cast(nCol) - aEingPos.Col()); + else + rSRD.SetAbsCol(nCol); + + // R O W + if ( bRowRel ) + rSRD.SetRelRow(static_cast(nRow) - aEingPos.Row()); + else + rSRD.SetAbsRow(nRow); + } +} + +// stream seeks to first byte after +void ExcelToSc8::GetAbsRefs( ScRangeList& r, XclImpStream& aIn, std::size_t nLen ) +{ + sal_uInt8 nOp; + sal_uInt16 nRow1, nRow2, nCol1, nCol2; + SCTAB nTab1, nTab2; + sal_uInt16 nIxti; + + std::size_t nSeek; + + std::size_t nEndPos = aIn.GetRecPos() + nLen; + + while( aIn.IsValid() && (aIn.GetRecPos() < nEndPos) ) + { + nOp = aIn.ReaduInt8(); + nSeek = 0; + + switch( nOp ) + { + case 0x44: + case 0x64: + case 0x24: // Cell Reference [319 270] + case 0x4C: + case 0x6C: + case 0x2C: // Cell Reference Within a Name [323 ] + // Cell Reference Within a Shared Formula[ 273] + nRow1 = aIn.ReaduInt16(); + nCol1 = aIn.ReaduInt16(); + + nRow2 = nRow1; + nCol2 = nCol1; + nTab1 = nTab2 = GetCurrScTab(); + goto _common; + case 0x45: + case 0x65: + case 0x25: // Area Reference [320 270] + case 0x4D: + case 0x6D: + case 0x2D: // Area Reference Within a Name [324 ] + // Area Reference Within a Shared Formula[ 274] + nRow1 = aIn.ReaduInt16(); + nRow2 = aIn.ReaduInt16(); + nCol1 = aIn.ReaduInt16(); + nCol2 = aIn.ReaduInt16(); + + nTab1 = nTab2 = GetCurrScTab(); + goto _common; + case 0x5A: + case 0x7A: + case 0x3A: // 3-D Cell Reference [ 275] + nIxti = aIn.ReaduInt16(); + nRow1 = aIn.ReaduInt16(); + nCol1 = aIn.ReaduInt16(); + + nRow2 = nRow1; + nCol2 = nCol1; + + goto _3d_common; + case 0x5B: + case 0x7B: + case 0x3B: // 3-D Area Reference [ 276] + nIxti = aIn.ReaduInt16(); + nRow1 = aIn.ReaduInt16(); + nRow2 = aIn.ReaduInt16(); + nCol1 = aIn.ReaduInt16(); + nCol2 = aIn.ReaduInt16(); + + _3d_common: + // skip references to deleted sheets + if( !rLinkMan.GetScTabRange( nTab1, nTab2, nIxti ) || !ValidTab( nTab1 ) || !ValidTab( nTab2 ) ) + break; + + goto _common; + _common: + // do not check abs/rel flags, linked controls have set them! + { + ScRange aScRange; + nCol1 &= 0x3FFF; + nCol2 &= 0x3FFF; + if( GetAddressConverter().ConvertRange( aScRange, XclRange( nCol1, nRow1, nCol2, nRow2 ), nTab1, nTab2, true ) ) + r.push_back( aScRange ); + } + break; + case 0x1C: // Error Value [314 266] + case 0x1D: // Boolean [315 266] + nSeek = 1; + break; + case 0x1E: // Integer [315 266] + case 0x41: + case 0x61: + case 0x21: // Function, Fixed Number of Arguments [333 282] + case 0x49: + case 0x69: + case 0x29: // Variable Reference Subexpression [331 281] + case 0x4E: + case 0x6E: + case 0x2E: // Reference Subexpression Within a Name [332 282] + case 0x4F: + case 0x6F: + case 0x2F: // Incomplete Reference Subexpression... [332 282] + case 0x58: + case 0x78: + case 0x38: // Command-Equivalent Function [333 ] + nSeek = 2; + break; + case 0x42: + case 0x62: + case 0x22: // Function, Variable Number of Arg. [333 283] + nSeek = 3; + break; + case 0x01: // Array Formula [325 ] + case 0x02: // Data Table [325 277] + case 0x43: + case 0x63: + case 0x23: // Name [318 269] + case 0x4A: + case 0x6A: + case 0x2A: // Deleted Cell Reference [323 273] + nSeek = 4; + break; + case 0x46: + case 0x66: + case 0x26: // Constant Reference Subexpression [321 271] + case 0x47: + case 0x67: + case 0x27: // Erroneous Constant Reference Subexpr. [322 272] + case 0x48: + case 0x68: + case 0x28: // Incomplete Constant Reference Subexpr.[331 281] + case 0x5C: + case 0x7C: + case 0x3C: // Deleted 3-D Cell Reference [ 277] + case 0x59: + case 0x79: + case 0x39: // Name or External Name [ 275] + nSeek = 6; + break; + case 0x40: + case 0x60: + case 0x20: // Array Constant [317 268] + nSeek = 7; + break; + case 0x1F: // Number [315 266] + case 0x4B: + case 0x6B: + case 0x2B: // Deleted Area Reference [323 273] + nSeek = 8; + break; + case 0x5D: + case 0x7D: + case 0x3D: // Deleted 3-D Area Reference [ 277] + nSeek = 10; + break; + case 0x17: // String Constant [314 266] + { + sal_uInt8 nStrLen; + nStrLen = aIn.ReaduInt8(); + aIn.IgnoreUniString( nStrLen ); // reads Grbit even if nLen==0 + nSeek = 0; + } + break; + case 0x19: // Special Attribute [327 279] + { + sal_uInt16 nData; + sal_uInt8 nOpt; + nOpt = aIn.ReaduInt8(); + nData = aIn.ReaduInt16(); + if( nOpt & 0x04 ) + {// nFactor -> skip bytes or words AttrChoose + nData++; + nSeek = nData * 2; + } + } + break; + } + + aIn.Ignore( nSeek ); + } + aIn.Seek( nEndPos ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excimp8.cxx b/sc/source/filter/excel/excimp8.cxx new file mode 100644 index 000000000..d5db209a1 --- /dev/null +++ b/sc/source/filter/excel/excimp8.cxx @@ -0,0 +1,817 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "xltoolbar.hxx" +#include +#include + +using namespace com::sun::star; +using namespace ::comphelper; + +//OleNameOverrideContainer + +namespace { + +class OleNameOverrideContainer : public ::cppu::WeakImplHelper< container::XNameContainer > +{ +private: + typedef std::unordered_map< OUString, uno::Reference< container::XIndexContainer > > NamedIndexToOleName; + NamedIndexToOleName IdToOleNameHash; + ::osl::Mutex m_aMutex; +public: + // XElementAccess + virtual uno::Type SAL_CALL getElementType( ) override { return cppu::UnoType::get(); } + virtual sal_Bool SAL_CALL hasElements( ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + return ( !IdToOleNameHash.empty() ); + } + // XNameAccess + virtual uno::Any SAL_CALL getByName( const OUString& aName ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !hasByName(aName) ) + throw container::NoSuchElementException(); + return uno::Any( IdToOleNameHash[ aName ] ); + } + virtual uno::Sequence< OUString > SAL_CALL getElementNames( ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + return comphelper::mapKeysToSequence( IdToOleNameHash); + } + virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + return ( IdToOleNameHash.find( aName ) != IdToOleNameHash.end() ); + } + + // XNameContainer + virtual void SAL_CALL insertByName( const OUString& aName, const uno::Any& aElement ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( hasByName( aName ) ) + throw container::ElementExistException(); + uno::Reference< container::XIndexContainer > xElement; + if ( ! ( aElement >>= xElement ) ) + throw lang::IllegalArgumentException(); + IdToOleNameHash[ aName ] = xElement; + } + virtual void SAL_CALL removeByName( const OUString& aName ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( IdToOleNameHash.erase( aName ) == 0 ) + throw container::NoSuchElementException(); + } + virtual void SAL_CALL replaceByName( const OUString& aName, const uno::Any& aElement ) override + { + ::osl::MutexGuard aGuard( m_aMutex ); + if ( !hasByName( aName ) ) + throw container::NoSuchElementException(); + uno::Reference< container::XIndexContainer > xElement; + if ( ! ( aElement >>= xElement ) ) + throw lang::IllegalArgumentException(); + IdToOleNameHash[ aName ] = xElement; + } +}; + +/** Future Record Type header. + @return whether read rt matches nRecordID + */ +bool readFrtHeader( XclImpStream& rStrm, sal_uInt16 nRecordID ) +{ + sal_uInt16 nRt = rStrm.ReaduInt16(); + rStrm.Ignore(10); // grbitFrt (2 bytes) and reserved (8 bytes) + return nRt == nRecordID; +} + +} + +ImportExcel8::ImportExcel8( XclImpRootData& rImpData, SvStream& rStrm ) : + ImportExcel( rImpData, rStrm ) +{ + // replace BIFF2-BIFF5 formula importer with BIFF8 formula importer + pFormConv.reset(new ExcelToSc8( GetRoot() )); + pExcRoot->pFmlaConverter = pFormConv.get(); +} + +ImportExcel8::~ImportExcel8() +{ +} + +void ImportExcel8::Calccount() +{ + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetIterCount( aIn.ReaduInt16() ); + rD.SetDocOptions( aOpt ); +} + +void ImportExcel8::Precision() +{ + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetCalcAsShown( aIn.ReaduInt16() == 0 ); + rD.SetDocOptions( aOpt ); +} + +void ImportExcel8::Delta() +{ + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetIterEps( aIn.ReadDouble() ); + rD.SetDocOptions( aOpt ); +} + +void ImportExcel8::Iteration() +{ + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetIter( aIn.ReaduInt16() == 1 ); + rD.SetDocOptions( aOpt ); +} + +void ImportExcel8::Boundsheet() +{ + sal_uInt8 nLen; + sal_uInt16 nGrbit; + + aIn.DisableDecryption(); + maSheetOffsets.push_back( aIn.ReaduInt32() ); + aIn.EnableDecryption(); + nGrbit = aIn.ReaduInt16(); + nLen = aIn.ReaduInt8(); + + OUString aName( aIn.ReadUniString( nLen ) ); + GetTabInfo().AppendXclTabName( aName, nBdshtTab ); + + SCTAB nScTab = nBdshtTab; + if( nScTab > 0 ) + { + OSL_ENSURE( !rD.HasTable( nScTab ), "ImportExcel8::Boundsheet - sheet exists already" ); + rD.MakeTable( nScTab ); + } + + if( ( nGrbit & 0x0001 ) || ( nGrbit & 0x0002 ) ) + rD.SetVisible( nScTab, false ); + + if( !rD.RenameTab( nScTab, aName ) ) + { + rD.CreateValidTabName( aName ); + rD.RenameTab( nScTab, aName ); + } + + nBdshtTab++; +} + +void ImportExcel8::Scenman() +{ + sal_uInt16 nLastDispl; + + aIn.Ignore( 4 ); + nLastDispl = aIn.ReaduInt16(); + + maScenList.nLastScenario = nLastDispl; +} + +void ImportExcel8::Scenario() +{ + maScenList.aEntries.push_back( std::make_unique( aIn, *pExcRoot ) ); +} + +void ImportExcel8::Labelsst() +{ + XclAddress aXclPos; + sal_uInt16 nXF; + sal_uInt32 nSst; + + aIn >> aXclPos; + nXF = aIn.ReaduInt16(); + nSst = aIn.ReaduInt32( ); + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + GetXFRangeBuffer().SetXF( aScPos, nXF ); + const XclImpString* pXclStr = GetSst().GetString(nSst); + if (pXclStr) + XclImpStringHelper::SetToDocument(GetDocImport(), aScPos, *this, *pXclStr, nXF); + } +} + +void ImportExcel8::FeatHdr() +{ + if (!readFrtHeader( aIn, 0x0867)) + return; + + // Feature type (isf) can be EXC_ISFPROTECTION, EXC_ISFFEC2 or + // EXC_ISFFACTOID. + sal_uInt16 nFeatureType = aIn.ReaduInt16(); + if (nFeatureType != EXC_ISFPROTECTION) + // We currently only support import of enhanced protection data. + return; + + aIn.Ignore(1); // always 1 + + GetSheetProtectBuffer().ReadOptions( aIn, GetCurrScTab() ); +} + +void ImportExcel8::Feat() +{ + if (!readFrtHeader( aIn, 0x0868)) + return; + + // Feature type (isf) can be EXC_ISFPROTECTION, EXC_ISFFEC2 or + // EXC_ISFFACTOID. + sal_uInt16 nFeatureType = aIn.ReaduInt16(); + if (nFeatureType != EXC_ISFPROTECTION) + // We currently only support import of enhanced protection data. + return; + + aIn.Ignore(5); // reserved1 (1 byte) and reserved2 (4 bytes) + + sal_uInt16 nCref = aIn.ReaduInt16(); // number of ref elements + aIn.Ignore(4); // size if EXC_ISFFEC2, else 0 and to be ignored + aIn.Ignore(2); // reserved3 (2 bytes) + + ScEnhancedProtection aProt; + if (nCref) + { + XclRangeList aRefs; + aRefs.Read( aIn, true, nCref); + if (!aRefs.empty()) + { + aProt.maRangeList = new ScRangeList; + GetAddressConverter().ConvertRangeList( *aProt.maRangeList, aRefs, GetCurrScTab(), false); + } + } + + // FeatProtection structure follows in record. + + aProt.mnAreserved = aIn.ReaduInt32(); + aProt.mnPasswordVerifier = aIn.ReaduInt32(); + aProt.maTitle = aIn.ReadUniString(); + if ((aProt.mnAreserved & 0x00000001) == 0x00000001) + { + sal_uInt32 nCbSD = aIn.ReaduInt32(); + // TODO: could here be some sanity check applied to not allocate 4GB? + aProt.maSecurityDescriptor.resize( nCbSD); + std::size_t nRead = aIn.Read(aProt.maSecurityDescriptor.data(), nCbSD); + if (nRead < nCbSD) + aProt.maSecurityDescriptor.resize( nRead); + } + + GetSheetProtectBuffer().AppendEnhancedProtection( aProt, GetCurrScTab() ); +} + +void ImportExcel8::ReadBasic() +{ + SfxObjectShell* pShell = GetDocShell(); + tools::SvRef xRootStrg = GetRootStorage(); + const SvtFilterOptions& rFilterOpt = SvtFilterOptions::Get(); + if( !pShell || !xRootStrg.is() ) + return; + + try + { + // #FIXME need to get rid of this, we can also do this from within oox + // via the "ooo.vba.VBAGlobals" service + if( ( rFilterOpt.IsLoadExcelBasicCode() || + rFilterOpt.IsLoadExcelBasicStorage() ) && + rFilterOpt.IsLoadExcelBasicExecutable() ) + { + // see if we have the XCB stream + tools::SvRef xXCB = xRootStrg->OpenSotStream( "XCB", StreamMode::STD_READ ); + if ( xXCB.is()|| ERRCODE_NONE == xXCB->GetError() ) + { + ScCTBWrapper wrapper; + if ( wrapper.Read( *xXCB ) ) + { +#ifdef DEBUG_SC_EXCEL + wrapper.Print( stderr ); +#endif + wrapper.ImportCustomToolBar( *pShell ); + } + } + } + try + { + uno::Reference< uno::XComponentContext > aCtx( ::comphelper::getProcessComponentContext() ); + SfxMedium& rMedium = GetMedium(); + uno::Reference< io::XInputStream > xIn = rMedium.GetInputStream(); + oox::ole::OleStorage root( aCtx, xIn, false ); + oox::StorageRef vbaStg = root.openSubStorage( "_VBA_PROJECT_CUR", false ); + if ( vbaStg ) + { + oox::ole::VbaProject aVbaPrj( aCtx, pShell->GetModel(), u"Calc" ); + // collect names of embedded form controls, as specified in the VBA project + uno::Reference< container::XNameContainer > xOleNameOverrideSink( new OleNameOverrideContainer ); + aVbaPrj.setOleOverridesSink( xOleNameOverrideSink ); + aVbaPrj.importVbaProject( *vbaStg ); + GetObjectManager().SetOleNameOverrideInfo( xOleNameOverrideSink ); + } + } + catch( uno::Exception& ) + { + } + } + catch( uno::Exception& ) + { + } +} + +void ImportExcel8::EndSheet() +{ + ImportExcel::EndSheet(); + GetCondFormatManager().Apply(); + GetValidationManager().Apply(); +} + +void ImportExcel8::PostDocLoad() +{ +#if HAVE_FEATURE_SCRIPTING + // reading basic has been delayed until sheet objects (codenames etc.) are read + if( HasBasic() ) + ReadBasic(); +#endif + // #i11776# filtered ranges before outlines and hidden rows + if( pExcRoot->pAutoFilterBuffer ) + pExcRoot->pAutoFilterBuffer->Apply(); + + GetWebQueryBuffer().Apply(); //TODO: test if extant + GetSheetProtectBuffer().Apply(); + GetDocProtectBuffer().Apply(); + + ImportExcel::PostDocLoad(); + + // check scenarios; Attention: This increases the table count of the document!! + if( !rD.IsClipboard() && !maScenList.aEntries.empty() ) + { + rD.UpdateChartListenerCollection(); // references in charts must be updated + + maScenList.Apply( GetRoot() ); + } + + // read doc info (no docshell while pasting from clipboard) + SfxObjectShell* pShell = GetDocShell(); + if(!pShell) + return; + + // BIFF5+ without storage is possible + tools::SvRef xRootStrg = GetRootStorage(); + if( xRootStrg.is() ) try + { + uno::Reference< document::XDocumentPropertiesSupplier > xDPS( pShell->GetModel(), uno::UNO_QUERY_THROW ); + uno::Reference< document::XDocumentProperties > xDocProps( xDPS->getDocumentProperties(), uno::UNO_SET_THROW ); + sfx2::LoadOlePropertySet( xDocProps, xRootStrg.get() ); + } + catch( uno::Exception& ) + { + } + + // #i45843# Pivot tables are now handled outside of PostDocLoad, so they are available + // when formula cells are calculated, for the GETPIVOTDATA function. +} + +// autofilter + +void ImportExcel8::FilterMode() +{ + // The FilterMode record exists: if either the AutoFilter + // record exists or an Advanced Filter is saved and stored + // in the sheet. Thus if the FilterMode records only exists + // then the latter is true... + if( !pExcRoot->pAutoFilterBuffer ) return; + + XclImpAutoFilterData* pData = pExcRoot->pAutoFilterBuffer->GetByTab( GetCurrScTab() ); + if( pData ) + pData->SetAutoOrAdvanced(); +} + +void ImportExcel8::AutoFilterInfo() +{ + if( !pExcRoot->pAutoFilterBuffer ) return; + + XclImpAutoFilterData* pData = pExcRoot->pAutoFilterBuffer->GetByTab( GetCurrScTab() ); + if( pData ) + { + pData->SetAdvancedRange( nullptr ); + pData->Activate(); + } +} + +void ImportExcel8::AutoFilter() +{ + if( !pExcRoot->pAutoFilterBuffer ) return; + + XclImpAutoFilterData* pData = pExcRoot->pAutoFilterBuffer->GetByTab( GetCurrScTab() ); + if( pData ) + pData->ReadAutoFilter(aIn, GetDoc().GetSharedStringPool()); +} + +XclImpAutoFilterData::XclImpAutoFilterData( RootData* pRoot, const ScRange& rRange ) : + ExcRoot( pRoot ), + pCurrDBData(nullptr), + bActive( false ), + bCriteria( false ), + bAutoOrAdvanced(false) +{ + aParam.nCol1 = rRange.aStart.Col(); + aParam.nRow1 = rRange.aStart.Row(); + aParam.nTab = rRange.aStart.Tab(); + aParam.nCol2 = rRange.aEnd.Col(); + aParam.nRow2 = rRange.aEnd.Row(); + + aParam.bInplace = true; + +} + +namespace { + +OUString CreateFromDouble( double fVal ) +{ + return rtl::math::doubleToUString(fVal, + rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, + ScGlobal::getLocaleData().getNumDecimalSep()[0], true); +} + +} + +void XclImpAutoFilterData::SetCellAttribs() +{ + ScDocument& rDoc = pExcRoot->pIR->GetDoc(); + for ( SCCOL nCol = StartCol(); nCol <= EndCol(); nCol++ ) + { + ScMF nFlag = rDoc.GetAttr( nCol, StartRow(), Tab(), ATTR_MERGE_FLAG )->GetValue(); + rDoc.ApplyAttr( nCol, StartRow(), Tab(), ScMergeFlagAttr( nFlag | ScMF::Auto) ); + } +} + +void XclImpAutoFilterData::InsertQueryParam() +{ + if (!pCurrDBData) + return; + + ScRange aAdvRange; + bool bHasAdv = pCurrDBData->GetAdvancedQuerySource( aAdvRange ); + if( bHasAdv ) + pExcRoot->pIR->GetDoc().CreateQueryParam(aAdvRange, aParam); + + pCurrDBData->SetQueryParam( aParam ); + if( bHasAdv ) + pCurrDBData->SetAdvancedQuerySource( &aAdvRange ); + else + { + pCurrDBData->SetAutoFilter( true ); + SetCellAttribs(); + } +} + +static void ExcelQueryToOooQuery( OUString& aStr, ScQueryEntry& rEntry ) +{ + if (rEntry.eOp != SC_EQUAL && rEntry.eOp != SC_NOT_EQUAL) + return; + + sal_Int32 nLen = aStr.getLength(); + sal_Unicode nStart = aStr[0]; + sal_Unicode nEnd = aStr[ nLen-1 ]; + if( nLen > 2 && nStart == '*' && nEnd == '*' ) + { + aStr = aStr.copy( 1, nLen-2 ); + rEntry.eOp = ( rEntry.eOp == SC_EQUAL ) ? SC_CONTAINS : SC_DOES_NOT_CONTAIN; + } + else if( nLen > 1 && nStart == '*' && nEnd != '*' ) + { + aStr = aStr.copy( 1 ); + rEntry.eOp = ( rEntry.eOp == SC_EQUAL ) ? SC_ENDS_WITH : SC_DOES_NOT_END_WITH; + } + else if( nLen > 1 && nStart != '*' && nEnd == '*' ) + { + aStr = aStr.copy( 0, nLen-1 ); + rEntry.eOp = ( rEntry.eOp == SC_EQUAL ) ? SC_BEGINS_WITH : SC_DOES_NOT_BEGIN_WITH; + } + else if( nLen == 2 && nStart == '*' && nEnd == '*' ) + { + aStr = aStr.copy( 1 ); + } +} + +void XclImpAutoFilterData::ReadAutoFilter( + XclImpStream& rStrm, svl::SharedStringPool& rPool ) +{ + sal_uInt16 nCol, nFlags; + nCol = rStrm.ReaduInt16(); + nFlags = rStrm.ReaduInt16(); + + ScQueryConnect eConn = ::get_flagvalue( nFlags, EXC_AFFLAG_ANDORMASK, SC_OR, SC_AND ); + bool bSimple1 = ::get_flag(nFlags, EXC_AFFLAG_SIMPLE1); + bool bSimple2 = ::get_flag(nFlags, EXC_AFFLAG_SIMPLE2); + bool bTop10 = ::get_flag(nFlags, EXC_AFFLAG_TOP10); + bool bTopOfTop10 = ::get_flag(nFlags, EXC_AFFLAG_TOP10TOP); + bool bPercent = ::get_flag(nFlags, EXC_AFFLAG_TOP10PERC); + sal_uInt16 nCntOfTop10 = nFlags >> 7; + + if( bTop10 ) + { + ScQueryEntry& aEntry = aParam.AppendEntry(); + ScQueryEntry::Item& rItem = aEntry.GetQueryItem(); + aEntry.bDoQuery = true; + aEntry.nField = static_cast(StartCol() + static_cast(nCol)); + aEntry.eOp = bTopOfTop10 ? + (bPercent ? SC_TOPPERC : SC_TOPVAL) : (bPercent ? SC_BOTPERC : SC_BOTVAL); + aEntry.eConnect = SC_AND; + + rItem.meType = ScQueryEntry::ByString; + rItem.maString = rPool.intern(OUString::number(nCntOfTop10)); + + rStrm.Ignore(20); + return; + } + + sal_uInt8 nType, nOper, nBoolErr, nVal; + sal_Int32 nRK; + double fVal; + + sal_uInt8 nStrLen[2] = { 0, 0 }; + ScQueryEntry aEntries[2]; + + for (size_t nE = 0; nE < 2; ++nE) + { + ScQueryEntry& rEntry = aEntries[nE]; + ScQueryEntry::Item& rItem = rEntry.GetQueryItem(); + bool bIgnore = false; + + nType = rStrm.ReaduInt8(); + nOper = rStrm.ReaduInt8(); + switch( nOper ) + { + case EXC_AFOPER_LESS: + rEntry.eOp = SC_LESS; + break; + case EXC_AFOPER_EQUAL: + rEntry.eOp = SC_EQUAL; + break; + case EXC_AFOPER_LESSEQUAL: + rEntry.eOp = SC_LESS_EQUAL; + break; + case EXC_AFOPER_GREATER: + rEntry.eOp = SC_GREATER; + break; + case EXC_AFOPER_NOTEQUAL: + rEntry.eOp = SC_NOT_EQUAL; + break; + case EXC_AFOPER_GREATEREQUAL: + rEntry.eOp = SC_GREATER_EQUAL; + break; + default: + rEntry.eOp = SC_EQUAL; + } + + switch( nType ) + { + case EXC_AFTYPE_RK: + nRK = rStrm.ReadInt32(); + rStrm.Ignore( 4 ); + rItem.maString = rPool.intern( + CreateFromDouble(XclTools::GetDoubleFromRK(nRK))); + break; + case EXC_AFTYPE_DOUBLE: + fVal = rStrm.ReadDouble(); + rItem.maString = rPool.intern(CreateFromDouble(fVal)); + break; + case EXC_AFTYPE_STRING: + rStrm.Ignore( 4 ); + nStrLen[ nE ] = rStrm.ReaduInt8(); + rStrm.Ignore( 3 ); + rItem.maString = svl::SharedString(); + break; + case EXC_AFTYPE_BOOLERR: + nBoolErr = rStrm.ReaduInt8(); + nVal = rStrm.ReaduInt8(); + rStrm.Ignore( 6 ); + rItem.maString = rPool.intern(OUString::number(nVal)); + bIgnore = (nBoolErr != 0); + break; + case EXC_AFTYPE_EMPTY: + rEntry.SetQueryByEmpty(); + break; + case EXC_AFTYPE_NOTEMPTY: + rEntry.SetQueryByNonEmpty(); + break; + default: + rStrm.Ignore( 8 ); + bIgnore = true; + } + + if (!bIgnore) + { + rEntry.bDoQuery = true; + rItem.meType = ScQueryEntry::ByString; + rEntry.nField = static_cast(StartCol() + static_cast(nCol)); + rEntry.eConnect = nE ? eConn : SC_AND; + } + } + + if (eConn == SC_AND) + { + for (size_t nE = 0; nE < 2; ++nE) + { + if (nStrLen[nE] && aEntries[nE].bDoQuery) + { + OUString aStr = rStrm.ReadUniString(nStrLen[nE]); + ExcelQueryToOooQuery(aStr, aEntries[nE]); + aEntries[nE].GetQueryItem().maString = rPool.intern(aStr); + aParam.AppendEntry() = aEntries[nE]; + } + } + } + else + { + assert( eConn == SC_OR && "eConn should be SC_AND or SC_OR"); + // Import only when both conditions are for simple equality, else + // import only the 1st condition due to conflict with the ordering of + // conditions. #i39464#. + // + // Example: Let A1 be a condition of column A, and B1 and B2 + // conditions of column B, connected with OR. Excel performs 'A1 AND + // (B1 OR B2)' in this case, but Calc would do '(A1 AND B1) OR B2' + // instead. + + if (bSimple1 && bSimple2 && nStrLen[0] && nStrLen[1]) + { + // Two simple OR'ed equal conditions. We can import this correctly. + ScQueryEntry& rEntry = aParam.AppendEntry(); + rEntry.bDoQuery = true; + rEntry.eOp = SC_EQUAL; + rEntry.eConnect = SC_AND; + ScQueryEntry::QueryItemsType aItems; + aItems.reserve(2); + ScQueryEntry::Item aItem1, aItem2; + aItem1.maString = rPool.intern(rStrm.ReadUniString(nStrLen[0])); + aItem1.meType = ScQueryEntry::ByString; + aItem2.maString = rPool.intern(rStrm.ReadUniString(nStrLen[1])); + aItem2.meType = ScQueryEntry::ByString; + aItems.push_back(aItem1); + aItems.push_back(aItem2); + rEntry.GetQueryItems().swap(aItems); + } + else if (nStrLen[0] && aEntries[0].bDoQuery) + { + // Due to conflict, we can import only the first condition. + OUString aStr = rStrm.ReadUniString(nStrLen[0]); + ExcelQueryToOooQuery(aStr, aEntries[0]); + aEntries[0].GetQueryItem().maString = rPool.intern(aStr); + aParam.AppendEntry() = aEntries[0]; + } + } +} + +void XclImpAutoFilterData::SetAdvancedRange( const ScRange* pRange ) +{ + if (pRange) + { + aCriteriaRange = *pRange; + bCriteria = true; + } + else + bCriteria = false; +} + +void XclImpAutoFilterData::SetExtractPos( const ScAddress& rAddr ) +{ + aParam.nDestCol = rAddr.Col(); + aParam.nDestRow = rAddr.Row(); + aParam.nDestTab = rAddr.Tab(); + aParam.bInplace = false; + aParam.bDestPers = true; +} + +void XclImpAutoFilterData::Apply() +{ + // Create the ScDBData() object if the AutoFilter is activated + // or if we need to create the Advanced Filter. + if( bActive || bCriteria) + { + ScDocument& rDoc = pExcRoot->pIR->GetDoc(); + pCurrDBData = new ScDBData(STR_DB_LOCAL_NONAME, Tab(), + StartCol(),StartRow(), EndCol(),EndRow() ); + if(bCriteria) + { + EnableRemoveFilter(); + + pCurrDBData->SetQueryParam( aParam ); + pCurrDBData->SetAdvancedQuerySource(&aCriteriaRange); + } + else + pCurrDBData->SetAdvancedQuerySource(nullptr); + rDoc.SetAnonymousDBData(Tab(), std::unique_ptr(pCurrDBData)); + } + + if( bActive ) + { + InsertQueryParam(); + } +} + +void XclImpAutoFilterData::EnableRemoveFilter() +{ + // only if this is a saved Advanced filter + if( !bActive && bAutoOrAdvanced ) + { + ScQueryEntry& aEntry = aParam.AppendEntry(); + aEntry.bDoQuery = true; + } + + // TBD: force the automatic activation of the + // "Remove Filter" by setting a virtual mouse click + // inside the advanced range +} + +void XclImpAutoFilterBuffer::Insert( RootData* pRoot, const ScRange& rRange) +{ + if( !GetByTab( rRange.aStart.Tab() ) ) + maFilters.push_back( std::make_shared( pRoot, rRange )); +} + +void XclImpAutoFilterBuffer::AddAdvancedRange( const ScRange& rRange ) +{ + XclImpAutoFilterData* pData = GetByTab( rRange.aStart.Tab() ); + if( pData ) + pData->SetAdvancedRange( &rRange ); +} + +void XclImpAutoFilterBuffer::AddExtractPos( const ScRange& rRange ) +{ + XclImpAutoFilterData* pData = GetByTab( rRange.aStart.Tab() ); + if( pData ) + pData->SetExtractPos( rRange.aStart ); +} + +void XclImpAutoFilterBuffer::Apply() +{ + for( const auto& rFilterPtr : maFilters ) + rFilterPtr->Apply(); +} + +XclImpAutoFilterData* XclImpAutoFilterBuffer::GetByTab( SCTAB nTab ) +{ + for( const auto& rFilterPtr : maFilters ) + { + if( rFilterPtr->Tab() == nTab ) + return rFilterPtr.get(); + } + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/excrecds.cxx b/sc/source/filter/excel/excrecds.cxx new file mode 100644 index 000000000..b175445bc --- /dev/null +++ b/sc/source/filter/excel/excrecds.cxx @@ -0,0 +1,1177 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace ::oox; + +using ::com::sun::star::uno::Sequence; + +//--------------------------------------------------------- class ExcDummy_00 - +const sal_uInt8 ExcDummy_00::pMyData[] = { + 0x5c, 0x00, 0x20, 0x00, 0x04, 'C', 'a', 'l', 'c', // WRITEACCESS + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 +}; +const std::size_t ExcDummy_00::nMyLen = sizeof( ExcDummy_00::pMyData ); + +//-------------------------------------------------------- class ExcDummy_04x - +const sal_uInt8 ExcDummy_040::pMyData[] = { + 0x40, 0x00, 0x02, 0x00, 0x00, 0x00, // BACKUP + 0x8d, 0x00, 0x02, 0x00, 0x00, 0x00, // HIDEOBJ +}; +const std::size_t ExcDummy_040::nMyLen = sizeof( ExcDummy_040::pMyData ); + +const sal_uInt8 ExcDummy_041::pMyData[] = { + 0x0e, 0x00, 0x02, 0x00, 0x01, 0x00, // PRECISION + 0xda, 0x00, 0x02, 0x00, 0x00, 0x00 // BOOKBOOL +}; +const std::size_t ExcDummy_041::nMyLen = sizeof( ExcDummy_041::pMyData ); + +//-------------------------------------------------------- class ExcDummy_02a - +const sal_uInt8 ExcDummy_02a::pMyData[] = { + 0x0d, 0x00, 0x02, 0x00, 0x01, 0x00, // CALCMODE + 0x0c, 0x00, 0x02, 0x00, 0x64, 0x00, // CALCCOUNT + 0x0f, 0x00, 0x02, 0x00, 0x01, 0x00, // REFMODE + 0x11, 0x00, 0x02, 0x00, 0x00, 0x00, // ITERATION + 0x10, 0x00, 0x08, 0x00, 0xfc, 0xa9, 0xf1, 0xd2, 0x4d, // DELTA + 0x62, 0x50, 0x3f, + 0x5f, 0x00, 0x02, 0x00, 0x01, 0x00 // SAVERECALC +}; +const std::size_t ExcDummy_02a::nMyLen = sizeof( ExcDummy_02a::pMyData ); + +//----------------------------------------------------------- class ExcRecord - + +void ExcRecord::Save( XclExpStream& rStrm ) +{ + SetRecHeader( GetNum(), GetLen() ); + XclExpRecord::Save( rStrm ); +} + +void ExcRecord::SaveCont( XclExpStream& /*rStrm*/ ) +{ +} + +void ExcRecord::WriteBody( XclExpStream& rStrm ) +{ + SaveCont( rStrm ); +} + +void ExcRecord::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ +} + +//--------------------------------------------------------- class ExcEmptyRec - + +void ExcEmptyRec::Save( XclExpStream& /*rStrm*/ ) +{ +} + +sal_uInt16 ExcEmptyRec::GetNum() const +{ + return 0; +} + +std::size_t ExcEmptyRec::GetLen() const +{ + return 0; +} + +//--------------------------------------------------------- class ExcDummyRec - + +void ExcDummyRec::Save( XclExpStream& rStrm ) +{ + rStrm.Write( GetData(), GetLen() ); // raw write mode +} + +sal_uInt16 ExcDummyRec::GetNum() const +{ + return 0x0000; +} + +//------------------------------------------------------- class ExcBoolRecord - + +void ExcBoolRecord::SaveCont( XclExpStream& rStrm ) +{ + rStrm << static_cast(bVal ? 0x0001 : 0x0000); +} + +std::size_t ExcBoolRecord::GetLen() const +{ + return 2; +} + +//--------------------------------------------------------- class ExcBof_Base - + +ExcBof_Base::ExcBof_Base() + : nDocType(0) + , nVers(0) + , nRupBuild(0x096C) // copied from Excel + , nRupYear(0x07C9) // copied from Excel +{ +} + +//-------------------------------------------------------------- class ExcBof - + +ExcBof::ExcBof() +{ + nDocType = 0x0010; + nVers = 0x0500; +} + +void ExcBof::SaveCont( XclExpStream& rStrm ) +{ + rStrm << nVers << nDocType << nRupBuild << nRupYear; +} + +sal_uInt16 ExcBof::GetNum() const +{ + return 0x0809; +} + +std::size_t ExcBof::GetLen() const +{ + return 8; +} + +//------------------------------------------------------------- class ExcBofW - + +ExcBofW::ExcBofW() +{ + nDocType = 0x0005; + nVers = 0x0500; +} + +void ExcBofW::SaveCont( XclExpStream& rStrm ) +{ + rStrm << nVers << nDocType << nRupBuild << nRupYear; +} + +sal_uInt16 ExcBofW::GetNum() const +{ + return 0x0809; +} + +std::size_t ExcBofW::GetLen() const +{ + return 8; +} + +//-------------------------------------------------------------- class ExcEof - + +sal_uInt16 ExcEof::GetNum() const +{ + return 0x000A; +} + +std::size_t ExcEof::GetLen() const +{ + return 0; +} + +//--------------------------------------------------------- class ExcDummy_00 - + +std::size_t ExcDummy_00::GetLen() const +{ + return nMyLen; +} + +const sal_uInt8* ExcDummy_00::GetData() const +{ + return pMyData; +} + +//-------------------------------------------------------- class ExcDummy_04x - + +std::size_t ExcDummy_040::GetLen() const +{ + return nMyLen; +} + +const sal_uInt8* ExcDummy_040::GetData() const +{ + return pMyData; +} + +std::size_t ExcDummy_041::GetLen() const +{ + return nMyLen; +} + +const sal_uInt8* ExcDummy_041::GetData() const +{ + return pMyData; +} + +//------------------------------------------------------------- class Exc1904 - + +Exc1904::Exc1904( const ScDocument& rDoc ) +{ + const Date& rDate = rDoc.GetFormatTable()->GetNullDate(); + bVal = (rDate == Date( 1, 1, 1904 )); + bDateCompatibility = (rDate != Date( 30, 12, 1899 )); +} + +sal_uInt16 Exc1904::GetNum() const +{ + return 0x0022; +} + +void Exc1904::SaveXml( XclExpXmlStream& rStrm ) +{ + bool bISOIEC = ( rStrm.getVersion() == oox::core::ISOIEC_29500_2008 ); + + if( bISOIEC ) + { + rStrm.WriteAttributes(XML_dateCompatibility, ToPsz(bDateCompatibility)); + } + + if( !bISOIEC || bDateCompatibility ) + { + rStrm.WriteAttributes(XML_date1904, ToPsz(bVal)); + } +} + +//------------------------------------------------------ class ExcBundlesheet - + +ExcBundlesheetBase::ExcBundlesheetBase( const RootData& rRootData, SCTAB nTabNum ) : + m_nStrPos( STREAM_SEEK_TO_END ), + m_nOwnPos( STREAM_SEEK_TO_END ), + nGrbit( rRootData.pER->GetTabInfo().IsVisibleTab( nTabNum ) ? 0x0000 : 0x0001 ), + nTab( nTabNum ) +{ +} + +ExcBundlesheetBase::ExcBundlesheetBase() : + m_nStrPos( STREAM_SEEK_TO_END ), + m_nOwnPos( STREAM_SEEK_TO_END ), + nGrbit( 0x0000 ), + nTab( SCTAB_GLOBAL ) +{ +} + +void ExcBundlesheetBase::UpdateStreamPos( XclExpStream& rStrm ) +{ + rStrm.SetSvStreamPos( m_nOwnPos ); + rStrm.DisableEncryption(); + rStrm << static_cast(m_nStrPos); + rStrm.EnableEncryption(); +} + +sal_uInt16 ExcBundlesheetBase::GetNum() const +{ + return 0x0085; +} + +ExcBundlesheet::ExcBundlesheet( const RootData& rRootData, SCTAB _nTab ) : + ExcBundlesheetBase( rRootData, _nTab ) +{ + OUString sTabName = rRootData.pER->GetTabInfo().GetScTabName( _nTab ); + OSL_ENSURE( sTabName.getLength() < 256, "ExcBundlesheet::ExcBundlesheet - table name too long" ); + aName = OUStringToOString(sTabName, rRootData.pER->GetTextEncoding()); +} + +void ExcBundlesheet::SaveCont( XclExpStream& rStrm ) +{ + m_nOwnPos = rStrm.GetSvStreamPos(); + rStrm << sal_uInt32(0x00000000) // dummy (stream position of the sheet) + << nGrbit; + rStrm.WriteByteString(aName); // 8 bit length, max 255 chars +} + +std::size_t ExcBundlesheet::GetLen() const +{ + return 7 + std::min( aName.getLength(), sal_Int32(255) ); +} + +//--------------------------------------------------------- class ExcDummy_02 - + +std::size_t ExcDummy_02a::GetLen() const +{ + return nMyLen; +} + +const sal_uInt8* ExcDummy_02a::GetData() const +{ + return pMyData; +} +//--------------------------------------------------------- class ExcDummy_02 - + +XclExpCountry::XclExpCountry( const XclExpRoot& rRoot ) : + XclExpRecord( EXC_ID_COUNTRY, 4 ) +{ + /* #i31530# set document country as UI country too - + needed for correct behaviour of number formats. */ + mnUICountry = mnDocCountry = static_cast< sal_uInt16 >( + ::msfilter::ConvertLanguageToCountry( rRoot.GetDocLanguage() ) ); +} + +void XclExpCountry::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnUICountry << mnDocCountry; +} + +// XclExpWsbool =============================================================== + +XclExpWsbool::XclExpWsbool( bool bFitToPages ) + : XclExpUInt16Record( EXC_ID_WSBOOL, EXC_WSBOOL_DEFAULTFLAGS ) +{ + if( bFitToPages ) + SetValue( GetValue() | EXC_WSBOOL_FITTOPAGE ); +} + +XclExpXmlSheetPr::XclExpXmlSheetPr( bool bFitToPages, SCTAB nScTab, const Color& rTabColor, XclExpFilterManager* pManager ) : + mnScTab(nScTab), mpManager(pManager), mbFitToPage(bFitToPages), maTabColor(rTabColor) {} + +void XclExpXmlSheetPr::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_sheetPr, + // OOXTODO: XML_syncHorizontal, + // OOXTODO: XML_syncVertical, + // OOXTODO: XML_syncRef, + // OOXTODO: XML_transitionEvaluation, + // OOXTODO: XML_transitionEntry, + // OOXTODO: XML_published, + // OOXTODO: XML_codeName, + XML_filterMode, mpManager ? ToPsz(mpManager->HasFilterMode(mnScTab)) : nullptr + // OOXTODO: XML_enableFormatConditionsCalculation + ); + + // Note : the order of child elements is significant. Don't change the order. + + // OOXTODO: XML_outlinePr + + if (maTabColor != COL_AUTO) + rWorksheet->singleElement(XML_tabColor, XML_rgb, XclXmlUtils::ToOString(maTabColor)); + + rWorksheet->singleElement(XML_pageSetUpPr, + // OOXTODO: XML_autoPageBreaks, + XML_fitToPage, ToPsz(mbFitToPage)); + + rWorksheet->endElement( XML_sheetPr ); +} + +// XclExpWindowProtection =============================================================== + +XclExpWindowProtection::XclExpWindowProtection(bool bValue) : + XclExpBoolRecord(EXC_ID_WINDOWPROTECT, bValue) +{ +} + +void XclExpWindowProtection::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.WriteAttributes(XML_lockWindows, ToPsz(GetBool())); +} + +// XclExpDocProtection =============================================================== + +XclExpProtection::XclExpProtection(bool bValue) : + XclExpBoolRecord(EXC_ID_PROTECT, bValue) +{ +} + +XclExpSheetProtection::XclExpSheetProtection(bool bValue, SCTAB nTab ) : + XclExpProtection( bValue), + mnTab(nTab) +{ +} + +void XclExpSheetProtection::SaveXml( XclExpXmlStream& rStrm ) +{ + ScDocument& rDoc = rStrm.GetRoot().GetDoc(); + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(mnTab); + if ( !pTabProtect ) + return; + + const ScOoxPasswordHash& rPH = pTabProtect->getPasswordHash(); + // Do not write any hash attributes if there is no password. + ScOoxPasswordHash aPH; + if (rPH.hasPassword()) + aPH = rPH; + + Sequence aHash = pTabProtect->getPasswordHash(PASSHASH_XL); + OString sHash; + if (aHash.getLength() >= 2) + { + sHash = OString::number( + ( static_cast(aHash[0]) << 8 + | static_cast(aHash[1]) ), + 16 ); + } + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->singleElement( XML_sheetProtection, + XML_algorithmName, aPH.maAlgorithmName.isEmpty() ? nullptr : aPH.maAlgorithmName.toUtf8().getStr(), + XML_hashValue, aPH.maHashValue.isEmpty() ? nullptr : aPH.maHashValue.toUtf8().getStr(), + XML_saltValue, aPH.maSaltValue.isEmpty() ? nullptr : aPH.maSaltValue.toUtf8().getStr(), + XML_spinCount, aPH.mnSpinCount ? OString::number( aPH.mnSpinCount).getStr() : nullptr, + XML_sheet, ToPsz( true ), + XML_password, sHash.isEmpty()? nullptr : sHash.getStr(), + XML_objects, pTabProtect->isOptionEnabled( ScTableProtection::OBJECTS ) ? nullptr : ToPsz( true ), + XML_scenarios, pTabProtect->isOptionEnabled( ScTableProtection::SCENARIOS ) ? nullptr : ToPsz( true ), + XML_formatCells, pTabProtect->isOptionEnabled( ScTableProtection::FORMAT_CELLS ) ? ToPsz( false ) : nullptr, + XML_formatColumns, pTabProtect->isOptionEnabled( ScTableProtection::FORMAT_COLUMNS ) ? ToPsz( false ) : nullptr, + XML_formatRows, pTabProtect->isOptionEnabled( ScTableProtection::FORMAT_ROWS ) ? ToPsz( false ) : nullptr, + XML_insertColumns, pTabProtect->isOptionEnabled( ScTableProtection::INSERT_COLUMNS ) ? ToPsz( false ) : nullptr, + XML_insertRows, pTabProtect->isOptionEnabled( ScTableProtection::INSERT_ROWS ) ? ToPsz( false ) : nullptr, + XML_insertHyperlinks, pTabProtect->isOptionEnabled( ScTableProtection::INSERT_HYPERLINKS ) ? ToPsz( false ) : nullptr, + XML_deleteColumns, pTabProtect->isOptionEnabled( ScTableProtection::DELETE_COLUMNS ) ? ToPsz( false ) : nullptr, + XML_deleteRows, pTabProtect->isOptionEnabled( ScTableProtection::DELETE_ROWS ) ? ToPsz( false ) : nullptr, + XML_selectLockedCells, pTabProtect->isOptionEnabled( ScTableProtection::SELECT_LOCKED_CELLS ) ? nullptr : ToPsz( true ), + XML_sort, pTabProtect->isOptionEnabled( ScTableProtection::SORT ) ? ToPsz( false ) : nullptr, + XML_autoFilter, pTabProtect->isOptionEnabled( ScTableProtection::AUTOFILTER ) ? ToPsz( false ) : nullptr, + XML_pivotTables, pTabProtect->isOptionEnabled( ScTableProtection::PIVOT_TABLES ) ? ToPsz( false ) : nullptr, + XML_selectUnlockedCells, pTabProtect->isOptionEnabled( ScTableProtection::SELECT_UNLOCKED_CELLS ) ? nullptr : ToPsz( true ) ); + + const ::std::vector& rProts( pTabProtect->getEnhancedProtection()); + if (rProts.empty()) + return; + + rWorksheet->startElement(XML_protectedRanges); + for (const auto& rProt : rProts) + { + SAL_WARN_IF( rProt.maSecurityDescriptorXML.isEmpty() && !rProt.maSecurityDescriptor.empty(), + "sc.filter", "XclExpSheetProtection::SaveXml: losing BIFF security descriptor"); + rWorksheet->singleElement( XML_protectedRange, + XML_name, rProt.maTitle.isEmpty() ? nullptr : rProt.maTitle.toUtf8().getStr(), + XML_securityDescriptor, rProt.maSecurityDescriptorXML.isEmpty() ? nullptr : rProt.maSecurityDescriptorXML.toUtf8().getStr(), + /* XXX 'password' is not part of OOXML, but Excel2013 + * writes it if loaded from BIFF, in which case + * 'algorithmName', 'hashValue', 'saltValue' and + * 'spinCount' are absent; so do we if it was present. */ + XML_password, rProt.mnPasswordVerifier ? OString::number( rProt.mnPasswordVerifier, 16).getStr() : nullptr, + XML_algorithmName, rProt.maPasswordHash.maAlgorithmName.isEmpty() ? nullptr : rProt.maPasswordHash.maAlgorithmName.toUtf8().getStr(), + XML_hashValue, rProt.maPasswordHash.maHashValue.isEmpty() ? nullptr : rProt.maPasswordHash.maHashValue.toUtf8().getStr(), + XML_saltValue, rProt.maPasswordHash.maSaltValue.isEmpty() ? nullptr : rProt.maPasswordHash.maSaltValue.toUtf8().getStr(), + XML_spinCount, rProt.maPasswordHash.mnSpinCount ? OString::number( rProt.maPasswordHash.mnSpinCount).getStr() : nullptr, + XML_sqref, rProt.maRangeList.is() ? XclXmlUtils::ToOString( rStrm.GetRoot().GetDoc(), *rProt.maRangeList).getStr() : nullptr); + } + rWorksheet->endElement( XML_protectedRanges); +} + +XclExpPassHash::XclExpPassHash(const Sequence& aHash) : + XclExpRecord(EXC_ID_PASSWORD, 2), + mnHash(0x0000) +{ + if (aHash.getLength() >= 2) + { + mnHash = ((aHash[0] << 8) & 0xFFFF); + mnHash |= (aHash[1] & 0xFF); + } +} + +XclExpPassHash::~XclExpPassHash() +{ +} + +void XclExpPassHash::WriteBody(XclExpStream& rStrm) +{ + rStrm << mnHash; +} + +XclExpFiltermode::XclExpFiltermode() : + XclExpEmptyRecord( EXC_ID_FILTERMODE ) +{ +} + +XclExpAutofilterinfo::XclExpAutofilterinfo( const ScAddress& rStartPos, SCCOL nScCol ) : + XclExpUInt16Record( EXC_ID_AUTOFILTERINFO, static_cast< sal_uInt16 >( nScCol ) ), + maStartPos( rStartPos ) +{ +} + +ExcFilterCondition::ExcFilterCondition() : + nType( EXC_AFTYPE_NOTUSED ), + nOper( EXC_AFOPER_EQUAL ) +{ +} + +ExcFilterCondition::~ExcFilterCondition() +{ +} + +std::size_t ExcFilterCondition::GetTextBytes() const +{ + return pText ? (1 + pText->GetBufferSize()) : 0; +} + +void ExcFilterCondition::SetCondition( sal_uInt8 nTp, sal_uInt8 nOp, const OUString* pT ) +{ + nType = nTp; + nOper = nOp; + pText.reset( pT ? new XclExpString( *pT, XclStrFlags::EightBitLength ) : nullptr); +} + +void ExcFilterCondition::Save( XclExpStream& rStrm ) +{ + rStrm << nType << nOper; + if (nType == EXC_AFTYPE_STRING) + { + OSL_ENSURE(pText, "ExcFilterCondition::Save() -- pText is NULL!"); + rStrm << sal_uInt32(0) << static_cast(pText->Len()) << sal_uInt16(0) << sal_uInt8(0); + } + else + rStrm << sal_uInt32(0) << sal_uInt32(0); +} + +static const char* lcl_GetOperator( sal_uInt8 nOper ) +{ + switch( nOper ) + { + case EXC_AFOPER_EQUAL: return "equal"; + case EXC_AFOPER_GREATER: return "greaterThan"; + case EXC_AFOPER_GREATEREQUAL: return "greaterThanOrEqual"; + case EXC_AFOPER_LESS: return "lessThan"; + case EXC_AFOPER_LESSEQUAL: return "lessThanOrEqual"; + case EXC_AFOPER_NOTEQUAL: return "notEqual"; + case EXC_AFOPER_NONE: + default: return "**none**"; + } +} + +static OString lcl_GetValue( sal_uInt8 nType, const XclExpString* pStr ) +{ + if (nType == EXC_AFTYPE_STRING) + return XclXmlUtils::ToOString(*pStr); + else + return OString(); +} + +void ExcFilterCondition::SaveXml( XclExpXmlStream& rStrm ) +{ + if( IsEmpty() ) + return; + + rStrm.GetCurrentStream()->singleElement( XML_customFilter, + XML_operator, lcl_GetOperator( nOper ), + XML_val, lcl_GetValue(nType, pText.get()) ); +} + +void ExcFilterCondition::SaveText( XclExpStream& rStrm ) +{ + if( nType == EXC_AFTYPE_STRING ) + { + OSL_ENSURE( pText, "ExcFilterCondition::SaveText() -- pText is NULL!" ); + pText->WriteFlagField( rStrm ); + pText->WriteBuffer( rStrm ); + } +} + +XclExpAutofilter::XclExpAutofilter( const XclExpRoot& rRoot, sal_uInt16 nC ) : + XclExpRecord( EXC_ID_AUTOFILTER, 24 ), + XclExpRoot( rRoot ), + meType(FilterCondition), + nCol( nC ), + nFlags( 0 ), + bHasBlankValue( false ) +{ +} + +bool XclExpAutofilter::AddCondition( ScQueryConnect eConn, sal_uInt8 nType, sal_uInt8 nOp, + const OUString* pText, bool bSimple ) +{ + if( !aCond[ 1 ].IsEmpty() ) + return false; + + sal_uInt16 nInd = aCond[ 0 ].IsEmpty() ? 0 : 1; + + if( nInd == 1 ) + nFlags |= (eConn == SC_OR) ? EXC_AFFLAG_OR : EXC_AFFLAG_AND; + if( bSimple ) + nFlags |= (nInd == 0) ? EXC_AFFLAG_SIMPLE1 : EXC_AFFLAG_SIMPLE2; + + aCond[ nInd ].SetCondition( nType, nOp, pText ); + + AddRecSize( aCond[ nInd ].GetTextBytes() ); + + return true; +} + +bool XclExpAutofilter::HasCondition() const +{ + return !aCond[0].IsEmpty(); +} + +bool XclExpAutofilter::AddEntry( const ScQueryEntry& rEntry ) +{ + const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + + if (rItems.empty()) + { + if (GetOutput() != EXC_OUTPUT_BINARY) + { + // tdf#123353 XLSX export + meType = BlankValue; + return false; + } + // XLS export + return true; + } + + if (GetOutput() != EXC_OUTPUT_BINARY && rItems.size() > 1) + { + AddMultiValueEntry(rEntry); + return false; + } + + bool bConflict = false; + OUString sText; + const ScQueryEntry::Item& rItem = rItems[0]; + if (!rItem.maString.isEmpty()) + { + sText = rItem.maString.getString(); + switch( rEntry.eOp ) + { + case SC_CONTAINS: + case SC_DOES_NOT_CONTAIN: + { + sText = "*" + sText + "*"; + } + break; + case SC_BEGINS_WITH: + case SC_DOES_NOT_BEGIN_WITH: + sText += "*"; + break; + case SC_ENDS_WITH: + case SC_DOES_NOT_END_WITH: + sText = "*" + sText; + break; + default: + { + //nothing + } + } + } + + // empty/nonempty fields + if (rEntry.IsQueryByEmpty()) + { + bConflict = !AddCondition(rEntry.eConnect, EXC_AFTYPE_EMPTY, EXC_AFOPER_NONE, nullptr, true); + bHasBlankValue = true; + } + else if(rEntry.IsQueryByNonEmpty()) + bConflict = !AddCondition( rEntry.eConnect, EXC_AFTYPE_NOTEMPTY, EXC_AFOPER_NONE, nullptr, true ); + else if (rEntry.IsQueryByTextColor() || rEntry.IsQueryByBackgroundColor()) + { + AddColorEntry(rEntry); + } + // other conditions + else + { + // top10 flags + sal_uInt16 nNewFlags = 0x0000; + switch( rEntry.eOp ) + { + case SC_TOPVAL: + nNewFlags = (EXC_AFFLAG_TOP10 | EXC_AFFLAG_TOP10TOP); + break; + case SC_BOTVAL: + nNewFlags = EXC_AFFLAG_TOP10; + break; + case SC_TOPPERC: + nNewFlags = (EXC_AFFLAG_TOP10 | EXC_AFFLAG_TOP10TOP | EXC_AFFLAG_TOP10PERC); + break; + case SC_BOTPERC: + nNewFlags = (EXC_AFFLAG_TOP10 | EXC_AFFLAG_TOP10PERC); + break; + default:; + } + bool bNewTop10 = ::get_flag( nNewFlags, EXC_AFFLAG_TOP10 ); + + bConflict = HasTop10() && bNewTop10; + if( !bConflict ) + { + if( bNewTop10 ) + { + sal_uInt32 nIndex = 0; + double fVal = 0.0; + if (GetFormatter().IsNumberFormat(sText, nIndex, fVal)) + { + if (fVal < 0) fVal = 0; + if (fVal >= 501) fVal = 500; + } + nFlags |= (nNewFlags | static_cast(fVal) << 7); + } + // normal condition + else + { + if (GetOutput() != EXC_OUTPUT_BINARY && rEntry.eOp == SC_EQUAL) + { + AddMultiValueEntry(rEntry); + return false; + } + + sal_uInt8 nOper = EXC_AFOPER_NONE; + + switch( rEntry.eOp ) + { + case SC_EQUAL: nOper = EXC_AFOPER_EQUAL; break; + case SC_LESS: nOper = EXC_AFOPER_LESS; break; + case SC_GREATER: nOper = EXC_AFOPER_GREATER; break; + case SC_LESS_EQUAL: nOper = EXC_AFOPER_LESSEQUAL; break; + case SC_GREATER_EQUAL: nOper = EXC_AFOPER_GREATEREQUAL; break; + case SC_NOT_EQUAL: nOper = EXC_AFOPER_NOTEQUAL; break; + case SC_CONTAINS: + case SC_BEGINS_WITH: + case SC_ENDS_WITH: + nOper = EXC_AFOPER_EQUAL; break; + case SC_DOES_NOT_CONTAIN: + case SC_DOES_NOT_BEGIN_WITH: + case SC_DOES_NOT_END_WITH: + nOper = EXC_AFOPER_NOTEQUAL; break; + default:; + } + bConflict = !AddCondition( rEntry.eConnect, EXC_AFTYPE_STRING, nOper, &sText); + } + } + } + return bConflict; +} + +void XclExpAutofilter::AddMultiValueEntry( const ScQueryEntry& rEntry ) +{ + meType = MultiValue; + const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + for (const auto& rItem : rItems) + { + if( rItem.maString.isEmpty() ) + bHasBlankValue = true; + else + maMultiValues.push_back(std::make_pair(rItem.maString.getString(), rItem.meType == ScQueryEntry::ByDate)); + } +} + +void XclExpAutofilter::AddColorEntry(const ScQueryEntry& rEntry) +{ + meType = ColorValue; + const ScQueryEntry::QueryItemsType& rItems = rEntry.GetQueryItems(); + for (const auto& rItem : rItems) + { + maColorValues.push_back( + std::make_pair(rItem.maColor, rItem.meType == ScQueryEntry::ByBackgroundColor)); + // Ensure that selected color(s) will be added to dxf: selection can be not in list + // of already added to dfx colors taken from filter range + if (GetDxfs().GetDxfByColor(rItem.maColor) == -1) + GetDxfs().AddColor(rItem.maColor); + } +} + +void XclExpAutofilter::WriteBody( XclExpStream& rStrm ) +{ + rStrm << nCol << nFlags; + aCond[ 0 ].Save( rStrm ); + aCond[ 1 ].Save( rStrm ); + aCond[ 0 ].SaveText( rStrm ); + aCond[ 1 ].SaveText( rStrm ); +} + +void XclExpAutofilter::SaveXml( XclExpXmlStream& rStrm ) +{ + if (meType == FilterCondition && !HasCondition() && !HasTop10()) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement( XML_filterColumn, + XML_colId, OString::number(nCol) + // OOXTODO: XML_hiddenButton, AutoFilter12 fHideArrow? + // OOXTODO: XML_showButton + ); + + switch (meType) + { + case FilterCondition: + { + if( HasTop10() ) + { + rWorksheet->singleElement( XML_top10, + XML_top, ToPsz( get_flag( nFlags, EXC_AFFLAG_TOP10TOP ) ), + XML_percent, ToPsz( get_flag( nFlags, EXC_AFFLAG_TOP10PERC ) ), + XML_val, OString::number(nFlags >> 7) + // OOXTODO: XML_filterVal + ); + } + else + { + rWorksheet->startElement(XML_customFilters, XML_and, + ToPsz((nFlags & EXC_AFFLAG_ANDORMASK) == EXC_AFFLAG_AND)); + aCond[0].SaveXml(rStrm); + aCond[1].SaveXml(rStrm); + rWorksheet->endElement(XML_customFilters); + } + // OOXTODO: XML_dynamicFilter, XML_extLst, XML_filters, XML_iconFilter + } + break; + case ColorValue: + { + if (!maColorValues.empty()) + { + Color color = maColorValues[0].first; + rtl::Reference pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + + if (maColorValues[0].second) // is background color + { + pAttrList->add(XML_cellColor, OString::number(1)); + } + else + { + pAttrList->add(XML_cellColor, OString::number(0)); + } + pAttrList->add(XML_dxfId, OString::number(GetDxfs().GetDxfByColor(color))); + rWorksheet->singleElement(XML_colorFilter, pAttrList); + } + } + break; + case BlankValue: + { + rWorksheet->singleElement(XML_filters, XML_blank, "1"); + } + break; + case MultiValue: + { + if( bHasBlankValue ) + rWorksheet->startElement(XML_filters, XML_blank, "1"); + else + rWorksheet->startElement(XML_filters); + + for (const auto& rMultiValue : maMultiValues) + { + OString aStr = OUStringToOString(rMultiValue.first, RTL_TEXTENCODING_UTF8); + if( !rMultiValue.second ) + { + const char* pz = aStr.getStr(); + rWorksheet->singleElement(XML_filter, XML_val, pz); + } + else + { + rtl::Reference pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + sal_Int32 aDateGroup[3] = { XML_year, XML_month, XML_day }; + sal_Int32 idx = 0; + for (size_t i = 0; idx >= 0 && i < 3; i++) + { + OString kw = aStr.getToken(0, '-', idx); + kw = kw.trim(); + if (!kw.isEmpty()) + { + pAttrList->add(aDateGroup[i], kw); + } + } + // TODO: date filter can only handle YYYY-MM-DD date formats, so XML_dateTimeGrouping value + // will be "day" as default, until date filter cannot handle HH:MM:SS. + pAttrList->add(XML_dateTimeGrouping, "day"); + rWorksheet->singleElement(XML_dateGroupItem, pAttrList); + } + } + rWorksheet->endElement(XML_filters); + } + break; + } + rWorksheet->endElement( XML_filterColumn ); +} + +ExcAutoFilterRecs::ExcAutoFilterRecs( const XclExpRoot& rRoot, SCTAB nTab, const ScDBData* pDefinedData ) : + XclExpRoot( rRoot ), + mbAutoFilter (false) +{ + XclExpNameManager& rNameMgr = GetNameManager(); + + bool bFound = false; + bool bAdvanced = false; + const ScDBData* pData = (pDefinedData ? pDefinedData : rRoot.GetDoc().GetAnonymousDBData(nTab)); + ScRange aAdvRange; + if (pData) + { + bAdvanced = pData->GetAdvancedQuerySource( aAdvRange ); + bFound = (pData->HasQueryParam() || pData->HasAutoFilter() || bAdvanced); + } + if( !bFound ) + return; + + ScQueryParam aParam; + pData->GetQueryParam( aParam ); + + ScRange aRange( aParam.nCol1, aParam.nRow1, aParam.nTab, + aParam.nCol2, aParam.nRow2, aParam.nTab ); + SCCOL nColCnt = aParam.nCol2 - aParam.nCol1 + 1; + + maRef = aRange; + + // #i2394# built-in defined names must be sorted by containing sheet name + if (!pDefinedData) + rNameMgr.InsertBuiltInName( EXC_BUILTIN_FILTERDATABASE, aRange ); + + // advanced filter + if( bAdvanced ) + { + // filter criteria, excel allows only same table + if( !pDefinedData && aAdvRange.aStart.Tab() == nTab ) + rNameMgr.InsertBuiltInName( EXC_BUILTIN_CRITERIA, aAdvRange ); + + // filter destination range, excel allows only same table + if( !aParam.bInplace ) + { + ScRange aDestRange( aParam.nDestCol, aParam.nDestRow, aParam.nDestTab ); + aDestRange.aEnd.IncCol( nColCnt - 1 ); + if( !pDefinedData && aDestRange.aStart.Tab() == nTab ) + rNameMgr.InsertBuiltInName( EXC_BUILTIN_EXTRACT, aDestRange ); + } + + m_pFilterMode = new XclExpFiltermode; + } + // AutoFilter + else + { + bool bConflict = false; + bool bContLoop = true; + bool bHasOr = false; + SCCOLROW nFirstField = aParam.GetEntry( 0 ).nField; + + // create AUTOFILTER records for filtered columns + for( SCSIZE nEntry = 0; !bConflict && bContLoop && (nEntry < aParam.GetEntryCount()); nEntry++ ) + { + const ScQueryEntry& rEntry = aParam.GetEntry( nEntry ); + + bContLoop = rEntry.bDoQuery; + if( bContLoop ) + { + XclExpAutofilter* pFilter = GetByCol( static_cast(rEntry.nField) - aRange.aStart.Col() ); + + if( nEntry > 0 ) + bHasOr |= (rEntry.eConnect == SC_OR); + + bConflict = (nEntry > 1) && bHasOr; + if( !bConflict ) + bConflict = (nEntry == 1) && (rEntry.eConnect == SC_OR) && + (nFirstField != rEntry.nField); + if( !bConflict ) + bConflict = pFilter->AddEntry( rEntry ); + } + } + + // additional tests for conflicts + for( size_t nPos = 0, nSize = maFilterList.GetSize(); !bConflict && (nPos < nSize); ++nPos ) + { + XclExpAutofilterRef xFilter = maFilterList.GetRecord( nPos ); + bConflict = xFilter->HasCondition() && xFilter->HasTop10(); + } + + if( bConflict ) + maFilterList.RemoveAllRecords(); + + if( !maFilterList.IsEmpty() ) + m_pFilterMode = new XclExpFiltermode; + m_pFilterInfo = new XclExpAutofilterinfo( aRange.aStart, nColCnt ); + + if (maFilterList.IsEmpty () && !bConflict) + mbAutoFilter = true; + + // get sort criteria + { + ScSortParam aSortParam; + pData->GetSortParam( aSortParam ); + + ScUserList* pList = ScGlobal::GetUserList(); + if (aSortParam.bUserDef && pList && pList->size() > aSortParam.nUserIndex) + { + // get sorted area without headers + maSortRef = ScRange( + aParam.nCol1, aParam.nRow1 + (aSortParam.bHasHeader? 1 : 0), aParam.nTab, + aParam.nCol2, aParam.nRow2, aParam.nTab ); + + // get sorted columns with custom lists + const ScUserListData& rData = (*pList)[aSortParam.nUserIndex]; + + // get column index and sorting direction + SCCOLROW nField = 0; + bool bSortAscending=true; + for (const auto & rKey : aSortParam.maKeyState) + { + if (rKey.bDoSort) + { + nField = rKey.nField; + bSortAscending = rKey.bAscending; + break; + } + } + + // remember sort criteria + const ScRange aSortedColumn( + nField, aParam.nRow1 + (aSortParam.bHasHeader? 1 : 0), aParam.nTab, + nField, aParam.nRow2, aParam.nTab ); + const OUString aItemList = rData.GetString(); + + maSortCustomList.emplace_back(aSortedColumn, aItemList, !bSortAscending); + } + } + } +} + +ExcAutoFilterRecs::~ExcAutoFilterRecs() +{ +} + +XclExpAutofilter* ExcAutoFilterRecs::GetByCol( SCCOL nCol ) +{ + XclExpAutofilterRef xFilter; + for( size_t nPos = 0, nSize = maFilterList.GetSize(); nPos < nSize; ++nPos ) + { + xFilter = maFilterList.GetRecord( nPos ); + if( xFilter->GetCol() == static_cast(nCol) ) + return xFilter.get(); + } + xFilter = new XclExpAutofilter( GetRoot(), static_cast(nCol) ); + maFilterList.AppendRecord( xFilter ); + return xFilter.get(); +} + +bool ExcAutoFilterRecs::IsFiltered( SCCOL nCol ) +{ + for( size_t nPos = 0, nSize = maFilterList.GetSize(); nPos < nSize; ++nPos ) + if( maFilterList.GetRecord( nPos )->GetCol() == static_cast(nCol) ) + return true; + return false; +} + +void ExcAutoFilterRecs::AddObjRecs() +{ + if( m_pFilterInfo ) + { + ScAddress aAddr( m_pFilterInfo->GetStartPos() ); + for( SCCOL nObj = 0, nCount = m_pFilterInfo->GetColCount(); nObj < nCount; nObj++ ) + { + std::unique_ptr pObjRec(new XclObjDropDown( GetObjectManager(), aAddr, IsFiltered( nObj ) )); + GetObjectManager().AddObj( std::move(pObjRec) ); + aAddr.IncCol(); + } + } +} + +void ExcAutoFilterRecs::Save( XclExpStream& rStrm ) +{ + if( m_pFilterMode ) + m_pFilterMode->Save( rStrm ); + if( m_pFilterInfo ) + m_pFilterInfo->Save( rStrm ); + maFilterList.Save( rStrm ); +} + +void ExcAutoFilterRecs::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maFilterList.IsEmpty() && !mbAutoFilter ) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_autoFilter, XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maRef)); + // OOXTODO: XML_extLst, XML_sortState + if( !maFilterList.IsEmpty() ) + maFilterList.SaveXml( rStrm ); + + if (!maSortCustomList.empty()) + { + rWorksheet->startElement(XML_sortState, XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maSortRef)); + + for (const auto & rSortCriteria : maSortCustomList) + { + if (std::get<2>(rSortCriteria)) + rWorksheet->singleElement(XML_sortCondition, + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), + std::get<0>(rSortCriteria)), + XML_descending, "1", + XML_customList, std::get<1>(rSortCriteria).toUtf8().getStr()); + else + rWorksheet->singleElement(XML_sortCondition, + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), + std::get<0>(rSortCriteria)), + XML_customList, std::get<1>(rSortCriteria).toUtf8().getStr()); + } + + rWorksheet->endElement(XML_sortState); + } + + rWorksheet->endElement( XML_autoFilter ); +} + +bool ExcAutoFilterRecs::HasFilterMode() const +{ + return m_pFilterMode != nullptr; +} + +XclExpFilterManager::XclExpFilterManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpFilterManager::InitTabFilter( SCTAB nScTab ) +{ + maFilterMap[ nScTab ] = new ExcAutoFilterRecs( GetRoot(), nScTab, nullptr ); +} + +XclExpRecordRef XclExpFilterManager::CreateRecord( SCTAB nScTab ) +{ + XclExpTabFilterRef xRec; + XclExpTabFilterMap::iterator aIt = maFilterMap.find( nScTab ); + if( aIt != maFilterMap.end() ) + { + xRec = aIt->second; + xRec->AddObjRecs(); + } + return xRec; +} + +bool XclExpFilterManager::HasFilterMode( SCTAB nScTab ) +{ + XclExpTabFilterMap::iterator aIt = maFilterMap.find( nScTab ); + if( aIt != maFilterMap.end() ) + { + return aIt->second->HasFilterMode(); + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/exctools.cxx b/sc/source/filter/excel/exctools.cxx new file mode 100644 index 000000000..6e9d91777 --- /dev/null +++ b/sc/source/filter/excel/exctools.cxx @@ -0,0 +1,245 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +RootData::RootData() +{ + eDateiTyp = BiffX; + pFmlaConverter = nullptr; + + pTabId = nullptr; + pUserBViewList = nullptr; + + pIR = nullptr; + pER = nullptr; + pColRowBuff = nullptr; +} + +RootData::~RootData() +{ + pExtSheetBuff.reset(); + pShrfmlaBuff.reset(); + pExtNameBuff.reset(); + pAutoFilterBuffer.reset(); +} + +XclImpOutlineBuffer::XclImpOutlineBuffer( SCSIZE nNewSize ) : + maLevels(0, nNewSize, 0), + mpOutlineArray(nullptr), + mnEndPos(nNewSize), + mnMaxLevel(0), + mbButtonAfter(true) +{ +} + +XclImpOutlineBuffer::~XclImpOutlineBuffer() +{ +} + +void XclImpOutlineBuffer::SetLevel( SCSIZE nIndex, sal_uInt8 nVal, bool bCollapsed ) +{ + maLevels.insert_back(nIndex, nIndex+1, nVal); + if (nVal > mnMaxLevel) + mnMaxLevel = nVal; + if (bCollapsed) + maCollapsedPosSet.insert(nIndex); +} + +void XclImpOutlineBuffer::SetOutlineArray( ScOutlineArray* pOArray ) +{ + mpOutlineArray = pOArray; +} + +void XclImpOutlineBuffer::MakeScOutline() +{ + if (!mpOutlineArray) + return; + + ::std::vector aOutlineStack; + aOutlineStack.reserve(mnMaxLevel); + for (const auto& [nPos, nLevel] : maLevels) + { + if (nPos >= mnEndPos) + { + // Don't go beyond the max allowed position. + OSL_ENSURE(aOutlineStack.empty(), "XclImpOutlineBuffer::MakeScOutline: outline stack not empty but expected to be."); + break; + } + sal_uInt8 nCurLevel = static_cast(aOutlineStack.size()); + if (nLevel > nCurLevel) + { + for (sal_uInt8 i = 0; i < nLevel - nCurLevel; ++i) + aOutlineStack.push_back(nPos); + } + else + { + OSL_ENSURE(nLevel <= nCurLevel, "XclImpOutlineBuffer::MakeScOutline: unexpected level!"); + for (sal_uInt8 i = 0; i < nCurLevel - nLevel; ++i) + { + if (aOutlineStack.empty()) + { + // Something is wrong. + return; + } + SCSIZE nFirstPos = aOutlineStack.back(); + aOutlineStack.pop_back(); + bool bCollapsed = false; + if (mbButtonAfter) + bCollapsed = maCollapsedPosSet.count(nPos) > 0; + else if (nFirstPos > 0) + bCollapsed = maCollapsedPosSet.count(nFirstPos-1) > 0; + + bool bDummy; + mpOutlineArray->Insert(nFirstPos, nPos-1, bDummy, bCollapsed); + } + } + } +} + +void XclImpOutlineBuffer::SetLevelRange( SCSIZE nF, SCSIZE nL, sal_uInt8 nVal, bool bCollapsed ) +{ + if (nF > nL) + // invalid range + return; + + maLevels.insert_back(nF, nL+1, nVal); + + if (bCollapsed) + maCollapsedPosSet.insert(nF); +} + +void XclImpOutlineBuffer::SetButtonMode( bool bRightOrUnder ) +{ + mbButtonAfter = bRightOrUnder; +} + +ExcScenarioCell::ExcScenarioCell( const sal_uInt16 nC, const sal_uInt16 nR ) + : nCol( nC ), nRow( nR ) +{ +} + +ExcScenario::ExcScenario( XclImpStream& rIn, const RootData& rR ) + : nTab( rR.pIR->GetCurrScTab() ) +{ + sal_uInt16 nCref; + sal_uInt8 nName, nComment; + + nCref = rIn.ReaduInt16(); + nProtected = rIn.ReaduInt8(); + rIn.Ignore( 1 ); // Hide + nName = rIn.ReaduInt8(); + nComment = rIn.ReaduInt8(); + rIn.Ignore( 1 ); // instead of nUser! + + if( nName ) + aName = rIn.ReadUniString( nName ); + else + { + aName = "Scenery"; + rIn.Ignore( 1 ); + } + + rIn.ReadUniString(); // username + + if( nComment ) + aComment = rIn.ReadUniString(); + + sal_uInt16 n = nCref; + sal_uInt16 nC, nR; + aEntries.reserve(n); + while( n ) + { + nR = rIn.ReaduInt16(); + nC = rIn.ReaduInt16(); + + aEntries.emplace_back( nC, nR ); + + n--; + } + + for (auto& rEntry : aEntries) + rEntry.SetValue(rIn.ReadUniString()); +} + +void ExcScenario::Apply( const XclImpRoot& rRoot, const bool bLast ) +{ + ScDocument& r = rRoot.GetDoc(); + OUString aSzenName( aName ); + sal_uInt16 nNewTab = nTab + 1; + + if( !r.InsertTab( nNewTab, aSzenName ) ) + return; + + r.SetScenario( nNewTab, true ); + // do not show scenario frames + const ScScenarioFlags nFlags = ScScenarioFlags::CopyAll + | (nProtected ? ScScenarioFlags::Protected : ScScenarioFlags::NONE); + /* | ScScenarioFlags::ShowFrame*/ + r.SetScenarioData( nNewTab, aComment, COL_LIGHTGRAY, nFlags); + + for (const auto& rEntry : aEntries) + { + sal_uInt16 nCol = rEntry.nCol; + sal_uInt16 nRow = rEntry.nRow; + OUString aVal = rEntry.GetValue(); + + r.ApplyFlagsTab( nCol, nRow, nCol, nRow, nNewTab, ScMF::Scenario ); + + r.SetString( nCol, nRow, nNewTab, aVal ); + } + + if( bLast ) + r.SetActiveScenario( nNewTab, true ); + + // modify what the Active tab is set to if the new + // scenario tab occurs before the active tab. + ScExtDocSettings& rDocSett = rRoot.GetExtDocOptions().GetDocSettings(); + if( (static_cast< SCCOL >( nTab ) < rDocSett.mnDisplTab) && (rDocSett.mnDisplTab < MAXTAB) ) + ++rDocSett.mnDisplTab; + rRoot.GetTabInfo().InsertScTab( nNewTab ); +} + +void ExcScenarioList::Apply( const XclImpRoot& rRoot ) +{ + sal_uInt16 n = static_cast(aEntries.size()); + + std::vector< std::unique_ptr >::reverse_iterator iter; + for (iter = aEntries.rbegin(); iter != aEntries.rend(); ++iter) + { + n--; + (*iter)->Apply(rRoot, n == nLastScenario); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/expop2.cxx b/sc/source/filter/excel/expop2.cxx new file mode 100644 index 000000000..ee8ba0fff --- /dev/null +++ b/sc/source/filter/excel/expop2.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 + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include + +#include + +#include +#include + +namespace com::sun::star::document { class XDocumentProperties; } + +namespace { + +enum class VBAExportMode +{ + NONE, + REEXPORT_STREAM, + FULL_EXPORT +}; + +} + +ExportBiff5::ExportBiff5( XclExpRootData& rExpData, SvStream& rStrm ): + ExportTyp( rStrm ), + XclExpRoot( rExpData ) +{ + // only need part of the Root data + pExcRoot = &GetOldRoot(); + pExcRoot->pER = this; // ExcRoot -> XclExpRoot + pExcRoot->eDateiTyp = Biff5; + pExcDoc.reset( new ExcDocument( *this ) ); +} + +ExportBiff5::~ExportBiff5() +{ +} + +ErrCode ExportBiff5::Write() +{ + SfxObjectShell* pDocShell = GetDocShell(); + OSL_ENSURE( pDocShell, "ExportBiff5::Write - no document shell" ); + + tools::SvRef xRootStrg = GetRootStorage(); + OSL_ENSURE( xRootStrg.is(), "ExportBiff5::Write - no root storage" ); + + VBAExportMode eVbaExportMode = VBAExportMode::NONE; + if( GetBiff() == EXC_BIFF8 ) + { + if (officecfg::Office::Calc::Filter::Import::VBA::UseExport::get()) + eVbaExportMode = VBAExportMode::FULL_EXPORT; + else + { + const SvtFilterOptions& rFilterOpt = SvtFilterOptions::Get(); + if (rFilterOpt.IsLoadExcelBasicStorage()) + eVbaExportMode = VBAExportMode::REEXPORT_STREAM; + } + } + + if ( pDocShell && xRootStrg.is() && eVbaExportMode == VBAExportMode::FULL_EXPORT) + { + VbaExport aExport(pDocShell->GetModel()); + if (aExport.containsVBAProject()) + { + tools::SvRef xVBARoot = xRootStrg->OpenSotStorage("_VBA_PROJECT_CUR"); + aExport.exportVBA( xVBARoot.get() ); + } + } + else if( pDocShell && xRootStrg.is() && eVbaExportMode == VBAExportMode::REEXPORT_STREAM ) + { + SvxImportMSVBasic aBasicImport( *pDocShell, *xRootStrg ); + const ErrCode nErr = aBasicImport.SaveOrDelMSVBAStorage( true, EXC_STORAGE_VBA_PROJECT ); + if( nErr != ERRCODE_NONE ) + pDocShell->SetError(nErr); + } + + pExcDoc->ReadDoc(); // ScDoc -> ExcDoc + pExcDoc->Write( aOut ); // wechstreamen + + if( pDocShell && xRootStrg.is() ) + { + using namespace ::com::sun::star; + uno::Reference xDPS( + pDocShell->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference xDocProps + = xDPS->getDocumentProperties(); + if ( SvtFilterOptions::Get().IsEnableCalcPreview() ) + { + std::shared_ptr xMetaFile = + pDocShell->GetPreviewMetaFile(); + uno::Sequence metaFile( + sfx2::convertMetaFile(xMetaFile.get())); + sfx2::SaveOlePropertySet( xDocProps, xRootStrg.get(), &metaFile ); + } + else + sfx2::SaveOlePropertySet( xDocProps, xRootStrg.get() ); + } + + const XclExpAddressConverter& rAddrConv = GetAddressConverter(); + if( rAddrConv.IsRowTruncated() ) + return SCWARN_EXPORT_MAXROW; + if( rAddrConv.IsColTruncated() ) + return SCWARN_EXPORT_MAXCOL; + if( rAddrConv.IsTabTruncated() ) + return SCWARN_EXPORT_MAXTAB; + + return ERRCODE_NONE; +} + +ExportBiff8::ExportBiff8( XclExpRootData& rExpData, SvStream& rStrm ) : + ExportBiff5( rExpData, rStrm ) +{ + pExcRoot->eDateiTyp = Biff8; +} + +ExportBiff8::~ExportBiff8() +{ +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/export/SparklineExt.cxx b/sc/source/filter/excel/export/SparklineExt.cxx new file mode 100644 index 000000000..487698e19 --- /dev/null +++ b/sc/source/filter/excel/export/SparklineExt.cxx @@ -0,0 +1,243 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +#include +#include +#include +#include +#include + +using namespace oox; + +namespace xcl::exp +{ +SparklineExt::SparklineExt(const XclExpRoot& rRoot) + : XclExpExt(rRoot) +{ + maURI = "{05C60535-1F16-4fd2-B633-F4F36F0B64E0}"; +} + +void SparklineExt::SaveXml(XclExpXmlStream& rStream) +{ + auto& rDocument = GetDoc(); + + auto* pSparklineList = rDocument.GetSparklineList(GetCurrScTab()); + if (!pSparklineList) + return; + + auto const& rSparklineGroups = pSparklineList->getSparklineGroups(); + + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + rWorksheet->startElement(XML_ext, FSNS(XML_xmlns, XML_x14), + rStream.getNamespaceURL(OOX_NS(xls14Lst)), XML_uri, maURI); + + rWorksheet->startElementNS(XML_x14, XML_sparklineGroups, FSNS(XML_xmlns, XML_xm), + rStream.getNamespaceURL(OOX_NS(xm))); + + for (auto const& pSparklineGroup : rSparklineGroups) + { + auto const& rSparklineVector = pSparklineList->getSparklinesFor(pSparklineGroup); + addSparklineGroup(rStream, *pSparklineGroup, rSparklineVector); + } + + rWorksheet->endElementNS(XML_x14, XML_sparklineGroups); + rWorksheet->endElement(XML_ext); +} + +void SparklineExt::addSparklineGroupAttributes( + rtl::Reference& pAttrList, + sc::SparklineAttributes& rAttributes) +{ + if (rAttributes.getLineWeight() != 0.75) + pAttrList->add(XML_lineWeight, OString::number(rAttributes.getLineWeight())); + + if (rAttributes.getType() != sc::SparklineType::Line) + { + if (rAttributes.getType() == sc::SparklineType::Column) + pAttrList->add(XML_type, "column"); + else if (rAttributes.getType() == sc::SparklineType::Stacked) + pAttrList->add(XML_type, "stacked"); + } + + if (rAttributes.isDateAxis()) + pAttrList->add(XML_dateAxis, "1"); + + if (rAttributes.getDisplayEmptyCellsAs() != sc::DisplayEmptyCellsAs::Zero) + { + if (rAttributes.getDisplayEmptyCellsAs() == sc::DisplayEmptyCellsAs::Gap) + pAttrList->add(XML_displayEmptyCellsAs, "gap"); + else if (rAttributes.getDisplayEmptyCellsAs() == sc::DisplayEmptyCellsAs::Span) + pAttrList->add(XML_displayEmptyCellsAs, "span"); + } + + if (rAttributes.isMarkers()) + pAttrList->add(XML_markers, "1"); + if (rAttributes.isHigh()) + pAttrList->add(XML_high, "1"); + if (rAttributes.isLow()) + pAttrList->add(XML_low, "1"); + if (rAttributes.isFirst()) + pAttrList->add(XML_first, "1"); + if (rAttributes.isLast()) + pAttrList->add(XML_last, "1"); + if (rAttributes.isNegative()) + pAttrList->add(XML_negative, "1"); + if (rAttributes.shouldDisplayXAxis()) + pAttrList->add(XML_displayXAxis, "1"); + if (rAttributes.shouldDisplayHidden()) + pAttrList->add(XML_displayHidden, "1"); + + if (rAttributes.getMinAxisType() != sc::AxisType::Individual) + { + if (rAttributes.getMinAxisType() == sc::AxisType::Group) + pAttrList->add(XML_minAxisType, "group"); + else if (rAttributes.getMinAxisType() == sc::AxisType::Custom) + pAttrList->add(XML_minAxisType, "custom"); + } + + if (rAttributes.getMaxAxisType() != sc::AxisType::Individual) + { + if (rAttributes.getMaxAxisType() == sc::AxisType::Group) + pAttrList->add(XML_maxAxisType, "group"); + else if (rAttributes.getMaxAxisType() == sc::AxisType::Custom) + pAttrList->add(XML_maxAxisType, "custom"); + } + + if (rAttributes.isRightToLeft()) + pAttrList->add(XML_rightToLeft, "1"); + + if (rAttributes.getManualMax() && rAttributes.getMaxAxisType() == sc::AxisType::Custom) + pAttrList->add(XML_manualMax, OString::number(*rAttributes.getManualMax())); + + if (rAttributes.getManualMin() && rAttributes.getMinAxisType() == sc::AxisType::Custom) + pAttrList->add(XML_manualMin, OString::number(*rAttributes.getManualMin())); +} + +void SparklineExt::addSparklineGroupColors(XclExpXmlStream& rStream, + sc::SparklineAttributes& rAttributes) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + + rWorksheet->singleElementNS(XML_x14, XML_colorSeries, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorSeries())); + + if (rAttributes.getColorNegative() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorNegative, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorNegative())); + } + + if (rAttributes.getColorAxis() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorAxis, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorAxis())); + } + + if (rAttributes.getColorMarkers() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorMarkers, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorMarkers())); + } + + if (rAttributes.getColorFirst() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorFirst, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorFirst())); + } + + if (rAttributes.getColorLast() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorLast, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorLast())); + } + + if (rAttributes.getColorHigh() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorHigh, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorHigh())); + } + + if (rAttributes.getColorLow() != COL_TRANSPARENT) + { + rWorksheet->singleElementNS(XML_x14, XML_colorLow, XML_rgb, + XclXmlUtils::ToOString(rAttributes.getColorLow())); + } +} + +void SparklineExt::addSparklineGroup(XclExpXmlStream& rStream, sc::SparklineGroup& rSparklineGroup, + std::vector> const& rSparklines) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStream.GetCurrentStream(); + + // Sparkline Group Attributes + auto pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + + // Write ID + OString sUID = rSparklineGroup.getID().getString(); + pAttrList->addNS(XML_xr2, XML_uid, sUID); + + // Write attributes + addSparklineGroupAttributes(pAttrList, rSparklineGroup.getAttributes()); + + rWorksheet->startElementNS(XML_x14, XML_sparklineGroup, pAttrList); + + addSparklineGroupColors(rStream, rSparklineGroup.getAttributes()); + + // Sparklines + + rWorksheet->startElementNS(XML_x14, XML_sparklines); + for (auto const& rSparkline : rSparklines) + { + rWorksheet->startElementNS(XML_x14, XML_sparkline); + + { + rWorksheet->startElementNS(XML_xm, XML_f); + + OUString sRangeFormula; + ScRefFlags eFlags = ScRefFlags::VALID | ScRefFlags::TAB_3D; + rSparkline->getInputRange().Format(sRangeFormula, eFlags, GetDoc(), + formula::FormulaGrammar::CONV_XL_OOX, ' ', true); + + rWorksheet->writeEscaped(sRangeFormula); + rWorksheet->endElementNS(XML_xm, XML_f); + } + + { + rWorksheet->startElementNS(XML_xm, XML_sqref); + + ScAddress::Details detailsXL(formula::FormulaGrammar::CONV_XL_OOX); + ScAddress aAddress(rSparkline->getColumn(), rSparkline->getRow(), GetCurrScTab()); + OUString sLocation = aAddress.Format(ScRefFlags::VALID, &GetDoc(), detailsXL); + + rWorksheet->writeEscaped(sLocation); + rWorksheet->endElementNS(XML_xm, XML_sqref); + } + + rWorksheet->endElementNS(XML_x14, XML_sparkline); + } + rWorksheet->endElementNS(XML_x14, XML_sparklines); + rWorksheet->endElementNS(XML_x14, XML_sparklineGroup); +} + +SparklineBuffer::SparklineBuffer(const XclExpRoot& rRoot, XclExtLstRef const& xExtLst) + : XclExpRoot(rRoot) +{ + auto& rDocument = GetDoc(); + auto* pSparklineList = rDocument.GetSparklineList(GetCurrScTab()); + if (pSparklineList && !pSparklineList->getSparklineGroups().empty()) + { + xExtLst->AddRecord(new xcl::exp::SparklineExt(GetRoot())); + } +} + +} // end namespace xcl::exp + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/fontbuff.cxx b/sc/source/filter/excel/fontbuff.cxx new file mode 100644 index 000000000..40e04fcb0 --- /dev/null +++ b/sc/source/filter/excel/fontbuff.cxx @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include + +void LotusFontBuffer::Fill( const sal_uInt8 nIndex, SfxItemSet& rItemSet ) +{ + sal_uInt8 nIntIndex = nIndex & 0x07; + + ENTRY* pCurrent = pData + nIntIndex; + + if( pCurrent->pFont ) + rItemSet.Put( *pCurrent->pFont ); + + if( pCurrent->pHeight ) + rItemSet.Put( *pCurrent->pHeight ); + + if( nIndex & 0x08 ) + { + SvxWeightItem aWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ); + rItemSet.Put( aWeightItem ); + } + + if( nIndex & 0x10 ) + { + SvxPostureItem aAttr( ITALIC_NORMAL, ATTR_FONT_POSTURE ); + rItemSet.Put( aAttr ); + } + + FontLineStyle eUnderline; + switch( nIndex & 0x60 ) // Bit 5+6 + { + case 0x60: + case 0x20: eUnderline = LINESTYLE_SINGLE; break; + case 0x40: eUnderline = LINESTYLE_DOUBLE; break; + default: eUnderline = LINESTYLE_NONE; + } + if( eUnderline != LINESTYLE_NONE ) + { + SvxUnderlineItem aUndItem( eUnderline, ATTR_FONT_UNDERLINE ); + rItemSet.Put( aUndItem ); + } +} + +void LotusFontBuffer::SetName( const sal_uInt16 nIndex, const OUString& rName ) +{ + OSL_ENSURE( nIndex < nSize, "*LotusFontBuffer::SetName(): Array too small!" ); + if( nIndex < nSize ) + { + ENTRY* pEntry = pData + nIndex; + pEntry->TmpName( rName ); + + if( pEntry->nType >= 0 ) + MakeFont( pEntry ); + } +} + +void LotusFontBuffer::SetHeight( const sal_uInt16 nIndex, const sal_uInt16 nHeight ) +{ + OSL_ENSURE( nIndex < nSize, "*LotusFontBuffer::SetHeight(): Array too small!" ); + if( nIndex < nSize ) + pData[ nIndex ].Height( std::make_unique( static_cast(nHeight) * 20, 100, ATTR_FONT_HEIGHT ) ); +} + +void LotusFontBuffer::SetType( const sal_uInt16 nIndex, const sal_uInt16 nType ) +{ + OSL_ENSURE( nIndex < nSize, "*LotusFontBuffer::SetType(): Array too small!" ); + if( nIndex < nSize ) + { + ENTRY* pEntry = pData + nIndex; + pEntry->Type( nType ); + + if( pEntry->xTmpName ) + MakeFont( pEntry ); + } +} + +void LotusFontBuffer::MakeFont( ENTRY* pEntry ) +{ + FontFamily eFamily = FAMILY_DONTKNOW; + FontPitch ePitch = PITCH_DONTKNOW; + rtl_TextEncoding eCharSet = RTL_TEXTENCODING_DONTKNOW; + + switch( pEntry->nType ) + { + case 0x00: // Helvetica + eFamily = FAMILY_SWISS; + ePitch = PITCH_VARIABLE; + break; + case 0x01: // Times Roman + eFamily = FAMILY_ROMAN; + ePitch = PITCH_VARIABLE; + break; + case 0x02: // Courier + ePitch = PITCH_FIXED; + break; + case 0x03: // Symbol + eCharSet = RTL_TEXTENCODING_SYMBOL; + break; + } + + pEntry->pFont.reset( new SvxFontItem( eFamily, *pEntry->xTmpName, OUString(), ePitch, eCharSet, ATTR_FONT ) ); + + pEntry->xTmpName.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/frmbase.cxx b/sc/source/filter/excel/frmbase.cxx new file mode 100644 index 000000000..73ef59dad --- /dev/null +++ b/sc/source/filter/excel/frmbase.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 + +#include + +ScRangeListTabs::ScRangeListTabs( const XclImpRoot& rRoot ) +: XclImpRoot( rRoot ) +{ +} + +ScRangeListTabs::~ScRangeListTabs() +{ +} + +void ScRangeListTabs::Append( const ScAddress& aSRD, SCTAB nTab ) +{ + ScAddress a = aSRD; + ScDocument& rDoc = GetRoot().GetDoc(); + + if (a.Tab() > MAXTAB) + a.SetTab(MAXTAB); + + if (a.Col() > rDoc.MaxCol()) + a.SetCol(rDoc.MaxCol()); + + if (a.Row() > rDoc.MaxRow()) + a.SetRow(rDoc.MaxRow()); + + if( nTab == SCTAB_MAX) + return; + if( nTab < 0) + nTab = a.Tab(); + + if (nTab < 0 || MAXTAB < nTab) + return; + + TabRangeType::iterator itr = m_TabRanges.find(nTab); + if (itr == m_TabRanges.end()) + { + // No entry for this table yet. Insert a new one. + std::pair r = + m_TabRanges.insert(std::make_pair(nTab, RangeListType())); + + if (!r.second) + // Insertion failed. + return; + + itr = r.first; + } + itr->second.push_back(ScRange(a.Col(),a.Row(),a.Tab())); +} + +void ScRangeListTabs::Append( const ScRange& aCRD, SCTAB nTab ) +{ + ScRange a = aCRD; + ScDocument& rDoc = GetRoot().GetDoc(); + + // ignore 3D ranges + if (a.aStart.Tab() != a.aEnd.Tab()) + return; + + if (a.aStart.Tab() > MAXTAB) + a.aStart.SetTab(MAXTAB); + else if (a.aStart.Tab() < 0) + a.aStart.SetTab(0); + + if (a.aStart.Col() > rDoc.MaxCol()) + a.aStart.SetCol(rDoc.MaxCol()); + else if (a.aStart.Col() < 0) + a.aStart.SetCol(0); + + if (a.aStart.Row() > rDoc.MaxRow()) + a.aStart.SetRow(rDoc.MaxRow()); + else if (a.aStart.Row() < 0) + a.aStart.SetRow(0); + + if (a.aEnd.Col() > rDoc.MaxCol()) + a.aEnd.SetCol(rDoc.MaxCol()); + else if (a.aEnd.Col() < 0) + a.aEnd.SetCol(0); + + if (a.aEnd.Row() > rDoc.MaxRow()) + a.aEnd.SetRow(rDoc.MaxRow()); + else if (a.aEnd.Row() < 0) + a.aEnd.SetRow(0); + + if( nTab == SCTAB_MAX) + return; + + if( nTab < -1) + nTab = a.aStart.Tab(); + + if (nTab < 0 || MAXTAB < nTab) + return; + + TabRangeType::iterator itr = m_TabRanges.find(nTab); + if (itr == m_TabRanges.end()) + { + // No entry for this table yet. Insert a new one. + std::pair r = + m_TabRanges.insert(std::make_pair(nTab, RangeListType())); + + if (!r.second) + // Insertion failed. + return; + + itr = r.first; + } + itr->second.push_back(a); +} + +const ScRange* ScRangeListTabs::First( SCTAB n ) +{ + OSL_ENSURE( ValidTab(n), "-ScRangeListTabs::First(): Good bye!" ); + + TabRangeType::iterator itr = m_TabRanges.find(n); + if (itr == m_TabRanges.end()) + // No range list exists for this table. + return nullptr; + + const RangeListType& rList = itr->second; + maItrCur = rList.begin(); + maItrCurEnd = rList.end(); + return rList.empty() ? nullptr : &(*maItrCur); +} + +const ScRange* ScRangeListTabs::Next () +{ + ++maItrCur; + if (maItrCur == maItrCurEnd) + return nullptr; + + return &(*maItrCur); +} + +ConverterBase::ConverterBase( svl::SharedStringPool& rSPool ) : + aPool(rSPool), + aEingPos( 0, 0, 0 ) +{ +} + +ConverterBase::~ConverterBase() +{ +} + +void ConverterBase::Reset() +{ + aPool.Reset(); + aStack.Reset(); +} + +ExcelConverterBase::ExcelConverterBase( svl::SharedStringPool& rSPool ) : + ConverterBase(rSPool) +{ +} + +ExcelConverterBase::~ExcelConverterBase() +{ +} + +void ExcelConverterBase::Reset( const ScAddress& rEingPos ) +{ + ConverterBase::Reset(); + aEingPos = rEingPos; +} + +void ExcelConverterBase::Reset() +{ + ConverterBase::Reset(); + aEingPos.Set( 0, 0, 0 ); +} + +LotusConverterBase::LotusConverterBase( SvStream &rStr, svl::SharedStringPool& rSPool ) : + ConverterBase(rSPool), + aIn( rStr ), + nBytesLeft( 0 ) +{ +} + +LotusConverterBase::~LotusConverterBase() +{ +} + +void LotusConverterBase::Reset( const ScAddress& rEingPos ) +{ + ConverterBase::Reset(); + nBytesLeft = 0; + aEingPos = rEingPos; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/impop.cxx b/sc/source/filter/excel/impop.cxx new file mode 100644 index 000000000..9ddc6e6e7 --- /dev/null +++ b/sc/source/filter/excel/impop.cxx @@ -0,0 +1,1414 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(_WIN32) +#include +#endif + +using namespace ::com::sun::star; + +ImportTyp::ImportTyp(ScDocument& rDoc, rtl_TextEncoding eQ) + : eQuellChar(eQ) + , rD(rDoc) + +{ +} + +ImportTyp::~ImportTyp() +{ +} + +ImportExcel::ImportExcel( XclImpRootData& rImpData, SvStream& rStrm ): + ImportTyp( rImpData.mrDoc, rImpData.meTextEnc ), + XclImpRoot( rImpData ), + maStrm( rStrm, GetRoot() ), + aIn( maStrm ), + maScOleSize( ScAddress::INITIALIZE_INVALID ), + pColOutlineBuff(nullptr), + pRowOutlineBuff(nullptr), + pColRowBuff(nullptr), + mpLastFormula(nullptr), + mnLastRefIdx( 0 ), + mnIxfeIndex( 0 ), + mnLastRecId(0), + mbBiff2HasXfs(false), + mbBiff2HasXfsValid(false) +{ + nBdshtTab = 0; + + // fill in root data - after new's without root as parameter + pExcRoot = &GetOldRoot(); + pExcRoot->pIR = this; // ExcRoot -> XclImpRoot + pExcRoot->eDateiTyp = BiffX; + pExcRoot->pExtSheetBuff.reset( new ExtSheetBuffer( pExcRoot ) ); //&aExtSheetBuff; + pExcRoot->pShrfmlaBuff.reset( new SharedFormulaBuffer( pExcRoot ) ); //&aShrfrmlaBuff; + pExcRoot->pExtNameBuff.reset( new ExtNameBuff ( *this ) ); + + pOutlineListBuffer.reset(new XclImpOutlineListBuffer); + + // from Biff8 on + pFormConv.reset(new ExcelToSc( GetRoot() )); + pExcRoot->pFmlaConverter = pFormConv.get(); + + bTabTruncated = false; + + // Excel document per Default on 31.12.1899, accords to Excel settings with 1.1.1900 + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetDate( 30, 12, 1899 ); + rD.SetDocOptions( aOpt ); + rD.GetFormatTable()->ChangeNullDate( 30, 12, 1899 ); + + ScDocOptions aDocOpt( rD.GetDocOptions() ); + aDocOpt.SetIgnoreCase( true ); // always in Excel + aDocOpt.SetFormulaRegexEnabled( false ); // regular expressions? what's that? + aDocOpt.SetFormulaWildcardsEnabled( true ); // Excel uses wildcard expressions + aDocOpt.SetLookUpColRowNames( false ); // default: no natural language refs + rD.SetDocOptions( aDocOpt ); +} + +ImportExcel::~ImportExcel() +{ + GetDoc().SetSrcCharSet( GetTextEncoding() ); + + pOutlineListBuffer.reset(); + + pFormConv.reset(); +} + +void ImportExcel::SetLastFormula( SCCOL nCol, SCROW nRow, double fVal, sal_uInt16 nXF, ScFormulaCell* pCell ) +{ + LastFormulaMapType::iterator it = maLastFormulaCells.find(nCol); + if (it == maLastFormulaCells.end()) + { + std::pair r = + maLastFormulaCells.emplace(nCol, LastFormula()); + it = r.first; + } + + it->second.mnCol = nCol; + it->second.mnRow = nRow; + it->second.mpCell = pCell; + it->second.mfValue = fVal; + it->second.mnXF = nXF; + + mpLastFormula = &it->second; +} + +void ImportExcel::ReadFileSharing() +{ + sal_uInt16 nRecommendReadOnly, nPasswordHash; + nRecommendReadOnly = maStrm.ReaduInt16(); + nPasswordHash = maStrm.ReaduInt16(); + + if((nRecommendReadOnly == 0) && (nPasswordHash == 0)) + return; + + if( SfxItemSet* pItemSet = GetMedium().GetItemSet() ) + pItemSet->Put( SfxBoolItem( SID_DOC_READONLY, true ) ); + + if( SfxObjectShell* pShell = GetDocShell() ) + { + if( nRecommendReadOnly != 0 ) + pShell->SetLoadReadonly( true ); + if( nPasswordHash != 0 ) + pShell->SetModifyPasswordHash( nPasswordHash ); + } +} + +sal_uInt16 ImportExcel::ReadXFIndex( const ScAddress& rScPos, bool bBiff2 ) +{ + sal_uInt16 nXFIdx = 0; + if( bBiff2 ) + { + /* #i71453# On first call, check if the file contains XF records (by + trying to access the first XF with index 0). If there are no XFs, + the explicit formatting information contained in each cell record + will be used instead. */ + if( !mbBiff2HasXfsValid ) + { + mbBiff2HasXfsValid = true; + mbBiff2HasXfs = GetXFBuffer().GetXF( 0 ) != nullptr; + } + // read formatting information (includes the XF identifier) + sal_uInt8 nFlags1, nFlags2, nFlags3; + nFlags1 = maStrm.ReaduInt8(); + nFlags2 = maStrm.ReaduInt8(); + nFlags3 = maStrm.ReaduInt8(); + /* If the file contains XFs, extract and set the XF identifier, + otherwise get the explicit formatting. */ + if( mbBiff2HasXfs ) + { + nXFIdx = ::extract_value< sal_uInt16 >( nFlags1, 0, 6 ); + /* If the identifier is equal to 63, then the real identifier is + contained in the preceding IXFE record (stored in mnBiff2XfId). */ + if( nXFIdx == 63 ) + nXFIdx = mnIxfeIndex; + } + else + { + /* Let the XclImpXF class do the conversion of the imported + formatting. The XF buffer is empty, therefore will not do any + conversion based on the XF index later on. */ + XclImpXF::ApplyPatternForBiff2CellFormat( GetRoot(), rScPos, nFlags1, nFlags2, nFlags3 ); + } + } + else + nXFIdx = aIn.ReaduInt16(); + return nXFIdx; +} + +void ImportExcel::ReadDimensions() +{ + XclRange aXclUsedArea; + if( (maStrm.GetRecId() == EXC_ID2_DIMENSIONS) || (GetBiff() <= EXC_BIFF5) ) + { + maStrm >> aXclUsedArea; + if( (aXclUsedArea.GetColCount() > 1) && (aXclUsedArea.GetRowCount() > 1) ) + { + // Excel stores first unused row/column index + --aXclUsedArea.maLast.mnCol; + --aXclUsedArea.maLast.mnRow; + // create the Calc range + SCTAB nScTab = GetCurrScTab(); + ScRange& rScUsedArea = GetExtDocOptions().GetOrCreateTabSettings( nScTab ).maUsedArea; + GetAddressConverter().ConvertRange( rScUsedArea, aXclUsedArea, nScTab, nScTab, false ); + // if any error occurs in ConvertRange(), rScUsedArea keeps untouched + } + } + else + { + sal_uInt32 nXclRow1 = 0, nXclRow2 = 0; + nXclRow1 = maStrm.ReaduInt32(); + nXclRow2 = maStrm.ReaduInt32(); + aXclUsedArea.maFirst.mnCol = maStrm.ReaduInt16(); + aXclUsedArea.maLast.mnCol = maStrm.ReaduInt16(); + if( (nXclRow1 < nXclRow2) && (aXclUsedArea.GetColCount() > 1) && + (nXclRow1 <= o3tl::make_unsigned( GetScMaxPos().Row() )) ) + { + // Excel stores first unused row/column index + --nXclRow2; + --aXclUsedArea.maLast.mnCol; + // convert row indexes to 16-bit values + aXclUsedArea.maFirst.mnRow = static_cast< sal_uInt16 >( nXclRow1 ); + aXclUsedArea.maLast.mnRow = limit_cast< sal_uInt16 >( nXclRow2, aXclUsedArea.maFirst.mnRow, SAL_MAX_UINT16 ); + // create the Calc range + SCTAB nScTab = GetCurrScTab(); + ScRange& rScUsedArea = GetExtDocOptions().GetOrCreateTabSettings( nScTab ).maUsedArea; + GetAddressConverter().ConvertRange( rScUsedArea, aXclUsedArea, nScTab, nScTab, false ); + // if any error occurs in ConvertRange(), rScUsedArea keeps untouched + } + } +} + +void ImportExcel::ReadBlank() +{ + XclAddress aXclPos; + aIn >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, maStrm.GetRecId() == EXC_ID2_BLANK ); + + GetXFRangeBuffer().SetBlankXF( aScPos, nXFIdx ); + } +} + +void ImportExcel::ReadInteger() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, true ); + sal_uInt16 nValue; + nValue = maStrm.ReaduInt16(); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + GetDocImport().setNumericCell(aScPos, nValue); + } +} + +void ImportExcel::ReadNumber() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, maStrm.GetRecId() == EXC_ID2_NUMBER ); + double fValue; + fValue = maStrm.ReadDouble(); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + GetDocImport().setNumericCell(aScPos, fValue); + } +} + +void ImportExcel::ReadLabel() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( !GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + return; + + /* Record ID BIFF XF type String type + 0x0004 2-7 3 byte 8-bit length, byte string + 0x0004 8 3 byte 16-bit length, unicode string + 0x0204 2-7 2 byte 16-bit length, byte string + 0x0204 8 2 byte 16-bit length, unicode string */ + bool bBiff2 = maStrm.GetRecId() == EXC_ID2_LABEL; + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, bBiff2 ); + XclStrFlags nFlags = (bBiff2 && (GetBiff() <= EXC_BIFF5)) ? XclStrFlags::EightBitLength : XclStrFlags::NONE; + XclImpString aString; + + // #i63105# use text encoding from FONT record + rtl_TextEncoding eOldTextEnc = GetTextEncoding(); + if( const XclImpFont* pFont = GetXFBuffer().GetFont( nXFIdx ) ) + SetTextEncoding( pFont->GetFontEncoding() ); + aString.Read( maStrm, nFlags ); + SetTextEncoding( eOldTextEnc ); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + XclImpStringHelper::SetToDocument(GetDocImport(), aScPos, GetRoot(), aString, nXFIdx); +} + +void ImportExcel::ReadBoolErr() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( !GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + return; + + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, maStrm.GetRecId() == EXC_ID2_BOOLERR ); + sal_uInt8 nValue, nType; + nValue = maStrm.ReaduInt8(); + nType = maStrm.ReaduInt8(); + + if( nType == EXC_BOOLERR_BOOL ) + GetXFRangeBuffer().SetBoolXF( aScPos, nXFIdx ); + else + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + + double fValue; + std::unique_ptr pScTokArr = ErrorToFormula( nType != EXC_BOOLERR_BOOL, nValue, fValue ); + ScFormulaCell* pCell = pScTokArr + ? new ScFormulaCell(rD, aScPos, std::move(pScTokArr)) + : new ScFormulaCell(rD, aScPos); + pCell->SetHybridDouble( fValue ); + GetDocImport().setFormulaCell(aScPos, pCell); +} + +void ImportExcel::ReadRk() +{ + XclAddress aXclPos; + maStrm >> aXclPos; + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + { + sal_uInt16 nXFIdx = ReadXFIndex( aScPos, false ); + sal_Int32 nRk; + nRk = maStrm.ReadInt32(); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + GetDocImport().setNumericCell(aScPos, XclTools::GetDoubleFromRK(nRk)); + } +} + +void ImportExcel::Window1() +{ + GetDocViewSettings().ReadWindow1( maStrm ); +} + +void ImportExcel::Row25() +{ + sal_uInt16 nRow, nRowHeight; + + nRow = aIn.ReaduInt16(); + aIn.Ignore( 4 ); + + if( !GetRoot().GetDoc().ValidRow( nRow ) ) + return; + + nRowHeight = aIn.ReaduInt16(); // specify direct in Twips + aIn.Ignore( 2 ); + + if( GetBiff() == EXC_BIFF2 ) + {// -------------------- BIFF2 + pColRowBuff->SetHeight( nRow, nRowHeight ); + } + else + {// -------------------- BIFF5 + sal_uInt16 nGrbit; + + aIn.Ignore( 2 ); // reserved + nGrbit = aIn.ReaduInt16(); + + sal_uInt8 nLevel = ::extract_value< sal_uInt8 >( nGrbit, 0, 3 ); + pRowOutlineBuff->SetLevel( nRow, nLevel, ::get_flag( nGrbit, EXC_ROW_COLLAPSED ) ); + pColRowBuff->SetRowSettings( nRow, nRowHeight, nGrbit ); + } +} + +void ImportExcel::Bof2() +{ + sal_uInt16 nSubType; + maStrm.DisableDecryption(); + maStrm.Ignore( 2 ); + nSubType = maStrm.ReaduInt16(); + + if( nSubType == 0x0020 ) // Chart + pExcRoot->eDateiTyp = Biff2C; + else if( nSubType == 0x0040 ) // Macro + pExcRoot->eDateiTyp = Biff2M; + else // #i51490# Excel interprets invalid indexes as worksheet + pExcRoot->eDateiTyp = Biff2; +} + +void ImportExcel::Eof() +{ + // POST: cannot be called after an invalid table! + EndSheet(); + IncCurrScTab(); +} + +void ImportExcel::SheetPassword() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetSheetProtectBuffer().ReadPasswordHash( aIn, GetCurrScTab() ); +} + +void ImportExcel::Externsheet() +{ + OUString aUrl, aTabName; + bool bSameWorkBook; + OUString aEncodedUrl( aIn.ReadByteString( false ) ); + XclImpUrlHelper::DecodeUrl( aUrl, aTabName, bSameWorkBook, *pExcRoot->pIR, aEncodedUrl ); + mnLastRefIdx = pExcRoot->pExtSheetBuff->Add( aUrl, aTabName, bSameWorkBook ); +} + +void ImportExcel:: WinProtection() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetDocProtectBuffer().ReadWinProtect( aIn ); +} + +void ImportExcel::Columndefault() +{// Default Cell Attributes + sal_uInt16 nColMic, nColMac; + sal_uInt8 nOpt0; + + nColMic = aIn.ReaduInt16(); + nColMac = aIn.ReaduInt16(); + + OSL_ENSURE( aIn.GetRecLeft() == static_cast(nColMac - nColMic) * 3 + 2, + "ImportExcel::Columndefault - wrong record size" ); + + nColMac--; + + if( nColMac > rD.MaxCol() ) + nColMac = static_cast(rD.MaxCol()); + + for( sal_uInt16 nCol = nColMic ; nCol <= nColMac ; nCol++ ) + { + nOpt0 = aIn.ReaduInt8(); + aIn.Ignore( 2 ); // only 0. Attribute-Byte used + + if( nOpt0 & 0x80 ) // Col hidden? + pColRowBuff->HideCol( nCol ); + } +} + +void ImportExcel::Array25() +{ + sal_uInt16 nFormLen; + sal_uInt16 nFirstRow = aIn.ReaduInt16(); + sal_uInt16 nLastRow = aIn.ReaduInt16(); + sal_uInt8 nFirstCol = aIn.ReaduInt8(); + sal_uInt8 nLastCol = aIn.ReaduInt8(); + + if( GetBiff() == EXC_BIFF2 ) + {// BIFF2 + aIn.Ignore( 1 ); + nFormLen = aIn.ReaduInt8(); + } + else + {// BIFF5 + aIn.Ignore( 6 ); + nFormLen = aIn.ReaduInt16(); + } + + std::unique_ptr pResult; + + if (GetRoot().GetDoc().ValidColRow(nLastCol, nLastRow)) + { + // the read mark is now on the formula, length in nFormLen + + pFormConv->Reset( ScAddress( static_cast(nFirstCol), + static_cast(nFirstRow), GetCurrScTab() ) ); + pFormConv->Convert(pResult, maStrm, nFormLen, true); + + SAL_WARN_IF(!pResult, "sc", "*ImportExcel::Array25(): ScTokenArray is NULL!"); + } + + if (pResult) + { + ScDocumentImport& rDoc = GetDocImport(); + ScRange aArrayRange(nFirstCol, nFirstRow, GetCurrScTab(), nLastCol, nLastRow, GetCurrScTab()); + rDoc.setMatrixCells(aArrayRange, *pResult, formula::FormulaGrammar::GRAM_ENGLISH_XL_A1); + } +} + +void ImportExcel::Rec1904() +{ + sal_uInt16 n1904; + + n1904 = aIn.ReaduInt16(); + + if( n1904 ) + {// 1904 date system + ScDocOptions aOpt = rD.GetDocOptions(); + aOpt.SetDate( 1, 1, 1904 ); + rD.SetDocOptions( aOpt ); + rD.GetFormatTable()->ChangeNullDate( 1, 1, 1904 ); + } +} + +void ImportExcel::Externname25() +{ + sal_uInt32 nRes; + sal_uInt16 nOpt; + + nOpt = aIn.ReaduInt16(); + nRes = aIn.ReaduInt32(); + + aIn.ReadByteString( false ); // name + + if( ( nOpt & 0x0001 ) || ( ( nOpt & 0xFFFE ) == 0x0000 ) ) + {// external name + pExcRoot->pExtNameBuff->AddName( mnLastRefIdx ); + } + else if( nOpt & 0x0010 ) + {// ole link + pExcRoot->pExtNameBuff->AddOLE( mnLastRefIdx, nRes ); // nRes is storage ID + } + else + {// dde link + pExcRoot->pExtNameBuff->AddDDE( mnLastRefIdx ); + } +} + +void ImportExcel::Colwidth() +{// Column Width + sal_uInt8 nColFirst, nColLast; + sal_uInt16 nColWidth; + + nColFirst = aIn.ReaduInt8(); + nColLast = aIn.ReaduInt8(); + nColWidth = aIn.ReaduInt16(); + +//TODO: add a check for the unlikely case of changed MAXCOL (-> XclImpAddressConverter) +// if( nColLast > rD.MaxCol() ) +// nColLast = static_cast(rD.MaxCol()); + + sal_uInt16 nScWidth = XclTools::GetScColumnWidth( nColWidth, GetCharWidth() ); + pColRowBuff->SetWidthRange( nColFirst, nColLast, nScWidth ); +} + +void ImportExcel::Defrowheight2() +{ + sal_uInt16 nDefHeight; + nDefHeight = maStrm.ReaduInt16(); + nDefHeight &= 0x7FFF; + pColRowBuff->SetDefHeight( nDefHeight, EXC_DEFROW_UNSYNCED ); +} + +void ImportExcel::SheetProtect() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetSheetProtectBuffer().ReadProtect( aIn, GetCurrScTab() ); +} + +void ImportExcel::DocProtect() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetDocProtectBuffer().ReadDocProtect( aIn ); +} + +void ImportExcel::DocPassword() +{ + if (GetRoot().GetBiff() != EXC_BIFF8) + return; + + GetRoot().GetDocProtectBuffer().ReadPasswordHash( aIn ); +} + +void ImportExcel::Codepage() +{ + SetCodePage( maStrm.ReaduInt16() ); +} + +void ImportExcel::Ixfe() +{ + mnIxfeIndex = maStrm.ReaduInt16(); +} + +void ImportExcel::DefColWidth() +{ + // stored as entire characters -> convert to 1/256 of characters (as in COLINFO) + double fDefWidth = 256.0 * maStrm.ReaduInt16(); + + if (!pColRowBuff) + { + SAL_WARN("sc", "*ImportExcel::DefColWidth(): pColRowBuff is NULL!"); + return; + } + + // #i3006# additional space for default width - Excel adds space depending on font size + tools::Long nFontHt = GetFontBuffer().GetAppFontData().mnHeight; + fDefWidth += XclTools::GetXclDefColWidthCorrection( nFontHt ); + + sal_uInt16 nScWidth = XclTools::GetScColumnWidth( limit_cast< sal_uInt16 >( fDefWidth ), GetCharWidth() ); + pColRowBuff->SetDefWidth( nScWidth ); +} + +void ImportExcel::Colinfo() +{// Column Formatting Information + sal_uInt16 nColFirst, nColLast, nColWidth, nXF; + sal_uInt16 nOpt; + + nColFirst = aIn.ReaduInt16(); + nColLast = aIn.ReaduInt16(); + nColWidth = aIn.ReaduInt16(); + nXF = aIn.ReaduInt16(); + nOpt = aIn.ReaduInt16(); + + if( nColFirst > rD.MaxCol() ) + return; + + if( nColLast > rD.MaxCol() ) + nColLast = static_cast(rD.MaxCol()); + + bool bHidden = ::get_flag( nOpt, EXC_COLINFO_HIDDEN ); + bool bCollapsed = ::get_flag( nOpt, EXC_COLINFO_COLLAPSED ); + sal_uInt8 nLevel = ::extract_value< sal_uInt8 >( nOpt, 8, 3 ); + pColOutlineBuff->SetLevelRange( nColFirst, nColLast, nLevel, bCollapsed ); + + if( bHidden ) + pColRowBuff->HideColRange( nColFirst, nColLast ); + + sal_uInt16 nScWidth = XclTools::GetScColumnWidth( nColWidth, GetCharWidth() ); + pColRowBuff->SetWidthRange( nColFirst, nColLast, nScWidth ); + pColRowBuff->SetDefaultXF( nColFirst, nColLast, nXF ); +} + +void ImportExcel::Wsbool() +{ + sal_uInt16 nFlags; + nFlags = aIn.ReaduInt16(); + + pRowOutlineBuff->SetButtonMode( ::get_flag( nFlags, EXC_WSBOOL_ROWBELOW ) ); + pColOutlineBuff->SetButtonMode( ::get_flag( nFlags, EXC_WSBOOL_COLBELOW ) ); + + GetPageSettings().SetFitToPages( ::get_flag( nFlags, EXC_WSBOOL_FITTOPAGE ) ); +} + +void ImportExcel::Boundsheet() +{ + sal_uInt16 nGrbit = 0; + + if( GetBiff() == EXC_BIFF5 ) + { + aIn.DisableDecryption(); + maSheetOffsets.push_back( aIn.ReaduInt32() ); + aIn.EnableDecryption(); + nGrbit = aIn.ReaduInt16(); + } + + OUString aName( aIn.ReadByteString( false ) ); + + SCTAB nScTab = nBdshtTab; + if( nScTab > 0 ) + { + OSL_ENSURE( !rD.HasTable( nScTab ), "ImportExcel::Boundsheet - sheet exists already" ); + rD.MakeTable( nScTab ); + } + + if( ( nGrbit & 0x0001 ) || ( nGrbit & 0x0002 ) ) + rD.SetVisible( nScTab, false ); + + if( !rD.RenameTab( nScTab, aName ) ) + { + rD.CreateValidTabName( aName ); + rD.RenameTab( nScTab, aName ); + } + + nBdshtTab++; +} + +void ImportExcel::Country() +{ + sal_uInt16 nUICountry, nDocCountry; + nUICountry = maStrm.ReaduInt16(); + nDocCountry = maStrm.ReaduInt16(); + + // Store system language in XclRoot + LanguageType eLanguage = ::msfilter::ConvertCountryToLanguage( static_cast< ::msfilter::CountryId >( nDocCountry ) ); + if( eLanguage != LANGUAGE_DONTKNOW ) + SetDocLanguage( eLanguage ); + + // Set Excel UI language in add-in name translator + eLanguage = ::msfilter::ConvertCountryToLanguage( static_cast< ::msfilter::CountryId >( nUICountry ) ); + if( eLanguage != LANGUAGE_DONTKNOW ) + SetUILanguage( eLanguage ); +} + +void ImportExcel::ReadUsesElfs() +{ + if( maStrm.ReaduInt16() != 0 ) + { + ScDocOptions aDocOpt = GetDoc().GetDocOptions(); + aDocOpt.SetLookUpColRowNames( true ); + GetDoc().SetDocOptions( aDocOpt ); + } +} + +void ImportExcel::Hideobj() +{ + sal_uInt16 nHide; + ScVObjMode eOle, eChart, eDraw; + + nHide = aIn.ReaduInt16(); + + ScViewOptions aOpts( rD.GetViewOptions() ); + + switch( nHide ) + { + case 1: // Placeholders + eOle = VOBJ_MODE_SHOW; // in Excel 97 only charts as place holder are displayed + eChart = VOBJ_MODE_SHOW; //#i80528# VOBJ_MODE_DUMMY replaced by VOBJ_MODE_SHOW now + eDraw = VOBJ_MODE_SHOW; + break; + case 2: // Hide all + eOle = VOBJ_MODE_HIDE; + eChart = VOBJ_MODE_HIDE; + eDraw = VOBJ_MODE_HIDE; + break; + default: // Show all + eOle = VOBJ_MODE_SHOW; + eChart = VOBJ_MODE_SHOW; + eDraw = VOBJ_MODE_SHOW; + break; + } + + aOpts.SetObjMode( VOBJ_TYPE_OLE, eOle ); + aOpts.SetObjMode( VOBJ_TYPE_CHART, eChart ); + aOpts.SetObjMode( VOBJ_TYPE_DRAW, eDraw ); + + rD.SetViewOptions( aOpts ); +} + +void ImportExcel::Standardwidth() +{ + sal_uInt16 nScWidth = XclTools::GetScColumnWidth( maStrm.ReaduInt16(), GetCharWidth() ); + if (!pColRowBuff) + { + SAL_WARN("sc", "*ImportExcel::Standardwidth(): pColRowBuff is NULL!"); + return; + } + pColRowBuff->SetDefWidth( nScWidth, true ); +} + +void ImportExcel::Shrfmla() +{ + switch (mnLastRecId) + { + case EXC_ID2_FORMULA: + case EXC_ID3_FORMULA: + case EXC_ID4_FORMULA: + // This record MUST immediately follow a FORMULA record. + break; + default: + return; + } + + if (!mpLastFormula) + // The last FORMULA record should have left this data. + return; + + aIn.Ignore( 8 ); + sal_uInt16 nLenExpr = aIn.ReaduInt16(); + + // read mark is now on the formula + + std::unique_ptr pResult; + + // The shared range in this record is erroneous more than half the time. + // Don't ever rely on it. Use the one from the formula cell above. + SCCOL nCol1 = mpLastFormula->mnCol; + SCROW nRow1 = mpLastFormula->mnRow; + + ScAddress aPos(nCol1, nRow1, GetCurrScTab()); + pFormConv->Reset(aPos); + pFormConv->Convert( pResult, maStrm, nLenExpr, true, FT_SharedFormula ); + + if (!pResult) + { + SAL_WARN("sc", "+ImportExcel::Shrfmla(): ScTokenArray is NULL!"); + return; + } + + pExcRoot->pShrfmlaBuff->Store(aPos, *pResult); + + // Create formula cell for the last formula record. + + ScDocumentImport& rDoc = GetDocImport(); + + ScFormulaCell* pCell = new ScFormulaCell(rD, aPos, std::move(pResult)); + pCell->GetCode()->WrapReference(aPos, EXC_MAXCOL8, EXC_MAXROW8); + rDoc.getDoc().CheckLinkFormulaNeedingCheck( *pCell->GetCode()); + rDoc.getDoc().EnsureTable(aPos.Tab()); + rDoc.setFormulaCell(aPos, pCell); + pCell->SetNeedNumberFormat(false); + if (std::isfinite(mpLastFormula->mfValue)) + pCell->SetResultDouble(mpLastFormula->mfValue); + + GetXFRangeBuffer().SetXF(aPos, mpLastFormula->mnXF); + mpLastFormula->mpCell = pCell; +} + +void ImportExcel::Mulrk() +{ + /* rw (2 bytes): An Rw structure that specifies the row containing the + cells with numeric data. + + colFirst (2 bytes): A Col structure that specifies the first column in + the series of numeric cells within the sheet. The value of colFirst.col + MUST be less than or equal to 254. + + rgrkrec (variable): An array of RkRec structures. Each element in the + array specifies an RkRec in the row. The number of entries in the array + MUST be equal to the value given by the following formula: + + Number of entries in rgrkrec = (colLast.col – colFirst.col +1) + + colLast (2 bytes): A Col structure that specifies the last column in + the set of numeric cells within the sheet. This colLast.col value MUST + be greater than the colFirst.col value. */ + + XclAddress aXclPos; + aIn >> aXclPos; + + XclAddress aCurrXclPos(aXclPos); + while (true) + { + if (aXclPos.mnCol > aCurrXclPos.mnCol) + break; + if (aIn.GetRecLeft() <= 2) + break; + + sal_uInt16 nXF = aIn.ReaduInt16(); + sal_Int32 nRkNum = aIn.ReadInt32(); + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aCurrXclPos, GetCurrScTab(), true ) ) + { + GetXFRangeBuffer().SetXF( aScPos, nXF ); + GetDocImport().setNumericCell(aScPos, XclTools::GetDoubleFromRK(nRkNum)); + } + ++aCurrXclPos.mnCol; + } +} + +void ImportExcel::Mulblank() +{ + /* rw (2 bytes): An Rw structure that specifies a row containing the blank + cells. + + colFirst (2 bytes): A Col structure that specifies the first column in + the series of blank cells within the sheet. The value of colFirst.col + MUST be less than or equal to 254. + + rgixfe (variable): An array of IXFCell structures. Each element of this + array contains an IXFCell structure corresponding to a blank cell in the + series. The number of entries in the array MUST be equal to the value + given by the following formula: + + Number of entries in rgixfe = (colLast.col – colFirst.col +1) + + colLast (2 bytes): A Col structure that specifies the last column in + the series of blank cells within the sheet. This colLast.col value MUST + be greater than colFirst.col value. */ + + XclAddress aXclPos; + aIn >> aXclPos; + + XclAddress aCurrXclPos(aXclPos); + while (true) + { + if (aXclPos.mnCol > aCurrXclPos.mnCol) + break; + if (aIn.GetRecLeft() <= 2) + break; + + sal_uInt16 nXF = aIn.ReaduInt16(); + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScPos, aCurrXclPos, GetCurrScTab(), true ) ) + GetXFRangeBuffer().SetBlankXF( aScPos, nXF ); + ++aCurrXclPos.mnCol; + } +} + +void ImportExcel::Rstring() +{ + XclAddress aXclPos; + sal_uInt16 nXFIdx; + aIn >> aXclPos; + nXFIdx = aIn.ReaduInt16(); + + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( !GetAddressConverter().ConvertAddress( aScPos, aXclPos, GetCurrScTab(), true ) ) + return; + + // unformatted Unicode string with separate formatting information + XclImpString aString; + aString.Read( maStrm ); + + // character formatting runs + if( !aString.IsRich() ) + aString.ReadFormats( maStrm ); + + GetXFRangeBuffer().SetXF( aScPos, nXFIdx ); + XclImpStringHelper::SetToDocument(GetDocImport(), aScPos, *this, aString, nXFIdx); +} + +void ImportExcel::Cellmerging() +{ + XclImpAddressConverter& rAddrConv = GetAddressConverter(); + SCTAB nScTab = GetCurrScTab(); + + sal_uInt16 nCount = maStrm.ReaduInt16(); + sal_uInt16 nIdx = 0; + while (true) + { + if (maStrm.GetRecLeft() < 8) + break; + if (nIdx >= nCount) + break; + XclRange aXclRange; + maStrm >> aXclRange; // 16-bit rows and columns + ScRange aScRange( ScAddress::UNINITIALIZED ); + if( rAddrConv.ConvertRange( aScRange, aXclRange, nScTab, nScTab, true ) ) + GetXFRangeBuffer().SetMerge( aScRange.aStart.Col(), aScRange.aStart.Row(), aScRange.aEnd.Col(), aScRange.aEnd.Row() ); + ++nIdx; + } +} + +void ImportExcel::Olesize() +{ + XclRange aXclOleSize( ScAddress::UNINITIALIZED ); + maStrm.Ignore( 2 ); + aXclOleSize.Read( maStrm, false ); + + SCTAB nScTab = GetCurrScTab(); + GetAddressConverter().ConvertRange( maScOleSize, aXclOleSize, nScTab, nScTab, false ); +} + +void ImportExcel::Row34() +{ + sal_uInt16 nRow, nRowHeight, nGrbit, nXF; + + nRow = aIn.ReaduInt16(); + aIn.Ignore( 4 ); + + SCROW nScRow = static_cast< SCROW >( nRow ); + + if( !GetRoot().GetDoc().ValidRow( nScRow ) ) + return; + + nRowHeight = aIn.ReaduInt16(); // specify direct in Twips + aIn.Ignore( 4 ); + + nRowHeight = nRowHeight & 0x7FFF; // Bit 15: Row Height not changed manually + if( !nRowHeight ) + nRowHeight = (GetBiff() == EXC_BIFF2) ? 0x25 : 0x225; + + nGrbit = aIn.ReaduInt16(); + nXF = aIn.ReaduInt16(); + + sal_uInt8 nLevel = ::extract_value< sal_uInt8 >( nGrbit, 0, 3 ); + pRowOutlineBuff->SetLevel( nScRow, nLevel, ::get_flag( nGrbit, EXC_ROW_COLLAPSED ) ); + pColRowBuff->SetRowSettings( nScRow, nRowHeight, nGrbit ); + + if( nGrbit & EXC_ROW_USEDEFXF ) + GetXFRangeBuffer().SetRowDefXF( nScRow, nXF & EXC_ROW_XFMASK ); +} + +void ImportExcel::Bof3() +{ + sal_uInt16 nSubType; + maStrm.DisableDecryption(); + maStrm.Ignore( 2 ); + nSubType = maStrm.ReaduInt16(); + + OSL_ENSURE( nSubType != 0x0100, "*ImportExcel::Bof3(): Biff3 as Workbook?!" ); + if( nSubType == 0x0100 ) // Book + pExcRoot->eDateiTyp = Biff3W; + else if( nSubType == 0x0020 ) // Chart + pExcRoot->eDateiTyp = Biff3C; + else if( nSubType == 0x0040 ) // Macro + pExcRoot->eDateiTyp = Biff3M; + else // #i51490# Excel interprets invalid indexes as worksheet + pExcRoot->eDateiTyp = Biff3; +} + +void ImportExcel::Array34() +{ + sal_uInt16 nFirstRow, nLastRow, nFormLen; + sal_uInt8 nFirstCol, nLastCol; + + nFirstRow = aIn.ReaduInt16(); + nLastRow = aIn.ReaduInt16(); + nFirstCol = aIn.ReaduInt8(); + nLastCol = aIn.ReaduInt8(); + aIn.Ignore( (GetBiff() >= EXC_BIFF5) ? 6 : 2 ); + nFormLen = aIn.ReaduInt16(); + + std::unique_ptr pResult; + + if( GetRoot().GetDoc().ValidColRow( nLastCol, nLastRow ) ) + { + // the read mark is now on the formula, length in nFormLen + + pFormConv->Reset( ScAddress( static_cast(nFirstCol), + static_cast(nFirstRow), GetCurrScTab() ) ); + pFormConv->Convert( pResult, maStrm, nFormLen, true ); + + SAL_WARN_IF(!pResult, "sc", "+ImportExcel::Array34(): ScTokenArray is NULL!"); + } + + if (pResult) + { + ScDocumentImport& rDoc = GetDocImport(); + ScRange aArrayRange(nFirstCol, nFirstRow, GetCurrScTab(), nLastCol, nLastRow, GetCurrScTab()); + rDoc.setMatrixCells(aArrayRange, *pResult, formula::FormulaGrammar::GRAM_ENGLISH_XL_A1); + } +} + +void ImportExcel::Defrowheight345() +{ + sal_uInt16 nFlags, nDefHeight; + nFlags = maStrm.ReaduInt16(); + nDefHeight = maStrm.ReaduInt16(); + + if (!pColRowBuff) + { + SAL_WARN("sc", "*ImportExcel::Defrowheight345(): pColRowBuff is NULL!"); + return; + } + + pColRowBuff->SetDefHeight( nDefHeight, nFlags ); +} + +void ImportExcel::TableOp() +{ + sal_uInt16 nFirstRow = aIn.ReaduInt16(); + sal_uInt16 nLastRow = aIn.ReaduInt16(); + sal_uInt8 nFirstCol = aIn.ReaduInt8(); + sal_uInt8 nLastCol = aIn.ReaduInt8(); + sal_uInt16 nGrbit = aIn.ReaduInt16(); + sal_uInt16 nInpRow = aIn.ReaduInt16(); + sal_uInt16 nInpCol = aIn.ReaduInt16(); + sal_uInt16 nInpRow2 = aIn.ReaduInt16(); + sal_uInt16 nInpCol2 = aIn.ReaduInt16(); + + if (utl::ConfigManager::IsFuzzing()) + { + //shrink to smallish arbitrary value to not timeout + nLastRow = std::min(nLastRow, MAXROW_30 / 2); + } + + if( GetRoot().GetDoc().ValidColRow( nLastCol, nLastRow ) ) + { + if( nFirstCol && nFirstRow ) + { + ScTabOpParam aTabOpParam; + aTabOpParam.meMode = (nGrbit & EXC_TABLEOP_BOTH) ? ScTabOpParam::Both : ((nGrbit & EXC_TABLEOP_ROW) ? ScTabOpParam::Row : ScTabOpParam::Column); + sal_uInt16 nCol = nFirstCol - 1; + sal_uInt16 nRow = nFirstRow - 1; + SCTAB nTab = GetCurrScTab(); + switch (aTabOpParam.meMode) + { + case ScTabOpParam::Column: + aTabOpParam.aRefFormulaCell.Set( + static_cast(nFirstCol), + static_cast(nFirstRow - 1), nTab, false, + false, false ); + aTabOpParam.aRefFormulaEnd.Set( + static_cast(nLastCol), + static_cast(nFirstRow - 1), nTab, false, + false, false ); + aTabOpParam.aRefColCell.Set( static_cast(nInpCol), + static_cast(nInpRow), nTab, false, false, + false ); + nRow++; + break; + case ScTabOpParam::Row: + aTabOpParam.aRefFormulaCell.Set( + static_cast(nFirstCol - 1), + static_cast(nFirstRow), nTab, false, false, + false ); + aTabOpParam.aRefFormulaEnd.Set( + static_cast(nFirstCol - 1), + static_cast(nLastRow), nTab, false, false, + false ); + aTabOpParam.aRefRowCell.Set( static_cast(nInpCol), + static_cast(nInpRow), nTab, false, false, + false ); + nCol++; + break; + case ScTabOpParam::Both: // TWO-INPUT + aTabOpParam.aRefFormulaCell.Set( + static_cast(nFirstCol - 1), + static_cast(nFirstRow - 1), nTab, false, + false, false ); + aTabOpParam.aRefRowCell.Set( static_cast(nInpCol), + static_cast(nInpRow), nTab, false, false, + false ); + aTabOpParam.aRefColCell.Set( static_cast(nInpCol2), + static_cast(nInpRow2), nTab, false, false, + false ); + break; + } + + ScDocumentImport& rDoc = GetDocImport(); + ScRange aTabOpRange(nCol, nRow, nTab, nLastCol, nLastRow, nTab); + rDoc.setTableOpCells(aTabOpRange, aTabOpParam); + } + } + else + { + bTabTruncated = true; + GetTracer().TraceInvalidRow(nLastRow, rD.MaxRow()); + } +} + +void ImportExcel::Bof4() +{ + sal_uInt16 nSubType; + maStrm.DisableDecryption(); + maStrm.Ignore( 2 ); + nSubType = maStrm.ReaduInt16(); + + if( nSubType == 0x0100 ) // Book + pExcRoot->eDateiTyp = Biff4W; + else if( nSubType == 0x0020 ) // Chart + pExcRoot->eDateiTyp = Biff4C; + else if( nSubType == 0x0040 ) // Macro + pExcRoot->eDateiTyp = Biff4M; + else // #i51490# Excel interprets invalid indexes as worksheet + pExcRoot->eDateiTyp = Biff4; +} + +void ImportExcel::Bof5() +{ + //POST: eDateiTyp = Type of the file to be read + sal_uInt16 nSubType, nVers; + BiffTyp eDatei; + + maStrm.DisableDecryption(); + nVers = maStrm.ReaduInt16(); + nSubType = maStrm.ReaduInt16( ); + + switch( nSubType ) + { + case 0x0005: eDatei = Biff5W; break; // workbook globals + case 0x0006: eDatei = Biff5V; break; // VB module + case 0x0020: eDatei = Biff5C; break; // chart + case 0x0040: eDatei = Biff5M4; break; // macro sheet + case 0x0010: // worksheet + default: eDatei = Biff5; break; // tdf#144732 Excel interprets invalid indexes as worksheet + } + + if( nVers == 0x0600 && (GetBiff() == EXC_BIFF8) ) + eDatei = static_cast( eDatei - Biff5 + Biff8 ); + + pExcRoot->eDateiTyp = eDatei; +} + +void ImportExcel::EndSheet() +{ + pExcRoot->pExtSheetBuff->Reset(); + + if( GetBiff() <= EXC_BIFF5 ) + { + pExcRoot->pExtNameBuff->Reset(); + mnLastRefIdx = 0; + } + + FinalizeTable(); +} + +void ImportExcel::NewTable() +{ + SCTAB nTab = GetCurrScTab(); + if( nTab > 0 && !rD.HasTable( nTab ) ) + rD.MakeTable( nTab ); + + if (nTab == 0 && GetBiff() == EXC_BIFF2) + { + // For Excel 2.1 Worksheet file, we need to set the file name as the + // sheet name. + INetURLObject aURL(GetDocUrl()); + rD.RenameTab(0, aURL.getBase()); + } + + pExcRoot->pShrfmlaBuff->Clear(); + maLastFormulaCells.clear(); + mpLastFormula = nullptr; + + InitializeTable( nTab ); + + XclImpOutlineDataBuffer* pNewItem = new XclImpOutlineDataBuffer( GetRoot(), nTab ); + pOutlineListBuffer->push_back( std::unique_ptr(pNewItem) ); + pExcRoot->pColRowBuff = pColRowBuff = pNewItem->GetColRowBuff(); + pColOutlineBuff = pNewItem->GetColOutline(); + pRowOutlineBuff = pNewItem->GetRowOutline(); +} + +std::unique_ptr ImportExcel::ErrorToFormula( bool bErrOrVal, sal_uInt8 nError, double& rVal ) +{ + return pFormConv->GetBoolErr( XclTools::ErrorToEnum( rVal, bErrOrVal, nError ) ); +} + +void ImportExcel::AdjustRowHeight() +{ + /* Speed up chart import: import all sheets without charts, then + update row heights (here), last load all charts -> do not any longer + update inside of ScDocShell::ConvertFrom() (causes update of existing + charts during each and every change of row height). */ + if( ScModelObj* pDocObj = GetDocModelObj() ) + pDocObj->UpdateAllRowHeights(); +} + +void ImportExcel::PostDocLoad() +{ + /* Set automatic page numbering in Default page style (default is "page number = 1"). + Otherwise hidden tables (i.e. for scenarios) which have Default page style will + break automatic page numbering. */ + if( SfxStyleSheetBase* pStyleSheet = GetStyleSheetPool().Find( ScResId( STR_STYLENAME_STANDARD ), SfxStyleFamily::Page ) ) + pStyleSheet->GetItemSet().Put( SfxUInt16Item( ATTR_PAGE_FIRSTPAGENO, 0 ) ); + + // outlines for all sheets, sets hidden rows and columns (#i11776# after filtered ranges) + for (auto& rxBuffer : *pOutlineListBuffer) + rxBuffer->Convert(); + + // document view settings (before visible OLE area) + GetDocViewSettings().Finalize(); + + // process all drawing objects (including OLE, charts, controls; after hiding rows/columns; before visible OLE area) + GetObjectManager().ConvertObjects(); + + // visible area (used if this document is an embedded OLE object) + if( SfxObjectShell* pDocShell = GetDocShell() ) + { + // visible area if embedded + const ScExtDocSettings& rDocSett = GetExtDocOptions().GetDocSettings(); + SCTAB nDisplScTab = rDocSett.mnDisplTab; + + /* #i44077# If a new OLE object is inserted from file, there is no + OLESIZE record in the Excel file. Calculate used area from file + contents (used cells and drawing objects). */ + if( !maScOleSize.IsValid() ) + { + // used area of displayed sheet (cell contents) + if( const ScExtTabSettings* pTabSett = GetExtDocOptions().GetTabSettings( nDisplScTab ) ) + maScOleSize = pTabSett->maUsedArea; + // add all valid drawing objects + ScRange aScObjArea = GetObjectManager().GetUsedArea( nDisplScTab ); + if( aScObjArea.IsValid() ) + maScOleSize.ExtendTo( aScObjArea ); + } + + // valid size found - set it at the document + if( maScOleSize.IsValid() ) + { + pDocShell->SetVisArea( GetDoc().GetMMRect( + maScOleSize.aStart.Col(), maScOleSize.aStart.Row(), + maScOleSize.aEnd.Col(), maScOleSize.aEnd.Row(), nDisplScTab ) ); + GetDoc().SetVisibleTab( nDisplScTab ); + } + } + + // open forms in alive mode (has no effect, if no controls in document) + if( ScModelObj* pDocObj = GetDocModelObj() ) + pDocObj->setPropertyValue( SC_UNO_APPLYFMDES, uno::Any( false ) ); + + // enables extended options to be set to the view after import + GetExtDocOptions().SetChanged( true ); + + // root data owns the extended document options -> create a new object + GetDoc().SetExtDocOptions( std::make_unique( GetExtDocOptions() ) ); + + const SCTAB nLast = rD.GetTableCount(); + const ScRange* p; + + if( GetRoot().GetPrintAreaBuffer().HasRanges() ) + { + for( SCTAB n = 0 ; n < nLast ; n++ ) + { + p = GetRoot().GetPrintAreaBuffer().First(n); + if( p ) + { + rD.ClearPrintRanges( n ); + while( p ) + { + rD.AddPrintRange( n, *p ); + p = GetRoot().GetPrintAreaBuffer().Next(); + } + } + else + { + // #i4063# no print ranges -> print entire sheet + rD.SetPrintEntireSheet( n ); + } + } + GetTracer().TracePrintRange(); + } + + if( !GetRoot().GetTitleAreaBuffer().HasRanges() ) + return; + + for( SCTAB n = 0 ; n < nLast ; n++ ) + { + p = GetRoot().GetTitleAreaBuffer().First(n); + if( p ) + { + bool bRowVirgin = true; + bool bColVirgin = true; + + while( p ) + { + if( p->aStart.Col() == 0 && p->aEnd.Col() == rD.MaxCol() && bRowVirgin ) + { + rD.SetRepeatRowRange( n, *p ); + bRowVirgin = false; + } + + if( p->aStart.Row() == 0 && p->aEnd.Row() == rD.MaxRow() && bColVirgin ) + { + rD.SetRepeatColRange( n, *p ); + bColVirgin = false; + } + + p = GetRoot().GetTitleAreaBuffer().Next(); + } + } + } +} + +XclImpOutlineDataBuffer::XclImpOutlineDataBuffer( const XclImpRoot& rRoot, SCTAB nScTab ) : + XclImpRoot( rRoot ), + mxColOutlineBuff( std::make_shared( rRoot.GetXclMaxPos().Col() + 1 ) ), + mxRowOutlineBuff( std::make_shared( rRoot.GetXclMaxPos().Row() + 1 ) ), + mxColRowBuff( std::make_shared( rRoot ) ), + mnScTab( nScTab ) +{ +} + +XclImpOutlineDataBuffer::~XclImpOutlineDataBuffer() +{ +} + +void XclImpOutlineDataBuffer::Convert() +{ + mxColOutlineBuff->SetOutlineArray( &GetDoc().GetOutlineTable( mnScTab, true )->GetColArray() ); + mxColOutlineBuff->MakeScOutline(); + + mxRowOutlineBuff->SetOutlineArray( &GetDoc().GetOutlineTable( mnScTab, true )->GetRowArray() ); + mxRowOutlineBuff->MakeScOutline(); + + mxColRowBuff->ConvertHiddenFlags( mnScTab ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/namebuff.cxx b/sc/source/filter/excel/namebuff.cxx new file mode 100644 index 000000000..523145209 --- /dev/null +++ b/sc/source/filter/excel/namebuff.cxx @@ -0,0 +1,187 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include + +#include +#include + +#include + +sal_uInt32 StringHashEntry::MakeHashCode( const OUString& r ) +{ + sal_uInt32 n = 0; + const sal_Unicode* pCurrent = r.getStr(); + sal_Unicode cCurrent = *pCurrent; + + while( cCurrent ) + { + n *= 70; + n += static_cast(cCurrent); + pCurrent++; + cCurrent = *pCurrent; + } + + return n; +} + +SharedFormulaBuffer::SharedFormulaBuffer(RootData* pRD) + : ExcRoot(pRD) +{ +} + +SharedFormulaBuffer::~SharedFormulaBuffer() +{ + Clear(); +} + +void SharedFormulaBuffer::Clear() +{ + maTokenArrays.clear(); +} + +void SharedFormulaBuffer::Store( const ScAddress& rPos, const ScTokenArray& rArray ) +{ + ScTokenArray aCode(rArray.CloneValue()); + aCode.GenHash(); + maTokenArrays.emplace(rPos, std::move(aCode)); +} + +const ScTokenArray* SharedFormulaBuffer::Find( const ScAddress& rRefPos ) const +{ + TokenArraysType::const_iterator it = maTokenArrays.find(rRefPos); + if (it == maTokenArrays.end()) + return nullptr; + + return &it->second; +} + +sal_Int16 ExtSheetBuffer::Add( const OUString& rFPAN, const OUString& rTN, const bool bSWB ) +{ + maEntries.emplace_back( rFPAN, rTN, bSWB ); + // return 1-based index of EXTERNSHEET + return static_cast< sal_Int16 >( maEntries.size() ); +} + +bool ExtSheetBuffer::GetScTabIndex( sal_uInt16 nExcIndex, sal_uInt16& rScIndex ) +{ + OSL_ENSURE( nExcIndex, + "*ExtSheetBuffer::GetScTabIndex(): Sheet-Index == 0!" ); + + if ( !nExcIndex || nExcIndex > maEntries.size() ) + return false; + + Cont* pCur = &maEntries[ nExcIndex - 1 ]; + sal_uInt16& rTabNum = pCur->nTabNum; + + if( rTabNum < 0xFFFD ) + { + rScIndex = rTabNum; + return true; + } + + if( rTabNum == 0xFFFF ) + {// create new table + SCTAB nNewTabNum; + if( pCur->bSWB ) + {// table is in the same workbook! + if( pExcRoot->pIR->GetDoc().GetTable( pCur->aTab, nNewTabNum ) ) + { + rScIndex = rTabNum = static_cast(nNewTabNum); + return true; + } + else + rTabNum = 0xFFFD; + } + else if( pExcRoot->pIR->GetDocShell() ) + {// table is 'really' external + if( pExcRoot->pIR->GetExtDocOptions().GetDocSettings().mnLinkCnt == 0 ) + { + OUString aURL( ScGlobal::GetAbsDocName( pCur->aFile, + pExcRoot->pIR->GetDocShell() ) ); + OUString aTabName( ScGlobal::GetDocTabName( aURL, pCur->aTab ) ); + if( pExcRoot->pIR->GetDoc().LinkExternalTab( nNewTabNum, aTabName, aURL, pCur->aTab ) ) + { + rScIndex = rTabNum = static_cast(nNewTabNum); + return true; + } + else + rTabNum = 0xFFFE; // no table is created for now -> and likely + // will not be created later... + } + else + rTabNum = 0xFFFE; + + } + } + + return false; +} + +void ExtSheetBuffer::Reset() +{ + maEntries.clear(); +} + +bool ExtName::IsOLE() const +{ + return ( nFlags & 0x0002 ) != 0; +} + +ExtNameBuff::ExtNameBuff( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void ExtNameBuff::AddDDE( sal_Int16 nRefIdx ) +{ + ExtName aNew( 0x0001 ); + maExtNames[ nRefIdx ].push_back( aNew ); +} + +void ExtNameBuff::AddOLE( sal_Int16 nRefIdx, sal_uInt32 nStorageId ) +{ + ExtName aNew( 0x0002 ); + aNew.nStorageId = nStorageId; + maExtNames[ nRefIdx ].push_back( aNew ); +} + +void ExtNameBuff::AddName( sal_Int16 nRefIdx ) +{ + ExtName aNew( 0x0004 ); + maExtNames[ nRefIdx ].push_back( aNew ); +} + +const ExtName* ExtNameBuff::GetNameByIndex( sal_Int16 nRefIdx, sal_uInt16 nNameIdx ) const +{ + OSL_ENSURE( nNameIdx > 0, "ExtNameBuff::GetNameByIndex() - invalid name index" ); + ExtNameMap::const_iterator aIt = maExtNames.find( nRefIdx ); + return ((aIt != maExtNames.end()) && (0 < nNameIdx) && (nNameIdx <= aIt->second.size())) ? &aIt->second[ nNameIdx - 1 ] : nullptr; +} + +void ExtNameBuff::Reset() +{ + maExtNames.clear(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/ooxml-export-TODO.txt b/sc/source/filter/excel/ooxml-export-TODO.txt new file mode 100644 index 000000000..d995598d2 --- /dev/null +++ b/sc/source/filter/excel/ooxml-export-TODO.txt @@ -0,0 +1,164 @@ +# +# This file is part of the LibreOffice project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This file incorporates work covered by the following license notice: +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed +# with this work for additional information regarding copyright +# ownership. The ASF licenses this file to you under the Apache +# License, Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.apache.org/licenses/LICENSE-2.0 . +# + +TODO/Unimplemented Calc OOXML Export Features: +============================================= + +Partially implemented features are not mentioned here; grep for OOXTODO within +sc/source/filter/*. + +In updated OfficeFileFormatsProtocols.zip [MS-XLS].pdf, +Section §2.3.1 (p.154) provides the record name :: record number mapping, and +Section §2.3.2 (p.165) provides the record number :: record name mapping. + +Elements: + - Workbook (§3.2): + - customWorkbookViews (§3.2.3) + - ext (§3.2.7) + - extLst (§3.2.10) + - fileRecoveryPr (§3.2.11) [ CRASHRECERR? 865h ] + - fileSharing (§3.2.12) [ FILESHARING 5Bh ] + - functionGroup (§3.2.14) [ FNGRP12 898h; FNGROUPNAME 9Ah ] + - functionGroups (§3.2.15) [ FNGROUPCOUNT: 9Ch ] + - oleSize (§3.2.16) [ OLESIZE DEh ] + - smartTagPr (§3.2.21) [ BOOKEXT 863h ] + - smartTagType (§3.2.22) [ unknown record ] + - smartTagTypes (§3.2.23) [ unknown record ] + - webPublishing (§3.2.24) [ WOPT 80Bh ] + - webPublishObject (§3.2.25) [ WEBPUB 801h ] + - webPublishObjects (§3.2.26) [ unsupported ] + - Worksheets (§3.3.1): + - autoFilter (§3.3.1.1) [ AutoFilter 9Eh ] + - cellSmartTag (§3.3.1.4) [ FEAT 868h ] + - cellSmartTagPr (§3.3.1.5) [ FEAT? 868h ] + - cellSmartTags (§3.3.1.6) [ FEAT 868h ] + - cellWatch (§3.3.1.7) [ CELLWATCH 86Ch ] + - cellWatches (§3.3.1.8) [ CELLWATCH 86Ch ] + - cfRule (§3.3.1.9) [ CF 1B1h ] + - cfvo (§3.3.1.10) [ CF12 87Ah ] + - chartsheet (§3.3.1.11) [ CHARTFRTINFO 850h, FRTWRAPPER 851h...] + - color (§3.3.1.14) [ DXF 88Dh xfpropBorder? + XFEXT 87Dh xclrType? ] + - colorScale (§3.3.1.15) [ DXF 88Dh? ] + - control (§3.3.1.18) [ ??? ] + - controls (§3.3.1.19) [ ??? ] + - customPr (§3.3.1.20) [ ??? ] + - customProperties (§3.3.1.21) [ ??? ] + - customSheetView (§3.3.1.22) [ ???; for charts; see chartsheet? ] + - customSheetView (§3.3.1.23) [ ??? ] + - customSheetViews (§3.3.1.24) [ ???; for charts; see chartsheet? ] + - customSheetViews (§3.3.1.25) [ ??? ] + - dataBar (§3.3.1.26) [ CF12 87Ah ct=Databar ] + - dataConsolidate (§3.3.1.27) [ DCON 50h ] + - dataRef (§3.3.1.28) [ DCONBIN 1B5h ] + - dataRefs (§3.3.1.29) [ ??? ] + - dialogsheet (§3.3.1.32) [ ??? ] + - drawing (§3.3.1.34) [ ??? ] + - evenFooter (§3.3.1.35) [ HeaderFooter 89Ch ] + - evenHeader (§3.3.1.36) [ HeaderFooter 89Ch ] + - firstFooter (§3.3.1.38) [ HeaderFooter 89Ch ] + - firstHeader (§3.3.1.39) [ HeaderFooter 89Ch ] + - formula (§3.3.1.40) [ CF 1B1h ] + - iconSet (§3.3.1.46) [ CF12 87Ah ct=CFMultistate ] + - ignoredError (§3.3.1.47) [ Feat/FeatFormulaErr2/FFErrorCheck 868h ] + - ignoredErrors (§3.3.1.48) [ Feat 868h ] + - legacyDrawing (§3.3.1.51) [ MsoDrawing ECh ] + - legacyDrawingHF (§3.3.1.52) [ ??? ] + - oleObject (§3.3.1.57) [ ??? ] + - oleObjects (§3.3.1.58) [ ??? ] + - outlinePr (§3.3.1.59) [ ??? ] + - pageSetup (§3.3.1.62) [ ???; for charts; see chartsheet? ] + - picture (§3.3.1.65) [ BkHim E9h; see XclExpBitmap ] + - pivotArea (§3.3.1.66) [ ??? ] + - pivotSelection (§3.3.1.67) [ ??? ] + - protectedRange (§3.3.1.69) [ ??? ] + - protectedRanges (§3.3.1.70) [ ??? ] + - sheetCalcPr (§3.3.1.76) [ REFRESHALL?? ] + - sheetFormatPr (§3.3.1.78) [ lots of records? ] + @defaultColWidth: DefColWidth + @defaultRowHeight: DEFROWHEIGHT + @baseColWidth: ColInfo/coldx? + @customHeight: ColInfo/fUserSet? + @zeroHeight: ColInfo/fHidden? + @thickTop: ? + @thickBottom: ? + @outlineLevelRow: ? + @outlineLevelCol: ColInfo/iOutLevel? + - sheetPr (§3.3.1.80) [ ??? ; for charts ] + - sheetView (§3.3.1.84) [ ??? ; for charts ] + - sheetViews (§3.3.1.86) [ ??? ; for charts ] + - smartTags (§3.3.1.87) [ FEAT 868h; isf=ISFFACTOID ] + - sortCondition (§3.3.1.88) [ SortData 895h? ] + - sortState (§3.3.1.89) [ Sort 90h ] + - tabColor (§3.3.1.90) [ SheetExt 862h ] + - tablePart (§3.3.1.91) [ ??? ] + - tableParts (§3.3.1.92) [ ??? ] + - webPublishItem (§3.3.1.94) [ WebPub 801h ] + - webPublishItems (§3.3.1.95) + - AutoFilter Settings (§3.3.2): + - colorFilter (§3.3.2.1) [ AutoFilter12 87Eh, + DXFN12NoCB struct ] + - dateGroupItem (§3.3.2.4) [ AutoFilter12 87Eh, + AF12DateInfo struct ] + - dynamicFilter (§3.3.2.5) [ AutoFilter12 87Eh, cft field ] + - filter (§3.3.2.6) [ AutoFilter12 87Eh, rgCriteria? ] + - filters (§3.3.2.9) [ AutoFilter12 87Eh, rgCriteria? ] + - iconFilter (§3.3.2.9) [ AutoFilter12 87Eh, + AF12CellIcon struct ] + - Shared String Table (§3.4): + - phoneticPr (§3.4.3) + - rPh (§3.4.6) + - Tables (§3.5.1): + - calculatedColumnFormula (§3.5.1.1) + [ ??? ] + - table (§3.5.1.2) [ ??? ] + - tableColumn (§3.5.1.3) [ ??? ] + - tableColumns (§3.5.1.4) [ ??? ] + - tableStyleInfo (§3.5.1.5) [ ??? ] + - totalRowFormula (§3.5.1.6) [ ??? ] + - xmlColumnPr (§3.5.1.7) [ ??? ] + - Single Cell Tables (§3.5.2): + - singleXmlCell (§3.5.2.1) [ ??? ] + - singleXmlCells (§3.5.2.2) [ ??? ] + - xmlCellPr (§3.5.2.3) [ ??? ] + - xmlPr (§3.5.2.4) [ ??? ] + - Calculation Chain (§3.6): + - c (§3.6.1) [ ??? ] + - calcChain (§3.6.2) [ ??? ] + - Comments (§3.7): + - Note: Excel *requires* that there be a drawing object associated + with the comment before it will show it. If you _just_ generate the + XML part and create a for it, Excel + will NOT display the comment. + - As drawing is not currently implemented, comments support is + incomplete. + - TODO: text formatting. Currently we only write unformatted text + into comments?.xml, as I'm not sure how formatted text is handled. + - Styles (§3.8): + - dxf (§3.8.14): [ DXF 88Dh; unsupported ] + - dxfs (§3.8.15): [ DXF 88Dh ] + - gradientFill (§3.8.23): [ ??? ] + - horizontal (§3.8.24): [ DXF 88Dh fNewBorder, xfprops ] + - mruColors (§3.8.28): [ ??? ] + - scheme (§3.8.36): [ ??? ] + - stop (§3.8.38): [ ??? ] + - tableStyle (§3.8.40): [ TableStyle 88Fh; unsupported ] + - tableStyleElement (§3.8.41): [ TableStyleElement 890h; unsupported ] + - tableStyles (§3.8.42): [ TableStyles 88Eh; unsupported ] + - vertical (§3.8.44): [ DXF 88Dh fNewBorder, xfprops ] + diff --git a/sc/source/filter/excel/read.cxx b/sc/source/filter/excel/read.cxx new file mode 100644 index 000000000..cf9465a37 --- /dev/null +++ b/sc/source/filter/excel/read.cxx @@ -0,0 +1,1314 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace +{ + bool TryStartNextRecord(XclImpStream& rIn, std::size_t nProgressBasePos) + { + bool bValid = true; + // i#115255 fdo#40304 BOUNDSHEET doesn't point to a valid + // BOF record position. Scan the records manually (from + // the BOUNDSHEET position) until we find a BOF. Some 3rd + // party Russian programs generate invalid xls docs with + // this kind of silliness. + if (rIn.PeekRecId(nProgressBasePos) == EXC_ID5_BOF) + // BOUNDSHEET points to a valid BOF record. Good. + rIn.StartNextRecord(nProgressBasePos); + else + { + while (bValid && rIn.GetRecId() != EXC_ID5_BOF) + bValid = rIn.StartNextRecord(); + } + return bValid; + } +} + +ErrCode ImportExcel::Read() +{ + XclImpPageSettings& rPageSett = GetPageSettings(); + XclImpTabViewSettings& rTabViewSett = GetTabViewSettings(); + XclImpPalette& rPal = GetPalette(); + XclImpFontBuffer& rFontBfr = GetFontBuffer(); + XclImpNumFmtBuffer& rNumFmtBfr = GetNumFmtBuffer(); + XclImpXFBuffer& rXFBfr = GetXFBuffer(); + XclImpNameManager& rNameMgr = GetNameManager(); + // call to GetCurrSheetDrawing() cannot be cached (changes in new sheets) + + enum STATE { + Z_BiffNull, // not a valid Biff-Format + Z_Biff2, // Biff2: only one table + + Z_Biff3, // Biff3: only one table + + Z_Biff4, // Biff4: only one table + Z_Biff4W, // Biff4 Workbook: Globals + Z_Biff4T, // Biff4 Workbook: a table itself + Z_Biff4E, // Biff4 Workbook: between tables + + Z_Biff5WPre,// Biff5: Prefetch Workbook + Z_Biff5W, // Biff5: Globals + Z_Biff5TPre,// Biff5: Prefetch for Shrfmla/Array Formula + Z_Biff5T, // Biff5: a table itself + Z_Biff5E, // Biff5: between tables + Z_Biffn0, // all Biffs: skip table till next EOF + Z_End }; + + STATE eCurrent = Z_BiffNull, ePrev = Z_BiffNull; + + ErrCode eLastErr = ERRCODE_NONE; + sal_uInt16 nOpcode; + sal_uInt16 nBofLevel = 0; + + std::unique_ptr< ScfSimpleProgressBar > pProgress( new ScfSimpleProgressBar( + aIn.GetSvStreamSize(), GetDocShell(), STR_LOAD_DOC ) ); + + /* #i104057# Need to track a base position for progress bar calculation, + because sheet substreams may not be in order of sheets. */ + std::size_t nProgressBasePos = 0; + std::size_t nProgressBaseSize = 0; + + for (; eCurrent != Z_End; mnLastRecId = nOpcode) + { + if( eCurrent == Z_Biff5E ) + { + sal_uInt16 nScTab = GetCurrScTab(); + if( nScTab < maSheetOffsets.size() ) + { + nProgressBaseSize += (aIn.GetSvStreamPos() - nProgressBasePos); + nProgressBasePos = maSheetOffsets[ nScTab ]; + + bool bValid = TryStartNextRecord(aIn, nProgressBasePos); + if (!bValid) + { + // Safeguard ourselves from potential infinite loop. + eCurrent = Z_End; + } + } + else + eCurrent = Z_End; + } + else + aIn.StartNextRecord(); + + nOpcode = aIn.GetRecId(); + + if( !aIn.IsValid() ) + { + // finalize table if EOF is missing + switch( eCurrent ) + { + case Z_Biff2: + case Z_Biff3: + case Z_Biff4: + case Z_Biff4T: + case Z_Biff5TPre: + case Z_Biff5T: + rNumFmtBfr.CreateScFormats(); + Eof(); + break; + default:; + } + break; + } + + if( eCurrent == Z_End ) + break; + + if( eCurrent != Z_Biff5TPre && eCurrent != Z_Biff5WPre ) + pProgress->ProgressAbs( nProgressBaseSize + aIn.GetSvStreamPos() - nProgressBasePos ); + + switch( eCurrent ) + { + + case Z_BiffNull: // ------------------------------- Z_BiffNull - + { + switch( nOpcode ) + { + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: + { + // #i23425# don't rely on the record ID, but on the detected BIFF version + switch( GetBiff() ) + { + case EXC_BIFF2: + Bof2(); + if( pExcRoot->eDateiTyp == Biff2 ) + { + eCurrent = Z_Biff2; + NewTable(); + } + break; + case EXC_BIFF3: + Bof3(); + if( pExcRoot->eDateiTyp == Biff3 ) + { + eCurrent = Z_Biff3; + NewTable(); + } + break; + case EXC_BIFF4: + Bof4(); + if( pExcRoot->eDateiTyp == Biff4 ) + { + eCurrent = Z_Biff4; + NewTable(); + } + else if( pExcRoot->eDateiTyp == Biff4W ) + eCurrent = Z_Biff4W; + break; + case EXC_BIFF5: + Bof5(); + if( pExcRoot->eDateiTyp == Biff5W ) + { + eCurrent = Z_Biff5WPre; + + nBdshtTab = 0; + + aIn.StoreGlobalPosition(); // store position + } + else if( pExcRoot->eDateiTyp == Biff5 ) + { + // #i62752# possible to have BIFF5 sheet without globals + NewTable(); + eCurrent = Z_Biff5TPre; // Shrfmla Prefetch, Row-Prefetch + nBofLevel = 0; + aIn.StoreGlobalPosition(); // store position + } + break; + default: + DBG_ERROR_BIFF(); + } + } + break; + } + } + break; + + case Z_Biff2: // ---------------------------------- Z_Biff2 - + { + switch( nOpcode ) + { + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case 0x06: Formula25(); break; // FORMULA [ 2 5] + case 0x08: Row25(); break; // ROW [ 2 5] + case 0x0A: // EOF [ 2345] + rNumFmtBfr.CreateScFormats(); + rNameMgr.ConvertAllTokens(); + Eof(); + eCurrent = Z_End; + break; + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x18: rNameMgr.ReadName( maStrm ); break; + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x1E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x20: Columndefault(); break; // COLUMNDEFAULT[ 2 ] + case 0x21: Array25(); break; // ARRAY [ 2 5] + case 0x23: Externname25(); break; // EXTERNNAME [ 2 5] + case 0x24: Colwidth(); break; // COLWIDTH [ 2 ] + case 0x25: Defrowheight2(); break; // DEFAULTROWHEI[ 2 ] + case 0x26: + case 0x27: + case 0x28: + case 0x29: rPageSett.ReadMargin( maStrm ); break; + case 0x2A: rPageSett.ReadPrintHeaders( maStrm ); break; + case 0x2B: rPageSett.ReadPrintGridLines( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID2_FONT: rFontBfr.ReadFont( maStrm ); break; + case EXC_ID_EFONT: rFontBfr.ReadEfont( maStrm ); break; + case 0x3E: rTabViewSett.ReadWindow2( maStrm, false );break; + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x43: rXFBfr.ReadXF( maStrm ); break; + case 0x44: Ixfe(); break; // IXFE [ 2 ] + } + } + break; + + case Z_Biff3: // ---------------------------------- Z_Biff3 - + { + switch( nOpcode ) + { + // skip chart substream + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case 0x0A: // EOF [ 2345] + rNumFmtBfr.CreateScFormats(); + rNameMgr.ConvertAllTokens(); + Eof(); + eCurrent = Z_End; + break; + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x1A: + case 0x1B: rPageSett.ReadPageBreaks( maStrm ); break; + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x1E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x22: Rec1904(); break; // 1904 [ 2345] + case 0x26: + case 0x27: + case 0x28: + case 0x29: rPageSett.ReadMargin( maStrm ); break; + case 0x2A: rPageSett.ReadPrintHeaders( maStrm ); break; + case 0x2B: rPageSett.ReadPrintGridLines( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x5D: GetCurrSheetDrawing().ReadObj( maStrm );break; + case 0x7D: Colinfo(); break; // COLINFO [ 345] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x92: rPal.ReadPalette( maStrm ); break; + case 0x0206: Formula3(); break; // FORMULA [ 3 ] + case 0x0208: Row34(); break; // ROW [ 34 ] + case 0x0218: rNameMgr.ReadName( maStrm ); break; + case 0x0221: Array34(); break; // ARRAY [ 34 ] + case 0x0223: break; // EXTERNNAME [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x0231: rFontBfr.ReadFont( maStrm ); break; + case 0x023E: rTabViewSett.ReadWindow2( maStrm, false );break; + case 0x0243: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + } + } + break; + + case Z_Biff4: // ---------------------------------- Z_Biff4 - + { + switch( nOpcode ) + { + // skip chart substream + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case 0x0A: // EOF [ 2345] + rNumFmtBfr.CreateScFormats(); + rNameMgr.ConvertAllTokens(); + Eof(); + eCurrent = Z_End; + break; + case 0x12: SheetProtect(); break; // SHEET PROTECTION + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x1A: + case 0x1B: rPageSett.ReadPageBreaks( maStrm ); break; + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x22: Rec1904(); break; // 1904 [ 2345] + case 0x26: + case 0x27: + case 0x28: + case 0x29: rPageSett.ReadMargin( maStrm ); break; + case 0x2A: rPageSett.ReadPrintHeaders( maStrm ); break; + case 0x2B: rPageSett.ReadPrintGridLines( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x55: DefColWidth(); break; + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x5D: GetCurrSheetDrawing().ReadObj( maStrm );break; + case 0x7D: Colinfo(); break; // COLINFO [ 345] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x92: rPal.ReadPalette( maStrm ); break; + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45] + case 0xA1: rPageSett.ReadSetup( maStrm ); break; + case 0x0208: Row34(); break; // ROW [ 34 ] + case 0x0218: rNameMgr.ReadName( maStrm ); break; + case 0x0221: Array34(); break; // ARRAY [ 34 ] + case 0x0223: break; // EXTERNNAME [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x0231: rFontBfr.ReadFont( maStrm ); break; + case 0x023E: rTabViewSett.ReadWindow2( maStrm, false );break; + case 0x0406: Formula4(); break; // FORMULA [ 4 ] + case 0x041E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x0443: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + } + } + break; + + case Z_Biff4W: // --------------------------------- Z_Biff4W - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + rNameMgr.ConvertAllTokens(); + eCurrent = Z_End; + break; + case 0x12: DocProtect(); break; // PROTECT [ 5] + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x55: DefColWidth(); break; + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x8F: break; // BUNDLEHEADER [ 4 ] + case 0x92: rPal.ReadPalette( maStrm ); break; + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45] + case 0x0218: rNameMgr.ReadName( maStrm ); break; + case 0x0223: break; // EXTERNNAME [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x0231: rFontBfr.ReadFont( maStrm ); break; + case EXC_ID4_BOF: // BOF [ 4 ] + Bof4(); + if( pExcRoot->eDateiTyp == Biff4 ) + { + eCurrent = Z_Biff4T; + NewTable(); + } + else + eCurrent = Z_End; + break; + case 0x041E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x0443: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + } + + } + break; + + case Z_Biff4T: // --------------------------------- Z_Biff4T - + { + switch( nOpcode ) + { + // skip chart substream + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case 0x0A: // EOF [ 2345] + rNameMgr.ConvertAllTokens(); + Eof(); + eCurrent = Z_Biff4E; + break; + case 0x12: SheetProtect(); break; // SHEET PROTECTION + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x1A: + case 0x1B: rPageSett.ReadPageBreaks( maStrm ); break; + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x55: DefColWidth(); break; + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x5D: GetCurrSheetDrawing().ReadObj( maStrm );break; + case 0x7D: Colinfo(); break; // COLINFO [ 345] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x8F: break; // BUNDLEHEADER [ 4 ] + case 0x92: rPal.ReadPalette( maStrm ); break; + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45] + case 0xA1: rPageSett.ReadSetup( maStrm ); break; + case 0x0208: Row34(); break; // ROW [ 34 ] + case 0x0218: rNameMgr.ReadName( maStrm ); break; + case 0x0221: Array34(); break; + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x0231: rFontBfr.ReadFont( maStrm ); break; + case 0x023E: rTabViewSett.ReadWindow2( maStrm, false );break; + case 0x0406: Formula4(); break; + case 0x041E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x0443: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + } + + } + break; + + case Z_Biff4E: // --------------------------------- Z_Biff4E - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + eCurrent = Z_End; + break; + case 0x8F: break; // BUNDLEHEADER [ 4 ] + case EXC_ID4_BOF: // BOF [ 4 ] + Bof4(); + NewTable(); + if( pExcRoot->eDateiTyp == Biff4 ) + { + eCurrent = Z_Biff4T; + } + else + { + ePrev = eCurrent; + eCurrent = Z_Biffn0; + } + break; + } + + } + break; + case Z_Biff5WPre: // ------------------------------ Z_Biff5WPre - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + eCurrent = Z_Biff5W; + aIn.SeekGlobalPosition(); // and back to old position + break; + case 0x12: DocProtect(); break; // PROTECT [ 5] + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x3D: Window1(); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x85: Boundsheet(); break; // BOUNDSHEET [ 5] + case 0x8C: Country(); break; // COUNTRY [ 345] + // PALETTE follows XFs, but already needed while reading the XFs + case 0x92: rPal.ReadPalette( maStrm ); break; + } + } + break; + case Z_Biff5W: // --------------------------------- Z_Biff5W - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + rNumFmtBfr.CreateScFormats(); + rXFBfr.CreateUserStyles(); + rNameMgr.ConvertAllTokens(); + eCurrent = Z_Biff5E; + break; + case 0x18: rNameMgr.ReadName( maStrm ); break; + case 0x1E: rNumFmtBfr.ReadFormat( maStrm ); break; + case 0x22: Rec1904(); break; // 1904 [ 2345] + case 0x31: rFontBfr.ReadFont( maStrm ); break; + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x8D: Hideobj(); break; // HIDEOBJ [ 345] + case 0xDE: Olesize(); break; + case 0xE0: rXFBfr.ReadXF( maStrm ); break; + case 0x0293: rXFBfr.ReadStyle( maStrm ); break; + case 0x041E: rNumFmtBfr.ReadFormat( maStrm ); break; + } + + } + break; + + case Z_Biff5TPre: // ------------------------------- Z_Biff5Pre - + { + if (nOpcode == EXC_ID5_BOF) + nBofLevel++; + else if( (nOpcode == 0x000A) && nBofLevel ) + nBofLevel--; + else if( !nBofLevel ) // don't read chart records + { + switch( nOpcode ) + { + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + case 0x08: Row25(); break; // ROW [ 2 5] + case 0x0A: // EOF [ 2345] + eCurrent = Z_Biff5T; + aIn.SeekGlobalPosition(); // and back to old position + break; + case 0x12: SheetProtect(); break; // SHEET PROTECTION + case 0x1A: + case 0x1B: rPageSett.ReadPageBreaks( maStrm ); break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x21: Array25(); break; // ARRAY [ 2 5] + case 0x23: Externname25(); break; // EXTERNNAME [ 2 5] + case 0x41: rTabViewSett.ReadPane( maStrm ); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345] + case 0x55: DefColWidth(); break; + case 0x7D: Colinfo(); break; // COLINFO [ 345] + case 0x81: Wsbool(); break; // WSBOOL [ 2345] + case 0x8C: Country(); break; // COUNTRY [ 345] + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45] + case 0x0208: Row34(); break; // ROW [ 34 ] + case 0x0221: Array34(); break; // ARRAY [ 34 ] + case 0x0223: break; // EXTERNNAME [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345] + case 0x023E: rTabViewSett.ReadWindow2( maStrm, false );break; + } + } + } + break; + + case Z_Biff5T: // --------------------------------- Z_Biff5T - + { + switch( nOpcode ) + { + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case EXC_ID2_FORMULA: + case EXC_ID3_FORMULA: + case EXC_ID4_FORMULA: Formula25(); break; + case EXC_ID_SHRFMLA: Shrfmla(); break; + case 0x0A: Eof(); eCurrent = Z_Biff5E; break; + case 0x14: + case 0x15: rPageSett.ReadHeaderFooter( maStrm ); break; + case 0x17: Externsheet(); break; // EXTERNSHEET [ 2345] + case 0x1C: GetCurrSheetDrawing().ReadNote( maStrm );break; + case 0x1D: rTabViewSett.ReadSelection( maStrm ); break; + case 0x23: Externname25(); break; // EXTERNNAME [ 2 5] + case 0x26: + case 0x27: + case 0x28: + case 0x29: rPageSett.ReadMargin( maStrm ); break; + case 0x2A: rPageSett.ReadPrintHeaders( maStrm ); break; + case 0x2B: rPageSett.ReadPrintGridLines( maStrm ); break; + case 0x2F: // FILEPASS [ 2345] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = Z_End; + break; + case 0x5D: GetCurrSheetDrawing().ReadObj( maStrm );break; + case 0x83: + case 0x84: rPageSett.ReadCenter( maStrm ); break; + case 0xA0: rTabViewSett.ReadScl( maStrm ); break; + case 0xA1: rPageSett.ReadSetup( maStrm ); break; + case 0xBD: Mulrk(); break; // MULRK [ 5] + case 0xBE: Mulblank(); break; // MULBLANK [ 5] + case 0xD6: Rstring(); break; // RSTRING [ 5] + case 0x00E5: Cellmerging(); break; // #i62300# + case 0x0236: TableOp(); break; // TABLE [ 5] + case EXC_ID5_BOF: // BOF [ 5] + XclTools::SkipSubStream( maStrm ); + break; + } + + } + break; + + case Z_Biff5E: // --------------------------------- Z_Biff5E - + { + switch( nOpcode ) + { + case EXC_ID5_BOF: // BOF [ 5] + Bof5(); + NewTable(); + switch( pExcRoot->eDateiTyp ) + { + case Biff5: + case Biff5M4: + eCurrent = Z_Biff5TPre; // Shrfmla Prefetch, Row-Prefetch + nBofLevel = 0; + aIn.StoreGlobalPosition(); // store position + break; + case Biff5C: // chart sheet + GetCurrSheetDrawing().ReadTabChart( maStrm ); + Eof(); + GetTracer().TraceChartOnlySheet(); + break; + case Biff5V: + default: + rD.SetVisible( GetCurrScTab(), false ); + ePrev = eCurrent; + eCurrent = Z_Biffn0; + } + OSL_ENSURE( pExcRoot->eDateiTyp != Biff5W, + "+ImportExcel::Read(): Doppel-Whopper-Workbook!" ); + + break; + } + + } + break; + case Z_Biffn0: // --------------------------------- Z_Biffn0 - + { + switch( nOpcode ) + { + case 0x0A: // EOF [ 2345] + eCurrent = ePrev; + IncCurrScTab(); + break; + } + + } + break; + + case Z_End: // ----------------------------------- Z_End - + OSL_FAIL( "*ImportExcel::Read(): Not possible state!" ); + break; + default: OSL_FAIL( "-ImportExcel::Read(): state forgotten!" ); + } + } + + if( eLastErr == ERRCODE_NONE ) + { + pProgress.reset(); + + GetDocImport().finalize(); + if (!utl::ConfigManager::IsFuzzing()) + AdjustRowHeight(); + PostDocLoad(); + + rD.CalcAfterLoad(false); + + const XclImpAddressConverter& rAddrConv = GetAddressConverter(); + if( rAddrConv.IsTabTruncated() ) + eLastErr = SCWARN_IMPORT_SHEET_OVERFLOW; + else if( bTabTruncated || rAddrConv.IsRowTruncated() ) + eLastErr = SCWARN_IMPORT_ROW_OVERFLOW; + else if( rAddrConv.IsColTruncated() ) + eLastErr = SCWARN_IMPORT_COLUMN_OVERFLOW; + } + + return eLastErr; +} + +ErrCode ImportExcel8::Read() +{ +#ifdef EXC_INCL_DUMPER + { + Biff8RecDumper aDumper( GetRoot(), sal_True ); + if( aDumper.Dump( aIn ) ) + return ERRCODE_ABORT; + } +#endif + // read the entire BIFF8 stream + // don't look too close - this stuff seriously needs to be reworked + + XclImpPageSettings& rPageSett = GetPageSettings(); + XclImpTabViewSettings& rTabViewSett = GetTabViewSettings(); + XclImpPalette& rPal = GetPalette(); + XclImpFontBuffer& rFontBfr = GetFontBuffer(); + XclImpNumFmtBuffer& rNumFmtBfr = GetNumFmtBuffer(); + XclImpXFBuffer& rXFBfr = GetXFBuffer(); + XclImpSst& rSst = GetSst(); + XclImpTabInfo& rTabInfo = GetTabInfo(); + XclImpNameManager& rNameMgr = GetNameManager(); + XclImpLinkManager& rLinkMgr = GetLinkManager(); + XclImpObjectManager& rObjMgr = GetObjectManager(); + // call to GetCurrSheetDrawing() cannot be cached (changes in new sheets) + XclImpCondFormatManager& rCondFmtMgr = GetCondFormatManager(); + XclImpValidationManager& rValidMgr = GetValidationManager(); + XclImpPivotTableManager& rPTableMgr = GetPivotTableManager(); + XclImpWebQueryBuffer& rWQBfr = GetWebQueryBuffer(); + + bool bInUserView = false; // true = In USERSVIEW(BEGIN|END) record block. + + enum XclImpReadState + { + EXC_STATE_BEFORE_GLOBALS, /// Before workbook globals (wait for initial BOF). + EXC_STATE_GLOBALS_PRE, /// Prefetch for workbook globals. + EXC_STATE_GLOBALS, /// Workbook globals. + EXC_STATE_BEFORE_SHEET, /// Before worksheet (wait for new worksheet BOF). + EXC_STATE_SHEET_PRE, /// Prefetch for worksheet. + EXC_STATE_SHEET, /// Worksheet. + EXC_STATE_END /// Stop reading. + }; + + XclImpReadState eCurrent = EXC_STATE_BEFORE_GLOBALS; + + ErrCode eLastErr = ERRCODE_NONE; + + std::unique_ptr< ScfSimpleProgressBar > pProgress( new ScfSimpleProgressBar( + aIn.GetSvStreamSize(), GetDocShell(), STR_LOAD_DOC ) ); + + /* #i104057# Need to track a base position for progress bar calculation, + because sheet substreams may not be in order of sheets. */ + std::size_t nProgressBasePos = 0; + std::size_t nProgressBaseSize = 0; + + bool bSheetHasCodeName = false; + + std::vector aCodeNames; + std::vector < SCTAB > nTabsWithNoCodeName; + + sal_uInt16 nRecId = 0; + + for (; eCurrent != EXC_STATE_END; mnLastRecId = nRecId) + { + if( eCurrent == EXC_STATE_BEFORE_SHEET ) + { + sal_uInt16 nScTab = GetCurrScTab(); + if( nScTab < maSheetOffsets.size() ) + { + nProgressBaseSize += (maStrm.GetSvStreamPos() - nProgressBasePos); + nProgressBasePos = maSheetOffsets[ nScTab ]; + + bool bValid = TryStartNextRecord(aIn, nProgressBasePos); + if (!bValid) + { + // Safeguard ourselves from potential infinite loop. + eCurrent = EXC_STATE_END; + } + + // import only 256 sheets + if( nScTab > GetScMaxPos().Tab() ) + { + if( maStrm.GetRecId() != EXC_ID_EOF ) + XclTools::SkipSubStream( maStrm ); + // #i29930# show warning box + GetAddressConverter().CheckScTab( nScTab ); + eCurrent = EXC_STATE_END; + } + else + { + // #i109800# SHEET record may point to any record inside the + // sheet substream + bool bIsBof = maStrm.GetRecId() == EXC_ID5_BOF; + if( bIsBof ) + Bof5(); // read the BOF record + else + pExcRoot->eDateiTyp = Biff8; // on missing BOF, assume a standard worksheet + NewTable(); + switch( pExcRoot->eDateiTyp ) + { + case Biff8: // worksheet + case Biff8M4: // macro sheet + eCurrent = EXC_STATE_SHEET_PRE; // Shrfmla Prefetch, Row-Prefetch + // go to next record + if( bIsBof ) maStrm.StartNextRecord(); + maStrm.StoreGlobalPosition(); + break; + case Biff8C: // chart sheet + GetCurrSheetDrawing().ReadTabChart( maStrm ); + Eof(); + GetTracer().TraceChartOnlySheet(); + break; + case Biff8W: // workbook + OSL_FAIL( "ImportExcel8::Read - double workbook globals" ); + [[fallthrough]]; + case Biff8V: // VB module + default: + // TODO: do not create a sheet in the Calc document + rD.SetVisible( nScTab, false ); + XclTools::SkipSubStream( maStrm ); + IncCurrScTab(); + } + } + } + else + eCurrent = EXC_STATE_END; + } + else + aIn.StartNextRecord(); + + if( !aIn.IsValid() ) + { + // #i63591# finalize table if EOF is missing + switch( eCurrent ) + { + case EXC_STATE_SHEET_PRE: + eCurrent = EXC_STATE_SHEET; + aIn.SeekGlobalPosition(); + continue; // next iteration in while loop + case EXC_STATE_SHEET: + Eof(); + eCurrent = EXC_STATE_END; + break; + default: + eCurrent = EXC_STATE_END; + } + } + + if( eCurrent == EXC_STATE_END ) + break; + + if( eCurrent != EXC_STATE_SHEET_PRE && eCurrent != EXC_STATE_GLOBALS_PRE ) + pProgress->ProgressAbs( nProgressBaseSize + aIn.GetSvStreamPos() - nProgressBasePos ); + + nRecId = aIn.GetRecId(); + + /* #i39464# Ignore records between USERSVIEWBEGIN and USERSVIEWEND + completely (user specific view settings). Otherwise view settings + and filters are loaded multiple times, which at least causes + problems in auto-filters. */ + switch( nRecId ) + { + case EXC_ID_USERSVIEWBEGIN: + OSL_ENSURE( !bInUserView, "ImportExcel8::Read - nested user view settings" ); + bInUserView = true; + break; + case EXC_ID_USERSVIEWEND: + OSL_ENSURE( bInUserView, "ImportExcel8::Read - not in user view settings" ); + bInUserView = false; + break; + } + + if( !bInUserView ) switch( eCurrent ) + { + + // before workbook globals: wait for initial workbook globals BOF + case EXC_STATE_BEFORE_GLOBALS: + { + if( nRecId == EXC_ID5_BOF ) + { + OSL_ENSURE( GetBiff() == EXC_BIFF8, "ImportExcel8::Read - wrong BIFF version" ); + Bof5(); + if( pExcRoot->eDateiTyp == Biff8W ) + { + eCurrent = EXC_STATE_GLOBALS_PRE; + maStrm.StoreGlobalPosition(); + nBdshtTab = 0; + } + else if( pExcRoot->eDateiTyp == Biff8 ) + { + // #i62752# possible to have BIFF8 sheet without globals + NewTable(); + eCurrent = EXC_STATE_SHEET_PRE; // Shrfmla Prefetch, Row-Prefetch + bSheetHasCodeName = false; // reset + aIn.StoreGlobalPosition(); + } + } + } + break; + + // prefetch for workbook globals + case EXC_STATE_GLOBALS_PRE: + { + switch( nRecId ) + { + case EXC_ID_EOF: + case EXC_ID_EXTSST: + /* #i56376# evil hack: if EOF for globals is missing, + simulate it. This hack works only for the bugdoc + given in the issue, where the sheet substreams + start directly after the EXTSST record. A future + implementation should be more robust against + missing EOFs. */ + if( (nRecId == EXC_ID_EOF) || + ((nRecId == EXC_ID_EXTSST) && (maStrm.GetNextRecId() == EXC_ID5_BOF)) ) + { + eCurrent = EXC_STATE_GLOBALS; + aIn.SeekGlobalPosition(); + } + break; + case 0x12: DocProtect(); break; // PROTECT [ 5678] + case 0x13: DocPassword(); break; + case 0x19: WinProtection(); break; + case 0x2F: // FILEPASS [ 2345 ] + eLastErr = XclImpDecryptHelper::ReadFilepass( maStrm ); + if( eLastErr != ERRCODE_NONE ) + eCurrent = EXC_STATE_END; + break; + case EXC_ID_FILESHARING: ReadFileSharing(); break; + case 0x3D: Window1(); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345 ] + case 0x85: Boundsheet(); break; // BOUNDSHEET [ 5 ] + case 0x8C: Country(); break; // COUNTRY [ 345 ] + + // PALETTE follows XFs, but already needed while reading the XFs + case EXC_ID_PALETTE: rPal.ReadPalette( maStrm ); break; + } + } + break; + + // workbook globals + case EXC_STATE_GLOBALS: + { + switch( nRecId ) + { + case EXC_ID_EOF: + case EXC_ID_EXTSST: + /* #i56376# evil hack: if EOF for globals is missing, + simulate it. This hack works only for the bugdoc + given in the issue, where the sheet substreams + start directly after the EXTSST record. A future + implementation should be more robust against + missing EOFs. */ + if( (nRecId == EXC_ID_EOF) || + ((nRecId == EXC_ID_EXTSST) && (maStrm.GetNextRecId() == EXC_ID5_BOF)) ) + { + rNumFmtBfr.CreateScFormats(); + rXFBfr.CreateUserStyles(); + rPTableMgr.ReadPivotCaches( maStrm ); + rNameMgr.ConvertAllTokens(); + eCurrent = EXC_STATE_BEFORE_SHEET; + } + break; + case 0x0E: Precision(); break; // PRECISION + case 0x22: Rec1904(); break; // 1904 [ 2345 ] + case 0x56: break; // BUILTINFMTCNT[ 34 ] + case 0x8D: Hideobj(); break; // HIDEOBJ [ 345 ] + case 0xD3: SetHasBasic(); break; + case 0xDE: Olesize(); break; + + case EXC_ID_CODENAME: ReadCodeName( aIn, true ); break; + case EXC_ID_USESELFS: ReadUsesElfs(); break; + + case EXC_ID2_FONT: rFontBfr.ReadFont( maStrm ); break; + case EXC_ID4_FORMAT: rNumFmtBfr.ReadFormat( maStrm ); break; + case EXC_ID5_XF: rXFBfr.ReadXF( maStrm ); break; + case EXC_ID_STYLE: rXFBfr.ReadStyle( maStrm ); break; + + case EXC_ID_SST: rSst.ReadSst( maStrm ); break; + case EXC_ID_TABID: rTabInfo.ReadTabid( maStrm ); break; + case EXC_ID_NAME: rNameMgr.ReadName( maStrm ); break; + + case EXC_ID_EXTERNSHEET: rLinkMgr.ReadExternsheet( maStrm ); break; + case EXC_ID_SUPBOOK: rLinkMgr.ReadSupbook( maStrm ); break; + case EXC_ID_XCT: rLinkMgr.ReadXct( maStrm ); break; + case EXC_ID_CRN: rLinkMgr.ReadCrn( maStrm ); break; + case EXC_ID_EXTERNNAME: rLinkMgr.ReadExternname( maStrm, pFormConv.get() ); break; + + case EXC_ID_MSODRAWINGGROUP:rObjMgr.ReadMsoDrawingGroup( maStrm ); break; + + case EXC_ID_SXIDSTM: rPTableMgr.ReadSxidstm( maStrm ); break; + case EXC_ID_SXVS: rPTableMgr.ReadSxvs( maStrm ); break; + case EXC_ID_DCONREF: rPTableMgr.ReadDconref( maStrm ); break; + case EXC_ID_DCONNAME: rPTableMgr.ReadDConName( maStrm ); break; + } + + } + break; + + // prefetch for worksheet + case EXC_STATE_SHEET_PRE: + { + switch( nRecId ) + { + // skip chart substream + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID_WINDOW2: rTabViewSett.ReadWindow2( maStrm, false );break; + case EXC_ID_SCL: rTabViewSett.ReadScl( maStrm ); break; + case EXC_ID_PANE: rTabViewSett.ReadPane( maStrm ); break; + case EXC_ID_SELECTION: rTabViewSett.ReadSelection( maStrm ); break; + + case EXC_ID2_DIMENSIONS: + case EXC_ID3_DIMENSIONS: ReadDimensions(); break; + + case EXC_ID_CODENAME: ReadCodeName( aIn, false ); bSheetHasCodeName = true; break; + + case 0x0A: // EOF [ 2345 ] + { + eCurrent = EXC_STATE_SHEET; + OUString sName; + GetDoc().GetName( GetCurrScTab(), sName ); + if ( !bSheetHasCodeName ) + { + nTabsWithNoCodeName.push_back( GetCurrScTab() ); + } + else + { + OUString sCodeName; + GetDoc().GetCodeName( GetCurrScTab(), sCodeName ); + aCodeNames.push_back( sCodeName ); + } + + bSheetHasCodeName = false; // reset + + aIn.SeekGlobalPosition(); // and back to old position + break; + } + case 0x12: SheetProtect(); break; + case 0x13: SheetPassword(); break; + case 0x42: Codepage(); break; // CODEPAGE [ 2345 ] + case 0x55: DefColWidth(); break; + case 0x7D: Colinfo(); break; // COLINFO [ 345 ] + case 0x81: Wsbool(); break; // WSBOOL [ 2345 ] + case 0x8C: Country(); break; // COUNTRY [ 345 ] + case 0x99: Standardwidth(); break; // STANDARDWIDTH[ 45 ] + case 0x9B: FilterMode(); break; // FILTERMODE + case EXC_ID_AUTOFILTERINFO: AutoFilterInfo(); break;// AUTOFILTERINFO + case EXC_ID_AUTOFILTER: AutoFilter(); break; // AUTOFILTER + case 0x0208: Row34(); break; // ROW [ 34 ] + case EXC_ID2_ARRAY: + case EXC_ID3_ARRAY: Array34(); break; // ARRAY [ 34 ] + case 0x0225: Defrowheight345();break;//DEFAULTROWHEI[ 345 ] + case 0x0867: FeatHdr(); break; // FEATHDR + case 0x0868: Feat(); break; // FEAT + } + } + break; + + // worksheet + case EXC_STATE_SHEET: + { + switch( nRecId ) + { + // skip unknown substreams + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( maStrm ); break; + + case EXC_ID_EOF: Eof(); eCurrent = EXC_STATE_BEFORE_SHEET; break; + + case EXC_ID2_BLANK: + case EXC_ID3_BLANK: ReadBlank(); break; + case EXC_ID2_INTEGER: ReadInteger(); break; + case EXC_ID2_NUMBER: + case EXC_ID3_NUMBER: ReadNumber(); break; + case EXC_ID2_LABEL: + case EXC_ID3_LABEL: ReadLabel(); break; + case EXC_ID2_BOOLERR: + case EXC_ID3_BOOLERR: ReadBoolErr(); break; + case EXC_ID_RK: ReadRk(); break; + + case EXC_ID2_FORMULA: + case EXC_ID3_FORMULA: + case EXC_ID4_FORMULA: Formula25(); break; + case EXC_ID_SHRFMLA: Shrfmla(); break; + case 0x000C: Calccount(); break; // CALCCOUNT + case 0x0010: Delta(); break; // DELTA + case 0x0011: Iteration(); break; // ITERATION + case 0x007E: + case 0x00AE: Scenman(); break; // SCENMAN + case 0x00AF: Scenario(); break; // SCENARIO + case 0x00BD: Mulrk(); break; // MULRK [ 5 ] + case 0x00BE: Mulblank(); break; // MULBLANK [ 5 ] + case 0x00D6: Rstring(); break; // RSTRING [ 5 ] + case 0x00E5: Cellmerging(); break; // CELLMERGING + case 0x00FD: Labelsst(); break; // LABELSST [ 8 ] + case 0x0236: TableOp(); break; // TABLE + + case EXC_ID_HORPAGEBREAKS: + case EXC_ID_VERPAGEBREAKS: rPageSett.ReadPageBreaks( maStrm ); break; + case EXC_ID_HEADER: + case EXC_ID_FOOTER: rPageSett.ReadHeaderFooter( maStrm ); break; + case EXC_ID_LEFTMARGIN: + case EXC_ID_RIGHTMARGIN: + case EXC_ID_TOPMARGIN: + case EXC_ID_BOTTOMMARGIN: rPageSett.ReadMargin( maStrm ); break; + case EXC_ID_PRINTHEADERS: rPageSett.ReadPrintHeaders( maStrm ); break; + case EXC_ID_PRINTGRIDLINES: rPageSett.ReadPrintGridLines( maStrm ); break; + case EXC_ID_HCENTER: + case EXC_ID_VCENTER: rPageSett.ReadCenter( maStrm ); break; + case EXC_ID_SETUP: rPageSett.ReadSetup( maStrm ); break; + case EXC_ID8_IMGDATA: rPageSett.ReadImgData( maStrm ); break; + + case EXC_ID_MSODRAWING: GetCurrSheetDrawing().ReadMsoDrawing( maStrm ); break; + // #i61786# weird documents: OBJ without MSODRAWING -> read in BIFF5 format + case EXC_ID_OBJ: GetCurrSheetDrawing().ReadObj( maStrm ); break; + case EXC_ID_NOTE: GetCurrSheetDrawing().ReadNote( maStrm ); break; + + case EXC_ID_HLINK: XclImpHyperlink::ReadHlink( maStrm ); break; + case EXC_ID_LABELRANGES: XclImpLabelranges::ReadLabelranges( maStrm ); break; + + case EXC_ID_CONDFMT: rCondFmtMgr.ReadCondfmt( maStrm ); break; + case EXC_ID_CF: rCondFmtMgr.ReadCF( maStrm ); break; + + case EXC_ID_DVAL: XclImpValidationManager::ReadDval( maStrm ); break; + case EXC_ID_DV: rValidMgr.ReadDV( maStrm ); break; + + case EXC_ID_QSI: rWQBfr.ReadQsi( maStrm ); break; + case EXC_ID_WQSTRING: rWQBfr.ReadWqstring( maStrm ); break; + case EXC_ID_PQRY: rWQBfr.ReadParamqry( maStrm ); break; + case EXC_ID_WQSETT: rWQBfr.ReadWqsettings( maStrm ); break; + case EXC_ID_WQTABLES: rWQBfr.ReadWqtables( maStrm ); break; + + case EXC_ID_SXVIEW: rPTableMgr.ReadSxview( maStrm ); break; + case EXC_ID_SXVD: rPTableMgr.ReadSxvd( maStrm ); break; + case EXC_ID_SXVI: rPTableMgr.ReadSxvi( maStrm ); break; + case EXC_ID_SXIVD: rPTableMgr.ReadSxivd( maStrm ); break; + case EXC_ID_SXPI: rPTableMgr.ReadSxpi( maStrm ); break; + case EXC_ID_SXDI: rPTableMgr.ReadSxdi( maStrm ); break; + case EXC_ID_SXVDEX: rPTableMgr.ReadSxvdex( maStrm ); break; + case EXC_ID_SXEX: rPTableMgr.ReadSxex( maStrm ); break; + case EXC_ID_SHEETEXT: rTabViewSett.ReadTabBgColor( maStrm, rPal ); break; + case EXC_ID_SXVIEWEX9: rPTableMgr.ReadSxViewEx9( maStrm ); break; + case EXC_ID_SXADDL: rPTableMgr.ReadSxAddl( maStrm ); break; + } + } + break; + + default:; + } + } + + if( eLastErr == ERRCODE_NONE ) + { + // In some strange circumstances the codename might be missing + // # Create any missing Sheet CodeNames + for ( const auto& rTab : nTabsWithNoCodeName ) + { + SCTAB nTab = 1; + while ( true ) + { + OUStringBuffer aBuf; + aBuf.append("Sheet"); + aBuf.append(static_cast(nTab++)); + OUString sTmpName = aBuf.makeStringAndClear(); + + if ( std::find(aCodeNames.begin(), aCodeNames.end(), sTmpName) == aCodeNames.end() ) // generated codename not found + { + // Set new codename + GetDoc().SetCodeName( rTab, sTmpName ); + // Record newly used codename + aCodeNames.push_back(sTmpName); + break; + } + } + } + // #i45843# Convert pivot tables before calculation, so they are available + // for the GETPIVOTDATA function. + if( GetBiff() == EXC_BIFF8 ) + GetPivotTableManager().ConvertPivotTables(); + + ScDocumentImport& rDoc = GetDocImport(); + rDoc.finalize(); + pProgress.reset(); +#if 0 + // Excel documents look much better without this call; better in the + // sense that the row heights are identical to the original heights in + // Excel. + if ( !rD.IsAdjustHeightLocked()) + AdjustRowHeight(); +#endif + PostDocLoad(); + + rD.CalcAfterLoad(false); + + // import change tracking data + XclImpChangeTrack aImpChTr( GetRoot(), maStrm ); + aImpChTr.Apply(); + + const XclImpAddressConverter& rAddrConv = GetAddressConverter(); + if( rAddrConv.IsTabTruncated() ) + eLastErr = SCWARN_IMPORT_SHEET_OVERFLOW; + else if( bTabTruncated || rAddrConv.IsRowTruncated() ) + eLastErr = SCWARN_IMPORT_ROW_OVERFLOW; + else if( rAddrConv.IsColTruncated() ) + eLastErr = SCWARN_IMPORT_COLUMN_OVERFLOW; + + if( GetBiff() == EXC_BIFF8 ) + GetPivotTableManager().MaybeRefreshPivotTables(); + } + + return eLastErr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/tokstack.cxx b/sc/source/filter/excel/tokstack.cxx new file mode 100644 index 000000000..a0e3974ca --- /dev/null +++ b/sc/source/filter/excel/tokstack.cxx @@ -0,0 +1,752 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include + +#include +#include + +const sal_uInt16 TokenPool::nScTokenOff = 8192; + +TokenStack::TokenStack( ) + : pStack( new TokenId[ nSize ] ) +{ + Reset(); +} + +TokenStack::~TokenStack() +{ +} + +// !ATTENTION!": to the outside the numbering starts with 1! +// !ATTENTION!": SC-Token are stored with an offset nScTokenOff +// -> to distinguish from other tokens + +TokenPool::TokenPool( svl::SharedStringPool& rSPool ) : + mrStringPool(rSPool) +{ + // pool for Id-sequences + nP_Id = 256; + pP_Id.reset( new sal_uInt16[ nP_Id ] ); + + // pool for Ids + nElement = 32; + pElement.reset( new sal_uInt16[ nElement ] ); + pType.reset( new E_TYPE[ nElement ] ); + pSize.reset( new sal_uInt16[ nElement ] ); + nP_IdLast = 0; + + nP_Matrix = 16; + ppP_Matrix.reset( new ScMatrix*[ nP_Matrix ] ); + memset( ppP_Matrix.get(), 0, sizeof( ScMatrix* ) * nP_Matrix ); + + Reset(); +} + +TokenPool::~TokenPool() +{ + ClearMatrix(); +} + +/** Returns the new number of elements, or 0 if overflow. */ +static sal_uInt16 lcl_canGrow( sal_uInt16 nOld ) +{ + if (!nOld) + return 1; + if (nOld == SAL_MAX_UINT16) + return 0; + sal_uInt32 nNew = ::std::max( static_cast(nOld) * 2, + static_cast(nOld) + 1); + if (nNew > SAL_MAX_UINT16) + nNew = SAL_MAX_UINT16; + if (nNew - 1 < nOld) + nNew = 0; + return static_cast(nNew); +} + +bool TokenPool::GrowId() +{ + sal_uInt16 nP_IdNew = lcl_canGrow( nP_Id); + if (!nP_IdNew) + return false; + + sal_uInt16* pP_IdNew = new (::std::nothrow) sal_uInt16[ nP_IdNew ]; + if (!pP_IdNew) + return false; + + for( sal_uInt16 nL = 0 ; nL < nP_Id ; nL++ ) + pP_IdNew[ nL ] = pP_Id[ nL ]; + + nP_Id = nP_IdNew; + + pP_Id.reset( pP_IdNew ); + return true; +} + +bool TokenPool::CheckElementOrGrow() +{ + // Last possible ID to be assigned somewhere is nElementCurrent+1 + if (nElementCurrent + 1 == nScTokenOff - 1) + { + SAL_WARN("sc.filter","TokenPool::CheckElementOrGrow - last possible ID " << nElementCurrent+1); + return false; + } + + if (nElementCurrent >= nElement) + return GrowElement(); + + return true; +} + +bool TokenPool::GrowElement() +{ + sal_uInt16 nElementNew = lcl_canGrow( nElement); + if (!nElementNew) + return false; + + std::unique_ptr pElementNew(new (::std::nothrow) sal_uInt16[ nElementNew ]); + std::unique_ptr pTypeNew(new (::std::nothrow) E_TYPE[ nElementNew ]); + std::unique_ptr pSizeNew(new (::std::nothrow) sal_uInt16[ nElementNew ]); + if (!pElementNew || !pTypeNew || !pSizeNew) + { + return false; + } + + for( sal_uInt16 nL = 0 ; nL < nElement ; nL++ ) + { + pElementNew[ nL ] = pElement[ nL ]; + pTypeNew[ nL ] = pType[ nL ]; + pSizeNew[ nL ] = pSize[ nL ]; + } + + nElement = nElementNew; + + pElement = std::move( pElementNew ); + pType = std::move( pTypeNew ); + pSize = std::move( pSizeNew ); + return true; +} + +bool TokenPool::GrowMatrix() +{ + sal_uInt16 nNewSize = lcl_canGrow( nP_Matrix); + if (!nNewSize) + return false; + + ScMatrix** ppNew = new (::std::nothrow) ScMatrix*[ nNewSize ]; + if (!ppNew) + return false; + + memset( ppNew, 0, sizeof( ScMatrix* ) * nNewSize ); + for( sal_uInt16 nL = 0 ; nL < nP_Matrix ; nL++ ) + ppNew[ nL ] = ppP_Matrix[ nL ]; + + ppP_Matrix.reset( ppNew ); + nP_Matrix = nNewSize; + return true; +} + +bool TokenPool::GetElement( const sal_uInt16 nId, ScTokenArray* pScToken ) +{ + if (nId >= nElementCurrent) + { + SAL_WARN("sc.filter","TokenPool::GetElement - Id too large, " << nId << " >= " << nElementCurrent); + return false; + } + + bool bRet = true; + if( pType[ nId ] == T_Id ) + bRet = GetElementRek( nId, pScToken ); + else + { + switch( pType[ nId ] ) + { + case T_Str: + { + sal_uInt16 n = pElement[ nId ]; + auto* p = ppP_Str.getIfInRange( n ); + if (p) + pScToken->AddString(mrStringPool.intern(**p)); + else + bRet = false; + } + break; + case T_D: + { + sal_uInt16 n = pElement[ nId ]; + if (n < pP_Dbl.m_writemark) + pScToken->AddDouble( pP_Dbl[ n ] ); + else + bRet = false; + } + break; + case T_Err: + break; +/* TODO: in case we had FormulaTokenArray::AddError() */ +#if 0 + { + sal_uInt16 n = pElement[ nId ]; + if (n < nP_Err) + pScToken->AddError( pP_Err[ n ] ); + else + bRet = false; + } +#endif + case T_RefC: + { + sal_uInt16 n = pElement[ nId ]; + auto p = ppP_RefTr.getIfInRange(n); + if (p) + pScToken->AddSingleReference( **p ); + else + bRet = false; + } + break; + case T_RefA: + { + sal_uInt16 n = pElement[ nId ]; + if (n < ppP_RefTr.m_writemark && ppP_RefTr[ n ] && n+1 < ppP_RefTr.m_writemark && ppP_RefTr[ n + 1 ]) + { + ScComplexRefData aScComplexRefData; + aScComplexRefData.Ref1 = *ppP_RefTr[ n ]; + aScComplexRefData.Ref2 = *ppP_RefTr[ n + 1 ]; + pScToken->AddDoubleReference( aScComplexRefData ); + } + else + bRet = false; + } + break; + case T_RN: + { + sal_uInt16 n = pElement[nId]; + if (n < maRangeNames.size()) + { + const RangeName& r = maRangeNames[n]; + pScToken->AddRangeName(r.mnIndex, r.mnSheet); + } + } + break; + case T_Ext: + { + sal_uInt16 n = pElement[ nId ]; + auto p = ppP_Ext.getIfInRange(n); + + if( p ) + { + if( (*p)->eId == ocEuroConvert ) + pScToken->AddOpCode( (*p)->eId ); + else + pScToken->AddExternal( (*p)->aText, (*p)->eId ); + } + else + bRet = false; + } + break; + case T_Nlf: + { + sal_uInt16 n = pElement[ nId ]; + auto p = ppP_Nlf.getIfInRange(n); + + if( p ) + pScToken->AddColRowName( **p ); + else + bRet = false; + } + break; + case T_Matrix: + { + sal_uInt16 n = pElement[ nId ]; + ScMatrix* p = ( n < nP_Matrix )? ppP_Matrix[ n ] : nullptr; + + if( p ) + pScToken->AddMatrix( p ); + else + bRet = false; + } + break; + case T_ExtName: + { + sal_uInt16 n = pElement[nId]; + if (n < maExtNames.size()) + { + const ExtName& r = maExtNames[n]; + pScToken->AddExternalName(r.mnFileId, mrStringPool.intern( r.maName)); + } + else + bRet = false; + } + break; + case T_ExtRefC: + { + sal_uInt16 n = pElement[nId]; + if (n < maExtCellRefs.size()) + { + const ExtCellRef& r = maExtCellRefs[n]; + pScToken->AddExternalSingleReference(r.mnFileId, mrStringPool.intern( r.maTabName), r.maRef); + } + else + bRet = false; + } + break; + case T_ExtRefA: + { + sal_uInt16 n = pElement[nId]; + if (n < maExtAreaRefs.size()) + { + const ExtAreaRef& r = maExtAreaRefs[n]; + pScToken->AddExternalDoubleReference(r.mnFileId, mrStringPool.intern( r.maTabName), r.maRef); + } + else + bRet = false; + } + break; + default: + OSL_FAIL("-TokenPool::GetElement(): undefined state!?"); + bRet = false; + } + } + return bRet; +} + +bool TokenPool::GetElementRek( const sal_uInt16 nId, ScTokenArray* pScToken ) +{ +#ifdef DBG_UTIL + m_nRek++; + OSL_ENSURE(m_nRek <= nP_Id, "*TokenPool::GetElement(): recursion loops!?"); +#endif + + OSL_ENSURE( nId < nElementCurrent, "*TokenPool::GetElementRek(): nId >= nElementCurrent" ); + + if (nId >= nElementCurrent) + { + SAL_WARN("sc.filter", "*TokenPool::GetElementRek(): nId >= nElementCurrent"); +#ifdef DBG_UTIL + m_nRek--; +#endif + return false; + } + + if (pType[ nId ] != T_Id) + { + SAL_WARN("sc.filter", "-TokenPool::GetElementRek(): pType[ nId ] != T_Id"); +#ifdef DBG_UTIL + m_nRek--; +#endif + return false; + } + + bool bRet = true; + sal_uInt16 nCnt = pSize[ nId ]; + sal_uInt16 nFirstId = pElement[ nId ]; + if (nFirstId >= nP_Id) + { + SAL_WARN("sc.filter", "TokenPool::GetElementRek: nFirstId >= nP_Id"); + nCnt = 0; + bRet = false; + } + sal_uInt16* pCurrent = nCnt ? &pP_Id[ nFirstId ] : nullptr; + if (nCnt > nP_Id - nFirstId) + { + SAL_WARN("sc.filter", "TokenPool::GetElementRek: nCnt > nP_Id - nFirstId"); + nCnt = nP_Id - nFirstId; + bRet = false; + } + for( ; nCnt > 0 ; nCnt--, pCurrent++ ) + { + assert(pCurrent); + if( *pCurrent < nScTokenOff ) + {// recursion or not? + if (*pCurrent >= nElementCurrent) + { + SAL_WARN("sc.filter", "TokenPool::GetElementRek: *pCurrent >= nElementCurrent"); + bRet = false; + } + else + { + if (pType[ *pCurrent ] == T_Id) + bRet = GetElementRek( *pCurrent, pScToken ); + else + bRet = GetElement( *pCurrent, pScToken ); + } + } + else // elementary SC_Token + pScToken->AddOpCode( static_cast( *pCurrent - nScTokenOff ) ); + } + +#ifdef DBG_UTIL + m_nRek--; +#endif + return bRet; +} + +void TokenPool::operator >>( TokenId& rId ) +{ + rId = static_cast( nElementCurrent + 1 ); + + if (!CheckElementOrGrow()) + return; + + pElement[ nElementCurrent ] = nP_IdLast; // Start of Token-sequence + pType[ nElementCurrent ] = T_Id; // set Typeinfo + pSize[ nElementCurrent ] = nP_IdCurrent - nP_IdLast; + // write from nP_IdLast to nP_IdCurrent-1 -> length of the sequence + + nElementCurrent++; // start value for next sequence + nP_IdLast = nP_IdCurrent; +} + +TokenId TokenPool::Store( const double& rDouble ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + if( pP_Dbl.m_writemark >= pP_Dbl.m_capacity ) + if (!pP_Dbl.Grow()) + return static_cast(nElementCurrent+1); + + pElement[ nElementCurrent ] = pP_Dbl.m_writemark; // Index in Double-Array + pType[ nElementCurrent ] = T_D; // set Typeinfo Double + + pP_Dbl[ pP_Dbl.m_writemark ] = rDouble; + + pSize[ nElementCurrent ] = 1; // does not matter + + nElementCurrent++; + pP_Dbl.m_writemark++; + + return static_cast(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::Store( const sal_uInt16 nIndex ) +{ + return StoreName(nIndex, -1); +} + +TokenId TokenPool::Store( const OUString& rString ) +{ + // mostly copied to Store( const char* ), to avoid a temporary string + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + if( ppP_Str.m_writemark >= ppP_Str.m_capacity ) + if (!ppP_Str.Grow()) + return static_cast(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_Str.m_writemark; // Index in String-Array + pType[ nElementCurrent ] = T_Str; // set Typeinfo String + + // create String + if( !ppP_Str[ ppP_Str.m_writemark ] ) + //...but only, if it does not exist already + ppP_Str[ ppP_Str.m_writemark ].reset( new OUString( rString ) ); + else + //...copy otherwise + *ppP_Str[ ppP_Str.m_writemark ] = rString; + + /* attention truncate to 16 bits */ + pSize[ nElementCurrent ] = static_cast(ppP_Str[ ppP_Str.m_writemark ]->getLength()); + + nElementCurrent++; + ppP_Str.m_writemark++; + + return static_cast(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::Store( const ScSingleRefData& rTr ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + if( ppP_RefTr.m_writemark >= ppP_RefTr.m_capacity ) + if (!ppP_RefTr.Grow()) + return static_cast(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_RefTr.m_writemark; + pType[ nElementCurrent ] = T_RefC; // set Typeinfo Cell-Ref + + if( !ppP_RefTr[ ppP_RefTr.m_writemark ] ) + ppP_RefTr[ ppP_RefTr.m_writemark ].reset( new ScSingleRefData( rTr ) ); + else + *ppP_RefTr[ ppP_RefTr.m_writemark ] = rTr; + + nElementCurrent++; + ppP_RefTr.m_writemark++; + + return static_cast(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::Store( const ScComplexRefData& rTr ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + if( ppP_RefTr.m_writemark + 1 >= ppP_RefTr.m_capacity ) + if (!ppP_RefTr.Grow(2)) + return static_cast(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_RefTr.m_writemark; + pType[ nElementCurrent ] = T_RefA; // setTypeinfo Area-Ref + + if( !ppP_RefTr[ ppP_RefTr.m_writemark ] ) + ppP_RefTr[ ppP_RefTr.m_writemark ].reset( new ScSingleRefData( rTr.Ref1 ) ); + else + *ppP_RefTr[ ppP_RefTr.m_writemark ] = rTr.Ref1; + ppP_RefTr.m_writemark++; + + if( !ppP_RefTr[ ppP_RefTr.m_writemark ] ) + ppP_RefTr[ ppP_RefTr.m_writemark ].reset( new ScSingleRefData( rTr.Ref2 ) ); + else + *ppP_RefTr[ ppP_RefTr.m_writemark ] = rTr.Ref2; + ppP_RefTr.m_writemark++; + + nElementCurrent++; + + return static_cast(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::Store( const DefTokenId e, const OUString& r ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + if( ppP_Ext.m_writemark >= ppP_Ext.m_capacity ) + if (!ppP_Ext.Grow()) + return static_cast(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_Ext.m_writemark; + pType[ nElementCurrent ] = T_Ext; // set Typeinfo String + + if( ppP_Ext[ ppP_Ext.m_writemark ] ) + { + ppP_Ext[ ppP_Ext.m_writemark ]->eId = e; + ppP_Ext[ ppP_Ext.m_writemark ]->aText = r; + } + else + ppP_Ext[ ppP_Ext.m_writemark ].reset( new EXTCONT( e, r ) ); + + nElementCurrent++; + ppP_Ext.m_writemark++; + + return static_cast(nElementCurrent); // return old value + 1! +} + +TokenId TokenPool::StoreNlf( const ScSingleRefData& rTr ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + if( ppP_Nlf.m_writemark >= ppP_Nlf.m_capacity ) + if (!ppP_Nlf.Grow()) + return static_cast(nElementCurrent+1); + + pElement[ nElementCurrent ] = ppP_Nlf.m_writemark; + pType[ nElementCurrent ] = T_Nlf; + + if( ppP_Nlf[ ppP_Nlf.m_writemark ] ) + { + *ppP_Nlf[ ppP_Nlf.m_writemark ] = rTr; + } + else + ppP_Nlf[ ppP_Nlf.m_writemark ].reset( new ScSingleRefData( rTr ) ); + + nElementCurrent++; + ppP_Nlf.m_writemark++; + + return static_cast(nElementCurrent); +} + +TokenId TokenPool::StoreMatrix() +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + if( nP_MatrixCurrent >= nP_Matrix ) + if (!GrowMatrix()) + return static_cast(nElementCurrent+1); + + pElement[ nElementCurrent ] = nP_MatrixCurrent; + pType[ nElementCurrent ] = T_Matrix; + + ScMatrix* pM = new ScMatrix( 0, 0 ); + pM->IncRef( ); + ppP_Matrix[ nP_MatrixCurrent ] = pM; + + nElementCurrent++; + nP_MatrixCurrent++; + + return static_cast(nElementCurrent); +} + +TokenId TokenPool::StoreName( sal_uInt16 nIndex, sal_Int16 nSheet ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + pElement[nElementCurrent] = static_cast(maRangeNames.size()); + pType[nElementCurrent] = T_RN; + + maRangeNames.emplace_back(); + RangeName& r = maRangeNames.back(); + r.mnIndex = nIndex; + r.mnSheet = nSheet; + + ++nElementCurrent; + + return static_cast(nElementCurrent); +} + +TokenId TokenPool::StoreExtName( sal_uInt16 nFileId, const OUString& rName ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + pElement[nElementCurrent] = static_cast(maExtNames.size()); + pType[nElementCurrent] = T_ExtName; + + maExtNames.emplace_back(); + ExtName& r = maExtNames.back(); + r.mnFileId = nFileId; + r.maName = rName; + + ++nElementCurrent; + + return static_cast(nElementCurrent); +} + +TokenId TokenPool::StoreExtRef( sal_uInt16 nFileId, const OUString& rTabName, const ScSingleRefData& rRef ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + pElement[nElementCurrent] = static_cast(maExtCellRefs.size()); + pType[nElementCurrent] = T_ExtRefC; + + maExtCellRefs.emplace_back(); + ExtCellRef& r = maExtCellRefs.back(); + r.mnFileId = nFileId; + r.maTabName = rTabName; + r.maRef = rRef; + + ++nElementCurrent; + + return static_cast(nElementCurrent); +} + +TokenId TokenPool::StoreExtRef( sal_uInt16 nFileId, const OUString& rTabName, const ScComplexRefData& rRef ) +{ + if (!CheckElementOrGrow()) + return static_cast(nElementCurrent+1); + + pElement[nElementCurrent] = static_cast(maExtAreaRefs.size()); + pType[nElementCurrent] = T_ExtRefA; + + maExtAreaRefs.emplace_back(); + ExtAreaRef& r = maExtAreaRefs.back(); + r.mnFileId = nFileId; + r.maTabName = rTabName; + r.maRef = rRef; + + ++nElementCurrent; + + return static_cast(nElementCurrent); +} + +void TokenPool::Reset() +{ + nP_IdCurrent = nP_IdLast = nElementCurrent + = ppP_Str.m_writemark = pP_Dbl.m_writemark = pP_Err.m_writemark + = ppP_RefTr.m_writemark = ppP_Ext.m_writemark = ppP_Nlf.m_writemark = nP_MatrixCurrent = 0; + maRangeNames.clear(); + maExtNames.clear(); + maExtCellRefs.clear(); + maExtAreaRefs.clear(); + ClearMatrix(); +} + +bool TokenPool::IsSingleOp( const TokenId& rId, const DefTokenId eId ) const +{ + sal_uInt16 nId = static_cast(rId); + if( nId && nId <= nElementCurrent ) + {// existent? + nId--; + if( T_Id == pType[ nId ] ) + {// Token-Sequence? + if( pSize[ nId ] == 1 ) + {// EXACTLY 1 Token + sal_uInt16 nPid = pElement[ nId ]; + if (nPid < nP_Id) + { + sal_uInt16 nSecId = pP_Id[ nPid ]; + if( nSecId >= nScTokenOff ) + {// Default-Token? + return static_cast( nSecId - nScTokenOff ) == eId; // wanted? + } + } + } + } + } + + return false; +} + +const OUString* TokenPool::GetExternal( const TokenId& rId ) const +{ + const OUString* p = nullptr; + sal_uInt16 n = static_cast(rId); + if( n && n <= nElementCurrent ) + { + n--; + if( pType[ n ] == T_Ext ) + { + sal_uInt16 nExt = pElement[ n ]; + if ( nExt < ppP_Ext.m_writemark && ppP_Ext[ nExt ] ) + p = &ppP_Ext[ nExt ]->aText; + } + } + + return p; +} + +ScMatrix* TokenPool::GetMatrix( unsigned int n ) const +{ + if( n < nP_MatrixCurrent ) + return ppP_Matrix[ n ]; + else + SAL_WARN("sc.filter", "GetMatrix: " << n << " >= " << nP_MatrixCurrent); + return nullptr; +} + +void TokenPool::ClearMatrix() +{ + for(sal_uInt16 n = 0 ; n < nP_Matrix ; n++ ) + { + if( ppP_Matrix[ n ] ) + { + ppP_Matrix[ n ]->DecRef( ); + ppP_Matrix[n] = nullptr; + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xechart.cxx b/sc/source/filter/excel/xechart.cxx new file mode 100644 index 000000000..64525457f --- /dev/null +++ b/sc/source/filter/excel/xechart.cxx @@ -0,0 +1,3475 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::i18n::XBreakIterator; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::drawing::XShapes; + +using ::com::sun::star::chart2::IncrementData; +using ::com::sun::star::chart2::RelativePosition; +using ::com::sun::star::chart2::RelativeSize; +using ::com::sun::star::chart2::ScaleData; +using ::com::sun::star::chart2::SubIncrement; +using ::com::sun::star::chart2::XAxis; +using ::com::sun::star::chart2::XChartDocument; +using ::com::sun::star::chart2::XChartTypeContainer; +using ::com::sun::star::chart2::XColorScheme; +using ::com::sun::star::chart2::XCoordinateSystem; +using ::com::sun::star::chart2::XCoordinateSystemContainer; +using ::com::sun::star::chart2::XChartType; +using ::com::sun::star::chart2::XDataSeries; +using ::com::sun::star::chart2::XDataSeriesContainer; +using ::com::sun::star::chart2::XDiagram; +using ::com::sun::star::chart2::XFormattedString; +using ::com::sun::star::chart2::XLegend; +using ::com::sun::star::chart2::XRegressionCurve; +using ::com::sun::star::chart2::XRegressionCurveContainer; +using ::com::sun::star::chart2::XTitle; +using ::com::sun::star::chart2::XTitled; + +using ::com::sun::star::chart2::data::XDataSequence; +using ::com::sun::star::chart2::data::XDataSource; +using ::com::sun::star::chart2::data::XLabeledDataSequence; + +using ::formula::FormulaToken; +using ::formula::FormulaTokenArrayPlainIterator; + +namespace cssc = ::com::sun::star::chart; +namespace cssc2 = ::com::sun::star::chart2; + +// Helpers ==================================================================== + +namespace { + +XclExpStream& operator<<( XclExpStream& rStrm, const XclChRectangle& rRect ) +{ + return rStrm << rRect.mnX << rRect.mnY << rRect.mnWidth << rRect.mnHeight; +} + +void lclSaveRecord( XclExpStream& rStrm, XclExpRecordRef const & xRec ) +{ + if( xRec ) + xRec->Save( rStrm ); +} + +/** Saves the passed record (group) together with a leading value record. */ +template< typename Type > +void lclSaveRecord( XclExpStream& rStrm, XclExpRecordRef const & xRec, sal_uInt16 nRecId, Type nValue ) +{ + if( xRec ) + { + XclExpValueRecord< Type >( nRecId, nValue ).Save( rStrm ); + xRec->Save( rStrm ); + } +} + +template +void lclSaveRecord(XclExpStream& rStrm, ValueType* pRec, sal_uInt16 nRecId, KeyType nValue) +{ + if (pRec) + { + XclExpValueRecord(nRecId, nValue).Save(rStrm); + pRec->Save(rStrm); + } +} + +void lclWriteChFrBlockRecord( XclExpStream& rStrm, const XclChFrBlock& rFrBlock, bool bBegin ) +{ + sal_uInt16 nRecId = bBegin ? EXC_ID_CHFRBLOCKBEGIN : EXC_ID_CHFRBLOCKEND; + rStrm.StartRecord( nRecId, 12 ); + rStrm << nRecId << EXC_FUTUREREC_EMPTYFLAGS << rFrBlock.mnType << rFrBlock.mnContext << rFrBlock.mnValue1 << rFrBlock.mnValue2; + rStrm.EndRecord(); +} + +template< typename Type > +bool lclIsAutoAnyOrGetValue( Type& rValue, const Any& rAny ) +{ + return !rAny.hasValue() || !(rAny >>= rValue); +} + +bool lclIsAutoAnyOrGetScaledValue( double& rfValue, const Any& rAny, bool bLogScale ) +{ + bool bIsAuto = lclIsAutoAnyOrGetValue( rfValue, rAny ); + if( !bIsAuto && bLogScale ) + rfValue = log( rfValue ) / log( 10.0 ); + return bIsAuto; +} + +sal_uInt16 lclGetTimeValue( const XclExpRoot& rRoot, double fSerialDate, sal_uInt16 nTimeUnit ) +{ + DateTime aDateTime = rRoot.GetDateTimeFromDouble( fSerialDate ); + switch( nTimeUnit ) + { + case EXC_CHDATERANGE_DAYS: + return ::limit_cast< sal_uInt16, double >( fSerialDate, 0, SAL_MAX_UINT16 ); + case EXC_CHDATERANGE_MONTHS: + return ::limit_cast< sal_uInt16, sal_uInt16 >( 12 * (aDateTime.GetYear() - rRoot.GetBaseYear()) + aDateTime.GetMonth() - 1, 0, SAL_MAX_INT16 ); + case EXC_CHDATERANGE_YEARS: + return ::limit_cast< sal_uInt16, sal_uInt16 >( aDateTime.GetYear() - rRoot.GetBaseYear(), 0, SAL_MAX_INT16 ); + default: + OSL_ENSURE( false, "lclGetTimeValue - unexpected time unit" ); + } + return ::limit_cast< sal_uInt16, double >( fSerialDate, 0, SAL_MAX_UINT16 ); +} + +bool lclConvertTimeValue( const XclExpRoot& rRoot, sal_uInt16& rnValue, const Any& rAny, sal_uInt16 nTimeUnit ) +{ + double fSerialDate = 0; + bool bAuto = lclIsAutoAnyOrGetValue( fSerialDate, rAny ); + if( !bAuto ) + rnValue = lclGetTimeValue( rRoot, fSerialDate, nTimeUnit ); + return bAuto; +} + +sal_uInt16 lclGetTimeUnit( sal_Int32 nApiTimeUnit ) +{ + switch( nApiTimeUnit ) + { + case cssc::TimeUnit::DAY: return EXC_CHDATERANGE_DAYS; + case cssc::TimeUnit::MONTH: return EXC_CHDATERANGE_MONTHS; + case cssc::TimeUnit::YEAR: return EXC_CHDATERANGE_YEARS; + default: OSL_ENSURE( false, "lclGetTimeUnit - unexpected time unit" ); + } + return EXC_CHDATERANGE_DAYS; +} + +bool lclConvertTimeInterval( sal_uInt16& rnValue, sal_uInt16& rnTimeUnit, const Any& rAny ) +{ + cssc::TimeInterval aInterval; + bool bAuto = lclIsAutoAnyOrGetValue( aInterval, rAny ); + if( !bAuto ) + { + rnValue = ::limit_cast< sal_uInt16, sal_Int32 >( aInterval.Number, 1, SAL_MAX_UINT16 ); + rnTimeUnit = lclGetTimeUnit( aInterval.TimeUnit ); + } + return bAuto; +} + +} // namespace + +// Common ===================================================================== + +/** Stores global data needed in various classes of the Chart export filter. */ +struct XclExpChRootData : public XclChRootData +{ + typedef ::std::vector< XclChFrBlock > XclChFrBlockVector; + + XclExpChChart& mrChartData; /// The chart data object. + XclChFrBlockVector maWrittenFrBlocks; /// Stack of future record levels already written out. + XclChFrBlockVector maUnwrittenFrBlocks; /// Stack of future record levels not yet written out. + + explicit XclExpChRootData( XclExpChChart& rChartData ) : mrChartData( rChartData ) {} + + /** Registers a new future record level. */ + void RegisterFutureRecBlock( const XclChFrBlock& rFrBlock ); + /** Initializes the current future record level (writes all unwritten CHFRBLOCKBEGIN records). */ + void InitializeFutureRecBlock( XclExpStream& rStrm ); + /** Finalizes the current future record level (writes CHFRBLOCKEND record if needed). */ + void FinalizeFutureRecBlock( XclExpStream& rStrm ); +}; + +void XclExpChRootData::RegisterFutureRecBlock( const XclChFrBlock& rFrBlock ) +{ + maUnwrittenFrBlocks.push_back( rFrBlock ); +} + +void XclExpChRootData::InitializeFutureRecBlock( XclExpStream& rStrm ) +{ + // first call from a future record writes all missing CHFRBLOCKBEGIN records + if( maUnwrittenFrBlocks.empty() ) + return; + + // write the leading CHFRINFO record + if( maWrittenFrBlocks.empty() ) + { + rStrm.StartRecord( EXC_ID_CHFRINFO, 20 ); + rStrm << EXC_ID_CHFRINFO << EXC_FUTUREREC_EMPTYFLAGS << EXC_CHFRINFO_EXCELXP2003 << EXC_CHFRINFO_EXCELXP2003 << sal_uInt16( 3 ); + rStrm << sal_uInt16( 0x0850 ) << sal_uInt16( 0x085A ) << sal_uInt16( 0x0861 ) << sal_uInt16( 0x0861 ) << sal_uInt16( 0x086A ) << sal_uInt16( 0x086B ); + rStrm.EndRecord(); + } + // write all unwritten CHFRBLOCKBEGIN records + for( const auto& rUnwrittenFrBlock : maUnwrittenFrBlocks ) + { + OSL_ENSURE( rUnwrittenFrBlock.mnType != EXC_CHFRBLOCK_TYPE_UNKNOWN, "XclExpChRootData::InitializeFutureRecBlock - unknown future record block type" ); + lclWriteChFrBlockRecord( rStrm, rUnwrittenFrBlock, true ); + } + // move all record infos to vector of written blocks + maWrittenFrBlocks.insert( maWrittenFrBlocks.end(), maUnwrittenFrBlocks.begin(), maUnwrittenFrBlocks.end() ); + maUnwrittenFrBlocks.clear(); +} + +void XclExpChRootData::FinalizeFutureRecBlock( XclExpStream& rStrm ) +{ + OSL_ENSURE( !maUnwrittenFrBlocks.empty() || !maWrittenFrBlocks.empty(), "XclExpChRootData::FinalizeFutureRecBlock - no future record level found" ); + if( !maUnwrittenFrBlocks.empty() ) + { + // no future record has been written, just forget the topmost level + maUnwrittenFrBlocks.pop_back(); + } + else if( !maWrittenFrBlocks.empty() ) + { + // write the CHFRBLOCKEND record for the topmost block and delete it + lclWriteChFrBlockRecord( rStrm, maWrittenFrBlocks.back(), false ); + maWrittenFrBlocks.pop_back(); + } +} + +XclExpChRoot::XclExpChRoot( const XclExpRoot& rRoot, XclExpChChart& rChartData ) : + XclExpRoot( rRoot ), + mxChData( std::make_shared( rChartData ) ) +{ +} + +XclExpChRoot::~XclExpChRoot() +{ +} + +Reference< XChartDocument > const & XclExpChRoot::GetChartDocument() const +{ + return mxChData->mxChartDoc; +} + +XclExpChChart& XclExpChRoot::GetChartData() const +{ + return mxChData->mrChartData; +} + +const XclChTypeInfo& XclExpChRoot::GetChartTypeInfo( XclChTypeId eType ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfo( eType ); +} + +const XclChTypeInfo& XclExpChRoot::GetChartTypeInfo( std::u16string_view rServiceName ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfoFromService( rServiceName ); +} + +const XclChFormatInfo& XclExpChRoot::GetFormatInfo( XclChObjectType eObjType ) const +{ + return mxChData->mxFmtInfoProv->GetFormatInfo( eObjType ); +} + +void XclExpChRoot::InitConversion( css::uno::Reference< css::chart2::XChartDocument > const & xChartDoc, const tools::Rectangle& rChartRect ) const +{ + mxChData->InitConversion( GetRoot(), xChartDoc, rChartRect ); +} + +void XclExpChRoot::FinishConversion() const +{ + mxChData->FinishConversion(); +} + +bool XclExpChRoot::IsSystemColor( const Color& rColor, sal_uInt16 nSysColorIdx ) const +{ + XclExpPalette& rPal = GetPalette(); + return rPal.IsSystemColor( nSysColorIdx ) && (rColor == rPal.GetDefColor( nSysColorIdx )); +} + +void XclExpChRoot::SetSystemColor( Color& rColor, sal_uInt32& rnColorId, sal_uInt16 nSysColorIdx ) const +{ + OSL_ENSURE( GetPalette().IsSystemColor( nSysColorIdx ), "XclExpChRoot::SetSystemColor - invalid color index" ); + rColor = GetPalette().GetDefColor( nSysColorIdx ); + rnColorId = XclExpPalette::GetColorIdFromIndex( nSysColorIdx ); +} + +sal_Int32 XclExpChRoot::CalcChartXFromHmm( sal_Int32 nPosX ) const +{ + return ::limit_cast< sal_Int32, double >( (nPosX - mxChData->mnBorderGapX) / mxChData->mfUnitSizeX, 0, EXC_CHART_TOTALUNITS ); +} + +sal_Int32 XclExpChRoot::CalcChartYFromHmm( sal_Int32 nPosY ) const +{ + return ::limit_cast< sal_Int32, double >( (nPosY - mxChData->mnBorderGapY) / mxChData->mfUnitSizeY, 0, EXC_CHART_TOTALUNITS ); +} + +XclChRectangle XclExpChRoot::CalcChartRectFromHmm( const css::awt::Rectangle& rRect ) const +{ + XclChRectangle aRect; + aRect.mnX = CalcChartXFromHmm( rRect.X ); + aRect.mnY = CalcChartYFromHmm( rRect.Y ); + aRect.mnWidth = CalcChartXFromHmm( rRect.Width ); + aRect.mnHeight = CalcChartYFromHmm( rRect.Height ); + return aRect; +} + +void XclExpChRoot::ConvertLineFormat( XclChLineFormat& rLineFmt, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().ReadLineProperties( + rLineFmt, *mxChData->mxLineDashTable, rPropSet, ePropMode ); +} + +bool XclExpChRoot::ConvertAreaFormat( XclChAreaFormat& rAreaFmt, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) const +{ + return GetChartPropSetHelper().ReadAreaProperties( rAreaFmt, rPropSet, ePropMode ); +} + +void XclExpChRoot::ConvertEscherFormat( + XclChEscherFormat& rEscherFmt, XclChPicFormat& rPicFmt, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().ReadEscherProperties( rEscherFmt, rPicFmt, + *mxChData->mxGradientTable, *mxChData->mxHatchTable, *mxChData->mxBitmapTable, rPropSet, ePropMode ); +} + +sal_uInt16 XclExpChRoot::ConvertFont( const ScfPropertySet& rPropSet, sal_Int16 nScript ) const +{ + XclFontData aFontData; + GetFontPropSetHelper().ReadFontProperties( aFontData, rPropSet, EXC_FONTPROPSET_CHART, nScript ); + return GetFontBuffer().Insert( aFontData, EXC_COLOR_CHARTTEXT ); +} + +sal_uInt16 XclExpChRoot::ConvertPieRotation( const ScfPropertySet& rPropSet ) +{ + sal_Int32 nApiRot = 0; + rPropSet.GetProperty( nApiRot, EXC_CHPROP_STARTINGANGLE ); + return static_cast< sal_uInt16 >( (450 - (nApiRot % 360)) % 360 ); +} + +void XclExpChRoot::RegisterFutureRecBlock( const XclChFrBlock& rFrBlock ) +{ + mxChData->RegisterFutureRecBlock( rFrBlock ); +} + +void XclExpChRoot::InitializeFutureRecBlock( XclExpStream& rStrm ) +{ + mxChData->InitializeFutureRecBlock( rStrm ); +} + +void XclExpChRoot::FinalizeFutureRecBlock( XclExpStream& rStrm ) +{ + mxChData->FinalizeFutureRecBlock( rStrm ); +} + +XclExpChGroupBase::XclExpChGroupBase( const XclExpChRoot& rRoot, + sal_uInt16 nFrType, sal_uInt16 nRecId, std::size_t nRecSize ) : + XclExpRecord( nRecId, nRecSize ), + XclExpChRoot( rRoot ), + maFrBlock( nFrType ) +{ +} + +XclExpChGroupBase::~XclExpChGroupBase() +{ +} + +void XclExpChGroupBase::Save( XclExpStream& rStrm ) +{ + // header record + XclExpRecord::Save( rStrm ); + // group records + if( !HasSubRecords() ) + return; + + // register the future record context corresponding to this record group + RegisterFutureRecBlock( maFrBlock ); + // CHBEGIN record + XclExpEmptyRecord( EXC_ID_CHBEGIN ).Save( rStrm ); + // embedded records + WriteSubRecords( rStrm ); + // finalize the future records, must be done before the closing CHEND + FinalizeFutureRecBlock( rStrm ); + // CHEND record + XclExpEmptyRecord( EXC_ID_CHEND ).Save( rStrm ); +} + +bool XclExpChGroupBase::HasSubRecords() const +{ + return true; +} + +void XclExpChGroupBase::SetFutureRecordContext( sal_uInt16 nFrContext, sal_uInt16 nFrValue1, sal_uInt16 nFrValue2 ) +{ + maFrBlock.mnContext = nFrContext; + maFrBlock.mnValue1 = nFrValue1; + maFrBlock.mnValue2 = nFrValue2; +} + +XclExpChFutureRecordBase::XclExpChFutureRecordBase( const XclExpChRoot& rRoot, + XclFutureRecType eRecType, sal_uInt16 nRecId, std::size_t nRecSize ) : + XclExpFutureRecord( eRecType, nRecId, nRecSize ), + XclExpChRoot( rRoot ) +{ +} + +void XclExpChFutureRecordBase::Save( XclExpStream& rStrm ) +{ + InitializeFutureRecBlock( rStrm ); + XclExpFutureRecord::Save( rStrm ); +} + +// Frame formatting =========================================================== + +XclExpChFramePos::XclExpChFramePos( sal_uInt16 nTLMode ) : + XclExpRecord( EXC_ID_CHFRAMEPOS, 20 ) +{ + maData.mnTLMode = nTLMode; + maData.mnBRMode = EXC_CHFRAMEPOS_PARENT; +} + +void XclExpChFramePos::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnTLMode << maData.mnBRMode << maData.maRect; +} + +XclExpChLineFormat::XclExpChLineFormat( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHLINEFORMAT, (rRoot.GetBiff() == EXC_BIFF8) ? 12 : 10 ), + mnColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ) +{ +} + +void XclExpChLineFormat::SetDefault( XclChFrameType eDefFrameType ) +{ + switch( eDefFrameType ) + { + case EXC_CHFRAMETYPE_AUTO: + SetAuto( true ); + break; + case EXC_CHFRAMETYPE_INVISIBLE: + SetAuto( false ); + maData.mnPattern = EXC_CHLINEFORMAT_NONE; + break; + default: + OSL_FAIL( "XclExpChLineFormat::SetDefault - unknown frame type" ); + } +} + +void XclExpChLineFormat::Convert( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + rRoot.ConvertLineFormat( maData, rPropSet, rFmtInfo.mePropMode ); + if( HasLine() ) + { + // detect system color, set color identifier (TODO: detect automatic series line) + if( (eObjType != EXC_CHOBJTYPE_LINEARSERIES) && rRoot.IsSystemColor( maData.maColor, rFmtInfo.mnAutoLineColorIdx ) ) + { + // store color index from automatic format data + mnColorId = XclExpPalette::GetColorIdFromIndex( rFmtInfo.mnAutoLineColorIdx ); + // try to set automatic mode + bool bAuto = (maData.mnPattern == EXC_CHLINEFORMAT_SOLID) && (maData.mnWeight == rFmtInfo.mnAutoLineWeight); + ::set_flag( maData.mnFlags, EXC_CHLINEFORMAT_AUTO, bAuto ); + } + else + { + // user defined color - register in palette + mnColorId = rRoot.GetPalette().InsertColor( maData.maColor, EXC_COLOR_CHARTLINE ); + } + } + else + { + // no line - set default system color + rRoot.SetSystemColor( maData.maColor, mnColorId, EXC_COLOR_CHWINDOWTEXT ); + } +} + +bool XclExpChLineFormat::IsDefault( XclChFrameType eDefFrameType ) const +{ + return + ((eDefFrameType == EXC_CHFRAMETYPE_INVISIBLE) && !HasLine()) || + ((eDefFrameType == EXC_CHFRAMETYPE_AUTO) && IsAuto()); +} + +void XclExpChLineFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maColor << maData.mnPattern << maData.mnWeight << maData.mnFlags; + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + rStrm << rStrm.GetRoot().GetPalette().GetColorIndex( mnColorId ); +} + +namespace { + +/** Creates a CHLINEFORMAT record from the passed property set. */ +XclExpChLineFormatRef lclCreateLineFormat( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + XclExpChLineFormatRef xLineFmt = new XclExpChLineFormat( rRoot ); + xLineFmt->Convert( rRoot, rPropSet, eObjType ); + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + if( rFmtInfo.mbDeleteDefFrame && xLineFmt->IsDefault( rFmtInfo.meDefFrameType ) ) + xLineFmt.clear(); + return xLineFmt; +} + +} // namespace + +XclExpChAreaFormat::XclExpChAreaFormat( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHAREAFORMAT, (rRoot.GetBiff() == EXC_BIFF8) ? 16 : 12 ), + mnPattColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ) ), + mnBackColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ) +{ +} + +bool XclExpChAreaFormat::Convert( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + bool bComplexFill = rRoot.ConvertAreaFormat( maData, rPropSet, rFmtInfo.mePropMode ); + if( HasArea() ) + { + bool bSolid = maData.mnPattern == EXC_PATT_SOLID; + // detect system color, set color identifier (TODO: detect automatic series area) + if( (eObjType != EXC_CHOBJTYPE_FILLEDSERIES) && rRoot.IsSystemColor( maData.maPattColor, rFmtInfo.mnAutoPattColorIdx ) ) + { + // store color index from automatic format data + mnPattColorId = XclExpPalette::GetColorIdFromIndex( rFmtInfo.mnAutoPattColorIdx ); + // set automatic mode + ::set_flag( maData.mnFlags, EXC_CHAREAFORMAT_AUTO, bSolid ); + } + else + { + // user defined color - register color in palette + mnPattColorId = rRoot.GetPalette().InsertColor( maData.maPattColor, EXC_COLOR_CHARTAREA ); + } + // background color (default system color for solid fills) + if( bSolid ) + rRoot.SetSystemColor( maData.maBackColor, mnBackColorId, EXC_COLOR_CHWINDOWTEXT ); + else + mnBackColorId = rRoot.GetPalette().InsertColor( maData.maBackColor, EXC_COLOR_CHARTAREA ); + } + else + { + // no area - set default system colors + rRoot.SetSystemColor( maData.maPattColor, mnPattColorId, EXC_COLOR_CHWINDOWBACK ); + rRoot.SetSystemColor( maData.maBackColor, mnBackColorId, EXC_COLOR_CHWINDOWTEXT ); + } + return bComplexFill; +} + +void XclExpChAreaFormat::SetDefault( XclChFrameType eDefFrameType ) +{ + switch( eDefFrameType ) + { + case EXC_CHFRAMETYPE_AUTO: + SetAuto( true ); + break; + case EXC_CHFRAMETYPE_INVISIBLE: + SetAuto( false ); + maData.mnPattern = EXC_PATT_NONE; + break; + default: + OSL_FAIL( "XclExpChAreaFormat::SetDefault - unknown frame type" ); + } +} + +bool XclExpChAreaFormat::IsDefault( XclChFrameType eDefFrameType ) const +{ + return + ((eDefFrameType == EXC_CHFRAMETYPE_INVISIBLE) && !HasArea()) || + ((eDefFrameType == EXC_CHFRAMETYPE_AUTO) && IsAuto()); +} + +void XclExpChAreaFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maPattColor << maData.maBackColor << maData.mnPattern << maData.mnFlags; + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + { + const XclExpPalette& rPal = rStrm.GetRoot().GetPalette(); + rStrm << rPal.GetColorIndex( mnPattColorId ) << rPal.GetColorIndex( mnBackColorId ); + } +} + +XclExpChEscherFormat::XclExpChEscherFormat( const XclExpChRoot& rRoot ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_UNKNOWN, EXC_ID_CHESCHERFORMAT ), + mnColor1Id( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ) ), + mnColor2Id( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ) ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF8 ); +} + +void XclExpChEscherFormat::Convert( const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + const XclChFormatInfo& rFmtInfo = GetFormatInfo( eObjType ); + ConvertEscherFormat( maData, maPicFmt, rPropSet, rFmtInfo.mePropMode ); + // register colors in palette + mnColor1Id = RegisterColor( ESCHER_Prop_fillColor ); + mnColor2Id = RegisterColor( ESCHER_Prop_fillBackColor ); +} + +bool XclExpChEscherFormat::IsValid() const +{ + return static_cast< bool >(maData.mxEscherSet); +} + +void XclExpChEscherFormat::Save( XclExpStream& rStrm ) +{ + if( maData.mxEscherSet ) + { + // replace RGB colors with palette indexes in the Escher container + const XclExpPalette& rPal = GetPalette(); + maData.mxEscherSet->AddOpt( ESCHER_Prop_fillColor, 0x08000000 | rPal.GetColorIndex( mnColor1Id ) ); + maData.mxEscherSet->AddOpt( ESCHER_Prop_fillBackColor, 0x08000000 | rPal.GetColorIndex( mnColor2Id ) ); + + // save the record group + XclExpChGroupBase::Save( rStrm ); + } +} + +bool XclExpChEscherFormat::HasSubRecords() const +{ + // no subrecords for gradients + return maPicFmt.mnBmpMode != EXC_CHPICFORMAT_NONE; +} + +void XclExpChEscherFormat::WriteSubRecords( XclExpStream& rStrm ) +{ + rStrm.StartRecord( EXC_ID_CHPICFORMAT, 14 ); + rStrm << maPicFmt.mnBmpMode << sal_uInt16( 0 ) << maPicFmt.mnFlags << maPicFmt.mfScale; + rStrm.EndRecord(); +} + +sal_uInt32 XclExpChEscherFormat::RegisterColor( sal_uInt16 nPropId ) +{ + sal_uInt32 nBGRValue; + if( maData.mxEscherSet && maData.mxEscherSet->GetOpt( nPropId, nBGRValue ) ) + { + // swap red and blue + Color aColor( nBGRValue & 0xff, (nBGRValue >> 8) & 0xff, (nBGRValue >> 16) & 0xff ); + return GetPalette().InsertColor( aColor, EXC_COLOR_CHARTAREA ); + } + return XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ); +} + +void XclExpChEscherFormat::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE( maData.mxEscherSet, "XclExpChEscherFormat::WriteBody - missing property container" ); + // write Escher property container via temporary memory stream + SvMemoryStream aMemStrm; + maData.mxEscherSet->Commit( aMemStrm ); + aMemStrm.FlushBuffer(); + aMemStrm.Seek( STREAM_SEEK_TO_BEGIN ); + rStrm.CopyFromStream( aMemStrm ); +} + +XclExpChFrameBase::XclExpChFrameBase() +{ +} + +XclExpChFrameBase::~XclExpChFrameBase() +{ +} + +void XclExpChFrameBase::ConvertFrameBase( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + // line format + mxLineFmt = new XclExpChLineFormat( rRoot ); + mxLineFmt->Convert( rRoot, rPropSet, eObjType ); + // area format (only for frame objects) + if( !rRoot.GetFormatInfo( eObjType ).mbIsFrame ) + return; + + mxAreaFmt = new XclExpChAreaFormat( rRoot ); + bool bComplexFill = mxAreaFmt->Convert( rRoot, rPropSet, eObjType ); + if( (rRoot.GetBiff() == EXC_BIFF8) && bComplexFill ) + { + mxEscherFmt = new XclExpChEscherFormat( rRoot ); + mxEscherFmt->Convert( rPropSet, eObjType ); + if( mxEscherFmt->IsValid() ) + mxAreaFmt->SetAuto( false ); + else + mxEscherFmt.clear(); + } +} + +void XclExpChFrameBase::SetDefaultFrameBase( const XclExpChRoot& rRoot, + XclChFrameType eDefFrameType, bool bIsFrame ) +{ + // line format + mxLineFmt = new XclExpChLineFormat( rRoot ); + mxLineFmt->SetDefault( eDefFrameType ); + // area format (only for frame objects) + if( bIsFrame ) + { + mxAreaFmt = new XclExpChAreaFormat( rRoot ); + mxAreaFmt->SetDefault( eDefFrameType ); + mxEscherFmt.clear(); + } +} + +bool XclExpChFrameBase::IsDefaultFrameBase( XclChFrameType eDefFrameType ) const +{ + return + (!mxLineFmt || mxLineFmt->IsDefault( eDefFrameType )) && + (!mxAreaFmt || mxAreaFmt->IsDefault( eDefFrameType )); +} + +void XclExpChFrameBase::WriteFrameRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxLineFmt ); + lclSaveRecord( rStrm, mxAreaFmt ); + lclSaveRecord( rStrm, mxEscherFmt ); +} + +XclExpChFrame::XclExpChFrame( const XclExpChRoot& rRoot, XclChObjectType eObjType ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_FRAME, EXC_ID_CHFRAME, 4 ), + meObjType( eObjType ) +{ +} + +void XclExpChFrame::Convert( const ScfPropertySet& rPropSet ) +{ + ConvertFrameBase( GetChRoot(), rPropSet, meObjType ); +} + +void XclExpChFrame::SetAutoFlags( bool bAutoPos, bool bAutoSize ) +{ + ::set_flag( maData.mnFlags, EXC_CHFRAME_AUTOPOS, bAutoPos ); + ::set_flag( maData.mnFlags, EXC_CHFRAME_AUTOSIZE, bAutoSize ); +} + +bool XclExpChFrame::IsDefault() const +{ + return IsDefaultFrameBase( GetFormatInfo( meObjType ).meDefFrameType ); +} + +bool XclExpChFrame::IsDeleteable() const +{ + return IsDefault() && GetFormatInfo( meObjType ).mbDeleteDefFrame; +} + +void XclExpChFrame::Save( XclExpStream& rStrm ) +{ + switch( meObjType ) + { + // wall/floor frame without CHFRAME header record + case EXC_CHOBJTYPE_WALL3D: + case EXC_CHOBJTYPE_FLOOR3D: + WriteFrameRecords( rStrm ); + break; + default: + XclExpChGroupBase::Save( rStrm ); + } +} + +void XclExpChFrame::WriteSubRecords( XclExpStream& rStrm ) +{ + WriteFrameRecords( rStrm ); +} + +void XclExpChFrame::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnFormat << maData.mnFlags; +} + +namespace { + +/** Creates a CHFRAME record from the passed property set. */ +XclExpChFrameRef lclCreateFrame( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + XclExpChFrameRef xFrame = new XclExpChFrame( rRoot, eObjType ); + xFrame->Convert( rPropSet ); + if( xFrame->IsDeleteable() ) + xFrame.clear(); + return xFrame; +} + +} // namespace + +// Source links =============================================================== + +namespace { + +void lclAddDoubleRefData( + ScTokenArray& orArray, const FormulaToken& rToken, + SCTAB nScTab1, SCCOL nScCol1, SCROW nScRow1, + SCTAB nScTab2, SCCOL nScCol2, SCROW nScRow2 ) +{ + ScComplexRefData aComplexRef; + aComplexRef.InitRange(ScRange(nScCol1,nScRow1,nScTab1,nScCol2,nScRow2,nScTab2)); + aComplexRef.Ref1.SetFlag3D( true ); + + if( orArray.GetLen() > 0 ) + orArray.AddOpCode( ocUnion ); + + OSL_ENSURE( (rToken.GetType() == ::formula::svDoubleRef) || (rToken.GetType() == ::formula::svExternalDoubleRef), + "lclAddDoubleRefData - double reference token expected"); + if( rToken.GetType() == ::formula::svExternalDoubleRef ) + orArray.AddExternalDoubleReference( + rToken.GetIndex(), rToken.GetString(), aComplexRef); + else + orArray.AddDoubleReference( aComplexRef ); +} + +} // namespace + +XclExpChSourceLink::XclExpChSourceLink( const XclExpChRoot& rRoot, sal_uInt8 nDestType ) : + XclExpRecord( EXC_ID_CHSOURCELINK ), + XclExpChRoot( rRoot ) +{ + maData.mnDestType = nDestType; + maData.mnLinkType = EXC_CHSRCLINK_DIRECTLY; +} + +sal_uInt16 XclExpChSourceLink::ConvertDataSequence( Reference< XDataSequence > const & xDataSeq, bool bSplitToColumns, sal_uInt16 nDefCount ) +{ + mxLinkFmla.reset(); + maData.mnLinkType = EXC_CHSRCLINK_DEFAULT; + + if( !xDataSeq.is() ) + return nDefCount; + + // Compile the range representation string into token array. Note that the + // source range text depends on the current grammar. + OUString aRangeRepr = xDataSeq->getSourceRangeRepresentation(); + ScCompiler aComp( GetDoc(), ScAddress(), GetDoc().GetGrammar() ); + std::unique_ptr pArray(aComp.CompileString(aRangeRepr)); + if( !pArray ) + return nDefCount; + + ScTokenArray aArray(GetRoot().GetDoc()); + sal_uInt32 nValueCount = 0; + FormulaTokenArrayPlainIterator aIter(*pArray); + for( const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next() ) + { + switch( pToken->GetType() ) + { + case ::formula::svSingleRef: + case ::formula::svExternalSingleRef: + // for a single ref token, just add it to the new token array as is + if( aArray.GetLen() > 0 ) + aArray.AddOpCode( ocUnion ); + aArray.AddToken( *pToken ); + ++nValueCount; + break; + + case ::formula::svDoubleRef: + case ::formula::svExternalDoubleRef: + { + // split 3-dimensional ranges into single sheets + const ScComplexRefData& rComplexRef = *pToken->GetDoubleRef(); + ScAddress aAbs1 = rComplexRef.Ref1.toAbs(GetRoot().GetDoc(), ScAddress()); + ScAddress aAbs2 = rComplexRef.Ref2.toAbs(GetRoot().GetDoc(), ScAddress()); + for (SCTAB nScTab = aAbs1.Tab(); nScTab <= aAbs2.Tab(); ++nScTab) + { + // split 2-dimensional ranges into single columns + if (bSplitToColumns && (aAbs1.Col() < aAbs2.Col()) && (aAbs1.Row() < aAbs2.Row())) + for (SCCOL nScCol = aAbs1.Col(); nScCol <= aAbs2.Col(); ++nScCol) + lclAddDoubleRefData(aArray, *pToken, nScTab, nScCol, aAbs1.Row(), nScTab, nScCol, aAbs2.Row()); + else + lclAddDoubleRefData(aArray, *pToken, nScTab, aAbs1.Col(), aAbs1.Row(), nScTab, aAbs2.Col(), aAbs2.Row()); + } + sal_uInt32 nTabs = static_cast(aAbs2.Tab() - aAbs1.Tab() + 1); + sal_uInt32 nCols = static_cast(aAbs2.Col() - aAbs1.Col() + 1); + sal_uInt32 nRows = static_cast(aAbs2.Row() - aAbs1.Row() + 1); + nValueCount += nCols * nRows * nTabs; + } + break; + + default:; + } + } + + const ScAddress aBaseCell; + mxLinkFmla = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CHART, aArray, &aBaseCell ); + maData.mnLinkType = EXC_CHSRCLINK_WORKSHEET; + return ulimit_cast< sal_uInt16 >( nValueCount, EXC_CHDATAFORMAT_MAXPOINTCOUNT ); +} + +void XclExpChSourceLink::ConvertString( const OUString& aString ) +{ + mxString = XclExpStringHelper::CreateString( GetRoot(), aString, XclStrFlags::ForceUnicode | XclStrFlags::EightBitLength | XclStrFlags::SeparateFormats ); +} + +sal_uInt16 XclExpChSourceLink::ConvertStringSequence( const Sequence< Reference< XFormattedString > >& rStringSeq ) +{ + mxString.reset(); + sal_uInt16 nFontIdx = EXC_FONT_APP; + if( rStringSeq.hasElements() ) + { + mxString = XclExpStringHelper::CreateString( GetRoot(), OUString(), XclStrFlags::ForceUnicode | XclStrFlags::EightBitLength | XclStrFlags::SeparateFormats ); + Reference< XBreakIterator > xBreakIt = GetDoc().GetBreakIterator(); + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + + // convert all formatted string entries from the sequence + for( const Reference< XFormattedString >& rString : rStringSeq ) + { + if( rString.is() ) + { + sal_uInt16 nWstrnFontIdx = EXC_FONT_NOTFOUND; + sal_uInt16 nAsianFontIdx = EXC_FONT_NOTFOUND; + sal_uInt16 nCmplxFontIdx = EXC_FONT_NOTFOUND; + OUString aText = rString->getString(); + ScfPropertySet aStrProp( rString ); + + // #i63255# get script type for leading weak characters + sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( GetRoot(), aText ); + + // process all script portions + sal_Int32 nPortionPos = 0; + sal_Int32 nTextLen = aText.getLength(); + while( nPortionPos < nTextLen ) + { + // get script type and end position of next script portion + sal_Int16 nScript = xBreakIt->getScriptType( aText, nPortionPos ); + sal_Int32 nPortionEnd = xBreakIt->endOfScript( aText, nPortionPos, nScript ); + + // reuse previous script for following weak portions + if( nScript == ApiScriptType::WEAK ) + nScript = nLastScript; + + // Excel start position of this portion + sal_uInt16 nXclPortionStart = mxString->Len(); + // add portion text to Excel string + XclExpStringHelper::AppendString( *mxString, GetRoot(), aText.subView( nPortionPos, nPortionEnd - nPortionPos ) ); + if( nXclPortionStart < mxString->Len() ) + { + // find font index variable dependent on script type + sal_uInt16& rnFontIdx = (nScript == ApiScriptType::COMPLEX) ? nCmplxFontIdx : + ((nScript == ApiScriptType::ASIAN) ? nAsianFontIdx : nWstrnFontIdx); + + // insert font into buffer (if not yet done) + if( rnFontIdx == EXC_FONT_NOTFOUND ) + rnFontIdx = ConvertFont( aStrProp, nScript ); + + // insert font index into format run vector + mxString->AppendFormat( nXclPortionStart, rnFontIdx ); + } + + // go to next script portion + nLastScript = nScript; + nPortionPos = nPortionEnd; + } + } + } + if( !mxString->IsEmpty() ) + { + // get leading font index + const XclFormatRunVec& rFormats = mxString->GetFormats(); + OSL_ENSURE( !rFormats.empty() && (rFormats.front().mnChar == 0), + "XclExpChSourceLink::ConvertStringSequenc - missing leading format" ); + // remove leading format run, if entire string is equally formatted + if( rFormats.size() == 1 ) + nFontIdx = mxString->RemoveLeadingFont(); + else if( !rFormats.empty() ) + nFontIdx = rFormats.front().mnFontIdx; + // add trailing format run, if string is rich-formatted + if( mxString->IsRich() ) + mxString->AppendTrailingFormat( EXC_FONT_APP ); + } + } + return nFontIdx; +} + +void XclExpChSourceLink::ConvertNumFmt( const ScfPropertySet& rPropSet, bool bPercent ) +{ + sal_Int32 nApiNumFmt = 0; + if( bPercent ? rPropSet.GetProperty( nApiNumFmt, EXC_CHPROP_PERCENTAGENUMFMT ) : rPropSet.GetProperty( nApiNumFmt, EXC_CHPROP_NUMBERFORMAT ) ) + { + ::set_flag( maData.mnFlags, EXC_CHSRCLINK_NUMFMT ); + maData.mnNumFmtIdx = GetNumFmtBuffer().Insert( static_cast< sal_uInt32 >( nApiNumFmt ) ); + } +} + +void XclExpChSourceLink::AppendString( std::u16string_view rStr ) +{ + if (!mxString) + return; + XclExpStringHelper::AppendString( *mxString, GetRoot(), rStr ); +} + +void XclExpChSourceLink::Save( XclExpStream& rStrm ) +{ + // CHFORMATRUNS record + if( mxString && mxString->IsRich() ) + { + std::size_t nRecSize = (1 + mxString->GetFormatsCount()) * ((GetBiff() == EXC_BIFF8) ? 2 : 1); + rStrm.StartRecord( EXC_ID_CHFORMATRUNS, nRecSize ); + mxString->WriteFormats( rStrm, true ); + rStrm.EndRecord(); + } + // CHSOURCELINK record + XclExpRecord::Save( rStrm ); + // CHSTRING record + if( mxString && !mxString->IsEmpty() ) + { + rStrm.StartRecord( EXC_ID_CHSTRING, 2 + mxString->GetSize() ); + rStrm << sal_uInt16( 0 ) << *mxString; + rStrm.EndRecord(); + } +} + +void XclExpChSourceLink::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnDestType + << maData.mnLinkType + << maData.mnFlags + << maData.mnNumFmtIdx + << mxLinkFmla; +} + +// Text ======================================================================= + +XclExpChFont::XclExpChFont( sal_uInt16 nFontIdx ) : + XclExpUInt16Record( EXC_ID_CHFONT, nFontIdx ) +{ +} + +XclExpChObjectLink::XclExpChObjectLink( sal_uInt16 nLinkTarget, const XclChDataPointPos& rPointPos ) : + XclExpRecord( EXC_ID_CHOBJECTLINK, 6 ) +{ + maData.mnTarget = nLinkTarget; + maData.maPointPos = rPointPos; +} + +void XclExpChObjectLink::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnTarget << maData.maPointPos.mnSeriesIdx << maData.maPointPos.mnPointIdx; +} + +XclExpChFrLabelProps::XclExpChFrLabelProps( const XclExpChRoot& rRoot ) : + XclExpChFutureRecordBase( rRoot, EXC_FUTUREREC_UNUSEDREF, EXC_ID_CHFRLABELPROPS, 4 ) +{ +} + +void XclExpChFrLabelProps::Convert( const ScfPropertySet& rPropSet, + bool bShowCateg, bool bShowValue, bool bShowPercent, bool bShowBubble ) +{ + // label value flags + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWSERIES, false ); + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWCATEG, bShowCateg ); + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWVALUE, bShowValue ); + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWPERCENT, bShowPercent ); + ::set_flag( maData.mnFlags, EXC_CHFRLABELPROPS_SHOWBUBBLE, bShowBubble ); + + // label value separator + maData.maSeparator = rPropSet.GetStringProperty( EXC_CHPROP_LABELSEPARATOR ); + if( maData.maSeparator.isEmpty() ) + maData.maSeparator = " "; +} + +void XclExpChFrLabelProps::WriteBody( XclExpStream& rStrm ) +{ + XclExpString aXclSep( maData.maSeparator, XclStrFlags::ForceUnicode | XclStrFlags::SmartFlags ); + rStrm << maData.mnFlags << aXclSep; +} + +XclExpChFontBase::~XclExpChFontBase() +{ +} + +void XclExpChFontBase::ConvertFontBase( const XclExpChRoot& rRoot, sal_uInt16 nFontIdx ) +{ + if( const XclExpFont* pFont = rRoot.GetFontBuffer().GetFont( nFontIdx ) ) + { + XclExpChFontRef xFont = new XclExpChFont( nFontIdx ); + SetFont( xFont, pFont->GetFontData().maColor, pFont->GetFontColorId() ); + } +} + +void XclExpChFontBase::ConvertFontBase( const XclExpChRoot& rRoot, const ScfPropertySet& rPropSet ) +{ + ConvertFontBase( rRoot, rRoot.ConvertFont( rPropSet, rRoot.GetDefApiScript() ) ); +} + +void XclExpChFontBase::ConvertRotationBase(const ScfPropertySet& rPropSet, bool bSupportsStacked ) +{ + sal_uInt16 nRotation = XclChPropSetHelper::ReadRotationProperties( rPropSet, bSupportsStacked ); + SetRotation( nRotation ); +} + +XclExpChText::XclExpChText( const XclExpChRoot& rRoot ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_TEXT, EXC_ID_CHTEXT, (rRoot.GetBiff() == EXC_BIFF8) ? 32 : 26 ), + mnTextColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ) +{ +} + +void XclExpChText::SetFont( XclExpChFontRef xFont, const Color& rColor, sal_uInt32 nColorId ) +{ + mxFont = xFont; + maData.maTextColor = rColor; + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOCOLOR, rColor == COL_AUTO ); + mnTextColorId = nColorId; +} + +void XclExpChText::SetRotation( sal_uInt16 nRotation ) +{ + maData.mnRotation = nRotation; + ::insert_value( maData.mnFlags, XclTools::GetXclOrientFromRot( nRotation ), 8, 3 ); +} + +void XclExpChText::ConvertTitle( Reference< XTitle > const & xTitle, sal_uInt16 nTarget, const OUString* pSubTitle ) +{ + switch( nTarget ) + { + case EXC_CHOBJLINK_TITLE: SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_TITLE ); break; + case EXC_CHOBJLINK_YAXIS: SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_AXISTITLE, 1 ); break; + case EXC_CHOBJLINK_XAXIS: SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_AXISTITLE ); break; + case EXC_CHOBJLINK_ZAXIS: SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_AXISTITLE, 2 ); break; + } + + mxSrcLink.clear(); + mxObjLink = new XclExpChObjectLink( nTarget, XclChDataPointPos( 0, 0 ) ); + + if( xTitle.is() ) + { + // title frame formatting + ScfPropertySet aTitleProp( xTitle ); + mxFrame = lclCreateFrame( GetChRoot(), aTitleProp, EXC_CHOBJTYPE_TEXT ); + + // string sequence + mxSrcLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_TITLE ); + sal_uInt16 nFontIdx = mxSrcLink->ConvertStringSequence( xTitle->getText() ); + if (pSubTitle) + { + // append subtitle as the 2nd line of the title. + OUString aSubTitle = "\n" + *pSubTitle; + mxSrcLink->AppendString(aSubTitle); + } + + ConvertFontBase( GetChRoot(), nFontIdx ); + + // rotation + ConvertRotationBase( aTitleProp, true ); + + // manual text position - only for main title + mxFramePos = new XclExpChFramePos( EXC_CHFRAMEPOS_PARENT ); + if( nTarget == EXC_CHOBJLINK_TITLE ) + { + Any aRelPos; + if( aTitleProp.GetAnyProperty( aRelPos, EXC_CHPROP_RELATIVEPOSITION ) && aRelPos.has< RelativePosition >() ) try + { + // calculate absolute position for CHTEXT record + Reference< cssc::XChartDocument > xChart1Doc( GetChartDocument(), UNO_QUERY_THROW ); + Reference< XShape > xTitleShape( xChart1Doc->getTitle(), UNO_SET_THROW ); + css::awt::Point aPos = xTitleShape->getPosition(); + css::awt::Size aSize = xTitleShape->getSize(); + css::awt::Rectangle aRect( aPos.X, aPos.Y, aSize.Width, aSize.Height ); + maData.maRect = CalcChartRectFromHmm( aRect ); + ::insert_value( maData.mnFlags2, EXC_CHTEXT_POS_MOVED, 0, 4 ); + // manual title position implies manual plot area + GetChartData().SetManualPlotArea(); + // calculate the default title position in chart units + sal_Int32 nDefPosX = ::std::max< sal_Int32 >( (EXC_CHART_TOTALUNITS - maData.maRect.mnWidth) / 2, 0 ); + sal_Int32 nDefPosY = 85; + // set the position relative to the standard position + XclChRectangle& rFrameRect = mxFramePos->GetFramePosData().maRect; + rFrameRect.mnX = maData.maRect.mnX - nDefPosX; + rFrameRect.mnY = maData.maRect.mnY - nDefPosY; + } + catch( Exception& ) + { + } + } + } + else + { + ::set_flag( maData.mnFlags, EXC_CHTEXT_DELETED ); + } +} + +void XclExpChText::ConvertLegend( const ScfPropertySet& rPropSet ) +{ + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOTEXT ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOGEN ); + ConvertFontBase( GetChRoot(), rPropSet ); +} + +bool XclExpChText::ConvertDataLabel( const ScfPropertySet& rPropSet, + const XclChTypeInfo& rTypeInfo, const XclChDataPointPos& rPointPos ) +{ + SetFutureRecordContext( EXC_CHFRBLOCK_TEXT_DATALABEL, rPointPos.mnPointIdx, rPointPos.mnSeriesIdx ); + + cssc2::DataPointLabel aPointLabel; + if( !rPropSet.GetProperty( aPointLabel, EXC_CHPROP_LABEL ) ) + return false; + + // percentage only allowed in pie and donut charts + bool bIsPie = rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE; + // bubble sizes only allowed in bubble charts + bool bIsBubble = rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES; + OSL_ENSURE( (GetBiff() == EXC_BIFF8) || !bIsBubble, "XclExpChText::ConvertDataLabel - bubble charts only in BIFF8" ); + + // raw show flags + bool bShowValue = !bIsBubble && aPointLabel.ShowNumber; // Chart2 uses 'ShowNumber' for bubble size + bool bShowPercent = bIsPie && aPointLabel.ShowNumberInPercent; // percentage only in pie/donut charts + bool bShowCateg = aPointLabel.ShowCategoryName; + bool bShowBubble = bIsBubble && aPointLabel.ShowNumber; // Chart2 uses 'ShowNumber' for bubble size + bool bShowAny = bShowValue || bShowPercent || bShowCateg || bShowBubble; + + // create the CHFRLABELPROPS record for extended settings in BIFF8 + if( bShowAny && (GetBiff() == EXC_BIFF8) ) + { + mxLabelProps = new XclExpChFrLabelProps( GetChRoot() ); + mxLabelProps->Convert( rPropSet, bShowCateg, bShowValue, bShowPercent, bShowBubble ); + } + + // restrict to combinations allowed in CHTEXT + if( bShowPercent ) bShowValue = false; // percent wins over value + if( bShowValue ) bShowCateg = false; // value wins over category + if( bShowValue || bShowCateg ) bShowBubble = false; // value or category wins over bubble size + + // set all flags + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOTEXT ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWVALUE, bShowValue ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWPERCENT, bShowPercent ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG, bShowCateg ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEGPERC, bShowPercent && bShowCateg ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWBUBBLE, bShowBubble ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWSYMBOL, bShowAny && aPointLabel.ShowLegendSymbol ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_DELETED, !bShowAny ); + + if( bShowAny ) + { + // font settings + ConvertFontBase( GetChRoot(), rPropSet ); + ConvertRotationBase( rPropSet, false ); + // label placement + sal_Int32 nPlacement = 0; + sal_uInt16 nLabelPos = EXC_CHTEXT_POS_AUTO; + if( rPropSet.GetProperty( nPlacement, EXC_CHPROP_LABELPLACEMENT ) ) + { + using namespace cssc::DataLabelPlacement; + if( nPlacement == rTypeInfo.mnDefaultLabelPos ) + { + nLabelPos = EXC_CHTEXT_POS_DEFAULT; + } + else switch( nPlacement ) + { + case AVOID_OVERLAP: nLabelPos = EXC_CHTEXT_POS_AUTO; break; + case CENTER: nLabelPos = EXC_CHTEXT_POS_CENTER; break; + case TOP: nLabelPos = EXC_CHTEXT_POS_ABOVE; break; + case TOP_LEFT: nLabelPos = EXC_CHTEXT_POS_LEFT; break; + case LEFT: nLabelPos = EXC_CHTEXT_POS_LEFT; break; + case BOTTOM_LEFT: nLabelPos = EXC_CHTEXT_POS_LEFT; break; + case BOTTOM: nLabelPos = EXC_CHTEXT_POS_BELOW; break; + case BOTTOM_RIGHT: nLabelPos = EXC_CHTEXT_POS_RIGHT; break; + case RIGHT: nLabelPos = EXC_CHTEXT_POS_RIGHT; break; + case TOP_RIGHT: nLabelPos = EXC_CHTEXT_POS_RIGHT; break; + case INSIDE: nLabelPos = EXC_CHTEXT_POS_INSIDE; break; + case OUTSIDE: nLabelPos = EXC_CHTEXT_POS_OUTSIDE; break; + case NEAR_ORIGIN: nLabelPos = EXC_CHTEXT_POS_AXIS; break; + default: OSL_FAIL( "XclExpChText::ConvertDataLabel - unknown label placement type" ); + } + } + ::insert_value( maData.mnFlags2, nLabelPos, 0, 4 ); + // source link (contains number format) + mxSrcLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_TITLE ); + if( bShowValue || bShowPercent ) + // percentage format wins over value format + mxSrcLink->ConvertNumFmt( rPropSet, bShowPercent ); + // object link + mxObjLink = new XclExpChObjectLink( EXC_CHOBJLINK_DATA, rPointPos ); + } + + /* Return true to indicate valid label settings: + - for existing labels at entire series + - for any settings at single data point (to be able to delete a point label) */ + return bShowAny || (rPointPos.mnPointIdx != EXC_CHDATAFORMAT_ALLPOINTS); +} + +void XclExpChText::ConvertTrendLineEquation( const ScfPropertySet& rPropSet, const XclChDataPointPos& rPointPos ) +{ + // required flags + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOTEXT ); + if( GetBiff() == EXC_BIFF8 ) + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG ); // must set this to make equation visible in Excel + // frame formatting + mxFrame = lclCreateFrame( GetChRoot(), rPropSet, EXC_CHOBJTYPE_TEXT ); + // font settings + maData.mnHAlign = EXC_CHTEXT_ALIGN_TOPLEFT; + maData.mnVAlign = EXC_CHTEXT_ALIGN_TOPLEFT; + ConvertFontBase( GetChRoot(), rPropSet ); + // source link (contains number format) + mxSrcLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_TITLE ); + mxSrcLink->ConvertNumFmt( rPropSet, false ); + // object link + mxObjLink = new XclExpChObjectLink( EXC_CHOBJLINK_DATA, rPointPos ); +} + +sal_uInt16 XclExpChText::GetAttLabelFlags() const +{ + sal_uInt16 nFlags = 0; + ::set_flag( nFlags, EXC_CHATTLABEL_SHOWVALUE, ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWVALUE ) ); + ::set_flag( nFlags, EXC_CHATTLABEL_SHOWPERCENT, ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWPERCENT ) ); + ::set_flag( nFlags, EXC_CHATTLABEL_SHOWCATEGPERC, ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEGPERC ) ); + ::set_flag( nFlags, EXC_CHATTLABEL_SHOWCATEG, ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG ) ); + return nFlags; +} + +void XclExpChText::WriteSubRecords( XclExpStream& rStrm ) +{ + // CHFRAMEPOS record + lclSaveRecord( rStrm, mxFramePos ); + // CHFONT record + lclSaveRecord( rStrm, mxFont ); + // CHSOURCELINK group + lclSaveRecord( rStrm, mxSrcLink ); + // CHFRAME group + lclSaveRecord( rStrm, mxFrame ); + // CHOBJECTLINK record + lclSaveRecord( rStrm, mxObjLink ); + // CHFRLABELPROPS record + lclSaveRecord( rStrm, mxLabelProps ); +} + +void XclExpChText::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnHAlign + << maData.mnVAlign + << maData.mnBackMode + << maData.maTextColor + << maData.maRect + << maData.mnFlags; + + if( GetBiff() == EXC_BIFF8 ) + { + rStrm << GetPalette().GetColorIndex( mnTextColorId ) + << maData.mnFlags2 + << maData.mnRotation; + } +} + +namespace { + +/** Creates and returns an Excel text object from the passed title. */ +XclExpChTextRef lclCreateTitle( const XclExpChRoot& rRoot, Reference< XTitled > const & xTitled, sal_uInt16 nTarget, + const OUString* pSubTitle = nullptr ) +{ + Reference< XTitle > xTitle; + if( xTitled.is() ) + xTitle = xTitled->getTitleObject(); + + XclExpChTextRef xText = new XclExpChText( rRoot ); + xText->ConvertTitle( xTitle, nTarget, pSubTitle ); + /* Do not delete the CHTEXT group for the main title. A missing CHTEXT + will be interpreted as auto-generated title showing the series title in + charts that contain exactly one data series. */ + if( (nTarget != EXC_CHOBJLINK_TITLE) && !xText->HasString() ) + xText.clear(); + + return xText; +} + +} + +// Data series ================================================================ + +XclExpChMarkerFormat::XclExpChMarkerFormat( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHMARKERFORMAT, (rRoot.GetBiff() == EXC_BIFF8) ? 20 : 12 ), + mnLineColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ), + mnFillColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWBACK ) ) +{ +} + +void XclExpChMarkerFormat::Convert( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) +{ + XclChPropSetHelper::ReadMarkerProperties( maData, rPropSet, nFormatIdx ); + /* Set marker line/fill color to series line color. + TODO: remove this if OOChart supports own colors in markers. */ + Color aLineColor; + if( rPropSet.GetColorProperty( aLineColor, EXC_CHPROP_COLOR ) ) + maData.maLineColor = maData.maFillColor = aLineColor; + // register colors in palette + RegisterColors( rRoot ); +} + +void XclExpChMarkerFormat::ConvertStockSymbol( const XclExpChRoot& rRoot, + const ScfPropertySet& rPropSet, bool bCloseSymbol ) +{ + // clear the automatic flag + ::set_flag( maData.mnFlags, EXC_CHMARKERFORMAT_AUTO, false ); + // symbol type and color + if( bCloseSymbol ) + { + // set symbol type for the 'close' data series + maData.mnMarkerType = EXC_CHMARKERFORMAT_DOWJ; + maData.mnMarkerSize = EXC_CHMARKERFORMAT_DOUBLESIZE; + // set symbol line/fill color to series line color + Color aLineColor; + if( rPropSet.GetColorProperty( aLineColor, EXC_CHPROP_COLOR ) ) + { + maData.maLineColor = maData.maFillColor = aLineColor; + RegisterColors( rRoot ); + } + } + else + { + // set invisible symbol + maData.mnMarkerType = EXC_CHMARKERFORMAT_NOSYMBOL; + } +} + +void XclExpChMarkerFormat::RegisterColors( const XclExpChRoot& rRoot ) +{ + if( HasMarker() ) + { + if( HasLineColor() ) + mnLineColorId = rRoot.GetPalette().InsertColor( maData.maLineColor, EXC_COLOR_CHARTLINE ); + if( HasFillColor() ) + mnFillColorId = rRoot.GetPalette().InsertColor( maData.maFillColor, EXC_COLOR_CHARTAREA ); + } +} + +void XclExpChMarkerFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maLineColor << maData.maFillColor << maData.mnMarkerType << maData.mnFlags; + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + { + const XclExpPalette& rPal = rStrm.GetRoot().GetPalette(); + rStrm << rPal.GetColorIndex( mnLineColorId ) << rPal.GetColorIndex( mnFillColorId ) << maData.mnMarkerSize; + } +} + +XclExpChPieFormat::XclExpChPieFormat() : + XclExpUInt16Record( EXC_ID_CHPIEFORMAT, 0 ) +{ +} + +void XclExpChPieFormat::Convert( const ScfPropertySet& rPropSet ) +{ + double fApiDist(0.0); + if( rPropSet.GetProperty( fApiDist, EXC_CHPROP_OFFSET ) ) + SetValue( limit_cast< sal_uInt16 >( fApiDist * 100.0, 0, 100 ) ); +} + +XclExpCh3dDataFormat::XclExpCh3dDataFormat() : + XclExpRecord( EXC_ID_CH3DDATAFORMAT, 2 ) +{ +} + +void XclExpCh3dDataFormat::Convert( const ScfPropertySet& rPropSet ) +{ + sal_Int32 nApiType(0); + if( !rPropSet.GetProperty( nApiType, EXC_CHPROP_GEOMETRY3D ) ) + return; + + using namespace cssc2::DataPointGeometry3D; + switch( nApiType ) + { + case CUBOID: + maData.mnBase = EXC_CH3DDATAFORMAT_RECT; + maData.mnTop = EXC_CH3DDATAFORMAT_STRAIGHT; + break; + case PYRAMID: + maData.mnBase = EXC_CH3DDATAFORMAT_RECT; + maData.mnTop = EXC_CH3DDATAFORMAT_SHARP; + break; + case CYLINDER: + maData.mnBase = EXC_CH3DDATAFORMAT_CIRC; + maData.mnTop = EXC_CH3DDATAFORMAT_STRAIGHT; + break; + case CONE: + maData.mnBase = EXC_CH3DDATAFORMAT_CIRC; + maData.mnTop = EXC_CH3DDATAFORMAT_SHARP; + break; + default: + OSL_FAIL( "XclExpCh3dDataFormat::Convert - unknown 3D bar format" ); + } +} + +void XclExpCh3dDataFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnBase << maData.mnTop; +} + +XclExpChAttachedLabel::XclExpChAttachedLabel( sal_uInt16 nFlags ) : + XclExpUInt16Record( EXC_ID_CHATTACHEDLABEL, nFlags ) +{ +} + +XclExpChDataFormat::XclExpChDataFormat( const XclExpChRoot& rRoot, + const XclChDataPointPos& rPointPos, sal_uInt16 nFormatIdx ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_DATAFORMAT, EXC_ID_CHDATAFORMAT, 8 ) +{ + maData.maPointPos = rPointPos; + maData.mnFormatIdx = nFormatIdx; +} + +void XclExpChDataFormat::ConvertDataSeries( const ScfPropertySet& rPropSet, const XclChExtTypeInfo& rTypeInfo ) +{ + // line and area formatting + ConvertFrameBase( GetChRoot(), rPropSet, rTypeInfo.GetSeriesObjectType() ); + + // data point symbols + bool bIsFrame = rTypeInfo.IsSeriesFrameFormat(); + if( !bIsFrame ) + { + mxMarkerFmt = new XclExpChMarkerFormat( GetChRoot() ); + mxMarkerFmt->Convert( GetChRoot(), rPropSet, maData.mnFormatIdx ); + } + + // pie segments + if( rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE ) + { + mxPieFmt = new XclExpChPieFormat(); + mxPieFmt->Convert( rPropSet ); + } + + // 3D bars (only allowed for entire series in BIFF8) + if( IsSeriesFormat() && (GetBiff() == EXC_BIFF8) && rTypeInfo.mb3dChart && (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_BAR) ) + { + mx3dDataFmt = new XclExpCh3dDataFormat(); + mx3dDataFmt->Convert( rPropSet ); + } + + // spline + if( IsSeriesFormat() && rTypeInfo.mbSpline && !bIsFrame ) + mxSeriesFmt = new XclExpUInt16Record( EXC_ID_CHSERIESFORMAT, EXC_CHSERIESFORMAT_SMOOTHED ); + + // data point labels + XclExpChTextRef xLabel = new XclExpChText( GetChRoot() ); + if( xLabel->ConvertDataLabel( rPropSet, rTypeInfo, maData.maPointPos ) ) + { + // CHTEXT groups for data labels are stored in global CHCHART group + GetChartData().SetDataLabel( xLabel ); + mxAttLabel = new XclExpChAttachedLabel( xLabel->GetAttLabelFlags() ); + } +} + +void XclExpChDataFormat::ConvertStockSeries( const ScfPropertySet& rPropSet, bool bCloseSymbol ) +{ + // set line format to invisible + SetDefaultFrameBase( GetChRoot(), EXC_CHFRAMETYPE_INVISIBLE, false ); + // set symbols to invisible or to 'close' series symbol + mxMarkerFmt = new XclExpChMarkerFormat( GetChRoot() ); + mxMarkerFmt->ConvertStockSymbol( GetChRoot(), rPropSet, bCloseSymbol ); +} + +void XclExpChDataFormat::ConvertLine( const ScfPropertySet& rPropSet, XclChObjectType eObjType ) +{ + ConvertFrameBase( GetChRoot(), rPropSet, eObjType ); +} + +void XclExpChDataFormat::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mx3dDataFmt ); + WriteFrameRecords( rStrm ); + lclSaveRecord( rStrm, mxPieFmt ); + lclSaveRecord( rStrm, mxMarkerFmt ); + lclSaveRecord( rStrm, mxSeriesFmt ); + lclSaveRecord( rStrm, mxAttLabel ); +} + +void XclExpChDataFormat::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maPointPos.mnPointIdx + << maData.maPointPos.mnSeriesIdx + << maData.mnFormatIdx + << maData.mnFlags; +} + +XclExpChSerTrendLine::XclExpChSerTrendLine( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHSERTRENDLINE, 28 ), + XclExpChRoot( rRoot ) +{ +} + +bool XclExpChSerTrendLine::Convert( Reference< XRegressionCurve > const & xRegCurve, sal_uInt16 nSeriesIdx ) +{ + if( !xRegCurve.is() ) + return false; + + // trend line type + ScfPropertySet aCurveProp( xRegCurve ); + + OUString aService = aCurveProp.GetServiceName(); + if( aService == "com.sun.star.chart2.LinearRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_POLYNOMIAL; + maData.mnOrder = 1; + } + else if( aService == "com.sun.star.chart2.ExponentialRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_EXPONENTIAL; + } + else if( aService == "com.sun.star.chart2.LogarithmicRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_LOGARITHMIC; + } + else if( aService == "com.sun.star.chart2.PotentialRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_POWER; + } + else if( aService == "com.sun.star.chart2.PolynomialRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_POLYNOMIAL; + sal_Int32 aDegree; + aCurveProp.GetProperty(aDegree, EXC_CHPROP_POLYNOMIAL_DEGREE); + maData.mnOrder = static_cast (aDegree); + } + else if( aService == "com.sun.star.chart2.MovingAverageRegressionCurve" ) + { + maData.mnLineType = EXC_CHSERTREND_MOVING_AVG; + sal_Int32 aPeriod; + aCurveProp.GetProperty(aPeriod, EXC_CHPROP_MOVING_AVERAGE_PERIOD); + maData.mnOrder = static_cast (aPeriod); + } + else + { + return false; + } + + aCurveProp.GetProperty(maData.mfForecastFor, EXC_CHPROP_EXTRAPOLATE_FORWARD); + aCurveProp.GetProperty(maData.mfForecastBack, EXC_CHPROP_EXTRAPOLATE_BACKWARD); + bool bIsForceIntercept = false; + aCurveProp.GetProperty(bIsForceIntercept, EXC_CHPROP_FORCE_INTERCEPT); + if (bIsForceIntercept) + aCurveProp.GetProperty(maData.mfIntercept, EXC_CHPROP_INTERCEPT_VALUE); + + // line formatting + XclChDataPointPos aPointPos( nSeriesIdx ); + mxDataFmt = new XclExpChDataFormat( GetChRoot(), aPointPos, 0 ); + mxDataFmt->ConvertLine( aCurveProp, EXC_CHOBJTYPE_TRENDLINE ); + + // #i83100# show equation and correlation coefficient + ScfPropertySet aEquationProp( xRegCurve->getEquationProperties() ); + maData.mnShowEquation = aEquationProp.GetBoolProperty( EXC_CHPROP_SHOWEQUATION ) ? 1 : 0; + maData.mnShowRSquared = aEquationProp.GetBoolProperty( EXC_CHPROP_SHOWCORRELATION ) ? 1 : 0; + + // #i83100# formatting of the equation text box + if( (maData.mnShowEquation != 0) || (maData.mnShowRSquared != 0) ) + { + mxLabel = new XclExpChText( GetChRoot() ); + mxLabel->ConvertTrendLineEquation( aEquationProp, aPointPos ); + } + + // missing features + // #i5085# manual trend line size + // #i34093# manual crossing point + return true; +} + +void XclExpChSerTrendLine::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnLineType + << maData.mnOrder + << maData.mfIntercept + << maData.mnShowEquation + << maData.mnShowRSquared + << maData.mfForecastFor + << maData.mfForecastBack; +} + +XclExpChSerErrorBar::XclExpChSerErrorBar( const XclExpChRoot& rRoot, sal_uInt8 nBarType ) : + XclExpRecord( EXC_ID_CHSERERRORBAR, 14 ), + XclExpChRoot( rRoot ) +{ + maData.mnBarType = nBarType; +} + +bool XclExpChSerErrorBar::Convert( XclExpChSourceLink& rValueLink, sal_uInt16& rnValueCount, const ScfPropertySet& rPropSet ) +{ + sal_Int32 nBarStyle = 0; + bool bOk = rPropSet.GetProperty( nBarStyle, EXC_CHPROP_ERRORBARSTYLE ); + if( bOk ) + { + switch( nBarStyle ) + { + case cssc::ErrorBarStyle::ABSOLUTE: + maData.mnSourceType = EXC_CHSERERR_FIXED; + rPropSet.GetProperty( maData.mfValue, EXC_CHPROP_POSITIVEERROR ); + break; + case cssc::ErrorBarStyle::RELATIVE: + maData.mnSourceType = EXC_CHSERERR_PERCENT; + rPropSet.GetProperty( maData.mfValue, EXC_CHPROP_POSITIVEERROR ); + break; + case cssc::ErrorBarStyle::STANDARD_DEVIATION: + maData.mnSourceType = EXC_CHSERERR_STDDEV; + rPropSet.GetProperty( maData.mfValue, EXC_CHPROP_WEIGHT ); + break; + case cssc::ErrorBarStyle::STANDARD_ERROR: + maData.mnSourceType = EXC_CHSERERR_STDERR; + break; + case cssc::ErrorBarStyle::FROM_DATA: + { + bOk = false; + maData.mnSourceType = EXC_CHSERERR_CUSTOM; + Reference< XDataSource > xDataSource( rPropSet.GetApiPropertySet(), UNO_QUERY ); + if( xDataSource.is() ) + { + // find first sequence with current role + OUString aRole = XclChartHelper::GetErrorBarValuesRole( maData.mnBarType ); + Reference< XDataSequence > xValueSeq; + + const Sequence< Reference< XLabeledDataSequence > > aLabeledSeqVec = xDataSource->getDataSequences(); + for( const Reference< XLabeledDataSequence >& rLabeledSeq : aLabeledSeqVec ) + { + Reference< XDataSequence > xTmpValueSeq = rLabeledSeq->getValues(); + ScfPropertySet aValueProp( xTmpValueSeq ); + OUString aCurrRole; + if( aValueProp.GetProperty( aCurrRole, EXC_CHPROP_ROLE ) && (aCurrRole == aRole) ) + { + xValueSeq = xTmpValueSeq; + break; + } + } + if( xValueSeq.is() ) + { + // #i86465# pass value count back to series + rnValueCount = maData.mnValueCount = rValueLink.ConvertDataSequence( xValueSeq, true ); + bOk = maData.mnValueCount > 0; + } + } + } + break; + default: + bOk = false; + } + } + return bOk; +} + +void XclExpChSerErrorBar::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnBarType + << maData.mnSourceType + << maData.mnLineEnd + << sal_uInt8( 1 ) // must be 1 to make line visible + << maData.mfValue + << maData.mnValueCount; +} + +namespace { + +/** Returns the property set of the specified data point. */ +ScfPropertySet lclGetPointPropSet( Reference< XDataSeries > const & xDataSeries, sal_Int32 nPointIdx ) +{ + ScfPropertySet aPropSet; + try + { + aPropSet.Set( xDataSeries->getDataPointByIndex( nPointIdx ) ); + } + catch( Exception& ) + { + OSL_FAIL( "lclGetPointPropSet - no data point property set" ); + } + return aPropSet; +} + +} // namespace + +XclExpChSeries::XclExpChSeries( const XclExpChRoot& rRoot, sal_uInt16 nSeriesIdx ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_SERIES, EXC_ID_CHSERIES, (rRoot.GetBiff() == EXC_BIFF8) ? 12 : 8 ), + mnGroupIdx( EXC_CHSERGROUP_NONE ), + mnSeriesIdx( nSeriesIdx ), + mnParentIdx( EXC_CHSERIES_INVALID ) +{ + // CHSOURCELINK records are always required, even if unused + mxTitleLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_TITLE ); + mxValueLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_VALUES ); + mxCategLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_CATEGORY ); + if( GetBiff() == EXC_BIFF8 ) + mxBubbleLink = new XclExpChSourceLink( GetChRoot(), EXC_CHSRCLINK_BUBBLES ); +} + +bool XclExpChSeries::ConvertDataSeries( + Reference< XDiagram > const & xDiagram, Reference< XDataSeries > const & xDataSeries, + const XclChExtTypeInfo& rTypeInfo, sal_uInt16 nGroupIdx, sal_uInt16 nFormatIdx ) +{ + bool bOk = false; + Reference< XDataSource > xDataSource( xDataSeries, UNO_QUERY ); + if( xDataSource.is() ) + { + Reference< XDataSequence > xYValueSeq, xTitleSeq, xXValueSeq, xBubbleSeq; + + // find first sequence with role 'values-y' + const Sequence< Reference< XLabeledDataSequence > > aLabeledSeqVec = xDataSource->getDataSequences(); + for( const Reference< XLabeledDataSequence >& rLabeledSeq : aLabeledSeqVec ) + { + Reference< XDataSequence > xTmpValueSeq = rLabeledSeq->getValues(); + ScfPropertySet aValueProp( xTmpValueSeq ); + OUString aRole; + if( aValueProp.GetProperty( aRole, EXC_CHPROP_ROLE ) ) + { + if( !xYValueSeq.is() && (aRole == EXC_CHPROP_ROLE_YVALUES) ) + { + xYValueSeq = xTmpValueSeq; + if( !xTitleSeq.is() ) + xTitleSeq = rLabeledSeq->getLabel(); // ignore role of label sequence + } + else if( !xXValueSeq.is() && !rTypeInfo.mbCategoryAxis && (aRole == EXC_CHPROP_ROLE_XVALUES) ) + { + xXValueSeq = xTmpValueSeq; + } + else if( !xBubbleSeq.is() && (rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES) && (aRole == EXC_CHPROP_ROLE_SIZEVALUES) ) + { + xBubbleSeq = xTmpValueSeq; + xTitleSeq = rLabeledSeq->getLabel(); // ignore role of label sequence + } + } + } + + bOk = xYValueSeq.is(); + if( bOk ) + { + // chart type group index + mnGroupIdx = nGroupIdx; + + // convert source links + maData.mnValueCount = mxValueLink->ConvertDataSequence( xYValueSeq, true ); + mxTitleLink->ConvertDataSequence( xTitleSeq, true ); + + // X values of XY charts + maData.mnCategCount = mxCategLink->ConvertDataSequence( xXValueSeq, false, maData.mnValueCount ); + + // size values of bubble charts + if( mxBubbleLink ) + mxBubbleLink->ConvertDataSequence( xBubbleSeq, false, maData.mnValueCount ); + + // series formatting + XclChDataPointPos aPointPos( mnSeriesIdx ); + ScfPropertySet aSeriesProp( xDataSeries ); + mxSeriesFmt = new XclExpChDataFormat( GetChRoot(), aPointPos, nFormatIdx ); + mxSeriesFmt->ConvertDataSeries( aSeriesProp, rTypeInfo ); + + // trend lines + CreateTrendLines( xDataSeries ); + + // error bars + CreateErrorBars( aSeriesProp, EXC_CHPROP_ERRORBARX, EXC_CHSERERR_XPLUS, EXC_CHSERERR_XMINUS ); + CreateErrorBars( aSeriesProp, EXC_CHPROP_ERRORBARY, EXC_CHSERERR_YPLUS, EXC_CHSERERR_YMINUS ); + + if( maData.mnValueCount > 0 ) + { + const sal_Int32 nMaxPointCount = maData.mnValueCount; + + /* #i91063# Create missing fill properties in pie/doughnut charts. + If freshly created (never saved to ODF), these charts show + varying point colors but do not return these points via API. */ + if( xDiagram.is() && (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE) ) + { + Reference< XColorScheme > xColorScheme = xDiagram->getDefaultColorScheme(); + if( xColorScheme.is() ) + { + static const OUStringLiteral aFillStyleName = u"FillStyle"; + static const OUStringLiteral aColorName = u"Color"; + namespace cssd = ::com::sun::star::drawing; + for( sal_Int32 nPointIdx = 0; nPointIdx < nMaxPointCount; ++nPointIdx ) + { + aPointPos.mnPointIdx = static_cast< sal_uInt16 >( nPointIdx ); + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, nPointIdx ); + // test that the point fill style is solid, but no color is set + cssd::FillStyle eFillStyle = cssd::FillStyle_NONE; + if( aPointProp.GetProperty( eFillStyle, aFillStyleName ) && + (eFillStyle == cssd::FillStyle_SOLID) && + !aPointProp.HasProperty( aColorName ) ) + { + aPointProp.SetProperty( aColorName, xColorScheme->getColorByIndex( nPointIdx ) ); + } + } + } + } + + // data point formatting + Sequence< sal_Int32 > aPointIndexes; + if( aSeriesProp.GetProperty( aPointIndexes, EXC_CHPROP_ATTRIBDATAPOINTS ) && aPointIndexes.hasElements() ) + { + for( const sal_Int32 nPointIndex : std::as_const(aPointIndexes) ) + { + if (nPointIndex >= nMaxPointCount) + break; + aPointPos.mnPointIdx = static_cast< sal_uInt16 >( nPointIndex ); + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, nPointIndex ); + XclExpChDataFormatRef xPointFmt = new XclExpChDataFormat( GetChRoot(), aPointPos, nFormatIdx ); + xPointFmt->ConvertDataSeries( aPointProp, rTypeInfo ); + maPointFmts.AppendRecord( xPointFmt ); + } + } + } + } + } + return bOk; +} + +bool XclExpChSeries::ConvertStockSeries( css::uno::Reference< css::chart2::XDataSeries > const & xDataSeries, + std::u16string_view rValueRole, sal_uInt16 nGroupIdx, sal_uInt16 nFormatIdx, bool bCloseSymbol ) +{ + bool bOk = false; + Reference< XDataSource > xDataSource( xDataSeries, UNO_QUERY ); + if( xDataSource.is() ) + { + Reference< XDataSequence > xYValueSeq, xTitleSeq; + + // find first sequence with passed role + const Sequence< Reference< XLabeledDataSequence > > aLabeledSeqVec = xDataSource->getDataSequences(); + for( const Reference< XLabeledDataSequence >& rLabeledSeq : aLabeledSeqVec ) + { + Reference< XDataSequence > xTmpValueSeq = rLabeledSeq->getValues(); + ScfPropertySet aValueProp( xTmpValueSeq ); + OUString aRole; + if( aValueProp.GetProperty( aRole, EXC_CHPROP_ROLE ) && (aRole == rValueRole) ) + { + xYValueSeq = xTmpValueSeq; + xTitleSeq = rLabeledSeq->getLabel(); // ignore role of label sequence + break; + } + } + + bOk = xYValueSeq.is(); + if( bOk ) + { + // chart type group index + mnGroupIdx = nGroupIdx; + // convert source links + maData.mnValueCount = mxValueLink->ConvertDataSequence( xYValueSeq, true ); + mxTitleLink->ConvertDataSequence( xTitleSeq, true ); + // series formatting + ScfPropertySet aSeriesProp( xDataSeries ); + mxSeriesFmt = new XclExpChDataFormat( GetChRoot(), XclChDataPointPos( mnSeriesIdx ), nFormatIdx ); + mxSeriesFmt->ConvertStockSeries( aSeriesProp, bCloseSymbol ); + } + } + return bOk; +} + +bool XclExpChSeries::ConvertTrendLine( const XclExpChSeries& rParent, Reference< XRegressionCurve > const & xRegCurve ) +{ + InitFromParent( rParent ); + + mxTrendLine = new XclExpChSerTrendLine( GetChRoot() ); + bool bOk = mxTrendLine->Convert( xRegCurve, mnSeriesIdx ); + if( bOk ) + { + OUString aName; + ScfPropertySet aProperties( xRegCurve ); + aProperties.GetProperty(aName, EXC_CHPROP_CURVENAME); + mxTitleLink->ConvertString(aName); + + mxSeriesFmt = mxTrendLine->GetDataFormat(); + GetChartData().SetDataLabel( mxTrendLine->GetDataLabel() ); + } + return bOk; +} + +bool XclExpChSeries::ConvertErrorBar( const XclExpChSeries& rParent, const ScfPropertySet& rPropSet, sal_uInt8 nBarId ) +{ + InitFromParent( rParent ); + // error bar settings + mxErrorBar = new XclExpChSerErrorBar( GetChRoot(), nBarId ); + bool bOk = mxErrorBar->Convert( *mxValueLink, maData.mnValueCount, rPropSet ); + if( bOk ) + { + // error bar formatting + mxSeriesFmt = new XclExpChDataFormat( GetChRoot(), XclChDataPointPos( mnSeriesIdx ), 0 ); + mxSeriesFmt->ConvertLine( rPropSet, EXC_CHOBJTYPE_ERRORBAR ); + } + return bOk; +} + +void XclExpChSeries::ConvertCategSequence( Reference< XLabeledDataSequence > const & xCategSeq ) +{ + if( xCategSeq.is() ) + maData.mnCategCount = mxCategLink->ConvertDataSequence( xCategSeq->getValues(), false ); +} + +void XclExpChSeries::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxTitleLink ); + lclSaveRecord( rStrm, mxValueLink ); + lclSaveRecord( rStrm, mxCategLink ); + lclSaveRecord( rStrm, mxBubbleLink ); + lclSaveRecord( rStrm, mxSeriesFmt ); + maPointFmts.Save( rStrm ); + if( mnGroupIdx != EXC_CHSERGROUP_NONE ) + XclExpUInt16Record( EXC_ID_CHSERGROUP, mnGroupIdx ).Save( rStrm ); + if( mnParentIdx != EXC_CHSERIES_INVALID ) + XclExpUInt16Record( EXC_ID_CHSERPARENT, mnParentIdx ).Save( rStrm ); + lclSaveRecord( rStrm, mxTrendLine ); + lclSaveRecord( rStrm, mxErrorBar ); +} + +void XclExpChSeries::InitFromParent( const XclExpChSeries& rParent ) +{ + // index to parent series is stored 1-based + mnParentIdx = rParent.mnSeriesIdx + 1; + /* #i86465# MSO2007 SP1 expects correct point counts in child series + (there was no problem in Excel2003 or Excel2007 without SP1...) */ + maData.mnCategCount = rParent.maData.mnCategCount; + maData.mnValueCount = rParent.maData.mnValueCount; +} + +void XclExpChSeries::CreateTrendLines( css::uno::Reference< css::chart2::XDataSeries > const & xDataSeries ) +{ + Reference< XRegressionCurveContainer > xRegCurveCont( xDataSeries, UNO_QUERY ); + if( xRegCurveCont.is() ) + { + const Sequence< Reference< XRegressionCurve > > aRegCurveSeq = xRegCurveCont->getRegressionCurves(); + for( const Reference< XRegressionCurve >& rRegCurve : aRegCurveSeq ) + { + XclExpChSeriesRef xSeries = GetChartData().CreateSeries(); + if( xSeries && !xSeries->ConvertTrendLine( *this, rRegCurve ) ) + GetChartData().RemoveLastSeries(); + } + } +} + +void XclExpChSeries::CreateErrorBars( const ScfPropertySet& rPropSet, + const OUString& rBarPropName, sal_uInt8 nPosBarId, sal_uInt8 nNegBarId ) +{ + Reference< XPropertySet > xErrorBar; + if( rPropSet.GetProperty( xErrorBar, rBarPropName ) && xErrorBar.is() ) + { + ScfPropertySet aErrorProp( xErrorBar ); + CreateErrorBar( aErrorProp, EXC_CHPROP_SHOWPOSITIVEERROR, nPosBarId ); + CreateErrorBar( aErrorProp, EXC_CHPROP_SHOWNEGATIVEERROR, nNegBarId ); + } +} + +void XclExpChSeries::CreateErrorBar( const ScfPropertySet& rPropSet, + const OUString& rShowPropName, sal_uInt8 nBarId ) +{ + if( rPropSet.GetBoolProperty( rShowPropName ) ) + { + XclExpChSeriesRef xSeries = GetChartData().CreateSeries(); + if( xSeries && !xSeries->ConvertErrorBar( *this, rPropSet, nBarId ) ) + GetChartData().RemoveLastSeries(); + } +} + +void XclExpChSeries::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnCategType << maData.mnValueType << maData.mnCategCount << maData.mnValueCount; + if( GetBiff() == EXC_BIFF8 ) + rStrm << maData.mnBubbleType << maData.mnBubbleCount; +} + +// Chart type groups ========================================================== + +XclExpChType::XclExpChType( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHUNKNOWN ), + XclExpChRoot( rRoot ), + maTypeInfo( rRoot.GetChartTypeInfo( EXC_CHTYPEID_UNKNOWN ) ) +{ +} + +void XclExpChType::Convert( Reference< XDiagram > const & xDiagram, Reference< XChartType > const & xChartType, + sal_Int32 nApiAxesSetIdx, bool bSwappedAxesSet, bool bHasXLabels ) +{ + if( !xChartType.is() ) + return; + + maTypeInfo = GetChartTypeInfo( xChartType->getChartType() ); + // special handling for some chart types + switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_BAR: + { + maTypeInfo = GetChartTypeInfo( bSwappedAxesSet ? EXC_CHTYPEID_HORBAR : EXC_CHTYPEID_BAR ); + ::set_flag( maData.mnFlags, EXC_CHBAR_HORIZONTAL, bSwappedAxesSet ); + ScfPropertySet aTypeProp( xChartType ); + Sequence< sal_Int32 > aInt32Seq; + maData.mnOverlap = 0; + if( aTypeProp.GetProperty( aInt32Seq, EXC_CHPROP_OVERLAPSEQ ) && (nApiAxesSetIdx < aInt32Seq.getLength()) ) + maData.mnOverlap = limit_cast< sal_Int16 >( -aInt32Seq[ nApiAxesSetIdx ], -100, 100 ); + maData.mnGap = 150; + if( aTypeProp.GetProperty( aInt32Seq, EXC_CHPROP_GAPWIDTHSEQ ) && (nApiAxesSetIdx < aInt32Seq.getLength()) ) + maData.mnGap = limit_cast< sal_uInt16 >( aInt32Seq[ nApiAxesSetIdx ], 0, 500 ); + } + break; + case EXC_CHTYPECATEG_RADAR: + ::set_flag( maData.mnFlags, EXC_CHRADAR_AXISLABELS, bHasXLabels ); + break; + case EXC_CHTYPECATEG_PIE: + { + ScfPropertySet aTypeProp( xChartType ); + bool bDonut = aTypeProp.GetBoolProperty( EXC_CHPROP_USERINGS ); + maTypeInfo = GetChartTypeInfo( bDonut ? EXC_CHTYPEID_DONUT : EXC_CHTYPEID_PIE ); + maData.mnPieHole = bDonut ? 50 : 0; + // #i85166# starting angle of first pie slice + ScfPropertySet aDiaProp( xDiagram ); + maData.mnRotation = XclExpChRoot::ConvertPieRotation( aDiaProp ); + } + break; + case EXC_CHTYPECATEG_SCATTER: + if( GetBiff() == EXC_BIFF8 ) + ::set_flag( maData.mnFlags, EXC_CHSCATTER_BUBBLES, maTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES ); + break; + default:; + } + SetRecId( maTypeInfo.mnRecId ); +} + +void XclExpChType::SetStacked( bool bPercent ) +{ + switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_LINE: + ::set_flag( maData.mnFlags, EXC_CHLINE_STACKED ); + ::set_flag( maData.mnFlags, EXC_CHLINE_PERCENT, bPercent ); + break; + case EXC_CHTYPECATEG_BAR: + ::set_flag( maData.mnFlags, EXC_CHBAR_STACKED ); + ::set_flag( maData.mnFlags, EXC_CHBAR_PERCENT, bPercent ); + maData.mnOverlap = -100; + break; + default:; + } +} + +void XclExpChType::WriteBody( XclExpStream& rStrm ) +{ + switch( GetRecId() ) + { + case EXC_ID_CHBAR: + rStrm << maData.mnOverlap << maData.mnGap << maData.mnFlags; + break; + + case EXC_ID_CHLINE: + case EXC_ID_CHAREA: + case EXC_ID_CHRADARLINE: + case EXC_ID_CHRADARAREA: + rStrm << maData.mnFlags; + break; + + case EXC_ID_CHPIE: + rStrm << maData.mnRotation << maData.mnPieHole; + if( GetBiff() == EXC_BIFF8 ) + rStrm << maData.mnFlags; + break; + + case EXC_ID_CHSCATTER: + if( GetBiff() == EXC_BIFF8 ) + rStrm << maData.mnBubbleSize << maData.mnBubbleType << maData.mnFlags; + break; + + default: + OSL_FAIL( "XclExpChType::WriteBody - unknown chart type" ); + } +} + +XclExpChChart3d::XclExpChChart3d() : + XclExpRecord( EXC_ID_CHCHART3D, 14 ) +{ +} + +void XclExpChChart3d::Convert( const ScfPropertySet& rPropSet, bool b3dWallChart ) +{ + sal_Int32 nRotationY = 0; + rPropSet.GetProperty( nRotationY, EXC_CHPROP_ROTATIONVERTICAL ); + sal_Int32 nRotationX = 0; + rPropSet.GetProperty( nRotationX, EXC_CHPROP_ROTATIONHORIZONTAL ); + sal_Int32 nPerspective = 15; + rPropSet.GetProperty( nPerspective, EXC_CHPROP_PERSPECTIVE ); + + if( b3dWallChart ) + { + // Y rotation (Excel [0..359], Chart2 [-179,180]) + if( nRotationY < 0 ) nRotationY += 360; + maData.mnRotation = static_cast< sal_uInt16 >( nRotationY ); + // X rotation a.k.a. elevation (Excel [-90..90], Chart2 [-179,180]) + maData.mnElevation = limit_cast< sal_Int16 >( nRotationX, -90, 90 ); + // perspective (Excel and Chart2 [0,100]) + maData.mnEyeDist = limit_cast< sal_uInt16 >( nPerspective, 0, 100 ); + // flags + maData.mnFlags = 0; + ::set_flag( maData.mnFlags, EXC_CHCHART3D_REAL3D, !rPropSet.GetBoolProperty( EXC_CHPROP_RIGHTANGLEDAXES ) ); + ::set_flag( maData.mnFlags, EXC_CHCHART3D_AUTOHEIGHT ); + ::set_flag( maData.mnFlags, EXC_CHCHART3D_HASWALLS ); + } + else + { + // Y rotation not used in pie charts, but 'first pie slice angle' + maData.mnRotation = XclExpChRoot::ConvertPieRotation( rPropSet ); + // X rotation a.k.a. elevation (map Chart2 [-80,-10] to Excel [10..80]) + maData.mnElevation = limit_cast< sal_Int16 >( (nRotationX + 270) % 180, 10, 80 ); + // perspective (Excel and Chart2 [0,100]) + maData.mnEyeDist = limit_cast< sal_uInt16 >( nPerspective, 0, 100 ); + // flags + maData.mnFlags = 0; + } +} + +void XclExpChChart3d::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnRotation + << maData.mnElevation + << maData.mnEyeDist + << maData.mnRelHeight + << maData.mnRelDepth + << maData.mnDepthGap + << maData.mnFlags; +} + +XclExpChLegend::XclExpChLegend( const XclExpChRoot& rRoot ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_LEGEND, EXC_ID_CHLEGEND, 20 ) +{ +} + +void XclExpChLegend::Convert( const ScfPropertySet& rPropSet ) +{ + // frame properties + mxFrame = lclCreateFrame( GetChRoot(), rPropSet, EXC_CHOBJTYPE_LEGEND ); + // text properties + mxText = new XclExpChText( GetChRoot() ); + mxText->ConvertLegend( rPropSet ); + + // legend position and size + Any aRelPosAny, aRelSizeAny; + rPropSet.GetAnyProperty( aRelPosAny, EXC_CHPROP_RELATIVEPOSITION ); + rPropSet.GetAnyProperty( aRelSizeAny, EXC_CHPROP_RELATIVESIZE ); + cssc::ChartLegendExpansion eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + rPropSet.GetProperty( eApiExpand, EXC_CHPROP_EXPANSION ); + if( aRelPosAny.has< RelativePosition >() || ((eApiExpand == cssc::ChartLegendExpansion_CUSTOM) && aRelSizeAny.has< RelativeSize >()) ) + { + try + { + /* The 'RelativePosition' or 'RelativeSize' properties are used as + indicator of manually changed legend position/size, but due to + the different anchor modes used by this property (in the + RelativePosition.Anchor member) it cannot be used to calculate + the position easily. For this, the Chart1 API will be used + instead. */ + Reference< cssc::XChartDocument > xChart1Doc( GetChartDocument(), UNO_QUERY_THROW ); + Reference< XShape > xChart1Legend( xChart1Doc->getLegend(), UNO_SET_THROW ); + // coordinates in CHLEGEND record written but not used by Excel + mxFramePos = new XclExpChFramePos( EXC_CHFRAMEPOS_CHARTSIZE ); + XclChFramePos& rFramePos = mxFramePos->GetFramePosData(); + rFramePos.mnTLMode = EXC_CHFRAMEPOS_CHARTSIZE; + css::awt::Point aLegendPos = xChart1Legend->getPosition(); + rFramePos.maRect.mnX = maData.maRect.mnX = CalcChartXFromHmm( aLegendPos.X ); + rFramePos.maRect.mnY = maData.maRect.mnY = CalcChartYFromHmm( aLegendPos.Y ); + // legend size, Excel expects points in CHFRAMEPOS record + rFramePos.mnBRMode = EXC_CHFRAMEPOS_ABSSIZE_POINTS; + css::awt::Size aLegendSize = xChart1Legend->getSize(); + rFramePos.maRect.mnWidth = o3tl::convert(aLegendSize.Width, o3tl::Length::mm100, o3tl::Length::pt); + rFramePos.maRect.mnHeight = o3tl::convert(aLegendSize.Height, o3tl::Length::mm100, o3tl::Length::pt); + maData.maRect.mnWidth = CalcChartXFromHmm( aLegendSize.Width ); + maData.maRect.mnHeight = CalcChartYFromHmm( aLegendSize.Height ); + eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + // manual legend position implies manual plot area + GetChartData().SetManualPlotArea(); + maData.mnDockMode = EXC_CHLEGEND_NOTDOCKED; + // a CHFRAME record with cleared auto flags is needed + if( !mxFrame ) + mxFrame = new XclExpChFrame( GetChRoot(), EXC_CHOBJTYPE_LEGEND ); + mxFrame->SetAutoFlags( false, false ); + } + catch( Exception& ) + { + OSL_FAIL( "XclExpChLegend::Convert - cannot get legend shape" ); + maData.mnDockMode = EXC_CHLEGEND_RIGHT; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + } + } + else + { + cssc2::LegendPosition eApiPos = cssc2::LegendPosition_LINE_END; + rPropSet.GetProperty( eApiPos, EXC_CHPROP_ANCHORPOSITION ); + switch( eApiPos ) + { + case cssc2::LegendPosition_LINE_START: maData.mnDockMode = EXC_CHLEGEND_LEFT; break; + case cssc2::LegendPosition_LINE_END: maData.mnDockMode = EXC_CHLEGEND_RIGHT; break; + case cssc2::LegendPosition_PAGE_START: maData.mnDockMode = EXC_CHLEGEND_TOP; break; + case cssc2::LegendPosition_PAGE_END: maData.mnDockMode = EXC_CHLEGEND_BOTTOM; break; + default: + OSL_FAIL( "XclExpChLegend::Convert - unrecognized legend position" ); + maData.mnDockMode = EXC_CHLEGEND_RIGHT; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + } + } + ::set_flag( maData.mnFlags, EXC_CHLEGEND_STACKED, eApiExpand == cssc::ChartLegendExpansion_HIGH ); + + // other flags + ::set_flag( maData.mnFlags, EXC_CHLEGEND_AUTOSERIES ); + const sal_uInt16 nAutoFlags = EXC_CHLEGEND_DOCKED | EXC_CHLEGEND_AUTOPOSX | EXC_CHLEGEND_AUTOPOSY; + ::set_flag( maData.mnFlags, nAutoFlags, maData.mnDockMode != EXC_CHLEGEND_NOTDOCKED ); +} + +void XclExpChLegend::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxFramePos ); + lclSaveRecord( rStrm, mxText ); + lclSaveRecord( rStrm, mxFrame ); +} + +void XclExpChLegend::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.maRect << maData.mnDockMode << maData.mnSpacing << maData.mnFlags; +} + +XclExpChDropBar::XclExpChDropBar( const XclExpChRoot& rRoot, XclChObjectType eObjType ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_DROPBAR, EXC_ID_CHDROPBAR, 2 ), + meObjType( eObjType ) +{ +} + +void XclExpChDropBar::Convert( const ScfPropertySet& rPropSet ) +{ + if( rPropSet.Is() ) + ConvertFrameBase( GetChRoot(), rPropSet, meObjType ); + else + SetDefaultFrameBase( GetChRoot(), EXC_CHFRAMETYPE_INVISIBLE, true ); +} + +void XclExpChDropBar::WriteSubRecords( XclExpStream& rStrm ) +{ + WriteFrameRecords( rStrm ); +} + +void XclExpChDropBar::WriteBody( XclExpStream& rStrm ) +{ + rStrm << sal_uInt16(100); // Distance between bars (CHDROPBAR record). +} + +XclExpChTypeGroup::XclExpChTypeGroup( const XclExpChRoot& rRoot, sal_uInt16 nGroupIdx ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_TYPEGROUP, EXC_ID_CHTYPEGROUP, 20 ), + maType( rRoot ), + maTypeInfo( maType.GetTypeInfo() ) +{ + maData.mnGroupIdx = nGroupIdx; +} + +void XclExpChTypeGroup::ConvertType( + Reference< XDiagram > const & xDiagram, Reference< XChartType > const & xChartType, + sal_Int32 nApiAxesSetIdx, bool b3dChart, bool bSwappedAxesSet, bool bHasXLabels ) +{ + // chart type settings + maType.Convert( xDiagram, xChartType, nApiAxesSetIdx, bSwappedAxesSet, bHasXLabels ); + + // spline - TODO: get from single series (#i66858#) + ScfPropertySet aTypeProp( xChartType ); + cssc2::CurveStyle eCurveStyle; + bool bSpline = aTypeProp.GetProperty( eCurveStyle, EXC_CHPROP_CURVESTYLE ) && + (eCurveStyle != cssc2::CurveStyle_LINES); + + // extended type info + maTypeInfo.Set( maType.GetTypeInfo(), b3dChart, bSpline ); + + // 3d chart settings + if( maTypeInfo.mb3dChart ) // only true, if Excel chart supports 3d mode + { + mxChart3d = new XclExpChChart3d(); + ScfPropertySet aDiaProp( xDiagram ); + mxChart3d->Convert( aDiaProp, Is3dWallChart() ); + } +} + +void XclExpChTypeGroup::ConvertSeries( + Reference< XDiagram > const & xDiagram, Reference< XChartType > const & xChartType, + sal_Int32 nGroupAxesSetIdx, bool bPercent, bool bConnectBars ) +{ + Reference< XDataSeriesContainer > xSeriesCont( xChartType, UNO_QUERY ); + if( !xSeriesCont.is() ) + return; + + std::vector< Reference< XDataSeries > > aSeriesVec; + + // copy data series attached to the current axes set to the vector + const Sequence< Reference< XDataSeries > > aSeriesSeq = xSeriesCont->getDataSeries(); + for( const Reference< XDataSeries >& rSeries : aSeriesSeq ) + { + ScfPropertySet aSeriesProp( rSeries ); + sal_Int32 nSeriesAxesSetIdx(0); + if( aSeriesProp.GetProperty( nSeriesAxesSetIdx, EXC_CHPROP_ATTAXISINDEX ) && (nSeriesAxesSetIdx == nGroupAxesSetIdx) ) + aSeriesVec.push_back( rSeries ); + } + + // Are there any series in the current axes set? + if( aSeriesVec.empty() ) + return; + + // stacking direction (stacked/percent/deep 3d) from first series + ScfPropertySet aSeriesProp( aSeriesVec.front() ); + cssc2::StackingDirection eStacking; + if( !aSeriesProp.GetProperty( eStacking, EXC_CHPROP_STACKINGDIR ) ) + eStacking = cssc2::StackingDirection_NO_STACKING; + + // stacked or percent chart + if( maTypeInfo.mbSupportsStacking && (eStacking == cssc2::StackingDirection_Y_STACKING) ) + { + // percent overrides simple stacking + maType.SetStacked( bPercent ); + + // connected data points (only in stacked bar charts) + if (bConnectBars && (maTypeInfo.meTypeCateg == EXC_CHTYPECATEG_BAR)) + { + sal_uInt16 nKey = EXC_CHCHARTLINE_CONNECT; + m_ChartLines.insert(std::make_pair(nKey, std::make_unique(GetChRoot()))); + } + } + else + { + // reverse series order for some unstacked 2D chart types + if( maTypeInfo.mbReverseSeries && !Is3dChart() ) + ::std::reverse( aSeriesVec.begin(), aSeriesVec.end() ); + } + + // deep 3d chart or clustered 3d chart (stacked is not clustered) + if( (eStacking == cssc2::StackingDirection_NO_STACKING) && Is3dWallChart() ) + mxChart3d->SetClustered(); + + // varied point colors + ::set_flag( maData.mnFlags, EXC_CHTYPEGROUP_VARIEDCOLORS, aSeriesProp.GetBoolProperty( EXC_CHPROP_VARYCOLORSBY ) ); + + // process all series + for( const auto& rxSeries : aSeriesVec ) + { + // create Excel series object, stock charts need special processing + if( maTypeInfo.meTypeId == EXC_CHTYPEID_STOCK ) + CreateAllStockSeries( xChartType, rxSeries ); + else + CreateDataSeries( xDiagram, rxSeries ); + } +} + +void XclExpChTypeGroup::ConvertCategSequence( Reference< XLabeledDataSequence > const & xCategSeq ) +{ + for( size_t nIdx = 0, nSize = maSeries.GetSize(); nIdx < nSize; ++nIdx ) + maSeries.GetRecord( nIdx )->ConvertCategSequence( xCategSeq ); +} + +void XclExpChTypeGroup::ConvertLegend( const ScfPropertySet& rPropSet ) +{ + if( rPropSet.GetBoolProperty( EXC_CHPROP_SHOW ) ) + { + mxLegend = new XclExpChLegend( GetChRoot() ); + mxLegend->Convert( rPropSet ); + } +} + +void XclExpChTypeGroup::WriteSubRecords( XclExpStream& rStrm ) +{ + maType.Save( rStrm ); + lclSaveRecord( rStrm, mxChart3d ); + lclSaveRecord( rStrm, mxLegend ); + lclSaveRecord( rStrm, mxUpBar ); + lclSaveRecord( rStrm, mxDownBar ); + for (auto const& it : m_ChartLines) + { + lclSaveRecord( rStrm, it.second.get(), EXC_ID_CHCHARTLINE, it.first ); + } +} + +sal_uInt16 XclExpChTypeGroup::GetFreeFormatIdx() const +{ + return static_cast< sal_uInt16 >( maSeries.GetSize() ); +} + +void XclExpChTypeGroup::CreateDataSeries( + Reference< XDiagram > const & xDiagram, Reference< XDataSeries > const & xDataSeries ) +{ + // let chart create series object with correct series index + XclExpChSeriesRef xSeries = GetChartData().CreateSeries(); + if( xSeries ) + { + if( xSeries->ConvertDataSeries( xDiagram, xDataSeries, maTypeInfo, GetGroupIdx(), GetFreeFormatIdx() ) ) + maSeries.AppendRecord( xSeries ); + else + GetChartData().RemoveLastSeries(); + } +} + +void XclExpChTypeGroup::CreateAllStockSeries( + Reference< XChartType > const & xChartType, Reference< XDataSeries > const & xDataSeries ) +{ + // create existing series objects + bool bHasOpen = CreateStockSeries( xDataSeries, EXC_CHPROP_ROLE_OPENVALUES, false ); + bool bHasHigh = CreateStockSeries( xDataSeries, EXC_CHPROP_ROLE_HIGHVALUES, false ); + bool bHasLow = CreateStockSeries( xDataSeries, EXC_CHPROP_ROLE_LOWVALUES, false ); + bool bHasClose = CreateStockSeries( xDataSeries, EXC_CHPROP_ROLE_CLOSEVALUES, !bHasOpen ); + + // formatting of special stock chart elements + ScfPropertySet aTypeProp( xChartType ); + // hi-lo lines + if( bHasHigh && bHasLow && aTypeProp.GetBoolProperty( EXC_CHPROP_SHOWHIGHLOW ) ) + { + ScfPropertySet aSeriesProp( xDataSeries ); + XclExpChLineFormatRef xLineFmt = new XclExpChLineFormat( GetChRoot() ); + xLineFmt->Convert( GetChRoot(), aSeriesProp, EXC_CHOBJTYPE_HILOLINE ); + sal_uInt16 nKey = EXC_CHCHARTLINE_HILO; + m_ChartLines.insert(std::make_pair(nKey, std::make_unique(GetChRoot()))); + } + // dropbars + if( !(bHasOpen && bHasClose) ) + return; + + // dropbar type is dependent on position in the file - always create both + Reference< XPropertySet > xWhitePropSet, xBlackPropSet; + // white dropbar format + aTypeProp.GetProperty( xWhitePropSet, EXC_CHPROP_WHITEDAY ); + ScfPropertySet aWhiteProp( xWhitePropSet ); + mxUpBar = new XclExpChDropBar( GetChRoot(), EXC_CHOBJTYPE_WHITEDROPBAR ); + mxUpBar->Convert( aWhiteProp ); + // black dropbar format + aTypeProp.GetProperty( xBlackPropSet, EXC_CHPROP_BLACKDAY ); + ScfPropertySet aBlackProp( xBlackPropSet ); + mxDownBar = new XclExpChDropBar( GetChRoot(), EXC_CHOBJTYPE_BLACKDROPBAR ); + mxDownBar->Convert( aBlackProp ); +} + +bool XclExpChTypeGroup::CreateStockSeries( Reference< XDataSeries > const & xDataSeries, + std::u16string_view rValueRole, bool bCloseSymbol ) +{ + bool bOk = false; + // let chart create series object with correct series index + XclExpChSeriesRef xSeries = GetChartData().CreateSeries(); + if( xSeries ) + { + bOk = xSeries->ConvertStockSeries( xDataSeries, + rValueRole, GetGroupIdx(), GetFreeFormatIdx(), bCloseSymbol ); + if( bOk ) + maSeries.AppendRecord( xSeries ); + else + GetChartData().RemoveLastSeries(); + } + return bOk; +} + +void XclExpChTypeGroup::WriteBody( XclExpStream& rStrm ) +{ + rStrm.WriteZeroBytes( 16 ); + rStrm << maData.mnFlags << maData.mnGroupIdx; +} + +// Axes ======================================================================= + +XclExpChLabelRange::XclExpChLabelRange( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHLABELRANGE, 8 ), + XclExpChRoot( rRoot ) +{ +} + +void XclExpChLabelRange::Convert( const ScaleData& rScaleData, const ScfPropertySet& rChart1Axis, bool bMirrorOrient ) +{ + /* Base time unit (using the property 'ExplicitTimeIncrement' from the old + chart API allows to detect axis type (date axis, if property exists), + and to receive the base time unit currently used in case the base time + unit is set to 'automatic'. */ + cssc::TimeIncrement aTimeIncrement; + if( rChart1Axis.GetProperty( aTimeIncrement, EXC_CHPROP_EXPTIMEINCREMENT ) ) + { + // property exists -> this is a date axis currently + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ); + + // automatic base time unit, if the UNO Any 'rScaleData.TimeIncrement.TimeResolution' does not contain a valid value... + bool bAutoBase = !rScaleData.TimeIncrement.TimeResolution.has< cssc::TimeIncrement >(); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOBASE, bAutoBase ); + + // ...but get the current base time unit from the property of the old chart API + sal_Int32 nApiTimeUnit = 0; + bool bValidBaseUnit = aTimeIncrement.TimeResolution >>= nApiTimeUnit; + OSL_ENSURE( bValidBaseUnit, "XclExpChLabelRange::Convert - cannot get base time unit" ); + maDateData.mnBaseUnit = bValidBaseUnit ? lclGetTimeUnit( nApiTimeUnit ) : EXC_CHDATERANGE_DAYS; + + /* Min/max values depend on base time unit, they specify the number of + days, months, or years starting from null date. */ + bool bAutoMin = lclConvertTimeValue( GetRoot(), maDateData.mnMinDate, rScaleData.Minimum, maDateData.mnBaseUnit ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMIN, bAutoMin ); + bool bAutoMax = lclConvertTimeValue( GetRoot(), maDateData.mnMaxDate, rScaleData.Maximum, maDateData.mnBaseUnit ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAX, bAutoMax ); + } + + // automatic axis type detection + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTODATE, rScaleData.AutoDateAxis ); + + // increment + bool bAutoMajor = lclConvertTimeInterval( maDateData.mnMajorStep, maDateData.mnMajorUnit, rScaleData.TimeIncrement.MajorTimeInterval ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAJOR, bAutoMajor ); + bool bAutoMinor = lclConvertTimeInterval( maDateData.mnMinorStep, maDateData.mnMinorUnit, rScaleData.TimeIncrement.MinorTimeInterval ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMINOR, bAutoMinor ); + + // origin + double fOrigin = 0.0; + if( !lclIsAutoAnyOrGetValue( fOrigin, rScaleData.Origin ) ) + maLabelData.mnCross = limit_cast< sal_uInt16 >( fOrigin, 1, 31999 ); + + // reverse order + if( (rScaleData.Orientation == cssc2::AxisOrientation_REVERSE) != bMirrorOrient ) + ::set_flag( maLabelData.mnFlags, EXC_CHLABELRANGE_REVERSE ); +} + +void XclExpChLabelRange::ConvertAxisPosition( const ScfPropertySet& rPropSet ) +{ + cssc::ChartAxisPosition eAxisPos = cssc::ChartAxisPosition_VALUE; + rPropSet.GetProperty( eAxisPos, EXC_CHPROP_CROSSOVERPOSITION ); + double fCrossingPos = 1.0; + rPropSet.GetProperty( fCrossingPos, EXC_CHPROP_CROSSOVERVALUE ); + + bool bDateAxis = ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ); + switch( eAxisPos ) + { + case cssc::ChartAxisPosition_ZERO: + case cssc::ChartAxisPosition_START: + maLabelData.mnCross = 1; + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS ); + break; + case cssc::ChartAxisPosition_END: + ::set_flag( maLabelData.mnFlags, EXC_CHLABELRANGE_MAXCROSS ); + break; + case cssc::ChartAxisPosition_VALUE: + maLabelData.mnCross = limit_cast< sal_uInt16 >( fCrossingPos, 1, 31999 ); + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS, false ); + if( bDateAxis ) + maDateData.mnCross = lclGetTimeValue( GetRoot(), fCrossingPos, maDateData.mnBaseUnit ); + break; + default: + maLabelData.mnCross = 1; + ::set_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS ); + } +} + +void XclExpChLabelRange::Save( XclExpStream& rStrm ) +{ + // the CHLABELRANGE record + XclExpRecord::Save( rStrm ); + + // the CHDATERANGE record with date axis settings (BIFF8 only) + if( GetBiff() != EXC_BIFF8 ) + return; + + rStrm.StartRecord( EXC_ID_CHDATERANGE, 18 ); + rStrm << maDateData.mnMinDate + << maDateData.mnMaxDate + << maDateData.mnMajorStep + << maDateData.mnMajorUnit + << maDateData.mnMinorStep + << maDateData.mnMinorUnit + << maDateData.mnBaseUnit + << maDateData.mnCross + << maDateData.mnFlags; + rStrm.EndRecord(); +} + +void XclExpChLabelRange::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maLabelData.mnCross << maLabelData.mnLabelFreq << maLabelData.mnTickFreq << maLabelData.mnFlags; +} + +XclExpChValueRange::XclExpChValueRange( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHVALUERANGE, 42 ), + XclExpChRoot( rRoot ) +{ +} + +void XclExpChValueRange::Convert( const ScaleData& rScaleData ) +{ + // scaling algorithm + bool bLogScale = ScfApiHelper::GetServiceName( rScaleData.Scaling ) == "com.sun.star.chart2.LogarithmicScaling"; + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE, bLogScale ); + + // min/max + bool bAutoMin = lclIsAutoAnyOrGetScaledValue( maData.mfMin, rScaleData.Minimum, bLogScale ); + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMIN, bAutoMin ); + bool bAutoMax = lclIsAutoAnyOrGetScaledValue( maData.mfMax, rScaleData.Maximum, bLogScale ); + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAX, bAutoMax ); + + // origin + bool bAutoCross = lclIsAutoAnyOrGetScaledValue( maData.mfCross, rScaleData.Origin, bLogScale ); + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS, bAutoCross ); + + // major increment + const IncrementData& rIncrementData = rScaleData.IncrementData; + const bool bAutoMajor = lclIsAutoAnyOrGetValue( maData.mfMajorStep, rIncrementData.Distance ) || (maData.mfMajorStep <= 0.0); + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAJOR, bAutoMajor ); + // minor increment + const Sequence< SubIncrement >& rSubIncrementSeq = rIncrementData.SubIncrements; + sal_Int32 nCount = 0; + + // tdf#114168 If IntervalCount is 5, then enable automatic minor calculation. + // During import, if minorUnit is set and majorUnit not, then it is impossible + // to calculate IntervalCount. + const bool bAutoMinor = bLogScale || bAutoMajor || !rSubIncrementSeq.hasElements() || + lclIsAutoAnyOrGetValue( nCount, rSubIncrementSeq[ 0 ].IntervalCount ) || (nCount < 1) || (nCount == 5); + + if( maData.mfMajorStep && !bAutoMinor ) + maData.mfMinorStep = maData.mfMajorStep / nCount; + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMINOR, bAutoMinor ); + + // reverse order + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_REVERSE, rScaleData.Orientation == cssc2::AxisOrientation_REVERSE ); +} + +void XclExpChValueRange::ConvertAxisPosition( const ScfPropertySet& rPropSet ) +{ + cssc::ChartAxisPosition eAxisPos = cssc::ChartAxisPosition_VALUE; + double fCrossingPos = 0.0; + if( !(rPropSet.GetProperty( eAxisPos, EXC_CHPROP_CROSSOVERPOSITION ) && rPropSet.GetProperty( fCrossingPos, EXC_CHPROP_CROSSOVERVALUE )) ) + return; + + switch( eAxisPos ) + { + case cssc::ChartAxisPosition_ZERO: + case cssc::ChartAxisPosition_START: + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS ); + break; + case cssc::ChartAxisPosition_END: + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_MAXCROSS ); + break; + case cssc::ChartAxisPosition_VALUE: + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS, false ); + maData.mfCross = ::get_flagvalue< double >( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE, log( fCrossingPos ) / log( 10.0 ), fCrossingPos ); + break; + default: + ::set_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS ); + } +} + +void XclExpChValueRange::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mfMin + << maData.mfMax + << maData.mfMajorStep + << maData.mfMinorStep + << maData.mfCross + << maData.mnFlags; +} + +namespace { + +sal_uInt8 lclGetXclTickPos( sal_Int32 nApiTickmarks ) +{ + using namespace cssc2::TickmarkStyle; + sal_uInt8 nXclTickPos = 0; + ::set_flag( nXclTickPos, EXC_CHTICK_INSIDE, ::get_flag( nApiTickmarks, INNER ) ); + ::set_flag( nXclTickPos, EXC_CHTICK_OUTSIDE, ::get_flag( nApiTickmarks, OUTER ) ); + return nXclTickPos; +} + +} // namespace + +XclExpChTick::XclExpChTick( const XclExpChRoot& rRoot ) : + XclExpRecord( EXC_ID_CHTICK, (rRoot.GetBiff() == EXC_BIFF8) ? 30 : 26 ), + XclExpChRoot( rRoot ), + mnTextColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_CHWINDOWTEXT ) ) +{ +} + +void XclExpChTick::Convert( const ScfPropertySet& rPropSet, const XclChExtTypeInfo& rTypeInfo, sal_uInt16 nAxisType ) +{ + // tick mark style + sal_Int32 nApiTickmarks = 0; + if( rPropSet.GetProperty( nApiTickmarks, EXC_CHPROP_MAJORTICKS ) ) + maData.mnMajor = lclGetXclTickPos( nApiTickmarks ); + if( rPropSet.GetProperty( nApiTickmarks, EXC_CHPROP_MINORTICKS ) ) + maData.mnMinor = lclGetXclTickPos( nApiTickmarks ); + + // axis labels + if( (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_RADAR) && (nAxisType == EXC_CHAXIS_X) ) + { + /* Radar charts disable their category labels via chart type, not via + axis, and axis labels are always 'near axis'. */ + maData.mnLabelPos = EXC_CHTICK_NEXT; + } + else if( !rPropSet.GetBoolProperty( EXC_CHPROP_DISPLAYLABELS ) ) + { + // no labels + maData.mnLabelPos = EXC_CHTICK_NOLABEL; + } + else if( rTypeInfo.mb3dChart && (nAxisType == EXC_CHAXIS_Y) ) + { + // Excel expects 'near axis' at Y axes in 3D charts + maData.mnLabelPos = EXC_CHTICK_NEXT; + } + else + { + cssc::ChartAxisLabelPosition eApiLabelPos = cssc::ChartAxisLabelPosition_NEAR_AXIS; + rPropSet.GetProperty( eApiLabelPos, EXC_CHPROP_LABELPOSITION ); + switch( eApiLabelPos ) + { + case cssc::ChartAxisLabelPosition_NEAR_AXIS: + case cssc::ChartAxisLabelPosition_NEAR_AXIS_OTHER_SIDE: maData.mnLabelPos = EXC_CHTICK_NEXT; break; + case cssc::ChartAxisLabelPosition_OUTSIDE_START: maData.mnLabelPos = EXC_CHTICK_LOW; break; + case cssc::ChartAxisLabelPosition_OUTSIDE_END: maData.mnLabelPos = EXC_CHTICK_HIGH; break; + default: maData.mnLabelPos = EXC_CHTICK_NEXT; + } + } +} + +void XclExpChTick::SetFontColor( const Color& rColor, sal_uInt32 nColorId ) +{ + maData.maTextColor = rColor; + ::set_flag( maData.mnFlags, EXC_CHTICK_AUTOCOLOR, rColor == COL_AUTO ); + mnTextColorId = nColorId; +} + +void XclExpChTick::SetRotation( sal_uInt16 nRotation ) +{ + maData.mnRotation = nRotation; + ::set_flag( maData.mnFlags, EXC_CHTICK_AUTOROT, false ); + ::insert_value( maData.mnFlags, XclTools::GetXclOrientFromRot( nRotation ), 2, 3 ); +} + +void XclExpChTick::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnMajor + << maData.mnMinor + << maData.mnLabelPos + << maData.mnBackMode; + rStrm.WriteZeroBytes( 16 ); + rStrm << maData.maTextColor + << maData.mnFlags; + if( GetBiff() == EXC_BIFF8 ) + rStrm << GetPalette().GetColorIndex( mnTextColorId ) << maData.mnRotation; +} + +namespace { + +/** Returns an API axis object from the passed coordinate system. */ +Reference< XAxis > lclGetApiAxis( Reference< XCoordinateSystem > const & xCoordSystem, + sal_Int32 nApiAxisDim, sal_Int32 nApiAxesSetIdx ) +{ + Reference< XAxis > xAxis; + if( (nApiAxisDim >= 0) && xCoordSystem.is() ) try + { + xAxis = xCoordSystem->getAxisByDimension( nApiAxisDim, nApiAxesSetIdx ); + } + catch( Exception& ) + { + } + return xAxis; +} + +Reference< cssc::XAxis > lclGetApiChart1Axis( Reference< XChartDocument > const & xChartDoc, + sal_Int32 nApiAxisDim, sal_Int32 nApiAxesSetIdx ) +{ + Reference< cssc::XAxis > xChart1Axis; + try + { + Reference< cssc::XChartDocument > xChart1Doc( xChartDoc, UNO_QUERY_THROW ); + Reference< cssc::XAxisSupplier > xChart1AxisSupp( xChart1Doc->getDiagram(), UNO_QUERY_THROW ); + switch( nApiAxesSetIdx ) + { + case EXC_CHART_AXESSET_PRIMARY: + xChart1Axis = xChart1AxisSupp->getAxis( nApiAxisDim ); + break; + case EXC_CHART_AXESSET_SECONDARY: + xChart1Axis = xChart1AxisSupp->getSecondaryAxis( nApiAxisDim ); + break; + } + } + catch( Exception& ) + { + } + return xChart1Axis; +} + +} // namespace + +XclExpChAxis::XclExpChAxis( const XclExpChRoot& rRoot, sal_uInt16 nAxisType ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_AXIS, EXC_ID_CHAXIS, 18 ), + mnNumFmtIdx( EXC_FORMAT_NOTFOUND ) +{ + maData.mnType = nAxisType; +} + +void XclExpChAxis::SetFont( XclExpChFontRef xFont, const Color& rColor, sal_uInt32 nColorId ) +{ + mxFont = xFont; + if( mxTick ) + mxTick->SetFontColor( rColor, nColorId ); +} + +void XclExpChAxis::SetRotation( sal_uInt16 nRotation ) +{ + if( mxTick ) + mxTick->SetRotation( nRotation ); +} + +void XclExpChAxis::Convert( Reference< XAxis > const & xAxis, Reference< XAxis > const & xCrossingAxis, + Reference< cssc::XAxis > const & xChart1Axis, const XclChExtTypeInfo& rTypeInfo ) +{ + ScfPropertySet aAxisProp( xAxis ); + bool bCategoryAxis = ((GetAxisType() == EXC_CHAXIS_X) && rTypeInfo.mbCategoryAxis) || (GetAxisType() == EXC_CHAXIS_Z); + + // axis line format ------------------------------------------------------- + + mxAxisLine = new XclExpChLineFormat( GetChRoot() ); + mxAxisLine->Convert( GetChRoot(), aAxisProp, EXC_CHOBJTYPE_AXISLINE ); + // #i58688# axis enabled + mxAxisLine->SetShowAxis( aAxisProp.GetBoolProperty( EXC_CHPROP_SHOW ) ); + + // axis scaling and increment --------------------------------------------- + + ScfPropertySet aCrossingProp( xCrossingAxis ); + if( bCategoryAxis ) + { + mxLabelRange = new XclExpChLabelRange( GetChRoot() ); + mxLabelRange->SetTicksBetweenCateg( rTypeInfo.mbTicksBetweenCateg ); + if( xAxis.is() ) + { + ScfPropertySet aChart1AxisProp( xChart1Axis ); + // #i71684# radar charts have reversed rotation direction + mxLabelRange->Convert( xAxis->getScaleData(), aChart1AxisProp, (GetAxisType() == EXC_CHAXIS_X) && (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_RADAR) ); + } + // get position of crossing axis on this axis from passed axis object + if( aCrossingProp.Is() ) + mxLabelRange->ConvertAxisPosition( aCrossingProp ); + } + else + { + mxValueRange = new XclExpChValueRange( GetChRoot() ); + if( xAxis.is() ) + mxValueRange->Convert( xAxis->getScaleData() ); + // get position of crossing axis on this axis from passed axis object + if( aCrossingProp.Is() ) + mxValueRange->ConvertAxisPosition( aCrossingProp ); + } + + // axis caption text ------------------------------------------------------ + + // axis ticks properties + mxTick = new XclExpChTick( GetChRoot() ); + mxTick->Convert( aAxisProp, rTypeInfo, GetAxisType() ); + + // axis label formatting and rotation + ConvertFontBase( GetChRoot(), aAxisProp ); + ConvertRotationBase( aAxisProp, true ); + + // axis number format + sal_Int32 nApiNumFmt = 0; + if( !bCategoryAxis && aAxisProp.GetProperty( nApiNumFmt, EXC_CHPROP_NUMBERFORMAT ) ) + { + bool bLinkNumberFmtToSource = false; + if ( !aAxisProp.GetProperty( bLinkNumberFmtToSource, EXC_CHPROP_NUMBERFORMAT_LINKSRC ) || !bLinkNumberFmtToSource ) + mnNumFmtIdx = GetNumFmtBuffer().Insert( static_cast< sal_uInt32 >( nApiNumFmt ) ); + } + + // grid ------------------------------------------------------------------- + + if( !xAxis.is() ) + return; + + // main grid + ScfPropertySet aGridProp( xAxis->getGridProperties() ); + if( aGridProp.GetBoolProperty( EXC_CHPROP_SHOW ) ) + mxMajorGrid = lclCreateLineFormat( GetChRoot(), aGridProp, EXC_CHOBJTYPE_GRIDLINE ); + // sub grid + Sequence< Reference< XPropertySet > > aSubGridPropSeq = xAxis->getSubGridProperties(); + if( aSubGridPropSeq.hasElements() ) + { + ScfPropertySet aSubGridProp( aSubGridPropSeq[ 0 ] ); + if( aSubGridProp.GetBoolProperty( EXC_CHPROP_SHOW ) ) + mxMinorGrid = lclCreateLineFormat( GetChRoot(), aSubGridProp, EXC_CHOBJTYPE_GRIDLINE ); + } +} + +void XclExpChAxis::ConvertWall( css::uno::Reference< css::chart2::XDiagram > const & xDiagram ) +{ + if( !xDiagram.is() ) + return; + + switch( GetAxisType() ) + { + case EXC_CHAXIS_X: + { + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxWallFrame = lclCreateFrame( GetChRoot(), aWallProp, EXC_CHOBJTYPE_WALL3D ); + } + break; + case EXC_CHAXIS_Y: + { + ScfPropertySet aFloorProp( xDiagram->getFloor() ); + mxWallFrame = lclCreateFrame( GetChRoot(), aFloorProp, EXC_CHOBJTYPE_FLOOR3D ); + } + break; + default: + mxWallFrame.clear(); + } +} + +void XclExpChAxis::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxLabelRange ); + lclSaveRecord( rStrm, mxValueRange ); + if( mnNumFmtIdx != EXC_FORMAT_NOTFOUND ) + XclExpUInt16Record( EXC_ID_CHFORMAT, mnNumFmtIdx ).Save( rStrm ); + lclSaveRecord( rStrm, mxTick ); + lclSaveRecord( rStrm, mxFont ); + lclSaveRecord( rStrm, mxAxisLine, EXC_ID_CHAXISLINE, EXC_CHAXISLINE_AXISLINE ); + lclSaveRecord( rStrm, mxMajorGrid, EXC_ID_CHAXISLINE, EXC_CHAXISLINE_MAJORGRID ); + lclSaveRecord( rStrm, mxMinorGrid, EXC_ID_CHAXISLINE, EXC_CHAXISLINE_MINORGRID ); + lclSaveRecord( rStrm, mxWallFrame, EXC_ID_CHAXISLINE, EXC_CHAXISLINE_WALLS ); +} + +void XclExpChAxis::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnType; + rStrm.WriteZeroBytes( 16 ); +} + +XclExpChAxesSet::XclExpChAxesSet( const XclExpChRoot& rRoot, sal_uInt16 nAxesSetId ) : + XclExpChGroupBase( rRoot, EXC_CHFRBLOCK_TYPE_AXESSET, EXC_ID_CHAXESSET, 18 ) +{ + maData.mnAxesSetId = nAxesSetId; + SetFutureRecordContext( 0, nAxesSetId ); + + /* Need to set a reasonable size for the plot area, otherwise Excel will + move away embedded shapes while auto-sizing the plot area. This is just + a wild guess, but will be fixed with implementing manual positioning of + chart elements. */ + maData.maRect.mnX = 262; + maData.maRect.mnY = 626; + maData.maRect.mnWidth = 3187; + maData.maRect.mnHeight = 2633; +} + +sal_uInt16 XclExpChAxesSet::Convert( Reference< XDiagram > const & xDiagram, sal_uInt16 nFirstGroupIdx ) +{ + /* First unused chart type group index is passed to be able to continue + counting of chart type groups for secondary axes set. */ + sal_uInt16 nGroupIdx = nFirstGroupIdx; + Reference< XCoordinateSystemContainer > xCoordSysCont( xDiagram, UNO_QUERY ); + if( xCoordSysCont.is() ) + { + Sequence< Reference< XCoordinateSystem > > aCoordSysSeq = xCoordSysCont->getCoordinateSystems(); + if( aCoordSysSeq.hasElements() ) + { + /* Process first coordinate system only. Import filter puts all + chart types into one coordinate system. */ + Reference< XCoordinateSystem > xCoordSystem = aCoordSysSeq[ 0 ]; + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + + // 3d mode + bool b3dChart = xCoordSystem.is() && (xCoordSystem->getDimension() == 3); + + // percent charts + namespace ApiAxisType = cssc2::AxisType; + Reference< XAxis > xApiYAxis = lclGetApiAxis( xCoordSystem, EXC_CHART_AXIS_Y, nApiAxesSetIdx ); + bool bPercent = xApiYAxis.is() && (xApiYAxis->getScaleData().AxisType == ApiAxisType::PERCENT); + + // connector lines in bar charts + ScfPropertySet aDiaProp( xDiagram ); + bool bConnectBars = aDiaProp.GetBoolProperty( EXC_CHPROP_CONNECTBARS ); + + // swapped axes sets + ScfPropertySet aCoordSysProp( xCoordSystem ); + bool bSwappedAxesSet = aCoordSysProp.GetBoolProperty( EXC_CHPROP_SWAPXANDYAXIS ); + + // X axis for later use + Reference< XAxis > xApiXAxis = lclGetApiAxis( xCoordSystem, EXC_CHART_AXIS_X, nApiAxesSetIdx ); + // X axis labels + ScfPropertySet aXAxisProp( xApiXAxis ); + bool bHasXLabels = aXAxisProp.GetBoolProperty( EXC_CHPROP_DISPLAYLABELS ); + + // process chart types + Reference< XChartTypeContainer > xChartTypeCont( xCoordSystem, UNO_QUERY ); + if( xChartTypeCont.is() ) + { + const Sequence< Reference< XChartType > > aChartTypeSeq = xChartTypeCont->getChartTypes(); + for( const Reference< XChartType >& rChartType : aChartTypeSeq ) + { + XclExpChTypeGroupRef xTypeGroup = new XclExpChTypeGroup( GetChRoot(), nGroupIdx ); + xTypeGroup->ConvertType( xDiagram, rChartType, nApiAxesSetIdx, b3dChart, bSwappedAxesSet, bHasXLabels ); + /* If new chart type group cannot be inserted into a combination + chart with existing type groups, insert all series into last + contained chart type group instead of creating a new group. */ + XclExpChTypeGroupRef xLastGroup = GetLastTypeGroup(); + if( xLastGroup && !(xTypeGroup->IsCombinable2d() && xLastGroup->IsCombinable2d()) ) + { + xLastGroup->ConvertSeries( xDiagram, rChartType, nApiAxesSetIdx, bPercent, bConnectBars ); + } + else + { + xTypeGroup->ConvertSeries( xDiagram, rChartType, nApiAxesSetIdx, bPercent, bConnectBars ); + if( xTypeGroup->IsValidGroup() ) + { + maTypeGroups.AppendRecord( xTypeGroup ); + ++nGroupIdx; + } + } + } + } + + if( XclExpChTypeGroup* pGroup = GetFirstTypeGroup().get() ) + { + const XclChExtTypeInfo& rTypeInfo = pGroup->GetTypeInfo(); + + // create axes according to chart type (no axes for pie and donut charts) + if( rTypeInfo.meTypeCateg != EXC_CHTYPECATEG_PIE ) + { + ConvertAxis( mxXAxis, EXC_CHAXIS_X, mxXAxisTitle, EXC_CHOBJLINK_XAXIS, xCoordSystem, rTypeInfo, EXC_CHART_AXIS_Y ); + ConvertAxis( mxYAxis, EXC_CHAXIS_Y, mxYAxisTitle, EXC_CHOBJLINK_YAXIS, xCoordSystem, rTypeInfo, EXC_CHART_AXIS_X ); + if( pGroup->Is3dDeepChart() ) + ConvertAxis( mxZAxis, EXC_CHAXIS_Z, mxZAxisTitle, EXC_CHOBJLINK_ZAXIS, xCoordSystem, rTypeInfo, EXC_CHART_AXIS_NONE ); + } + + // X axis category ranges + if( rTypeInfo.mbCategoryAxis && xApiXAxis.is() ) + { + const ScaleData aScaleData = xApiXAxis->getScaleData(); + for( size_t nIdx = 0, nSize = maTypeGroups.GetSize(); nIdx < nSize; ++nIdx ) + maTypeGroups.GetRecord( nIdx )->ConvertCategSequence( aScaleData.Categories ); + } + + // legend + if( xDiagram.is() && (GetAxesSetId() == EXC_CHAXESSET_PRIMARY) ) + { + Reference< XLegend > xLegend = xDiagram->getLegend(); + if( xLegend.is() ) + { + ScfPropertySet aLegendProp( xLegend ); + pGroup->ConvertLegend( aLegendProp ); + } + } + } + } + } + + // wall/floor/diagram frame formatting + if( xDiagram.is() && (GetAxesSetId() == EXC_CHAXESSET_PRIMARY) ) + { + XclExpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + if( xTypeGroup && xTypeGroup->Is3dWallChart() ) + { + // wall/floor formatting (3D charts) + if( mxXAxis ) + mxXAxis->ConvertWall( xDiagram ); + if( mxYAxis ) + mxYAxis->ConvertWall( xDiagram ); + } + else + { + // diagram background formatting + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxPlotFrame = lclCreateFrame( GetChRoot(), aWallProp, EXC_CHOBJTYPE_PLOTFRAME ); + } + } + + // inner and outer plot area position and size + try + { + Reference< cssc::XChartDocument > xChart1Doc( GetChartDocument(), UNO_QUERY_THROW ); + Reference< cssc::XDiagramPositioning > xPositioning( xChart1Doc->getDiagram(), UNO_QUERY_THROW ); + // set manual flag in chart data + if( !xPositioning->isAutomaticDiagramPositioning() ) + GetChartData().SetManualPlotArea(); + // the CHAXESSET record contains the inner plot area + maData.maRect = CalcChartRectFromHmm( xPositioning->calculateDiagramPositionExcludingAxes() ); + // the embedded CHFRAMEPOS record contains the outer plot area + mxFramePos = new XclExpChFramePos( EXC_CHFRAMEPOS_PARENT ); + // for pie charts, always use inner plot area size to exclude the data labels as Excel does + const XclExpChTypeGroup* pFirstTypeGroup = GetFirstTypeGroup().get(); + bool bPieChart = pFirstTypeGroup && (pFirstTypeGroup->GetTypeInfo().meTypeCateg == EXC_CHTYPECATEG_PIE); + mxFramePos->GetFramePosData().maRect = bPieChart ? maData.maRect : + CalcChartRectFromHmm( xPositioning->calculateDiagramPositionIncludingAxes() ); + } + catch( Exception& ) + { + } + + // return first unused chart type group index for next axes set + return nGroupIdx; +} + +bool XclExpChAxesSet::Is3dChart() const +{ + XclExpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + return xTypeGroup && xTypeGroup->Is3dChart(); +} + +void XclExpChAxesSet::WriteSubRecords( XclExpStream& rStrm ) +{ + lclSaveRecord( rStrm, mxFramePos ); + lclSaveRecord( rStrm, mxXAxis ); + lclSaveRecord( rStrm, mxYAxis ); + lclSaveRecord( rStrm, mxZAxis ); + lclSaveRecord( rStrm, mxXAxisTitle ); + lclSaveRecord( rStrm, mxYAxisTitle ); + lclSaveRecord( rStrm, mxZAxisTitle ); + if( mxPlotFrame ) + { + XclExpEmptyRecord( EXC_ID_CHPLOTFRAME ).Save( rStrm ); + mxPlotFrame->Save( rStrm ); + } + maTypeGroups.Save( rStrm ); +} + +XclExpChTypeGroupRef XclExpChAxesSet::GetFirstTypeGroup() const +{ + return maTypeGroups.GetFirstRecord(); +} + +XclExpChTypeGroupRef XclExpChAxesSet::GetLastTypeGroup() const +{ + return maTypeGroups.GetLastRecord(); +} + +void XclExpChAxesSet::ConvertAxis( + XclExpChAxisRef& rxChAxis, sal_uInt16 nAxisType, + XclExpChTextRef& rxChAxisTitle, sal_uInt16 nTitleTarget, + Reference< XCoordinateSystem > const & xCoordSystem, const XclChExtTypeInfo& rTypeInfo, + sal_Int32 nCrossingAxisDim ) +{ + // create and convert axis object + rxChAxis = new XclExpChAxis( GetChRoot(), nAxisType ); + sal_Int32 nApiAxisDim = rxChAxis->GetApiAxisDimension(); + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + Reference< XAxis > xAxis = lclGetApiAxis( xCoordSystem, nApiAxisDim, nApiAxesSetIdx ); + Reference< XAxis > xCrossingAxis = lclGetApiAxis( xCoordSystem, nCrossingAxisDim, nApiAxesSetIdx ); + Reference< cssc::XAxis > xChart1Axis = lclGetApiChart1Axis( GetChartDocument(), nApiAxisDim, nApiAxesSetIdx ); + rxChAxis->Convert( xAxis, xCrossingAxis, xChart1Axis, rTypeInfo ); + + // create and convert axis title + Reference< XTitled > xTitled( xAxis, UNO_QUERY ); + rxChAxisTitle = lclCreateTitle( GetChRoot(), xTitled, nTitleTarget ); +} + +void XclExpChAxesSet::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maData.mnAxesSetId << maData.maRect; +} + +// The chart object =========================================================== + +static void lcl_getChartSubTitle(const Reference& xChartDoc, + OUString& rSubTitle) +{ + Reference< css::chart::XChartDocument > xChartDoc1(xChartDoc, UNO_QUERY); + if (!xChartDoc1.is()) + return; + + Reference< XPropertySet > xProp(xChartDoc1->getSubTitle(), UNO_QUERY); + if (!xProp.is()) + return; + + OUString aTitle; + Any any = xProp->getPropertyValue("String"); + if (any >>= aTitle) + rSubTitle = aTitle; +} + +XclExpChChart::XclExpChChart( const XclExpRoot& rRoot, + Reference< XChartDocument > const & xChartDoc, const tools::Rectangle& rChartRect ) : + XclExpChGroupBase( XclExpChRoot( rRoot, *this ), EXC_CHFRBLOCK_TYPE_CHART, EXC_ID_CHCHART, 16 ) +{ + Size aPtSize = o3tl::convert( rChartRect.GetSize(), o3tl::Length::mm100, o3tl::Length::pt ); + // rectangle is stored in 16.16 fixed-point format + maRect.mnX = maRect.mnY = 0; + maRect.mnWidth = static_cast< sal_Int32 >( aPtSize.Width() << 16 ); + maRect.mnHeight = static_cast< sal_Int32 >( aPtSize.Height() << 16 ); + + // global chart properties (default values) + ::set_flag( maProps.mnFlags, EXC_CHPROPS_SHOWVISIBLEONLY, false ); + ::set_flag( maProps.mnFlags, EXC_CHPROPS_MANPLOTAREA ); + maProps.mnEmptyMode = EXC_CHPROPS_EMPTY_SKIP; + + // always create both axes set objects + mxPrimAxesSet = std::make_shared( GetChRoot(), EXC_CHAXESSET_PRIMARY ); + mxSecnAxesSet = std::make_shared( GetChRoot(), EXC_CHAXESSET_SECONDARY ); + + if( !xChartDoc.is() ) + return; + + Reference< XDiagram > xDiagram = xChartDoc->getFirstDiagram(); + + // global chart properties (only 'include hidden cells' attribute for now) + ScfPropertySet aDiagramProp( xDiagram ); + bool bIncludeHidden = aDiagramProp.GetBoolProperty( EXC_CHPROP_INCLUDEHIDDENCELLS ); + ::set_flag( maProps.mnFlags, EXC_CHPROPS_SHOWVISIBLEONLY, !bIncludeHidden ); + + // initialize API conversion (remembers xChartDoc and rChartRect internally) + InitConversion( xChartDoc, rChartRect ); + + // chart frame + ScfPropertySet aFrameProp( xChartDoc->getPageBackground() ); + mxFrame = lclCreateFrame( GetChRoot(), aFrameProp, EXC_CHOBJTYPE_BACKGROUND ); + + // chart title + Reference< XTitled > xTitled( xChartDoc, UNO_QUERY ); + OUString aSubTitle; + lcl_getChartSubTitle(xChartDoc, aSubTitle); + mxTitle = lclCreateTitle( GetChRoot(), xTitled, EXC_CHOBJLINK_TITLE, + !aSubTitle.isEmpty() ? &aSubTitle : nullptr ); + + // diagrams (axes sets) + sal_uInt16 nFreeGroupIdx = mxPrimAxesSet->Convert( xDiagram, 0 ); + if( !mxPrimAxesSet->Is3dChart() ) + mxSecnAxesSet->Convert( xDiagram, nFreeGroupIdx ); + + // treatment of missing values + ScfPropertySet aDiaProp( xDiagram ); + sal_Int32 nMissingValues = 0; + if( aDiaProp.GetProperty( nMissingValues, EXC_CHPROP_MISSINGVALUETREATMENT ) ) + { + using namespace cssc::MissingValueTreatment; + switch( nMissingValues ) + { + case LEAVE_GAP: maProps.mnEmptyMode = EXC_CHPROPS_EMPTY_SKIP; break; + case USE_ZERO: maProps.mnEmptyMode = EXC_CHPROPS_EMPTY_ZERO; break; + case CONTINUE: maProps.mnEmptyMode = EXC_CHPROPS_EMPTY_INTERPOLATE; break; + } + } + + // finish API conversion + FinishConversion(); +} + +XclExpChSeriesRef XclExpChChart::CreateSeries() +{ + XclExpChSeriesRef xSeries; + sal_uInt16 nSeriesIdx = static_cast< sal_uInt16 >( maSeries.GetSize() ); + if( nSeriesIdx <= EXC_CHSERIES_MAXSERIES ) + { + xSeries = new XclExpChSeries( GetChRoot(), nSeriesIdx ); + maSeries.AppendRecord( xSeries ); + } + return xSeries; +} + +void XclExpChChart::RemoveLastSeries() +{ + if( !maSeries.IsEmpty() ) + maSeries.RemoveRecord( maSeries.GetSize() - 1 ); +} + +void XclExpChChart::SetDataLabel( XclExpChTextRef const & xText ) +{ + if( xText ) + maLabels.AppendRecord( xText ); +} + +void XclExpChChart::SetManualPlotArea() +{ + // this flag does not exist in BIFF5 + if( GetBiff() == EXC_BIFF8 ) + ::set_flag( maProps.mnFlags, EXC_CHPROPS_USEMANPLOTAREA ); +} + +void XclExpChChart::WriteSubRecords( XclExpStream& rStrm ) +{ + // background format + lclSaveRecord( rStrm, mxFrame ); + + // data series + maSeries.Save( rStrm ); + + // CHPROPERTIES record + rStrm.StartRecord( EXC_ID_CHPROPERTIES, 4 ); + rStrm << maProps.mnFlags << maProps.mnEmptyMode << sal_uInt8( 0 ); + rStrm.EndRecord(); + + // axes sets (always save primary axes set) + sal_uInt16 nUsedAxesSets = mxSecnAxesSet->IsValidAxesSet() ? 2 : 1; + XclExpUInt16Record( EXC_ID_CHUSEDAXESSETS, nUsedAxesSets ).Save( rStrm ); + mxPrimAxesSet->Save( rStrm ); + if( mxSecnAxesSet->IsValidAxesSet() ) + mxSecnAxesSet->Save( rStrm ); + + // chart title and data labels + lclSaveRecord( rStrm, mxTitle ); + maLabels.Save( rStrm ); +} + +void XclExpChChart::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maRect; +} + +XclExpChartDrawing::XclExpChartDrawing( const XclExpRoot& rRoot, + const Reference< XModel >& rxModel, const Size& rChartSize ) : + XclExpRoot( rRoot ) +{ + if( rChartSize.IsEmpty() ) + return; + + ScfPropertySet aPropSet( rxModel ); + Reference< XShapes > xShapes; + if( !(aPropSet.GetProperty( xShapes, EXC_CHPROP_ADDITIONALSHAPES ) && xShapes.is() && (xShapes->getCount() > 0)) ) + return; + + /* Create a new independent object manager with own DFF stream for the + DGCONTAINER, pass global manager as parent for shared usage of + global DFF data (picture container etc.). */ + mxObjMgr = std::make_shared( GetObjectManager(), rChartSize, EXC_CHART_TOTALUNITS, EXC_CHART_TOTALUNITS ); + // initialize the drawing object list + mxObjMgr->StartSheet(); + // process the draw page (convert all shapes) + mxObjRecs = mxObjMgr->ProcessDrawing( xShapes ); + // finalize the DFF stream + mxObjMgr->EndDocument(); +} + +XclExpChartDrawing::~XclExpChartDrawing() +{ +} + +void XclExpChartDrawing::Save( XclExpStream& rStrm ) +{ + if( mxObjRecs ) + mxObjRecs->Save( rStrm ); +} + +XclExpChart::XclExpChart( const XclExpRoot& rRoot, Reference< XModel > const & xModel, const tools::Rectangle& rChartRect ) : + XclExpSubStream( EXC_BOF_CHART ), + XclExpRoot( rRoot ) +{ + AppendNewRecord( new XclExpChartPageSettings( rRoot ) ); + AppendNewRecord( new XclExpBoolRecord( EXC_ID_PROTECT, false ) ); + AppendNewRecord( new XclExpChartDrawing( rRoot, xModel, rChartRect.GetSize() ) ); + AppendNewRecord( new XclExpUInt16Record( EXC_ID_CHUNITS, EXC_CHUNITS_TWIPS ) ); + + Reference< XChartDocument > xChartDoc( xModel, UNO_QUERY ); + AppendNewRecord( new XclExpChChart( rRoot, xChartDoc, rChartRect ) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xecontent.cxx b/sc/source/filter/excel/xecontent.cxx new file mode 100644 index 000000000..65f8bb2f4 --- /dev/null +++ b/sc/source/filter/excel/xecontent.cxx @@ -0,0 +1,2211 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::oox; + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::table::CellRangeAddress; +using ::com::sun::star::sheet::XAreaLinks; +using ::com::sun::star::sheet::XAreaLink; + +// Shared string table ======================================================== + +namespace { + +/** A single string entry in the hash table. */ +struct XclExpHashEntry +{ + const XclExpString* mpString; /// Pointer to the string (no ownership). + sal_uInt32 mnSstIndex; /// The SST index of this string. + explicit XclExpHashEntry( const XclExpString* pString, sal_uInt32 nSstIndex ) : + mpString( pString ), mnSstIndex( nSstIndex ) {} +}; + +/** Function object for strict weak ordering. */ +struct XclExpHashEntrySWO +{ + bool operator()( const XclExpHashEntry& rLeft, const XclExpHashEntry& rRight ) const + { return *rLeft.mpString < *rRight.mpString; } +}; + +} + +/** Implementation of the SST export. + @descr Stores all passed strings in a hash table and prevents repeated + insertion of equal strings. */ +class XclExpSstImpl +{ +public: + explicit XclExpSstImpl(); + + /** Inserts the passed string, if not already inserted, and returns the unique SST index. */ + sal_uInt32 Insert( XclExpStringRef xString ); + + /** Writes the complete SST and EXTSST records. */ + void Save( XclExpStream& rStrm ); + void SaveXml( XclExpXmlStream& rStrm ); + +private: + typedef ::std::vector< XclExpHashEntry > XclExpHashVec; + + std::vector< XclExpStringRef > maStringVector; /// List of unique strings (in SST ID order). + std::vector< XclExpHashVec > + maHashTab; /// Hashed table that manages string pointers. + sal_uInt32 mnTotal; /// Total count of strings (including doubles). + sal_uInt32 mnSize; /// Size of the SST (count of unique strings). +}; + +const sal_uInt32 EXC_SST_HASHTABLE_SIZE = 2048; + +XclExpSstImpl::XclExpSstImpl() : + maHashTab( EXC_SST_HASHTABLE_SIZE ), + mnTotal( 0 ), + mnSize( 0 ) +{ +} + +sal_uInt32 XclExpSstImpl::Insert( XclExpStringRef xString ) +{ + OSL_ENSURE( xString, "XclExpSstImpl::Insert - empty pointer not allowed" ); + if( !xString ) + xString.reset( new XclExpString ); + + ++mnTotal; + sal_uInt32 nSstIndex = 0; + + // calculate hash value in range [0,EXC_SST_HASHTABLE_SIZE) + sal_uInt16 nHash = xString->GetHash(); + nHash = (nHash ^ (nHash / EXC_SST_HASHTABLE_SIZE)) % EXC_SST_HASHTABLE_SIZE; + + XclExpHashVec& rVec = maHashTab[ nHash ]; + XclExpHashEntry aEntry( xString.get(), mnSize ); + XclExpHashVec::iterator aIt = ::std::lower_bound( rVec.begin(), rVec.end(), aEntry, XclExpHashEntrySWO() ); + if( (aIt == rVec.end()) || (*aIt->mpString != *xString) ) + { + nSstIndex = mnSize; + maStringVector.push_back( xString ); + rVec.insert( aIt, aEntry ); + ++mnSize; + } + else + { + nSstIndex = aIt->mnSstIndex; + } + + return nSstIndex; +} + +void XclExpSstImpl::Save( XclExpStream& rStrm ) +{ + if( maStringVector.empty() ) + return; + + SvMemoryStream aExtSst( 8192 ); + + sal_uInt32 nBucket = mnSize; + while( nBucket > 0x0100 ) + nBucket /= 2; + + sal_uInt16 nPerBucket = llimit_cast< sal_uInt16 >( nBucket, 8 ); + sal_uInt16 nBucketIndex = 0; + + // *** write the SST record *** + + rStrm.StartRecord( EXC_ID_SST, 8 ); + + rStrm << mnTotal << mnSize; + for (auto const& elem : maStringVector) + { + if( !nBucketIndex ) + { + // write bucket info before string to get correct record position + sal_uInt32 nStrmPos = static_cast< sal_uInt32 >( rStrm.GetSvStreamPos() ); + sal_uInt16 nRecPos = rStrm.GetRawRecPos() + 4; + aExtSst.WriteUInt32( nStrmPos ) // stream position + .WriteUInt16( nRecPos ) // position from start of SST or CONTINUE + .WriteUInt16( 0 ); // reserved + } + + rStrm << *elem; + + if( ++nBucketIndex == nPerBucket ) + nBucketIndex = 0; + } + + rStrm.EndRecord(); + + // *** write the EXTSST record *** + + rStrm.StartRecord( EXC_ID_EXTSST, 0 ); + + rStrm << nPerBucket; + rStrm.SetSliceSize( 8 ); // size of one bucket info + aExtSst.Seek( STREAM_SEEK_TO_BEGIN ); + rStrm.CopyFromStream( aExtSst ); + + rStrm.EndRecord(); +} + +void XclExpSstImpl::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maStringVector.empty() ) + return; + + sax_fastparser::FSHelperPtr pSst = rStrm.CreateOutputStream( + "xl/sharedStrings.xml", + u"sharedStrings.xml", + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", + oox::getRelationship(Relationship::SHAREDSTRINGS)); + rStrm.PushStream( pSst ); + + pSst->startElement( XML_sst, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)), + XML_count, OString::number(mnTotal), + XML_uniqueCount, OString::number(mnSize) ); + + for (auto const& elem : maStringVector) + { + pSst->startElement(XML_si); + elem->WriteXml( rStrm ); + pSst->endElement( XML_si ); + } + + pSst->endElement( XML_sst ); + + rStrm.PopStream(); +} + +XclExpSst::XclExpSst() : + mxImpl( new XclExpSstImpl ) +{ +} + +XclExpSst::~XclExpSst() +{ +} + +sal_uInt32 XclExpSst::Insert( const XclExpStringRef& xString ) +{ + return mxImpl->Insert( xString ); +} + +void XclExpSst::Save( XclExpStream& rStrm ) +{ + mxImpl->Save( rStrm ); +} + +void XclExpSst::SaveXml( XclExpXmlStream& rStrm ) +{ + mxImpl->SaveXml( rStrm ); +} + +// Merged cells =============================================================== + +XclExpMergedcells::XclExpMergedcells( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpMergedcells::AppendRange( const ScRange& rRange, sal_uInt32 nBaseXFId ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + maMergedRanges.push_back( rRange ); + maBaseXFIds.push_back( nBaseXFId ); + } +} + +sal_uInt32 XclExpMergedcells::GetBaseXFId( const ScAddress& rPos ) const +{ + OSL_ENSURE( maBaseXFIds.size() == maMergedRanges.size(), "XclExpMergedcells::GetBaseXFId - invalid lists" ); + ScfUInt32Vec::const_iterator aIt = maBaseXFIds.begin(); + ScRangeList& rNCRanges = const_cast< ScRangeList& >( maMergedRanges ); + for ( size_t i = 0, nRanges = rNCRanges.size(); i < nRanges; ++i, ++aIt ) + { + const ScRange & rScRange = rNCRanges[ i ]; + if( rScRange.Contains( rPos ) ) + return *aIt; + } + return EXC_XFID_NOTFOUND; +} + +void XclExpMergedcells::Save( XclExpStream& rStrm ) +{ + if( GetBiff() != EXC_BIFF8 ) + return; + + XclRangeList aXclRanges; + GetAddressConverter().ConvertRangeList( aXclRanges, maMergedRanges, true ); + size_t nFirstRange = 0; + size_t nRemainingRanges = aXclRanges.size(); + while( nRemainingRanges > 0 ) + { + size_t nRangeCount = ::std::min< size_t >( nRemainingRanges, EXC_MERGEDCELLS_MAXCOUNT ); + rStrm.StartRecord( EXC_ID_MERGEDCELLS, 2 + 8 * nRangeCount ); + aXclRanges.WriteSubList( rStrm, nFirstRange, nRangeCount ); + rStrm.EndRecord(); + nFirstRange += nRangeCount; + nRemainingRanges -= nRangeCount; + } +} + +void XclExpMergedcells::SaveXml( XclExpXmlStream& rStrm ) +{ + size_t nCount = maMergedRanges.size(); + if( !nCount ) + return; + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_mergeCells, XML_count, OString::number(nCount)); + for( size_t i = 0; i < nCount; ++i ) + { + const ScRange & rRange = maMergedRanges[ i ]; + rWorksheet->singleElement(XML_mergeCell, XML_ref, + XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), rRange)); + } + rWorksheet->endElement( XML_mergeCells ); +} + +// Hyperlinks ================================================================= + +XclExpHyperlink::XclExpHyperlink( const XclExpRoot& rRoot, const SvxURLField& rUrlField, const ScAddress& rScPos ) : + XclExpRecord( EXC_ID_HLINK ), + maScPos( rScPos ), + mxVarData( new SvMemoryStream ), + mnFlags( 0 ) +{ + const OUString& rUrl = rUrlField.GetURL(); + const OUString& rRepr = rUrlField.GetRepresentation(); + INetURLObject aUrlObj( rUrl ); + const INetProtocol eProtocol = aUrlObj.GetProtocol(); + bool bWithRepr = !rRepr.isEmpty(); + XclExpStream aXclStrm( *mxVarData, rRoot ); // using in raw write mode. + + // description + if( bWithRepr ) + { + XclExpString aDescr( rRepr, XclStrFlags::ForceUnicode, 255 ); + aXclStrm << sal_uInt32( aDescr.Len() + 1 ); // string length + 1 trailing zero word + aDescr.WriteBuffer( aXclStrm ); // NO flags + aXclStrm << sal_uInt16( 0 ); + + mnFlags |= EXC_HLINK_DESCR; + m_Repr = rRepr; + } + + // file link or URL + if( eProtocol == INetProtocol::File || eProtocol == INetProtocol::Smb ) + { + sal_uInt16 nLevel; + bool bRel; + OUString aFileName( + BuildFileName(nLevel, bRel, rUrl, rRoot, rRoot.GetOutput() == EXC_OUTPUT_XML_2007)); + + if( eProtocol == INetProtocol::Smb ) + { + // #n382718# (and #n261623#) Convert smb notation to '\\' + aFileName = aUrlObj.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + aFileName = aFileName.copy(4); // skip the 'smb:' part + aFileName = aFileName.replace('/', '\\'); + } + + if( !bRel ) + mnFlags |= EXC_HLINK_ABS; + mnFlags |= EXC_HLINK_BODY; + + OString aAsciiLink(OUStringToOString(aFileName, + rRoot.GetTextEncoding())); + XclExpString aLink( aFileName, XclStrFlags::ForceUnicode, 255 ); + aXclStrm << XclTools::maGuidFileMoniker + << nLevel + << sal_uInt32( aAsciiLink.getLength() + 1 ); // string length + 1 trailing zero byte + aXclStrm.Write( aAsciiLink.getStr(), aAsciiLink.getLength() ); + aXclStrm << sal_uInt8( 0 ) + << sal_uInt32( 0xDEADFFFF ); + aXclStrm.WriteZeroBytes( 20 ); + aXclStrm << sal_uInt32( aLink.GetBufferSize() + 6 ) + << sal_uInt32( aLink.GetBufferSize() ) // byte count, not string length + << sal_uInt16( 0x0003 ); + aLink.WriteBuffer( aXclStrm ); // NO flags + + if (m_Repr.isEmpty()) + m_Repr = aFileName; + + msTarget = XclXmlUtils::ToOUString( aLink ); + + if( bRel ) + { + for( int i = 0; i < nLevel; ++i ) + msTarget = "../" + msTarget; + } + else if (rRoot.GetOutput() != EXC_OUTPUT_XML_2007) + { + // xls expects the file:/// part appended ( or at least + // ms2007 does, ms2010 is more tolerant ) + msTarget = "file:///" + msTarget; + } + } + else if( eProtocol != INetProtocol::NotValid ) + { + XclExpString aUrl( aUrlObj.GetURLNoMark(), XclStrFlags::ForceUnicode, 255 ); + aXclStrm << XclTools::maGuidUrlMoniker + << sal_uInt32( aUrl.GetBufferSize() + 2 ); // byte count + 1 trailing zero word + aUrl.WriteBuffer( aXclStrm ); // NO flags + aXclStrm << sal_uInt16( 0 ); + + mnFlags |= EXC_HLINK_BODY | EXC_HLINK_ABS; + if (m_Repr.isEmpty()) + m_Repr = rUrl; + + msTarget = XclXmlUtils::ToOUString( aUrl ); + } + else if( !rUrl.isEmpty() && rUrl[0] == '#' ) // hack for #89066# + { + OUString aTextMark( rUrl.copy( 1 ) ); + + sal_Int32 nSepPos = aTextMark.lastIndexOf( '!' ); + sal_Int32 nPointPos = aTextMark.lastIndexOf( '.' ); + // last dot is the separator, if there is no ! after it + if(nSepPos < nPointPos) + { + nSepPos = nPointPos; + aTextMark = aTextMark.replaceAt( nSepPos, 1, u"!" ); + } + + if (nSepPos != -1) + { + std::u16string_view aSheetName(aTextMark.subView(0, nSepPos)); + + if (aSheetName.find(' ') != std::u16string_view::npos && aSheetName[0] != '\'') + { + aTextMark = "'" + aTextMark.replaceAt(nSepPos, 0, u"'"); + } + } + else + { + SCTAB nTab; + if (rRoot.GetDoc().GetTable(aTextMark, nTab)) + aTextMark += "!A1"; // tdf#143220 link to sheet not valid without cell reference + } + + mxTextMark.reset( new XclExpString( aTextMark, XclStrFlags::ForceUnicode, 255 ) ); + } + + // text mark + if( !mxTextMark && aUrlObj.HasMark() ) + mxTextMark.reset( new XclExpString( aUrlObj.GetMark(), XclStrFlags::ForceUnicode, 255 ) ); + + if( mxTextMark ) + { + aXclStrm << sal_uInt32( mxTextMark->Len() + 1 ); // string length + 1 trailing zero word + mxTextMark->WriteBuffer( aXclStrm ); // NO flags + aXclStrm << sal_uInt16( 0 ); + + mnFlags |= EXC_HLINK_MARK; + + OUString location = XclXmlUtils::ToOUString(*mxTextMark); + if (!location.isEmpty() && msTarget.endsWith(OUStringConcatenation("#" + location))) + msTarget = msTarget.copy(0, msTarget.getLength() - location.getLength() - 1); + } + + SetRecSize( 32 + mxVarData->Tell() ); +} + +XclExpHyperlink::~XclExpHyperlink() +{ +} + +OUString XclExpHyperlink::BuildFileName( + sal_uInt16& rnLevel, bool& rbRel, const OUString& rUrl, const XclExpRoot& rRoot, bool bEncoded ) +{ + INetURLObject aURLObject( rUrl ); + OUString aDosName(bEncoded ? aURLObject.GetMainURL(INetURLObject::DecodeMechanism::ToIUri) + : aURLObject.getFSysPath(FSysStyle::Dos)); + rnLevel = 0; + rbRel = rRoot.IsRelUrl(); + + if( rbRel ) + { + // try to convert to relative file name + OUString aTmpName( aDosName ); + aDosName = INetURLObject::GetRelURL( rRoot.GetBasePath(), rUrl, + INetURLObject::EncodeMechanism::WasEncoded, + (bEncoded ? INetURLObject::DecodeMechanism::ToIUri : INetURLObject::DecodeMechanism::WithCharset)); + + if (aDosName.startsWith(INET_FILE_SCHEME)) + { + // not converted to rel -> back to old, return absolute flag + aDosName = aTmpName; + rbRel = false; + } + else if (aDosName.startsWith("./")) + { + aDosName = aDosName.copy(2); + } + else + { + while (aDosName.startsWith("../")) + { + aDosName = aDosName.copy(3); + ++rnLevel; + } + } + } + return aDosName; +} + +void XclExpHyperlink::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt16 nXclCol = static_cast< sal_uInt16 >( maScPos.Col() ); + sal_uInt16 nXclRow = static_cast< sal_uInt16 >( maScPos.Row() ); + rStrm << nXclRow << nXclRow << nXclCol << nXclCol; + WriteEmbeddedData( rStrm ); +} + +void XclExpHyperlink::WriteEmbeddedData( XclExpStream& rStrm ) +{ + rStrm << XclTools::maGuidStdLink + << sal_uInt32( 2 ) + << mnFlags; + + mxVarData->Seek( STREAM_SEEK_TO_BEGIN ); + rStrm.CopyFromStream( *mxVarData ); +} + +void XclExpHyperlink::SaveXml( XclExpXmlStream& rStrm ) +{ + OUString sId = !msTarget.isEmpty() ? rStrm.addRelation( rStrm.GetCurrentStream()->getOutputStream(), + oox::getRelationship(Relationship::HYPERLINK), + msTarget, true ) : OUString(); + std::optional sTextMark; + if (mxTextMark) + sTextMark = XclXmlUtils::ToOString(*mxTextMark); + rStrm.GetCurrentStream()->singleElement( XML_hyperlink, + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maScPos), + FSNS( XML_r, XML_id ), sax_fastparser::UseIf(sId, !sId.isEmpty()), + XML_location, sTextMark, + // OOXTODO: XML_tooltip, from record HLinkTooltip 800h wzTooltip + XML_display, m_Repr ); +} + +// Label ranges =============================================================== + +XclExpLabelranges::XclExpLabelranges( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ + SCTAB nScTab = GetCurrScTab(); + // row label ranges + FillRangeList( maRowRanges, rRoot.GetDoc().GetRowNameRangesRef(), nScTab ); + // row labels only over 1 column (restriction of Excel97/2000/XP) + for ( size_t i = 0, nRanges = maRowRanges.size(); i < nRanges; ++i ) + { + ScRange & rScRange = maRowRanges[ i ]; + if( rScRange.aStart.Col() != rScRange.aEnd.Col() ) + rScRange.aEnd.SetCol( rScRange.aStart.Col() ); + } + // col label ranges + FillRangeList( maColRanges, rRoot.GetDoc().GetColNameRangesRef(), nScTab ); +} + +void XclExpLabelranges::FillRangeList( ScRangeList& rScRanges, + const ScRangePairListRef& xLabelRangesRef, SCTAB nScTab ) +{ + for ( size_t i = 0, nPairs = xLabelRangesRef->size(); i < nPairs; ++i ) + { + const ScRangePair & rRangePair = (*xLabelRangesRef)[i]; + const ScRange& rScRange = rRangePair.GetRange( 0 ); + if( rScRange.aStart.Tab() == nScTab ) + rScRanges.push_back( rScRange ); + } +} + +void XclExpLabelranges::Save( XclExpStream& rStrm ) +{ + XclExpAddressConverter& rAddrConv = GetAddressConverter(); + XclRangeList aRowXclRanges, aColXclRanges; + rAddrConv.ConvertRangeList( aRowXclRanges, maRowRanges, false ); + rAddrConv.ConvertRangeList( aColXclRanges, maColRanges, false ); + if( !aRowXclRanges.empty() || !aColXclRanges.empty() ) + { + rStrm.StartRecord( EXC_ID_LABELRANGES, 4 + 8 * (aRowXclRanges.size() + aColXclRanges.size()) ); + rStrm << aRowXclRanges << aColXclRanges; + rStrm.EndRecord(); + } +} + +// Conditional formatting ==================================================== + +/** Represents a CF record that contains one condition of a conditional format. */ +class XclExpCFImpl : protected XclExpRoot +{ +public: + explicit XclExpCFImpl( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin ); + + /** Writes the body of the CF record. */ + void WriteBody( XclExpStream& rStrm ); + void SaveXml( XclExpXmlStream& rStrm ); + +private: + const ScCondFormatEntry& mrFormatEntry; /// Calc conditional format entry. + ScAddress maOrigin; /// Top left cell of the combined range + XclFontData maFontData; /// Font formatting attributes. + XclExpCellBorder maBorder; /// Border formatting attributes. + XclExpCellArea maArea; /// Pattern formatting attributes. + XclTokenArrayRef mxTokArr1; /// Formula for first condition. + XclTokenArrayRef mxTokArr2; /// Formula for second condition. + sal_uInt32 mnFontColorId; /// Font color ID. + sal_uInt8 mnType; /// Type of the condition (cell/formula). + sal_uInt8 mnOperator; /// Comparison operator for cell type. + sal_Int32 mnPriority; /// Priority of this entry; needed for oox export + bool mbFontUsed; /// true = Any font attribute used. + bool mbHeightUsed; /// true = Font height used. + bool mbWeightUsed; /// true = Font weight used. + bool mbColorUsed; /// true = Font color used. + bool mbUnderlUsed; /// true = Font underline type used. + bool mbItalicUsed; /// true = Font posture used. + bool mbStrikeUsed; /// true = Font strikeout used. + bool mbBorderUsed; /// true = Border attribute used. + bool mbPattUsed; /// true = Pattern attribute used. + bool mbFormula2; +}; + +XclExpCFImpl::XclExpCFImpl( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin ) : + XclExpRoot( rRoot ), + mrFormatEntry( rFormatEntry ), + maOrigin( aOrigin ), + mnFontColorId( 0 ), + mnType( EXC_CF_TYPE_CELL ), + mnOperator( EXC_CF_CMP_NONE ), + mnPriority( nPriority ), + mbFontUsed( false ), + mbHeightUsed( false ), + mbWeightUsed( false ), + mbColorUsed( false ), + mbUnderlUsed( false ), + mbItalicUsed( false ), + mbStrikeUsed( false ), + mbBorderUsed( false ), + mbPattUsed( false ), + mbFormula2(false) +{ + // Set correct tab for maOrigin from GetValidSrcPos() of the format-entry. + ScAddress aValidSrcPos = mrFormatEntry.GetValidSrcPos(); + maOrigin.SetTab(aValidSrcPos.Tab()); + + /* Get formatting attributes here, and not in WriteBody(). This is needed to + correctly insert all colors into the palette. */ + + if( SfxStyleSheetBase* pStyleSheet = GetDoc().GetStyleSheetPool()->Find( mrFormatEntry.GetStyle(), SfxStyleFamily::Para ) ) + { + const SfxItemSet& rItemSet = pStyleSheet->GetItemSet(); + + // font + mbHeightUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_HEIGHT, true ); + mbWeightUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_WEIGHT, true ); + mbColorUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_COLOR, true ); + mbUnderlUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_UNDERLINE, true ); + mbItalicUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_POSTURE, true ); + mbStrikeUsed = ScfTools::CheckItem( rItemSet, ATTR_FONT_CROSSEDOUT, true ); + mbFontUsed = mbHeightUsed || mbWeightUsed || mbColorUsed || mbUnderlUsed || mbItalicUsed || mbStrikeUsed; + if( mbFontUsed ) + { + vcl::Font aFont; + ScPatternAttr::GetFont( aFont, rItemSet, SC_AUTOCOL_RAW ); + maFontData.FillFromVclFont( aFont ); + mnFontColorId = GetPalette().InsertColor( maFontData.maColor, EXC_COLOR_CELLTEXT ); + } + + // border + mbBorderUsed = ScfTools::CheckItem( rItemSet, ATTR_BORDER, true ); + if( mbBorderUsed ) + maBorder.FillFromItemSet( rItemSet, GetPalette(), GetBiff() ); + + // pattern + mbPattUsed = ScfTools::CheckItem( rItemSet, ATTR_BACKGROUND, true ); + if( mbPattUsed ) + maArea.FillFromItemSet( rItemSet, GetPalette(), true ); + } + + // *** mode and comparison operator *** + + switch( rFormatEntry.GetOperation() ) + { + case ScConditionMode::NONE: + mnType = EXC_CF_TYPE_NONE; + break; + case ScConditionMode::Between: + mnOperator = EXC_CF_CMP_BETWEEN; + mbFormula2 = true; + break; + case ScConditionMode::NotBetween: + mnOperator = EXC_CF_CMP_NOT_BETWEEN; + mbFormula2 = true; + break; + case ScConditionMode::Equal: + mnOperator = EXC_CF_CMP_EQUAL; + break; + case ScConditionMode::NotEqual: + mnOperator = EXC_CF_CMP_NOT_EQUAL; + break; + case ScConditionMode::Greater: + mnOperator = EXC_CF_CMP_GREATER; + break; + case ScConditionMode::Less: + mnOperator = EXC_CF_CMP_LESS; + break; + case ScConditionMode::EqGreater: + mnOperator = EXC_CF_CMP_GREATER_EQUAL; + break; + case ScConditionMode::EqLess: + mnOperator = EXC_CF_CMP_LESS_EQUAL; + break; + case ScConditionMode::Direct: + mnType = EXC_CF_TYPE_FMLA; + break; + default: + mnType = EXC_CF_TYPE_NONE; + OSL_FAIL( "XclExpCF::WriteBody - unknown condition type" ); + } +} + +void XclExpCFImpl::WriteBody( XclExpStream& rStrm ) +{ + + // *** formulas *** + + XclExpFormulaCompiler& rFmlaComp = GetFormulaCompiler(); + + std::unique_ptr< ScTokenArray > xScTokArr( mrFormatEntry.CreateFlatCopiedTokenArray( 0 ) ); + mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_CONDFMT, *xScTokArr ); + + if (mbFormula2) + { + xScTokArr = mrFormatEntry.CreateFlatCopiedTokenArray( 1 ); + mxTokArr2 = rFmlaComp.CreateFormula( EXC_FMLATYPE_CONDFMT, *xScTokArr ); + } + + // *** mode and comparison operator *** + + rStrm << mnType << mnOperator; + + // *** formula sizes *** + + sal_uInt16 nFmlaSize1 = mxTokArr1 ? mxTokArr1->GetSize() : 0; + sal_uInt16 nFmlaSize2 = mxTokArr2 ? mxTokArr2->GetSize() : 0; + rStrm << nFmlaSize1 << nFmlaSize2; + + // *** formatting blocks *** + + if( mbFontUsed || mbBorderUsed || mbPattUsed ) + { + sal_uInt32 nFlags = EXC_CF_ALLDEFAULT; + + ::set_flag( nFlags, EXC_CF_BLOCK_FONT, mbFontUsed ); + ::set_flag( nFlags, EXC_CF_BLOCK_BORDER, mbBorderUsed ); + ::set_flag( nFlags, EXC_CF_BLOCK_AREA, mbPattUsed ); + + // attributes used -> set flags to 0. + ::set_flag( nFlags, EXC_CF_BORDER_ALL, !mbBorderUsed ); + ::set_flag( nFlags, EXC_CF_AREA_ALL, !mbPattUsed ); + + rStrm << nFlags << sal_uInt16( 0 ); + + if( mbFontUsed ) + { + // font height, 0xFFFFFFFF indicates unused + sal_uInt32 nHeight = mbHeightUsed ? maFontData.mnHeight : 0xFFFFFFFF; + // font style: italic and strikeout + sal_uInt32 nStyle = 0; + ::set_flag( nStyle, EXC_CF_FONT_STYLE, maFontData.mbItalic ); + ::set_flag( nStyle, EXC_CF_FONT_STRIKEOUT, maFontData.mbStrikeout ); + // font color, 0xFFFFFFFF indicates unused + sal_uInt32 nColor = mbColorUsed ? GetPalette().GetColorIndex( mnFontColorId ) : 0xFFFFFFFF; + // font used flags for italic, weight, and strikeout -> 0 = used, 1 = default + sal_uInt32 nFontFlags1 = EXC_CF_FONT_ALLDEFAULT; + ::set_flag( nFontFlags1, EXC_CF_FONT_STYLE, !(mbItalicUsed || mbWeightUsed) ); + ::set_flag( nFontFlags1, EXC_CF_FONT_STRIKEOUT, !mbStrikeUsed ); + // font used flag for underline -> 0 = used, 1 = default + sal_uInt32 nFontFlags3 = mbUnderlUsed ? 0 : EXC_CF_FONT_UNDERL; + + rStrm.WriteZeroBytesToRecord( 64 ); + rStrm << nHeight + << nStyle + << maFontData.mnWeight + << EXC_FONTESC_NONE + << maFontData.mnUnderline; + rStrm.WriteZeroBytesToRecord( 3 ); + rStrm << nColor + << sal_uInt32( 0 ) + << nFontFlags1 + << EXC_CF_FONT_ESCAPEM // escapement never used -> set the flag + << nFontFlags3; + rStrm.WriteZeroBytesToRecord( 16 ); + rStrm << sal_uInt16( 1 ); // must be 1 + } + + if( mbBorderUsed ) + { + sal_uInt16 nLineStyle = 0; + sal_uInt32 nLineColor = 0; + maBorder.SetFinalColors( GetPalette() ); + maBorder.FillToCF8( nLineStyle, nLineColor ); + rStrm << nLineStyle << nLineColor << sal_uInt16( 0 ); + } + + if( mbPattUsed ) + { + sal_uInt16 nPattern = 0, nColor = 0; + maArea.SetFinalColors( GetPalette() ); + maArea.FillToCF8( nPattern, nColor ); + rStrm << nPattern << nColor; + } + } + else + { + // no data blocks at all + rStrm << sal_uInt32( 0 ) << sal_uInt16( 0 ); + } + + // *** formulas *** + + if( mxTokArr1 ) + mxTokArr1->WriteArray( rStrm ); + if( mxTokArr2 ) + mxTokArr2->WriteArray( rStrm ); +} + +namespace { + +const char* GetOperatorString(ScConditionMode eMode, bool& bFrmla2) +{ + const char *pRet = nullptr; + switch(eMode) + { + case ScConditionMode::Equal: + pRet = "equal"; + break; + case ScConditionMode::Less: + pRet = "lessThan"; + break; + case ScConditionMode::Greater: + pRet = "greaterThan"; + break; + case ScConditionMode::EqLess: + pRet = "lessThanOrEqual"; + break; + case ScConditionMode::EqGreater: + pRet = "greaterThanOrEqual"; + break; + case ScConditionMode::NotEqual: + pRet = "notEqual"; + break; + case ScConditionMode::Between: + bFrmla2 = true; + pRet = "between"; + break; + case ScConditionMode::NotBetween: + bFrmla2 = true; + pRet = "notBetween"; + break; + case ScConditionMode::Duplicate: + pRet = nullptr; + break; + case ScConditionMode::NotDuplicate: + pRet = nullptr; + break; + case ScConditionMode::BeginsWith: + pRet = "beginsWith"; + break; + case ScConditionMode::EndsWith: + pRet = "endsWith"; + break; + case ScConditionMode::ContainsText: + pRet = "containsText"; + break; + case ScConditionMode::NotContainsText: + pRet = "notContains"; + break; + case ScConditionMode::Direct: + break; + case ScConditionMode::NONE: + default: + break; + } + return pRet; +} + +const char* GetTypeString(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::Direct: + return "expression"; + case ScConditionMode::Top10: + case ScConditionMode::TopPercent: + case ScConditionMode::Bottom10: + case ScConditionMode::BottomPercent: + return "top10"; + case ScConditionMode::AboveAverage: + case ScConditionMode::BelowAverage: + case ScConditionMode::AboveEqualAverage: + case ScConditionMode::BelowEqualAverage: + return "aboveAverage"; + case ScConditionMode::NotDuplicate: + return "uniqueValues"; + case ScConditionMode::Duplicate: + return "duplicateValues"; + case ScConditionMode::Error: + return "containsErrors"; + case ScConditionMode::NoError: + return "notContainsErrors"; + case ScConditionMode::BeginsWith: + return "beginsWith"; + case ScConditionMode::EndsWith: + return "endsWith"; + case ScConditionMode::ContainsText: + return "containsText"; + case ScConditionMode::NotContainsText: + return "notContainsText"; + default: + return "cellIs"; + } +} + +bool IsTopBottomRule(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::Top10: + case ScConditionMode::Bottom10: + case ScConditionMode::TopPercent: + case ScConditionMode::BottomPercent: + return true; + default: + break; + } + + return false; +} + +bool IsTextRule(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::BeginsWith: + case ScConditionMode::EndsWith: + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + return true; + default: + break; + } + + return false; +} + +bool RequiresFormula(ScConditionMode eMode) +{ + if (IsTopBottomRule(eMode)) + return false; + else if (IsTextRule(eMode)) + return false; + + switch (eMode) + { + case ScConditionMode::NoError: + case ScConditionMode::Error: + case ScConditionMode::Duplicate: + case ScConditionMode::NotDuplicate: + return false; + default: + break; + } + + return true; +} + +bool RequiresFixedFormula(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::NoError: + case ScConditionMode::Error: + case ScConditionMode::BeginsWith: + case ScConditionMode::EndsWith: + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + return true; + default: + break; + } + + return false; +} + +OString GetFixedFormula(ScConditionMode eMode, const ScAddress& rAddress, std::string_view rText) +{ + OStringBuffer aBuffer; + XclXmlUtils::ToOString(aBuffer, rAddress); + OString aPos = aBuffer.makeStringAndClear(); + switch (eMode) + { + case ScConditionMode::Error: + return OString("ISERROR(" + aPos + ")") ; + case ScConditionMode::NoError: + return OString("NOT(ISERROR(" + aPos + "))") ; + case ScConditionMode::BeginsWith: + return OString("LEFT(" + aPos + ",LEN(\"" + rText + "\"))=\"" + rText + "\""); + case ScConditionMode::EndsWith: + return OString("RIGHT(" + aPos +",LEN(\"" + rText + "\"))=\"" + rText + "\""); + case ScConditionMode::ContainsText: + return OString(OString::Concat("NOT(ISERROR(SEARCH(\"") + rText + "\"," + aPos + ")))"); + case ScConditionMode::NotContainsText: + return OString(OString::Concat("ISERROR(SEARCH(\"") + rText + "\"," + aPos + "))"); + default: + break; + } + + return ""; +} + +} + +void XclExpCFImpl::SaveXml( XclExpXmlStream& rStrm ) +{ + bool bFmla2 = false; + ScConditionMode eOperation = mrFormatEntry.GetOperation(); + bool bAboveAverage = eOperation == ScConditionMode::AboveAverage || + eOperation == ScConditionMode::AboveEqualAverage; + bool bEqualAverage = eOperation == ScConditionMode::AboveEqualAverage || + eOperation == ScConditionMode::BelowEqualAverage; + bool bBottom = eOperation == ScConditionMode::Bottom10 + || eOperation == ScConditionMode::BottomPercent; + bool bPercent = eOperation == ScConditionMode::TopPercent || + eOperation == ScConditionMode::BottomPercent; + OUString aRank("0"); + if(IsTopBottomRule(eOperation)) + { + // position and formula grammar are not important + // we only store a number there + aRank = mrFormatEntry.GetExpression(ScAddress(0,0,0), 0); + } + OString aText; + if(IsTextRule(eOperation)) + { + // we need to write the text without quotes + // we have to actually get the string from + // the token array for that + std::unique_ptr pTokenArray(mrFormatEntry.CreateFlatCopiedTokenArray(0)); + if(pTokenArray->GetLen()) + aText = pTokenArray->FirstToken()->GetString().getString().toUtf8(); + } + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_cfRule, + XML_type, GetTypeString( mrFormatEntry.GetOperation() ), + XML_priority, OString::number(mnPriority + 1), + XML_operator, GetOperatorString( mrFormatEntry.GetOperation(), bFmla2 ), + XML_aboveAverage, ToPsz10(bAboveAverage), + XML_equalAverage, ToPsz10(bEqualAverage), + XML_bottom, ToPsz10(bBottom), + XML_percent, ToPsz10(bPercent), + XML_rank, aRank, + XML_text, aText, + XML_dxfId, OString::number(GetDxfs().GetDxfId(mrFormatEntry.GetStyle())) ); + + if (RequiresFixedFormula(eOperation)) + { + rWorksheet->startElement(XML_formula); + OString aFormula = GetFixedFormula(eOperation, maOrigin, aText); + rWorksheet->writeEscaped(aFormula.getStr()); + rWorksheet->endElement( XML_formula ); + } + else if(RequiresFormula(eOperation)) + { + rWorksheet->startElement(XML_formula); + std::unique_ptr pTokenArray(mrFormatEntry.CreateFlatCopiedTokenArray(0)); + rWorksheet->writeEscaped(XclXmlUtils::ToOUString( GetCompileFormulaContext(), mrFormatEntry.GetValidSrcPos(), + pTokenArray.get())); + rWorksheet->endElement( XML_formula ); + if (bFmla2) + { + rWorksheet->startElement(XML_formula); + std::unique_ptr pTokenArray2(mrFormatEntry.CreateFlatCopiedTokenArray(1)); + rWorksheet->writeEscaped(XclXmlUtils::ToOUString( GetCompileFormulaContext(), mrFormatEntry.GetValidSrcPos(), + pTokenArray2.get())); + rWorksheet->endElement( XML_formula ); + } + } + // OOXTODO: XML_extLst + rWorksheet->endElement( XML_cfRule ); +} + +XclExpCF::XclExpCF( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormatEntry, sal_Int32 nPriority, ScAddress aOrigin ) : + XclExpRecord( EXC_ID_CF ), + XclExpRoot( rRoot ), + mxImpl( new XclExpCFImpl( rRoot, rFormatEntry, nPriority, aOrigin ) ) +{ +} + +XclExpCF::~XclExpCF() +{ +} + +void XclExpCF::WriteBody( XclExpStream& rStrm ) +{ + mxImpl->WriteBody( rStrm ); +} + +void XclExpCF::SaveXml( XclExpXmlStream& rStrm ) +{ + mxImpl->SaveXml( rStrm ); +} + +XclExpDateFormat::XclExpDateFormat( const XclExpRoot& rRoot, const ScCondDateFormatEntry& rFormatEntry, sal_Int32 nPriority ): + XclExpRecord( EXC_ID_CF ), + XclExpRoot( rRoot ), + mrFormatEntry(rFormatEntry), + mnPriority(nPriority) +{ +} + +XclExpDateFormat::~XclExpDateFormat() +{ +} + +namespace { + +const char* getTimePeriodString( condformat::ScCondFormatDateType eType ) +{ + switch(eType) + { + case condformat::TODAY: + return "today"; + case condformat::YESTERDAY: + return "yesterday"; + case condformat::TOMORROW: + return "yesterday"; + case condformat::THISWEEK: + return "thisWeek"; + case condformat::LASTWEEK: + return "lastWeek"; + case condformat::NEXTWEEK: + return "nextWeek"; + case condformat::THISMONTH: + return "thisMonth"; + case condformat::LASTMONTH: + return "lastMonth"; + case condformat::NEXTMONTH: + return "nextMonth"; + case condformat::LAST7DAYS: + return "last7Days"; + default: + break; + } + return nullptr; +} + +} + +void XclExpDateFormat::SaveXml( XclExpXmlStream& rStrm ) +{ + // only write the supported entries into OOXML + const char* sTimePeriod = getTimePeriodString(mrFormatEntry.GetDateType()); + if(!sTimePeriod) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_cfRule, + XML_type, "timePeriod", + XML_priority, OString::number(mnPriority + 1), + XML_timePeriod, sTimePeriod, + XML_dxfId, OString::number(GetDxfs().GetDxfId(mrFormatEntry.GetStyleName())) ); + rWorksheet->endElement( XML_cfRule); +} + +XclExpCfvo::XclExpCfvo(const XclExpRoot& rRoot, const ScColorScaleEntry& rEntry, const ScAddress& rAddr, bool bFirst): + XclExpRoot( rRoot ), + mrEntry(rEntry), + maSrcPos(rAddr), + mbFirst(bFirst) +{ +} + +namespace { + +OString getColorScaleType( const ScColorScaleEntry& rEntry, bool bFirst ) +{ + switch(rEntry.GetType()) + { + case COLORSCALE_MIN: + return "min"; + case COLORSCALE_MAX: + return "max"; + case COLORSCALE_PERCENT: + return "percent"; + case COLORSCALE_FORMULA: + return "formula"; + case COLORSCALE_AUTO: + if(bFirst) + return "min"; + else + return "max"; + case COLORSCALE_PERCENTILE: + return "percentile"; + default: + break; + } + + return "num"; +} + +} + +void XclExpCfvo::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + OString aValue; + if(mrEntry.GetType() == COLORSCALE_FORMULA) + { + OUString aFormula = XclXmlUtils::ToOUString( GetCompileFormulaContext(), maSrcPos, + mrEntry.GetFormula()); + aValue = OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8 ); + } + else + { + aValue = OString::number( mrEntry.GetValue() ); + } + + rWorksheet->startElement( XML_cfvo, + XML_type, getColorScaleType(mrEntry, mbFirst), + XML_val, aValue ); + + rWorksheet->endElement( XML_cfvo ); +} + +XclExpColScaleCol::XclExpColScaleCol( const XclExpRoot& rRoot, const Color& rColor ): + XclExpRoot( rRoot ), + mrColor( rColor ) +{ +} + +XclExpColScaleCol::~XclExpColScaleCol() +{ +} + +void XclExpColScaleCol::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement(XML_color, XML_rgb, XclXmlUtils::ToOString(mrColor)); + + rWorksheet->endElement( XML_color ); +} + +namespace { + +OString createHexStringFromDigit(sal_uInt8 nDigit) +{ + OString aString = OString::number( nDigit, 16 ); + if(aString.getLength() == 1) + aString += OString::number(0); + return aString; +} + +OString createGuidStringFromInt(sal_uInt8 nGuid[16]) +{ + OStringBuffer aBuffer; + aBuffer.append('{'); + for(size_t i = 0; i < 16; ++i) + { + aBuffer.append(createHexStringFromDigit(nGuid[i])); + if(i == 3|| i == 5 || i == 7 || i == 9 ) + aBuffer.append('-'); + } + aBuffer.append('}'); + OString aString = aBuffer.makeStringAndClear(); + return aString.toAsciiUpperCase(); +} + +OString generateGUIDString() +{ + sal_uInt8 nGuid[16]; + rtl_createUuid(nGuid, nullptr, true); + return createGuidStringFromInt(nGuid); +} + +} + +XclExpCondfmt::XclExpCondfmt( const XclExpRoot& rRoot, const ScConditionalFormat& rCondFormat, const XclExtLstRef& xExtLst, sal_Int32& rIndex ) : + XclExpRecord( EXC_ID_CONDFMT ), + XclExpRoot( rRoot ) +{ + const ScRangeList& aScRanges = rCondFormat.GetRange(); + GetAddressConverter().ConvertRangeList( maXclRanges, aScRanges, true ); + if( maXclRanges.empty() ) + return; + + std::vector aExtEntries; + ScAddress aOrigin = aScRanges.Combine().aStart; + for( size_t nIndex = 0, nCount = rCondFormat.size(); nIndex < nCount; ++nIndex ) + if( const ScFormatEntry* pFormatEntry = rCondFormat.GetEntry( nIndex ) ) + { + if(pFormatEntry->GetType() == ScFormatEntry::Type::Condition) + maCFList.AppendNewRecord( new XclExpCF( GetRoot(), static_cast(*pFormatEntry), ++rIndex, aOrigin ) ); + else if(pFormatEntry->GetType() == ScFormatEntry::Type::ExtCondition) + { + const ScCondFormatEntry& rFormat = static_cast(*pFormatEntry); + XclExpExtCondFormatData aExtEntry; + aExtEntry.nPriority = ++rIndex; + aExtEntry.aGUID = generateGUIDString(); + aExtEntry.pEntry = &rFormat; + aExtEntries.push_back(aExtEntry); + } + else if(pFormatEntry->GetType() == ScFormatEntry::Type::Colorscale) + maCFList.AppendNewRecord( new XclExpColorScale( GetRoot(), static_cast(*pFormatEntry), ++rIndex ) ); + else if(pFormatEntry->GetType() == ScFormatEntry::Type::Databar) + { + const ScDataBarFormat& rFormat = static_cast(*pFormatEntry); + XclExpExtCondFormatData aExtEntry; + aExtEntry.nPriority = -1; + aExtEntry.aGUID = generateGUIDString(); + aExtEntry.pEntry = &rFormat; + aExtEntries.push_back(aExtEntry); + + maCFList.AppendNewRecord( new XclExpDataBar( GetRoot(), rFormat, ++rIndex, aExtEntry.aGUID)); + } + else if(pFormatEntry->GetType() == ScFormatEntry::Type::Iconset) + { + // don't export iconSet entries that are not in OOXML + const ScIconSetFormat& rIconSet = static_cast(*pFormatEntry); + bool bNeedsExt = false; + switch (rIconSet.GetIconSetData()->eIconSetType) + { + case IconSet_3Smilies: + case IconSet_3ColorSmilies: + case IconSet_3Stars: + case IconSet_3Triangles: + case IconSet_5Boxes: + { + bNeedsExt = true; + } + break; + default: + break; + } + + bNeedsExt |= rIconSet.GetIconSetData()->mbCustom; + + if (bNeedsExt) + { + XclExpExtCondFormatData aExtEntry; + aExtEntry.nPriority = ++rIndex; + aExtEntry.aGUID = generateGUIDString(); + aExtEntry.pEntry = &rIconSet; + aExtEntries.push_back(aExtEntry); + } + else + maCFList.AppendNewRecord( new XclExpIconSet( GetRoot(), rIconSet, ++rIndex ) ); + } + else if(pFormatEntry->GetType() == ScFormatEntry::Type::Date) + maCFList.AppendNewRecord( new XclExpDateFormat( GetRoot(), static_cast(*pFormatEntry), ++rIndex ) ); + } + aScRanges.Format( msSeqRef, ScRefFlags::VALID, GetDoc(), formula::FormulaGrammar::CONV_XL_OOX, ' ', true ); + + if(!aExtEntries.empty() && xExtLst) + { + XclExpExt* pParent = xExtLst->GetItem( XclExpExtDataBarType ); + if( !pParent ) + { + xExtLst->AddRecord( new XclExpExtCondFormat( *xExtLst ) ); + pParent = xExtLst->GetItem( XclExpExtDataBarType ); + } + static_cast(xExtLst->GetItem( XclExpExtDataBarType ))->AddRecord( + new XclExpExtConditionalFormatting( *pParent, aExtEntries, aScRanges)); + } +} + +XclExpCondfmt::~XclExpCondfmt() +{ +} + +bool XclExpCondfmt::IsValidForBinary() const +{ + // ccf (2 bytes): An unsigned integer that specifies the count of CF records that follow this + // record. MUST be greater than or equal to 0x0001, and less than or equal to 0x0003. + + SAL_WARN_IF( maCFList.GetSize() > 3, "sc.filter", "More than 3 conditional filters for cell(s), won't export"); + + return !maCFList.IsEmpty() && maCFList.GetSize() <= 3 && !maXclRanges.empty(); +} + +bool XclExpCondfmt::IsValidForXml() const +{ + return !maCFList.IsEmpty() && !maXclRanges.empty(); +} + +void XclExpCondfmt::Save( XclExpStream& rStrm ) +{ + if( IsValidForBinary() ) + { + XclExpRecord::Save( rStrm ); + maCFList.Save( rStrm ); + } +} + +void XclExpCondfmt::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE( !maCFList.IsEmpty(), "XclExpCondfmt::WriteBody - no CF records to write" ); + OSL_ENSURE( !maXclRanges.empty(), "XclExpCondfmt::WriteBody - no cell ranges found" ); + + rStrm << static_cast< sal_uInt16 >( maCFList.GetSize() ) + << sal_uInt16( 1 ) + << maXclRanges.GetEnclosingRange() + << maXclRanges; +} + +void XclExpCondfmt::SaveXml( XclExpXmlStream& rStrm ) +{ + if( !IsValidForXml() ) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_conditionalFormatting, + XML_sqref, msSeqRef + // OOXTODO: XML_pivot + ); + + maCFList.SaveXml( rStrm ); + + rWorksheet->endElement( XML_conditionalFormatting ); +} + +XclExpColorScale::XclExpColorScale( const XclExpRoot& rRoot, const ScColorScaleFormat& rFormat, sal_Int32 nPriority ): + XclExpRoot( rRoot ), + mnPriority( nPriority ) +{ + const ScRange & rRange = rFormat.GetRange().front(); + ScAddress aAddr = rRange.aStart; + for(const auto& rxColorScaleEntry : rFormat) + { + // exact position is not important, we allow only absolute refs + + XclExpCfvoList::RecordRefType xCfvo( new XclExpCfvo( GetRoot(), *rxColorScaleEntry, aAddr ) ); + maCfvoList.AppendRecord( xCfvo ); + XclExpColScaleColList::RecordRefType xClo( new XclExpColScaleCol( GetRoot(), rxColorScaleEntry->GetColor() ) ); + maColList.AppendRecord( xClo ); + } +} + +void XclExpColorScale::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement( XML_cfRule, + XML_type, "colorScale", + XML_priority, OString::number(mnPriority + 1) ); + + rWorksheet->startElement(XML_colorScale); + + maCfvoList.SaveXml(rStrm); + maColList.SaveXml(rStrm); + + rWorksheet->endElement( XML_colorScale ); + + rWorksheet->endElement( XML_cfRule ); +} + +XclExpDataBar::XclExpDataBar( const XclExpRoot& rRoot, const ScDataBarFormat& rFormat, sal_Int32 nPriority, const OString& rGUID): + XclExpRoot( rRoot ), + mrFormat( rFormat ), + mnPriority( nPriority ), + maGUID(rGUID) +{ + const ScRange & rRange = rFormat.GetRange().front(); + ScAddress aAddr = rRange.aStart; + // exact position is not important, we allow only absolute refs + mpCfvoLowerLimit.reset( + new XclExpCfvo(GetRoot(), *mrFormat.GetDataBarData()->mpLowerLimit, aAddr, true)); + mpCfvoUpperLimit.reset( + new XclExpCfvo(GetRoot(), *mrFormat.GetDataBarData()->mpUpperLimit, aAddr, false)); + + mpCol.reset( new XclExpColScaleCol( GetRoot(), mrFormat.GetDataBarData()->maPositiveColor ) ); +} + +void XclExpDataBar::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement( XML_cfRule, + XML_type, "dataBar", + XML_priority, OString::number(mnPriority + 1) ); + + rWorksheet->startElement( XML_dataBar, + XML_showValue, ToPsz10(!mrFormat.GetDataBarData()->mbOnlyBar), + XML_minLength, OString::number(sal_uInt32(mrFormat.GetDataBarData()->mnMinLength)), + XML_maxLength, OString::number(sal_uInt32(mrFormat.GetDataBarData()->mnMaxLength)) ); + + mpCfvoLowerLimit->SaveXml(rStrm); + mpCfvoUpperLimit->SaveXml(rStrm); + mpCol->SaveXml(rStrm); + + rWorksheet->endElement( XML_dataBar ); + + // extLst entries for Excel 2010 and 2013 + rWorksheet->startElement(XML_extLst); + rWorksheet->startElement(XML_ext, + FSNS(XML_xmlns, XML_x14), rStrm.getNamespaceURL(OOX_NS(xls14Lst)), + XML_uri, "{B025F937-C7B1-47D3-B67F-A62EFF666E3E}"); + + rWorksheet->startElementNS( XML_x14, XML_id ); + rWorksheet->write(maGUID); + rWorksheet->endElementNS( XML_x14, XML_id ); + + rWorksheet->endElement( XML_ext ); + rWorksheet->endElement( XML_extLst ); + + rWorksheet->endElement( XML_cfRule ); +} + +XclExpIconSet::XclExpIconSet( const XclExpRoot& rRoot, const ScIconSetFormat& rFormat, sal_Int32 nPriority ): + XclExpRoot( rRoot ), + mrFormat( rFormat ), + mnPriority( nPriority ) +{ + const ScRange & rRange = rFormat.GetRange().front(); + ScAddress aAddr = rRange.aStart; + for (auto const& itr : rFormat) + { + // exact position is not important, we allow only absolute refs + + XclExpCfvoList::RecordRefType xCfvo( new XclExpCfvo( GetRoot(), *itr, aAddr ) ); + maCfvoList.AppendRecord( xCfvo ); + } +} + +void XclExpIconSet::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement( XML_cfRule, + XML_type, "iconSet", + XML_priority, OString::number(mnPriority + 1) ); + + const char* pIconSetName = ScIconSetFormat::getIconSetName(mrFormat.GetIconSetData()->eIconSetType); + rWorksheet->startElement( XML_iconSet, + XML_iconSet, pIconSetName, + XML_showValue, sax_fastparser::UseIf("0", !mrFormat.GetIconSetData()->mbShowValue), + XML_reverse, sax_fastparser::UseIf("1", mrFormat.GetIconSetData()->mbReverse)); + + maCfvoList.SaveXml( rStrm ); + + rWorksheet->endElement( XML_iconSet ); + rWorksheet->endElement( XML_cfRule ); +} + +XclExpCondFormatBuffer::XclExpCondFormatBuffer( const XclExpRoot& rRoot, const XclExtLstRef& xExtLst ) : + XclExpRoot( rRoot ) +{ + if( const ScConditionalFormatList* pCondFmtList = GetDoc().GetCondFormList(GetCurrScTab()) ) + { + sal_Int32 nIndex = 0; + for( const auto& rxCondFmt : *pCondFmtList) + { + XclExpCondfmtList::RecordRefType xCondfmtRec( new XclExpCondfmt( GetRoot(), *rxCondFmt, xExtLst, nIndex )); + if( xCondfmtRec->IsValidForXml() ) + maCondfmtList.AppendRecord( xCondfmtRec ); + } + } +} + +void XclExpCondFormatBuffer::Save( XclExpStream& rStrm ) +{ + maCondfmtList.Save( rStrm ); +} + +void XclExpCondFormatBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + maCondfmtList.SaveXml( rStrm ); +} + +// Validation ================================================================= + +namespace { + +/** Writes a formula for the DV record. */ +void lclWriteDvFormula( XclExpStream& rStrm, const XclTokenArray* pXclTokArr ) +{ + sal_uInt16 nFmlaSize = pXclTokArr ? pXclTokArr->GetSize() : 0; + rStrm << nFmlaSize << sal_uInt16( 0 ); + if( pXclTokArr ) + pXclTokArr->WriteArray( rStrm ); +} + +/** Writes a formula for the DV record, based on a single string. */ +void lclWriteDvFormula( XclExpStream& rStrm, const XclExpString& rString ) +{ + // fake a formula with a single tStr token + rStrm << static_cast< sal_uInt16 >( rString.GetSize() + 1 ) + << sal_uInt16( 0 ) + << EXC_TOKID_STR + << rString; +} + +const char* lcl_GetValidationType( sal_uInt32 nFlags ) +{ + switch( nFlags & EXC_DV_MODE_MASK ) + { + case EXC_DV_MODE_ANY: return "none"; + case EXC_DV_MODE_WHOLE: return "whole"; + case EXC_DV_MODE_DECIMAL: return "decimal"; + case EXC_DV_MODE_LIST: return "list"; + case EXC_DV_MODE_DATE: return "date"; + case EXC_DV_MODE_TIME: return "time"; + case EXC_DV_MODE_TEXTLEN: return "textLength"; + case EXC_DV_MODE_CUSTOM: return "custom"; + } + return nullptr; +} + +const char* lcl_GetOperatorType( sal_uInt32 nFlags ) +{ + switch( nFlags & EXC_DV_COND_MASK ) + { + case EXC_DV_COND_BETWEEN: return "between"; + case EXC_DV_COND_NOTBETWEEN: return "notBetween"; + case EXC_DV_COND_EQUAL: return "equal"; + case EXC_DV_COND_NOTEQUAL: return "notEqual"; + case EXC_DV_COND_GREATER: return "greaterThan"; + case EXC_DV_COND_LESS: return "lessThan"; + case EXC_DV_COND_EQGREATER: return "greaterThanOrEqual"; + case EXC_DV_COND_EQLESS: return "lessThanOrEqual"; + } + return nullptr; +} + +const char* lcl_GetErrorType( sal_uInt32 nFlags ) +{ + switch (nFlags & EXC_DV_ERROR_MASK) + { + case EXC_DV_ERROR_STOP: return "stop"; + case EXC_DV_ERROR_WARNING: return "warning"; + case EXC_DV_ERROR_INFO: return "information"; + } + return nullptr; +} + +void lcl_SetValidationText(const OUString& rText, XclExpString& rValidationText) +{ + if ( !rText.isEmpty() ) + { + // maximum length allowed in Excel is 255 characters + if ( rText.getLength() > 255 ) + { + OUStringBuffer aBuf( rText ); + rValidationText.Assign( + comphelper::string::truncateToLength(aBuf, 255).makeStringAndClear() ); + } + else + rValidationText.Assign( rText ); + } + else + rValidationText.Assign( '\0' ); +} + +} // namespace + +XclExpDV::XclExpDV( const XclExpRoot& rRoot, sal_uLong nScHandle ) : + XclExpRecord( EXC_ID_DV ), + XclExpRoot( rRoot ), + mnFlags( 0 ), + mnScHandle( nScHandle ) +{ + if( const ScValidationData* pValData = GetDoc().GetValidationEntry( mnScHandle ) ) + { + // prompt box - empty string represented by single NUL character + OUString aTitle, aText; + bool bShowPrompt = pValData->GetInput( aTitle, aText ); + lcl_SetValidationText(aTitle, maPromptTitle); + lcl_SetValidationText(aText, maPromptText); + + // error box - empty string represented by single NUL character + ScValidErrorStyle eScErrorStyle; + bool bShowError = pValData->GetErrMsg( aTitle, aText, eScErrorStyle ); + lcl_SetValidationText(aTitle, maErrorTitle); + lcl_SetValidationText(aText, maErrorText); + + // flags + switch( pValData->GetDataMode() ) + { + case SC_VALID_ANY: mnFlags |= EXC_DV_MODE_ANY; break; + case SC_VALID_WHOLE: mnFlags |= EXC_DV_MODE_WHOLE; break; + case SC_VALID_DECIMAL: mnFlags |= EXC_DV_MODE_DECIMAL; break; + case SC_VALID_LIST: mnFlags |= EXC_DV_MODE_LIST; break; + case SC_VALID_DATE: mnFlags |= EXC_DV_MODE_DATE; break; + case SC_VALID_TIME: mnFlags |= EXC_DV_MODE_TIME; break; + case SC_VALID_TEXTLEN: mnFlags |= EXC_DV_MODE_TEXTLEN; break; + case SC_VALID_CUSTOM: mnFlags |= EXC_DV_MODE_CUSTOM; break; + default: OSL_FAIL( "XclExpDV::XclExpDV - unknown mode" ); + } + + switch( pValData->GetOperation() ) + { + case ScConditionMode::NONE: + case ScConditionMode::Equal: mnFlags |= EXC_DV_COND_EQUAL; break; + case ScConditionMode::Less: mnFlags |= EXC_DV_COND_LESS; break; + case ScConditionMode::Greater: mnFlags |= EXC_DV_COND_GREATER; break; + case ScConditionMode::EqLess: mnFlags |= EXC_DV_COND_EQLESS; break; + case ScConditionMode::EqGreater: mnFlags |= EXC_DV_COND_EQGREATER; break; + case ScConditionMode::NotEqual: mnFlags |= EXC_DV_COND_NOTEQUAL; break; + case ScConditionMode::Between: mnFlags |= EXC_DV_COND_BETWEEN; break; + case ScConditionMode::NotBetween: mnFlags |= EXC_DV_COND_NOTBETWEEN; break; + default: OSL_FAIL( "XclExpDV::XclExpDV - unknown condition" ); + } + switch( eScErrorStyle ) + { + case SC_VALERR_STOP: mnFlags |= EXC_DV_ERROR_STOP; break; + case SC_VALERR_WARNING: mnFlags |= EXC_DV_ERROR_WARNING; break; + case SC_VALERR_INFO: mnFlags |= EXC_DV_ERROR_INFO; break; + case SC_VALERR_MACRO: + // set INFO for validity with macro call, delete title + mnFlags |= EXC_DV_ERROR_INFO; + maErrorTitle.Assign( '\0' ); // contains macro name + break; + default: OSL_FAIL( "XclExpDV::XclExpDV - unknown error style" ); + } + ::set_flag( mnFlags, EXC_DV_IGNOREBLANK, pValData->IsIgnoreBlank() ); + ::set_flag( mnFlags, EXC_DV_SUPPRESSDROPDOWN, pValData->GetListType() == css::sheet::TableValidationVisibility::INVISIBLE ); + ::set_flag( mnFlags, EXC_DV_SHOWPROMPT, bShowPrompt ); + ::set_flag( mnFlags, EXC_DV_SHOWERROR, bShowError ); + + // formulas + XclExpFormulaCompiler& rFmlaComp = GetFormulaCompiler(); + + // first formula + std::unique_ptr< ScTokenArray > xScTokArr = pValData->CreateFlatCopiedTokenArray( 0 ); + if (xScTokArr) + { + if( pValData->GetDataMode() == SC_VALID_LIST ) + { + OUString aString; + if( XclTokenArrayHelper::GetStringList( aString, *xScTokArr, '\n' ) ) + { + bool bList = false; + OUStringBuffer sListBuf; + OUStringBuffer sFormulaBuf; + sFormulaBuf.append( '"' ); + /* Formula is a list of string tokens -> build the Excel string. + Data validity is BIFF8 only (important for the XclExpString object). + Excel uses the NUL character as string list separator. */ + mxString1.reset( new XclExpString( XclStrFlags::EightBitLength ) ); + if (!aString.isEmpty()) + { + sal_Int32 nStringIx = 0; + for(;;) + { + const std::u16string_view aToken( o3tl::getToken(aString, 0, '\n', nStringIx ) ); + if (aToken.find(',') != std::u16string_view::npos) + { + sListBuf.append('"'); + sListBuf.append(aToken); + sListBuf.append('"'); + bList = true; + } + else + sListBuf.append(aToken); + mxString1->Append( aToken ); + sFormulaBuf.append( aToken ); + if (nStringIx<0) + break; + sal_Unicode cUnicodeChar = 0; + mxString1->Append( std::u16string_view(&cUnicodeChar, 1) ); + sFormulaBuf.append( ',' ); + sListBuf.append( ',' ); + } + } + ::set_flag( mnFlags, EXC_DV_STRINGLIST ); + + // maximum length allowed in Excel is 255 characters, and don't end with an empty token + // It should be 8192 but Excel doesn't accept it for unknown reason + // See also https://bugs.documentfoundation.org/show_bug.cgi?id=137167#c2 for more details + sal_uInt32 nLen = sFormulaBuf.getLength(); + if( nLen > 256 ) // 255 + beginning quote mark + { + nLen = 256; + if( sFormulaBuf[nLen - 1] == ',' ) + --nLen; + sFormulaBuf.truncate(nLen); + } + + sFormulaBuf.append( '"' ); + msFormula1 = sFormulaBuf.makeStringAndClear(); + if (bList) + msList = sListBuf.makeStringAndClear(); + else + sListBuf.remove(0, sListBuf.getLength()); + } + else + { + /* All other formulas in validation are stored like conditional + formatting formulas (with tRefN/tAreaN tokens as value or + array class). But NOT the cell references and defined names + in list validation - they are stored as reference class + tokens... Example: + 1) Cell must be equal to A1 -> formula is =A1 -> writes tRefNV token + 2) List is taken from A1 -> formula is =A1 -> writes tRefNR token + Formula compiler supports this by offering two different functions + CreateDataValFormula() and CreateListValFormula(). */ + if(GetOutput() == EXC_OUTPUT_BINARY) + mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_LISTVAL, *xScTokArr ); + else + msFormula1 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(), + xScTokArr.get()); + } + } + else + { + // no list validation -> convert the formula + if(GetOutput() == EXC_OUTPUT_BINARY) + mxTokArr1 = rFmlaComp.CreateFormula( EXC_FMLATYPE_DATAVAL, *xScTokArr ); + else + msFormula1 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(), + xScTokArr.get()); + } + } + + // second formula + xScTokArr = pValData->CreateFlatCopiedTokenArray( 1 ); + if (xScTokArr) + { + if(GetOutput() == EXC_OUTPUT_BINARY) + mxTokArr2 = rFmlaComp.CreateFormula( EXC_FMLATYPE_DATAVAL, *xScTokArr ); + else + msFormula2 = XclXmlUtils::ToOUString( GetCompileFormulaContext(), pValData->GetSrcPos(), + xScTokArr.get()); + } + } + else + { + OSL_FAIL( "XclExpDV::XclExpDV - missing core data" ); + mnScHandle = ULONG_MAX; + } +} + +XclExpDV::~XclExpDV() +{ +} + +void XclExpDV::InsertCellRange( const ScRange& rRange ) +{ + maScRanges.Join( rRange ); +} + +bool XclExpDV::Finalize() +{ + GetAddressConverter().ConvertRangeList( maXclRanges, maScRanges, true ); + return (mnScHandle != ULONG_MAX) && !maXclRanges.empty(); +} + +void XclExpDV::WriteBody( XclExpStream& rStrm ) +{ + // flags and strings + rStrm << mnFlags << maPromptTitle << maErrorTitle << maPromptText << maErrorText; + // condition formulas + if( mxString1 ) + lclWriteDvFormula( rStrm, *mxString1 ); + else + lclWriteDvFormula( rStrm, mxTokArr1.get() ); + lclWriteDvFormula( rStrm, mxTokArr2.get() ); + // cell ranges + rStrm << maXclRanges; +} + +void XclExpDV::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_dataValidation, + XML_allowBlank, ToPsz( ::get_flag( mnFlags, EXC_DV_IGNOREBLANK ) ), + XML_error, XESTRING_TO_PSZ( maErrorText ), + XML_errorStyle, lcl_GetErrorType(mnFlags), + XML_errorTitle, XESTRING_TO_PSZ( maErrorTitle ), + // OOXTODO: XML_imeMode, + XML_operator, lcl_GetOperatorType( mnFlags ), + XML_prompt, XESTRING_TO_PSZ( maPromptText ), + XML_promptTitle, XESTRING_TO_PSZ( maPromptTitle ), + // showDropDown should have been showNoDropDown - check oox/xlsx import for details + XML_showDropDown, ToPsz( ::get_flag( mnFlags, EXC_DV_SUPPRESSDROPDOWN ) ), + XML_showErrorMessage, ToPsz( ::get_flag( mnFlags, EXC_DV_SHOWERROR ) ), + XML_showInputMessage, ToPsz( ::get_flag( mnFlags, EXC_DV_SHOWPROMPT ) ), + XML_sqref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maScRanges), + XML_type, lcl_GetValidationType(mnFlags) ); + if (!msList.isEmpty()) + { + rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent), + FSNS(XML_xmlns, XML_x12ac),rStrm.getNamespaceURL(OOX_NS(x12ac)), + FSNS(XML_xmlns, XML_mc),rStrm.getNamespaceURL(OOX_NS(mce))); + rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x12ac"); + rWorksheet->startElement(FSNS(XML_x12ac, XML_list)); + rWorksheet->writeEscaped(msList); + rWorksheet->endElement(FSNS(XML_x12ac, XML_list)); + rWorksheet->endElement(FSNS(XML_mc, XML_Choice)); + rWorksheet->startElement(FSNS(XML_mc, XML_Fallback)); + rWorksheet->startElement(XML_formula1); + rWorksheet->writeEscaped(msFormula1); + rWorksheet->endElement(XML_formula1); + rWorksheet->endElement(FSNS(XML_mc, XML_Fallback)); + rWorksheet->endElement(FSNS(XML_mc, XML_AlternateContent)); + } + if (msList.isEmpty() && !msFormula1.isEmpty()) + { + rWorksheet->startElement(XML_formula1); + rWorksheet->writeEscaped( msFormula1 ); + rWorksheet->endElement( XML_formula1 ); + } + if( !msFormula2.isEmpty() ) + { + rWorksheet->startElement(XML_formula2); + rWorksheet->writeEscaped( msFormula2 ); + rWorksheet->endElement( XML_formula2 ); + } + rWorksheet->endElement( XML_dataValidation ); +} + +XclExpDval::XclExpDval( const XclExpRoot& rRoot ) : + XclExpRecord( EXC_ID_DVAL, 18 ), + XclExpRoot( rRoot ) +{ +} + +XclExpDval::~XclExpDval() +{ +} + +void XclExpDval::InsertCellRange( const ScRange& rRange, sal_uLong nScHandle ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + XclExpDV& rDVRec = SearchOrCreateDv( nScHandle ); + rDVRec.InsertCellRange( rRange ); + } +} + +void XclExpDval::Save( XclExpStream& rStrm ) +{ + // check all records + size_t nPos = maDVList.GetSize(); + while( nPos ) + { + --nPos; // backwards to keep nPos valid + XclExpDVRef xDVRec = maDVList.GetRecord( nPos ); + if( !xDVRec->Finalize() ) + maDVList.RemoveRecord( nPos ); + } + + // write the DVAL and the DV's + if( !maDVList.IsEmpty() ) + { + XclExpRecord::Save( rStrm ); + maDVList.Save( rStrm ); + } +} + +void XclExpDval::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maDVList.IsEmpty() ) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_dataValidations, + XML_count, OString::number(maDVList.GetSize()) + // OOXTODO: XML_disablePrompts, + // OOXTODO: XML_xWindow, + // OOXTODO: XML_yWindow + ); + maDVList.SaveXml( rStrm ); + rWorksheet->endElement( XML_dataValidations ); +} + +XclExpDV& XclExpDval::SearchOrCreateDv( sal_uLong nScHandle ) +{ + // test last found record + if( mxLastFoundDV && (mxLastFoundDV->GetScHandle() == nScHandle) ) + return *mxLastFoundDV; + + // binary search + size_t nCurrPos = 0; + if( !maDVList.IsEmpty() ) + { + size_t nFirstPos = 0; + size_t nLastPos = maDVList.GetSize() - 1; + bool bLoop = true; + sal_uLong nCurrScHandle = ::std::numeric_limits< sal_uLong >::max(); + while( (nFirstPos <= nLastPos) && bLoop ) + { + nCurrPos = (nFirstPos + nLastPos) / 2; + mxLastFoundDV = maDVList.GetRecord( nCurrPos ); + nCurrScHandle = mxLastFoundDV->GetScHandle(); + if( nCurrScHandle == nScHandle ) + bLoop = false; + else if( nCurrScHandle < nScHandle ) + nFirstPos = nCurrPos + 1; + else if( nCurrPos ) + nLastPos = nCurrPos - 1; + else // special case for nLastPos = -1 + bLoop = false; + } + if( nCurrScHandle == nScHandle ) + return *mxLastFoundDV; + else if( nCurrScHandle < nScHandle ) + ++nCurrPos; + } + + // create new DV record + mxLastFoundDV = new XclExpDV( *this, nScHandle ); + maDVList.InsertRecord( mxLastFoundDV, nCurrPos ); + return *mxLastFoundDV; +} + +void XclExpDval::WriteBody( XclExpStream& rStrm ) +{ + rStrm.WriteZeroBytes( 10 ); + rStrm << EXC_DVAL_NOOBJ << static_cast< sal_uInt32 >( maDVList.GetSize() ); +} + +// Web Queries ================================================================ + +XclExpWebQuery::XclExpWebQuery( + const OUString& rRangeName, + const OUString& rUrl, + std::u16string_view rSource, + sal_Int32 nRefrSecs ) : + maDestRange( rRangeName ), + maUrl( rUrl ), + // refresh delay time: seconds -> minutes + mnRefresh( ulimit_cast< sal_Int16 >( (nRefrSecs + 59) / 60 ) ), + mbEntireDoc( false ) +{ + // comma separated list of HTML table names or indexes + OUString aNewTables; + OUString aAppendTable; + bool bExitLoop = false; + if (!rSource.empty()) + { + sal_Int32 nStringIx = 0; + do + { + OUString aToken( o3tl::getToken(rSource, 0, ';', nStringIx ) ); + mbEntireDoc = ScfTools::IsHTMLDocName( aToken ); + bExitLoop = mbEntireDoc || ScfTools::IsHTMLTablesName( aToken ); + if( !bExitLoop && ScfTools::GetHTMLNameFromName( aToken, aAppendTable ) ) + aNewTables = ScGlobal::addToken( aNewTables, aAppendTable, ',' ); + } + while (nStringIx>0 && !bExitLoop); + } + + if( !bExitLoop ) // neither HTML_all nor HTML_tables found + { + if( !aNewTables.isEmpty() ) + mxQryTables.reset( new XclExpString( aNewTables ) ); + else + mbEntireDoc = true; + } +} + +XclExpWebQuery::~XclExpWebQuery() +{ +} + +void XclExpWebQuery::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( !mbEntireDoc || !mxQryTables, "XclExpWebQuery::Save - illegal mode" ); + sal_uInt16 nFlags; + + // QSI record + rStrm.StartRecord( EXC_ID_QSI, 10 + maDestRange.GetSize() ); + rStrm << EXC_QSI_DEFAULTFLAGS + << sal_uInt16( 0x0010 ) + << sal_uInt16( 0x0012 ) + << sal_uInt32( 0x00000000 ) + << maDestRange; + rStrm.EndRecord(); + + // PARAMQRY record + nFlags = 0; + ::insert_value( nFlags, EXC_PQRYTYPE_WEBQUERY, 0, 3 ); + ::set_flag( nFlags, EXC_PQRY_WEBQUERY ); + ::set_flag( nFlags, EXC_PQRY_TABLES, !mbEntireDoc ); + rStrm.StartRecord( EXC_ID_PQRY, 12 ); + rStrm << nFlags + << sal_uInt16( 0x0000 ) + << sal_uInt16( 0x0001 ); + rStrm.WriteZeroBytes( 6 ); + rStrm.EndRecord(); + + // WQSTRING record + rStrm.StartRecord( EXC_ID_WQSTRING, maUrl.GetSize() ); + rStrm << maUrl; + rStrm.EndRecord(); + + // unknown record 0x0802 + rStrm.StartRecord( EXC_ID_0802, 16 + maDestRange.GetSize() ); + rStrm << EXC_ID_0802; // repeated record id ?!? + rStrm.WriteZeroBytes( 6 ); + rStrm << sal_uInt16( 0x0003 ) + << sal_uInt32( 0x00000000 ) + << sal_uInt16( 0x0010 ) + << maDestRange; + rStrm.EndRecord(); + + // WEBQRYSETTINGS record + nFlags = mxQryTables ? EXC_WQSETT_SPECTABLES : EXC_WQSETT_ALL; + rStrm.StartRecord( EXC_ID_WQSETT, 28 ); + rStrm << EXC_ID_WQSETT // repeated record id ?!? + << sal_uInt16( 0x0000 ) + << sal_uInt16( 0x0004 ) + << sal_uInt16( 0x0000 ) + << EXC_WQSETT_DEFAULTFLAGS + << nFlags; + rStrm.WriteZeroBytes( 10 ); + rStrm << mnRefresh // refresh delay in minutes + << EXC_WQSETT_FORMATFULL + << sal_uInt16( 0x0000 ); + rStrm.EndRecord(); + + // WEBQRYTABLES record + if( mxQryTables ) + { + rStrm.StartRecord( EXC_ID_WQTABLES, 4 + mxQryTables->GetSize() ); + rStrm << EXC_ID_WQTABLES // repeated record id ?!? + << sal_uInt16( 0x0000 ) + << *mxQryTables; // comma separated list of source tables + rStrm.EndRecord(); + } +} + +XclExpWebQueryBuffer::XclExpWebQueryBuffer( const XclExpRoot& rRoot ) +{ + SCTAB nScTab = rRoot.GetCurrScTab(); + SfxObjectShell* pShell = rRoot.GetDocShell(); + if( !pShell ) return; + ScfPropertySet aModelProp( pShell->GetModel() ); + if( !aModelProp.Is() ) return; + + Reference< XAreaLinks > xAreaLinks; + aModelProp.GetProperty( xAreaLinks, SC_UNO_AREALINKS ); + if( !xAreaLinks.is() ) return; + + for( sal_Int32 nIndex = 0, nCount = xAreaLinks->getCount(); nIndex < nCount; ++nIndex ) + { + Reference< XAreaLink > xAreaLink( xAreaLinks->getByIndex( nIndex ), UNO_QUERY ); + if( xAreaLink.is() ) + { + CellRangeAddress aDestRange( xAreaLink->getDestArea() ); + if( static_cast< SCTAB >( aDestRange.Sheet ) == nScTab ) + { + ScfPropertySet aLinkProp( xAreaLink ); + OUString aFilter; + if( aLinkProp.GetProperty( aFilter, SC_UNONAME_FILTER ) && + (aFilter == EXC_WEBQRY_FILTER) ) + { + // get properties + OUString /*aFilterOpt,*/ aUrl; + sal_Int32 nRefresh = 0; + +// aLinkProp.GetProperty( aFilterOpt, SC_UNONAME_FILTOPT ); + aLinkProp.GetProperty( aUrl, SC_UNONAME_LINKURL ); + aLinkProp.GetProperty( nRefresh, SC_UNONAME_REFDELAY ); + + OUString aAbsDoc( ScGlobal::GetAbsDocName( aUrl, pShell ) ); + INetURLObject aUrlObj( aAbsDoc ); + OUString aWebQueryUrl( aUrlObj.getFSysPath( FSysStyle::Dos ) ); + if( aWebQueryUrl.isEmpty() ) + aWebQueryUrl = aAbsDoc; + + // find range or create a new range + OUString aRangeName; + ScRange aScDestRange; + ScUnoConversion::FillScRange( aScDestRange, aDestRange ); + if( const ScRangeData* pRangeData = rRoot.GetNamedRanges().findByRange( aScDestRange ) ) + { + aRangeName = pRangeData->GetName(); + } + else + { + XclExpFormulaCompiler& rFmlaComp = rRoot.GetFormulaCompiler(); + XclExpNameManager& rNameMgr = rRoot.GetNameManager(); + + // create a new unique defined name containing the range + XclTokenArrayRef xTokArr = rFmlaComp.CreateFormula( EXC_FMLATYPE_WQUERY, aScDestRange ); + sal_uInt16 nNameIdx = rNameMgr.InsertUniqueName( aUrlObj.getBase(), xTokArr, nScTab ); + aRangeName = rNameMgr.GetOrigName( nNameIdx ); + } + + // create and store the web query record + if( !aRangeName.isEmpty() ) + AppendNewRecord( new XclExpWebQuery( + aRangeName, aWebQueryUrl, xAreaLink->getSourceArea(), nRefresh ) ); + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xedbdata.cxx b/sc/source/filter/excel/xedbdata.cxx new file mode 100644 index 000000000..350f6f70e --- /dev/null +++ b/sc/source/filter/excel/xedbdata.cxx @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 +#include +#include +#include +#include +#include + +using namespace oox; + +namespace { + +/** (So far) dummy implementation of table export for BIFF5/BIFF7. */ +class XclExpTablesImpl5 : public XclExpTables +{ +public: + explicit XclExpTablesImpl5( const XclExpRoot& rRoot ); + + virtual void Save( XclExpStream& rStrm ) override; + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +/** Implementation of table export for OOXML, so far dummy for BIFF8. */ +class XclExpTablesImpl8 : public XclExpTables +{ +public: + explicit XclExpTablesImpl8( const XclExpRoot& rRoot ); + + virtual void Save( XclExpStream& rStrm ) override; + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +} + +XclExpTablesImpl5::XclExpTablesImpl5( const XclExpRoot& rRoot ) : + XclExpTables( rRoot ) +{ +} + +void XclExpTablesImpl5::Save( XclExpStream& /*rStrm*/ ) +{ + // not implemented +} + +void XclExpTablesImpl5::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ + // not applicable +} + + +XclExpTablesImpl8::XclExpTablesImpl8( const XclExpRoot& rRoot ) : + XclExpTables( rRoot ) +{ +} + +void XclExpTablesImpl8::Save( XclExpStream& /*rStrm*/ ) +{ + // not implemented +} + +void XclExpTablesImpl8::SaveXml( XclExpXmlStream& rStrm ) +{ + + sax_fastparser::FSHelperPtr& pWorksheetStrm = rStrm.GetCurrentStream(); + pWorksheetStrm->startElement(XML_tableParts); + for (auto const& it : maTables) + { + OUString aRelId; + sax_fastparser::FSHelperPtr pTableStrm = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/tables/", "table", it.mnTableId), + XclXmlUtils::GetStreamName("../tables/", "table", it.mnTableId), + pWorksheetStrm->getOutputStream(), + CREATE_XL_CONTENT_TYPE("table"), + CREATE_OFFICEDOC_RELATION_TYPE("table"), + &aRelId); + + pWorksheetStrm->singleElement(XML_tablePart, FSNS(XML_r, XML_id), aRelId.toUtf8()); + + rStrm.PushStream( pTableStrm); + SaveTableXml( rStrm, it); + rStrm.PopStream(); + } + pWorksheetStrm->endElement( XML_tableParts); +} + + +XclExpTablesManager::XclExpTablesManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +XclExpTablesManager::~XclExpTablesManager() +{ +} + +void XclExpTablesManager::Initialize() +{ + // All non-const to be able to call RefreshTableColumnNames(). + ScDocument& rDoc = GetDoc(); + ScDBCollection* pDBColl = rDoc.GetDBCollection(); + if (!pDBColl) + return; + + ScDBCollection::NamedDBs& rDBs = pDBColl->getNamedDBs(); + if (rDBs.empty()) + return; + + sal_Int32 nTableId = 0; + for (const auto& rxDB : rDBs) + { + ScDBData* pDBData = rxDB.get(); + pDBData->RefreshTableColumnNames( &rDoc); // currently not in sync, so refresh + ScRange aRange( ScAddress::UNINITIALIZED); + pDBData->GetArea( aRange); + SCTAB nTab = aRange.aStart.Tab(); + TablesMapType::iterator it = maTablesMap.find( nTab); + if (it == maTablesMap.end()) + { + rtl::Reference< XclExpTables > pNew; + switch( GetBiff() ) + { + case EXC_BIFF5: + pNew = new XclExpTablesImpl5( GetRoot()); + break; + case EXC_BIFF8: + pNew = new XclExpTablesImpl8( GetRoot()); + break; + default: + assert(!"Unknown BIFF type!"); + continue; // for + } + ::std::pair< TablesMapType::iterator, bool > ins( maTablesMap.insert( ::std::make_pair( nTab, pNew))); + if (!ins.second) + { + assert(!"XclExpTablesManager::Initialize - XclExpTables insert failed"); + continue; // for + } + it = ins.first; + } + it->second->AppendTable( pDBData, ++nTableId); + } +} + +rtl::Reference< XclExpTables > XclExpTablesManager::GetTablesBySheet( SCTAB nTab ) +{ + TablesMapType::iterator it = maTablesMap.find(nTab); + return it == maTablesMap.end() ? nullptr : it->second; +} + +XclExpTables::Entry::Entry( const ScDBData* pData, sal_Int32 nTableId ) : + mpData(pData), mnTableId(nTableId) +{ +} + +XclExpTables::XclExpTables( const XclExpRoot& rRoot ) : + XclExpRoot(rRoot) +{ +} + +XclExpTables::~XclExpTables() +{ +} + +void XclExpTables::AppendTable( const ScDBData* pData, sal_Int32 nTableId ) +{ + maTables.emplace_back( pData, nTableId); +} + +void XclExpTables::SaveTableXml( XclExpXmlStream& rStrm, const Entry& rEntry ) +{ + const ScDBData& rData = *rEntry.mpData; + ScRange aRange( ScAddress::UNINITIALIZED); + rData.GetArea( aRange); + sax_fastparser::FSHelperPtr& pTableStrm = rStrm.GetCurrentStream(); + pTableStrm->startElement( XML_table, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + XML_id, OString::number( rEntry.mnTableId), + XML_name, rData.GetName().toUtf8(), + XML_displayName, rData.GetName().toUtf8(), + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aRange), + XML_headerRowCount, ToPsz10(rData.HasHeader()), + XML_totalsRowCount, ToPsz10(rData.HasTotals()), + XML_totalsRowShown, ToPsz10(rData.HasTotals()) // we don't support that but if there are totals they are shown + // OOXTODO: XML_comment, ..., + // OOXTODO: XML_connectionId, ..., + // OOXTODO: XML_dataCellStyle, ..., + // OOXTODO: XML_dataDxfId, ..., + // OOXTODO: XML_headerRowBorderDxfId, ..., + // OOXTODO: XML_headerRowCellStyle, ..., + // OOXTODO: XML_headerRowDxfId, ..., + // OOXTODO: XML_insertRow, ..., + // OOXTODO: XML_insertRowShift, ..., + // OOXTODO: XML_published, ..., + // OOXTODO: XML_tableBorderDxfId, ..., + // OOXTODO: XML_tableType, ..., + // OOXTODO: XML_totalsRowBorderDxfId, ..., + // OOXTODO: XML_totalsRowCellStyle, ..., + // OOXTODO: XML_totalsRowDxfId, ... + ); + + if (rData.HasAutoFilter()) + { + /* TODO: does this need to exclude totals row? */ + + /* TODO: in OOXML 12.3.21 Table Definition Part has information + * that an applied autoFilter has child elements + * . + * When not applied but buttons hidden, Excel writes, for example, + * */ + + ExcAutoFilterRecs aAutoFilter( rStrm.GetRoot(), aRange.aStart.Tab(), &rData); + aAutoFilter.SaveXml( rStrm); + } + + const std::vector< OUString >& rColNames = rData.GetTableColumnNames(); + if (!rColNames.empty()) + { + pTableStrm->startElement(XML_tableColumns, + XML_count, OString::number(aRange.aEnd.Col() - aRange.aStart.Col() + 1)); + + for (size_t i=0, n=rColNames.size(); i < n; ++i) + { + // OOXTODO: write once we support it, in + // which case we'd need start/endElement XML_tableColumn for such + // column. + + // OOXTODO: write once we support it. + + pTableStrm->singleElement( XML_tableColumn, + XML_id, OString::number(i+1), + XML_name, rColNames[i].toUtf8() + // OOXTODO: XML_dataCellStyle, ..., + // OOXTODO: XML_dataDxfId, ..., + // OOXTODO: XML_headerRowCellStyle, ..., + // OOXTODO: XML_headerRowDxfId, ..., + // OOXTODO: XML_queryTableFieldId, ..., + // OOXTODO: XML_totalsRowCellStyle, ..., + // OOXTODO: XML_totalsRowDxfId, ..., + // OOXTODO: XML_totalsRowFunction, ..., + // OOXTODO: XML_totalsRowLabel, ..., + // OOXTODO: XML_uniqueName, ... + ); + } + + pTableStrm->endElement( XML_tableColumns); + } + + // OOXTODO: write once we have table styles. + + pTableStrm->endElement( XML_table); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeescher.cxx b/sc/source/filter/excel/xeescher.cxx new file mode 100644 index 000000000..d166b172a --- /dev/null +++ b/sc/source/filter/excel/xeescher.cxx @@ -0,0 +1,2046 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::lang::XServiceInfo; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::drawing::XShapes; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::awt::XControlModel; +using ::com::sun::star::form::binding::XBindableValue; +using ::com::sun::star::form::binding::XListEntrySink; +using ::com::sun::star::script::ScriptEventDescriptor; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; +using ::oox::drawingml::DrawingML; +using ::oox::drawingml::ChartExport; +using namespace oox; + +namespace +{ + +const char *ToHorizAlign( SdrTextHorzAdjust eAdjust ) +{ + switch( eAdjust ) + { + case SDRTEXTHORZADJUST_CENTER: + return "center"; + case SDRTEXTHORZADJUST_RIGHT: + return "right"; + case SDRTEXTHORZADJUST_BLOCK: + return "justify"; + case SDRTEXTHORZADJUST_LEFT: + default: + return "left"; + } +} + +const char *ToVertAlign( SdrTextVertAdjust eAdjust ) +{ + switch( eAdjust ) + { + case SDRTEXTVERTADJUST_CENTER: + return "center"; + case SDRTEXTVERTADJUST_BOTTOM: + return "bottom"; + case SDRTEXTVERTADJUST_BLOCK: + return "justify"; + case SDRTEXTVERTADJUST_TOP: + default: + return "top"; + } +} + +void lcl_WriteAnchorVertex( sax_fastparser::FSHelperPtr const & rComments, const tools::Rectangle &aRect ) +{ + rComments->startElement(FSNS(XML_xdr, XML_col)); + rComments->writeEscaped( OUString::number( aRect.Left() ) ); + rComments->endElement( FSNS( XML_xdr, XML_col ) ); + rComments->startElement(FSNS(XML_xdr, XML_colOff)); + rComments->writeEscaped( OUString::number( aRect.Top() ) ); + rComments->endElement( FSNS( XML_xdr, XML_colOff ) ); + rComments->startElement(FSNS(XML_xdr, XML_row)); + rComments->writeEscaped( OUString::number( aRect.Right() ) ); + rComments->endElement( FSNS( XML_xdr, XML_row ) ); + rComments->startElement(FSNS(XML_xdr, XML_rowOff)); + rComments->writeEscaped( OUString::number( aRect.Bottom() ) ); + rComments->endElement( FSNS( XML_xdr, XML_rowOff ) ); +} + +tools::Long lcl_hmm2output(tools::Long value, bool bInEMU) +{ + return o3tl::convert(value, o3tl::Length::mm100, bInEMU ? o3tl::Length::emu : o3tl::Length::px); +} + +void lcl_GetFromTo( const XclExpRoot& rRoot, const tools::Rectangle &aRect, sal_Int32 nTab, tools::Rectangle &aFrom, tools::Rectangle &aTo, bool bInEMU = false ) +{ + sal_Int32 nCol = 0, nRow = 0; + sal_Int32 nColOff = 0, nRowOff= 0; + + const bool bRTL = rRoot.GetDoc().IsNegativePage( nTab ); + if (!bRTL) + { + while(true) + { + tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab ); + if( r.Left() <= aRect.Left() ) + { + nCol++; + nColOff = aRect.Left() - r.Left(); + } + if( r.Top() <= aRect.Top() ) + { + nRow++; + nRowOff = aRect.Top() - r.Top(); + } + if( r.Left() > aRect.Left() && r.Top() > aRect.Top() ) + { + aFrom = tools::Rectangle( nCol-1, lcl_hmm2output( nColOff, bInEMU ), + nRow-1, lcl_hmm2output( nRowOff, bInEMU ) ); + break; + } + } + } + else + { + while(true) + { + tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab ); + if( r.Left() >= aRect.Left() ) + { + nCol++; + nColOff = r.Left() - aRect.Left(); + } + if( r.Top() <= aRect.Top() ) + { + nRow++; + nRowOff = aRect.Top() - r.Top(); + } + if( r.Left() < aRect.Left() && r.Top() > aRect.Top() ) + { + aFrom = tools::Rectangle( nCol-1, lcl_hmm2output( nColOff, bInEMU ), + nRow-1, lcl_hmm2output( nRowOff, bInEMU ) ); + break; + } + } + } + if (!bRTL) + { + while(true) + { + tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab ); + if( r.Right() < aRect.Right() ) + nCol++; + if( r.Bottom() < aRect.Bottom() ) + nRow++; + if( r.Right() >= aRect.Right() && r.Bottom() >= aRect.Bottom() ) + { + aTo = tools::Rectangle( nCol, lcl_hmm2output( aRect.Right() - r.Left(), bInEMU ), + nRow, lcl_hmm2output( aRect.Bottom() - r.Top(), bInEMU )); + break; + } + } + } + else + { + while(true) + { + tools::Rectangle r = rRoot.GetDoc().GetMMRect( nCol,nRow,nCol,nRow,nTab ); + if( r.Right() >= aRect.Right() ) + nCol++; + if( r.Bottom() < aRect.Bottom() ) + nRow++; + if( r.Right() < aRect.Right() && r.Bottom() >= aRect.Bottom() ) + { + aTo = tools::Rectangle( nCol, lcl_hmm2output( r.Left() - aRect.Right(), bInEMU ), + nRow, lcl_hmm2output( aRect.Bottom() - r.Top(), bInEMU )); + break; + } + } + } +} + +} // namespace + +// Escher client anchor ======================================================= + +XclExpDffAnchorBase::XclExpDffAnchorBase( const XclExpRoot& rRoot, sal_uInt16 nFlags ) : + XclExpRoot( rRoot ), + mnFlags( nFlags ) +{ +} + +void XclExpDffAnchorBase::SetFlags( const SdrObject& rSdrObj ) +{ + ImplSetFlags( rSdrObj ); +} + +void XclExpDffAnchorBase::SetSdrObject( const SdrObject& rSdrObj ) +{ + ImplSetFlags( rSdrObj ); + ImplCalcAnchorRect( rSdrObj.GetCurrentBoundRect(), MapUnit::Map100thMM ); +} + +void XclExpDffAnchorBase::WriteDffData( EscherEx& rEscherEx ) const +{ + rEscherEx.AddAtom( 18, ESCHER_ClientAnchor ); + rEscherEx.GetStream().WriteUInt16( mnFlags ); + WriteXclObjAnchor( rEscherEx.GetStream(), maAnchor ); +} + +void XclExpDffAnchorBase::WriteData( EscherEx& rEscherEx, const tools::Rectangle& rRect ) +{ + // the passed rectangle is in twips + ImplCalcAnchorRect( rRect, MapUnit::MapTwip ); + WriteDffData( rEscherEx ); +} + +void XclExpDffAnchorBase::ImplSetFlags( const SdrObject& ) +{ + OSL_FAIL( "XclExpDffAnchorBase::ImplSetFlags - not implemented" ); +} + +void XclExpDffAnchorBase::ImplCalcAnchorRect( const tools::Rectangle&, MapUnit ) +{ + OSL_FAIL( "XclExpDffAnchorBase::ImplCalcAnchorRect - not implemented" ); +} + +XclExpDffSheetAnchor::XclExpDffSheetAnchor( const XclExpRoot& rRoot ) : + XclExpDffAnchorBase( rRoot ), + mnScTab( rRoot.GetCurrScTab() ) +{ +} + +void XclExpDffSheetAnchor::ImplSetFlags( const SdrObject& rSdrObj ) +{ + // set flags for cell/page anchoring + if ( ScDrawLayer::GetAnchorType( rSdrObj ) == SCA_CELL ) + mnFlags = 0; + else + mnFlags = EXC_ESC_ANCHOR_LOCKED; +} + +void XclExpDffSheetAnchor::ImplCalcAnchorRect( const tools::Rectangle& rRect, MapUnit eMapUnit ) +{ + maAnchor.SetRect( GetRoot(), mnScTab, rRect, eMapUnit ); +} + +XclExpDffEmbeddedAnchor::XclExpDffEmbeddedAnchor( const XclExpRoot& rRoot, + const Size& rPageSize, sal_Int32 nScaleX, sal_Int32 nScaleY ) : + XclExpDffAnchorBase( rRoot ), + maPageSize( rPageSize ), + mnScaleX( nScaleX ), + mnScaleY( nScaleY ) +{ +} + +void XclExpDffEmbeddedAnchor::ImplSetFlags( const SdrObject& /*rSdrObj*/ ) +{ + // TODO (unsupported feature): fixed size +} + +void XclExpDffEmbeddedAnchor::ImplCalcAnchorRect( const tools::Rectangle& rRect, MapUnit eMapUnit ) +{ + maAnchor.SetRect( maPageSize, mnScaleX, mnScaleY, rRect, eMapUnit ); +} + +XclExpDffNoteAnchor::XclExpDffNoteAnchor( const XclExpRoot& rRoot, const tools::Rectangle& rRect ) : + XclExpDffAnchorBase( rRoot, EXC_ESC_ANCHOR_SIZELOCKED ) +{ + maAnchor.SetRect( rRoot, rRoot.GetCurrScTab(), rRect, MapUnit::Map100thMM ); +} + +XclExpDffDropDownAnchor::XclExpDffDropDownAnchor( const XclExpRoot& rRoot, const ScAddress& rScPos ) : + XclExpDffAnchorBase( rRoot, EXC_ESC_ANCHOR_POSLOCKED ) +{ + GetAddressConverter().ConvertAddress( maAnchor.maFirst, rScPos, true ); + maAnchor.maLast.mnCol = maAnchor.maFirst.mnCol + 1; + maAnchor.maLast.mnRow = maAnchor.maFirst.mnRow + 1; + maAnchor.mnLX = maAnchor.mnTY = maAnchor.mnRX = maAnchor.mnBY = 0; +} + +// MSODRAWING* records ======================================================== + +XclExpMsoDrawingBase::XclExpMsoDrawingBase( XclEscherEx& rEscherEx, sal_uInt16 nRecId ) : + XclExpRecord( nRecId ), + mrEscherEx( rEscherEx ), + mnFragmentKey( rEscherEx.InitNextDffFragment() ) +{ +} + +void XclExpMsoDrawingBase::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE( mrEscherEx.GetStreamPos() == mrEscherEx.GetDffFragmentPos( mnFragmentKey ), + "XclExpMsoDrawingBase::WriteBody - DFF stream position mismatch" ); + rStrm.CopyFromStream( mrEscherEx.GetStream(), mrEscherEx.GetDffFragmentSize( mnFragmentKey ) ); +} + +XclExpMsoDrawingGroup::XclExpMsoDrawingGroup( XclEscherEx& rEscherEx ) : + XclExpMsoDrawingBase( rEscherEx, EXC_ID_MSODRAWINGGROUP ) +{ + SvStream& rDffStrm = mrEscherEx.GetStream(); + + // write the DGGCONTAINER with some default settings + mrEscherEx.OpenContainer( ESCHER_DggContainer ); + + // TODO: stuff the OPT atom with our own document defaults? + static const sal_uInt8 spnDffOpt[] = { + 0xBF, 0x00, 0x08, 0x00, 0x08, 0x00, 0x81, 0x01, + 0x09, 0x00, 0x00, 0x08, 0xC0, 0x01, 0x40, 0x00, + 0x00, 0x08 + }; + mrEscherEx.AddAtom( sizeof( spnDffOpt ), ESCHER_OPT, 3, 3 ); + rDffStrm.WriteBytes(spnDffOpt, sizeof(spnDffOpt)); + + // SPLITMENUCOLORS contains colors in toolbar + static const sal_uInt8 spnDffSplitMenuColors[] = { + 0x0D, 0x00, 0x00, 0x08, 0x0C, 0x00, 0x00, 0x08, + 0x17, 0x00, 0x00, 0x08, 0xF7, 0x00, 0x00, 0x10 + }; + mrEscherEx.AddAtom( sizeof( spnDffSplitMenuColors ), ESCHER_SplitMenuColors, 0, 4 ); + rDffStrm.WriteBytes(spnDffSplitMenuColors, sizeof(spnDffSplitMenuColors)); + + // close the DGGCONTAINER + mrEscherEx.CloseContainer(); + mrEscherEx.UpdateDffFragmentEnd(); +} + +XclExpMsoDrawing::XclExpMsoDrawing( XclEscherEx& rEscherEx ) : + XclExpMsoDrawingBase( rEscherEx, EXC_ID_MSODRAWING ) +{ +} + +XclExpImgData::XclExpImgData( const Graphic& rGraphic, sal_uInt16 nRecId ) : + maGraphic( rGraphic ), + mnRecId( nRecId ) +{ +} + +void XclExpImgData::Save( XclExpStream& rStrm ) +{ + Bitmap aBmp = maGraphic.GetBitmapEx().GetBitmap(); + if (aBmp.getPixelFormat() != vcl::PixelFormat::N24_BPP) + aBmp.Convert( BmpConversion::N24Bit ); + + Bitmap::ScopedReadAccess pAccess(aBmp); + if( !pAccess ) + return; + + sal_Int32 nWidth = ::std::min< sal_Int32 >( pAccess->Width(), 0xFFFF ); + sal_Int32 nHeight = ::std::min< sal_Int32 >( pAccess->Height(), 0xFFFF ); + if( (nWidth <= 0) || (nHeight <= 0) ) + return; + + sal_uInt8 nPadding = static_cast< sal_uInt8 >( nWidth & 0x03 ); + sal_uInt32 nTmpSize = static_cast< sal_uInt32 >( (nWidth * 3 + nPadding) * nHeight + 12 ); + + rStrm.StartRecord( mnRecId, nTmpSize + 4 ); + + rStrm << EXC_IMGDATA_BMP // BMP format + << EXC_IMGDATA_WIN // Windows + << nTmpSize // size after _this_ field + << sal_uInt32( 12 ) // BITMAPCOREHEADER size + << static_cast< sal_uInt16 >( nWidth ) // width + << static_cast< sal_uInt16 >( nHeight ) // height + << sal_uInt16( 1 ) // planes + << sal_uInt16( 24 ); // bits per pixel + + for( sal_Int32 nY = nHeight - 1; nY >= 0; --nY ) + { + Scanline pScanline = pAccess->GetScanline( nY ); + for( sal_Int32 nX = 0; nX < nWidth; ++nX ) + { + const BitmapColor& rBmpColor = pAccess->GetPixelFromData( pScanline, nX ); + rStrm << rBmpColor.GetBlue() << rBmpColor.GetGreen() << rBmpColor.GetRed(); + } + rStrm.WriteZeroBytes( nPadding ); + } + + rStrm.EndRecord(); +} + +void XclExpImgData::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr pWorksheet = rStrm.GetCurrentStream(); + + DrawingML aDML(pWorksheet, &rStrm, drawingml::DOCUMENT_XLSX); + OUString rId = aDML.WriteImage( maGraphic ); + pWorksheet->singleElement(XML_picture, FSNS(XML_r, XML_id), rId); +} + +XclExpControlHelper::XclExpControlHelper( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnEntryCount( 0 ) +{ +} + +XclExpControlHelper::~XclExpControlHelper() +{ +} + +void XclExpControlHelper::ConvertSheetLinks( Reference< XShape > const & xShape ) +{ + mxCellLink.reset(); + mxCellLinkAddress.SetInvalid(); + mxSrcRange.reset(); + mnEntryCount = 0; + + // get control model + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( xShape ); + if( !xCtrlModel.is() ) + return; + + // *** cell link *** ------------------------------------------------------ + + Reference< XBindableValue > xBindable( xCtrlModel, UNO_QUERY ); + if( xBindable.is() ) + { + Reference< XServiceInfo > xServInfo( xBindable->getValueBinding(), UNO_QUERY ); + if( xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_VALBIND ) ) + { + ScfPropertySet aBindProp( xServInfo ); + CellAddress aApiAddress; + if( aBindProp.GetProperty( aApiAddress, SC_UNONAME_BOUNDCELL ) ) + { + ScUnoConversion::FillScAddress( mxCellLinkAddress, aApiAddress ); + if( GetTabInfo().IsExportTab( mxCellLinkAddress.Tab() ) ) + mxCellLink = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CONTROL, mxCellLinkAddress ); + } + } + } + + // *** source range *** --------------------------------------------------- + + Reference< XListEntrySink > xEntrySink( xCtrlModel, UNO_QUERY ); + if( !xEntrySink.is() ) + return; + + Reference< XServiceInfo > xServInfo( xEntrySink->getListEntrySource(), UNO_QUERY ); + if( !(xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_LISTSOURCE )) ) + return; + + ScfPropertySet aSinkProp( xServInfo ); + CellRangeAddress aApiRange; + if( aSinkProp.GetProperty( aApiRange, SC_UNONAME_CELLRANGE ) ) + { + ScRange aSrcRange; + ScUnoConversion::FillScRange( aSrcRange, aApiRange ); + if( (aSrcRange.aStart.Tab() == aSrcRange.aEnd.Tab()) && GetTabInfo().IsExportTab( aSrcRange.aStart.Tab() ) ) + mxSrcRange = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CONTROL, aSrcRange ); + mnEntryCount = static_cast< sal_uInt16 >( aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1 ); + } +} + +void XclExpControlHelper::WriteFormula( XclExpStream& rStrm, const XclTokenArray& rTokArr ) +{ + sal_uInt16 nFmlaSize = rTokArr.GetSize(); + rStrm << nFmlaSize << sal_uInt32( 0 ); + rTokArr.WriteArray( rStrm ); + if( nFmlaSize & 1 ) // pad to 16-bit + rStrm << sal_uInt8( 0 ); +} + +void XclExpControlHelper::WriteFormulaSubRec( XclExpStream& rStrm, sal_uInt16 nSubRecId, const XclTokenArray& rTokArr ) +{ + rStrm.StartRecord( nSubRecId, (rTokArr.GetSize() + 5) & ~1 ); + WriteFormula( rStrm, rTokArr ); + rStrm.EndRecord(); +} + +//delete for exporting OCX +//#if EXC_EXP_OCX_CTRL + +XclExpOcxControlObj::XclExpOcxControlObj( XclExpObjectManager& rObjMgr, Reference< XShape > const & xShape, + const tools::Rectangle* pChildAnchor, const OUString& rClassName, sal_uInt32 nStrmStart, sal_uInt32 nStrmSize ) : + XclObj( rObjMgr, EXC_OBJTYPE_PICTURE, true ), + XclExpControlHelper( rObjMgr.GetRoot() ), + maClassName( rClassName ), + mnStrmStart( nStrmStart ), + mnStrmSize( nStrmSize ) +{ + ScfPropertySet aCtrlProp( XclControlHelper::GetControlModel( xShape ) ); + + // OBJ record flags + SetLocked( true ); + SetPrintable( aCtrlProp.GetBoolProperty( "Printable" ) ); + SetAutoFill( false ); + SetAutoLine( false ); + + // fill DFF property set + mrEscherEx.OpenContainer( ESCHER_SpContainer ); + mrEscherEx.AddShape( ESCHER_ShpInst_HostControl, + ShapeFlag::HaveShapeProperty | ShapeFlag::HaveAnchor | ShapeFlag::OLEShape ); + tools::Rectangle aDummyRect; + EscherPropertyContainer aPropOpt( mrEscherEx.GetGraphicProvider(), mrEscherEx.QueryPictureStream(), aDummyRect ); + aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape, 0x00080008 ); // bool field + aPropOpt.AddOpt( ESCHER_Prop_lineColor, 0x08000040 ); + aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash, 0x00080000 ); // bool field + + // #i51348# name of the control, may overwrite shape name + OUString aCtrlName; + if( aCtrlProp.GetProperty( aCtrlName, "Name" ) && !aCtrlName.isEmpty() ) + aPropOpt.AddOpt( ESCHER_Prop_wzName, aCtrlName ); + + // meta file + //TODO - needs check + Reference< XPropertySet > xShapePS( xShape, UNO_QUERY ); + if( xShapePS.is() && aPropOpt.CreateGraphicProperties( xShapePS, "MetaFile", false ) ) + { + sal_uInt32 nBlipId; + if( aPropOpt.GetOpt( ESCHER_Prop_pib, nBlipId ) ) + aPropOpt.AddOpt( ESCHER_Prop_pictureId, nBlipId ); + } + + // write DFF property set to stream + aPropOpt.Commit( mrEscherEx.GetStream() ); + + // anchor + ImplWriteAnchor( SdrObject::getSdrObjectFromXShape( xShape ), pChildAnchor ); + + mrEscherEx.AddAtom( 0, ESCHER_ClientData ); // OBJ record + mrEscherEx.CloseContainer(); // ESCHER_SpContainer + mrEscherEx.UpdateDffFragmentEnd(); + + // spreadsheet links + ConvertSheetLinks( xShape ); +} + +void XclExpOcxControlObj::WriteSubRecs( XclExpStream& rStrm ) +{ + // OBJCF - clipboard format + rStrm.StartRecord( EXC_ID_OBJCF, 2 ); + rStrm << sal_uInt16( 2 ); + rStrm.EndRecord(); + + // OBJFLAGS + rStrm.StartRecord( EXC_ID_OBJFLAGS, 2 ); + rStrm << sal_uInt16( 0x0031 ); + rStrm.EndRecord(); + + // OBJPICTFMLA + XclExpString aClass( maClassName ); + sal_uInt16 nClassNameSize = static_cast< sal_uInt16 >( aClass.GetSize() ); + sal_uInt16 nClassNamePad = nClassNameSize & 1; + sal_uInt16 nFirstPartSize = 12 + nClassNameSize + nClassNamePad; + + const XclTokenArray* pCellLink = GetCellLinkTokArr(); + sal_uInt16 nCellLinkSize = pCellLink ? ((pCellLink->GetSize() + 7) & 0xFFFE) : 0; + + const XclTokenArray* pSrcRange = GetSourceRangeTokArr(); + sal_uInt16 nSrcRangeSize = pSrcRange ? ((pSrcRange->GetSize() + 7) & 0xFFFE) : 0; + + sal_uInt16 nPictFmlaSize = nFirstPartSize + nCellLinkSize + nSrcRangeSize + 18; + rStrm.StartRecord( EXC_ID_OBJPICTFMLA, nPictFmlaSize ); + + rStrm << nFirstPartSize // size of first part + << sal_uInt16( 5 ) // formula size + << sal_uInt32( 0 ) // unknown ID + << sal_uInt8( 0x02 ) << sal_uInt32( 0 ) // tTbl token with unknown ID + << sal_uInt8( 3 ) // pad to word + << aClass; // "Forms.***.1" + rStrm.WriteZeroBytes( nClassNamePad ); // pad to word + rStrm << mnStrmStart // start in 'Ctls' stream + << mnStrmSize // size in 'Ctls' stream + << sal_uInt32( 0 ); // class ID size + // cell link + rStrm << nCellLinkSize; + if( pCellLink ) + WriteFormula( rStrm, *pCellLink ); + // list source range + rStrm << nSrcRangeSize; + if( pSrcRange ) + WriteFormula( rStrm, *pSrcRange ); + + rStrm.EndRecord(); +} + +//#else + +XclExpTbxControlObj::XclExpTbxControlObj( XclExpObjectManager& rRoot, Reference< XShape > const & xShape , const tools::Rectangle* pChildAnchor ) : + XclObj( rRoot, EXC_OBJTYPE_UNKNOWN, true ), + XclMacroHelper( rRoot ), + mxShape( xShape ), + meEventType( EXC_TBX_EVENT_ACTION ), + mnHeight( 0 ), + mnState( 0 ), + mnLineCount( 0 ), + mnSelEntry( 0 ), + mnScrollValue( 0 ), + mnScrollMin( 0 ), + mnScrollMax( 100 ), + mnScrollStep( 1 ), + mnScrollPage( 10 ), + mbFlatButton( false ), + mbFlatBorder( false ), + mbMultiSel( false ), + mbScrollHor( false ), + mbPrint( false ), + mbVisible( false ), + mnShapeId( 0 ), + mrRoot(rRoot) +{ + namespace FormCompType = css::form::FormComponentType; + namespace AwtVisualEffect = css::awt::VisualEffect; + namespace AwtScrollOrient = css::awt::ScrollBarOrientation; + + ScfPropertySet aCtrlProp( XclControlHelper::GetControlModel( xShape ) ); + if( !xShape.is() || !aCtrlProp.Is() ) + return; + + mnHeight = xShape->getSize().Height; + if( mnHeight <= 0 ) + return; + + // control type + sal_Int16 nClassId = 0; + if( aCtrlProp.GetProperty( nClassId, "ClassId" ) ) + { + switch( nClassId ) + { + case FormCompType::COMMANDBUTTON: mnObjType = EXC_OBJTYPE_BUTTON; meEventType = EXC_TBX_EVENT_ACTION; break; + case FormCompType::RADIOBUTTON: mnObjType = EXC_OBJTYPE_OPTIONBUTTON; meEventType = EXC_TBX_EVENT_ACTION; break; + case FormCompType::CHECKBOX: mnObjType = EXC_OBJTYPE_CHECKBOX; meEventType = EXC_TBX_EVENT_ACTION; break; + case FormCompType::LISTBOX: mnObjType = EXC_OBJTYPE_LISTBOX; meEventType = EXC_TBX_EVENT_CHANGE; break; + case FormCompType::COMBOBOX: mnObjType = EXC_OBJTYPE_DROPDOWN; meEventType = EXC_TBX_EVENT_CHANGE; break; + case FormCompType::GROUPBOX: mnObjType = EXC_OBJTYPE_GROUPBOX; meEventType = EXC_TBX_EVENT_MOUSE; break; + case FormCompType::FIXEDTEXT: mnObjType = EXC_OBJTYPE_LABEL; meEventType = EXC_TBX_EVENT_MOUSE; break; + case FormCompType::SCROLLBAR: mnObjType = EXC_OBJTYPE_SCROLLBAR; meEventType = EXC_TBX_EVENT_VALUE; break; + case FormCompType::SPINBUTTON: mnObjType = EXC_OBJTYPE_SPIN; meEventType = EXC_TBX_EVENT_VALUE; break; + } + } + if( mnObjType == EXC_OBJTYPE_UNKNOWN ) + return; + + // OBJ record flags + SetLocked( true ); + mbPrint = aCtrlProp.GetBoolProperty( "Printable" ); + SetPrintable( mbPrint ); + SetAutoFill( false ); + SetAutoLine( false ); + + // fill DFF property set + mrEscherEx.OpenContainer( ESCHER_SpContainer ); + mrEscherEx.AddShape( ESCHER_ShpInst_HostControl, ShapeFlag::HaveAnchor | ShapeFlag::HaveShapeProperty ); + EscherPropertyContainer aPropOpt; + mbVisible = aCtrlProp.GetBoolProperty( "EnableVisible" ); + aPropOpt.AddOpt( ESCHER_Prop_fPrint, mbVisible ? 0x00080000 : 0x00080002 ); // visible flag + + aPropOpt.AddOpt( ESCHER_Prop_LockAgainstGrouping, 0x01000100 ); // bool field + aPropOpt.AddOpt( ESCHER_Prop_lTxid, 0 ); // Text ID + aPropOpt.AddOpt( ESCHER_Prop_WrapText, 0x00000001 ); + aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape, 0x001A0008 ); // bool field + aPropOpt.AddOpt( ESCHER_Prop_fNoFillHitTest, 0x00100000 ); // bool field + aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash, 0x00080000 ); // bool field + + // #i51348# name of the control, may overwrite shape name + if( aCtrlProp.GetProperty( msCtrlName, "Name" ) && !msCtrlName.isEmpty() ) + aPropOpt.AddOpt( ESCHER_Prop_wzName, msCtrlName ); + + //Export description as alt text + if( SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape ) ) + { + OUString aAltTxt; + OUString aDescrText = pSdrObj->GetDescription(); + if(!aDescrText.isEmpty()) + aAltTxt = aDescrText.copy( 0, std::min(MSPROP_DESCRIPTION_MAX_LEN, aDescrText.getLength()) ); + aPropOpt.AddOpt( ESCHER_Prop_wzDescription, aAltTxt ); + } + + // write DFF property set to stream + aPropOpt.Commit( mrEscherEx.GetStream() ); + + // anchor + ImplWriteAnchor( SdrObject::getSdrObjectFromXShape( xShape ), pChildAnchor ); + + mrEscherEx.AddAtom( 0, ESCHER_ClientData ); // OBJ record + mrEscherEx.UpdateDffFragmentEnd(); + + // control label + if( aCtrlProp.GetProperty( msLabel, "Label" ) ) + { + /* Be sure to construct the MSODRAWING record containing the + ClientTextbox atom after the base OBJ's MSODRAWING record data is + completed. */ + pClientTextbox.reset( new XclExpMsoDrawing( mrEscherEx ) ); + mrEscherEx.AddAtom( 0, ESCHER_ClientTextbox ); // TXO record + mrEscherEx.UpdateDffFragmentEnd(); + + sal_uInt16 nXclFont = EXC_FONT_APP; + if( !msLabel.isEmpty() ) + { + XclFontData aFontData; + GetFontPropSetHelper().ReadFontProperties( aFontData, aCtrlProp, EXC_FONTPROPSET_CONTROL ); + if( (!aFontData.maName.isEmpty() ) && (aFontData.mnHeight > 0) ) + nXclFont = GetFontBuffer().Insert( aFontData, EXC_COLOR_CTRLTEXT ); + } + + pTxo.reset( new XclTxo( msLabel, nXclFont ) ); + pTxo->SetHorAlign( (mnObjType == EXC_OBJTYPE_BUTTON) ? EXC_OBJ_HOR_CENTER : EXC_OBJ_HOR_LEFT ); + pTxo->SetVerAlign( EXC_OBJ_VER_CENTER ); + } + + mrEscherEx.CloseContainer(); // ESCHER_SpContainer + + // other properties + aCtrlProp.GetProperty( mnLineCount, "LineCount" ); + + // border style + sal_Int16 nApiButton = AwtVisualEffect::LOOK3D; + sal_Int16 nApiBorder = AwtVisualEffect::LOOK3D; + switch( nClassId ) + { + case FormCompType::LISTBOX: + case FormCompType::COMBOBOX: + aCtrlProp.GetProperty( nApiBorder, "Border" ); + break; + case FormCompType::CHECKBOX: + case FormCompType::RADIOBUTTON: + aCtrlProp.GetProperty( nApiButton, "VisualEffect" ); + nApiBorder = AwtVisualEffect::NONE; + break; + // Push button cannot be set to flat in Excel + case FormCompType::COMMANDBUTTON: + nApiBorder = AwtVisualEffect::LOOK3D; + break; + // Label does not support a border in Excel + case FormCompType::FIXEDTEXT: + nApiBorder = AwtVisualEffect::NONE; + break; + /* Scroll bar and spin button have a "Border" property, but it is + really used for a border, and not for own 3D/flat look (#i34712#). */ + case FormCompType::SCROLLBAR: + case FormCompType::SPINBUTTON: + nApiButton = AwtVisualEffect::LOOK3D; + nApiBorder = AwtVisualEffect::NONE; + break; + // Group box does not support flat style (#i34712#) + case FormCompType::GROUPBOX: + nApiBorder = AwtVisualEffect::LOOK3D; + break; + } + mbFlatButton = nApiButton != AwtVisualEffect::LOOK3D; + mbFlatBorder = nApiBorder != AwtVisualEffect::LOOK3D; + + // control state + sal_Int16 nApiState = 0; + if( aCtrlProp.GetProperty( nApiState, "State" ) ) + { + switch( nApiState ) + { + case 0: mnState = EXC_OBJ_CHECKBOX_UNCHECKED; break; + case 1: mnState = EXC_OBJ_CHECKBOX_CHECKED; break; + case 2: mnState = EXC_OBJ_CHECKBOX_TRISTATE; break; + } + } + + // special control contents + switch( nClassId ) + { + case FormCompType::LISTBOX: + { + mbMultiSel = aCtrlProp.GetBoolProperty( "MultiSelection" ); + Sequence< sal_Int16 > aSelection; + if( aCtrlProp.GetProperty( aSelection, "SelectedItems" ) ) + { + if( aSelection.hasElements() ) + { + mnSelEntry = aSelection[ 0 ] + 1; + comphelper::sequenceToContainer(maMultiSel, aSelection); + } + } + + // convert listbox with dropdown button to Excel dropdown + if( aCtrlProp.GetBoolProperty( "Dropdown" ) ) + mnObjType = EXC_OBJTYPE_DROPDOWN; + } + break; + + case FormCompType::COMBOBOX: + { + Sequence< OUString > aStringList; + OUString aDefText; + if( aCtrlProp.GetProperty( aStringList, "StringItemList" ) && + aCtrlProp.GetProperty( aDefText, "Text" ) && + aStringList.hasElements() && !aDefText.isEmpty() ) + { + auto nIndex = comphelper::findValue(aStringList, aDefText); + if( nIndex != -1 ) + mnSelEntry = static_cast< sal_Int16 >( nIndex + 1 ); // 1-based + if( mnSelEntry > 0 ) + maMultiSel.resize( 1, mnSelEntry - 1 ); + } + + // convert combobox without dropdown button to Excel listbox + if( !aCtrlProp.GetBoolProperty( "Dropdown" ) ) + mnObjType = EXC_OBJTYPE_LISTBOX; + } + break; + + case FormCompType::SCROLLBAR: + { + sal_Int32 nApiValue = 0; + if( aCtrlProp.GetProperty( nApiValue, "ScrollValueMin" ) ) + mnScrollMin = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "ScrollValueMax" ) ) + mnScrollMax = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "ScrollValue" ) ) + mnScrollValue = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, mnScrollMax ); + if( aCtrlProp.GetProperty( nApiValue, "LineIncrement" ) ) + mnScrollStep = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "BlockIncrement" ) ) + mnScrollPage = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "Orientation" ) ) + mbScrollHor = nApiValue == AwtScrollOrient::HORIZONTAL; + } + break; + + case FormCompType::SPINBUTTON: + { + sal_Int32 nApiValue = 0; + if( aCtrlProp.GetProperty( nApiValue, "SpinValueMin" ) ) + mnScrollMin = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "SpinValueMax" ) ) + mnScrollMax = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "SpinValue" ) ) + mnScrollValue = limit_cast< sal_uInt16 >( nApiValue, mnScrollMin, mnScrollMax ); + if( aCtrlProp.GetProperty( nApiValue, "SpinIncrement" ) ) + mnScrollStep = limit_cast< sal_uInt16 >( nApiValue, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + if( aCtrlProp.GetProperty( nApiValue, "Orientation" ) ) + mbScrollHor = nApiValue == AwtScrollOrient::HORIZONTAL; + } + break; + } + + { + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( xShape ); + if( xCtrlModel.is() ) + { + Reference< XBindableValue > xBindable( xCtrlModel, UNO_QUERY ); + if( xBindable.is() ) + { + Reference< XServiceInfo > xServInfo( xBindable->getValueBinding(), UNO_QUERY ); + if( xServInfo.is() && xServInfo->supportsService( SC_SERVICENAME_VALBIND ) ) + { + ScfPropertySet aBindProp( xServInfo ); + CellAddress aApiAddress; + if( aBindProp.GetProperty( aApiAddress, SC_UNONAME_BOUNDCELL ) ) + { + ScUnoConversion::FillScAddress( mxCellLinkAddress, aApiAddress ); + if( SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape ) ) + lcl_GetFromTo( rRoot, pSdrObj->GetLogicRect(), mxCellLinkAddress.Tab(), maAreaFrom, maAreaTo, true ); + } + } + } + } + } + + // spreadsheet links + ConvertSheetLinks( xShape ); +} + +bool XclExpTbxControlObj::SetMacroLink( const ScriptEventDescriptor& rEvent ) +{ + return XclMacroHelper::SetMacroLink( rEvent, meEventType ); +} + +void XclExpTbxControlObj::WriteSubRecs( XclExpStream& rStrm ) +{ + switch( mnObjType ) + { + // *** Push buttons, labels *** + + case EXC_OBJTYPE_BUTTON: + case EXC_OBJTYPE_LABEL: + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + break; + + // *** Check boxes, option buttons *** + + case EXC_OBJTYPE_CHECKBOX: + case EXC_OBJTYPE_OPTIONBUTTON: + { + // ftCbls - box properties + sal_uInt16 nStyle = 0; + ::set_flag( nStyle, EXC_OBJ_CHECKBOX_FLAT, mbFlatButton ); + + rStrm.StartRecord( EXC_ID_OBJCBLS, 12 ); + rStrm << mnState; + rStrm.WriteZeroBytes( 8 ); + rStrm << nStyle; + rStrm.EndRecord(); + + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + // ftCblsFmla subrecord - cell link + WriteCellLinkSubRec( rStrm, EXC_ID_OBJCBLSFMLA ); + + // ftCblsData subrecord - box properties, again + rStrm.StartRecord( EXC_ID_OBJCBLS, 8 ); + rStrm << mnState; + rStrm.WriteZeroBytes( 4 ); + rStrm << nStyle; + rStrm.EndRecord(); + } + break; + + // *** List boxes, combo boxes *** + + case EXC_OBJTYPE_LISTBOX: + case EXC_OBJTYPE_DROPDOWN: + { + sal_uInt16 nEntryCount = GetSourceEntryCount(); + + // ftSbs subrecord - Scroll bars + sal_Int32 nLineHeight = XclTools::GetHmmFromTwips( 200 ); // always 10pt + if( mnObjType == EXC_OBJTYPE_LISTBOX ) + mnLineCount = static_cast< sal_uInt16 >( mnHeight / nLineHeight ); + mnScrollValue = 0; + mnScrollMin = 0; + sal_uInt16 nInvisLines = (nEntryCount >= mnLineCount) ? (nEntryCount - mnLineCount) : 0; + mnScrollMax = limit_cast< sal_uInt16 >( nInvisLines, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + mnScrollStep = 1; + mnScrollPage = limit_cast< sal_uInt16 >( mnLineCount, EXC_OBJ_SCROLLBAR_MIN, EXC_OBJ_SCROLLBAR_MAX ); + mbScrollHor = false; + WriteSbs( rStrm ); + + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + // ftSbsFmla subrecord - cell link + WriteCellLinkSubRec( rStrm, EXC_ID_OBJSBSFMLA ); + + // ftLbsData - source data range and box properties + sal_uInt16 nStyle = 0; + ::insert_value( nStyle, mbMultiSel ? EXC_OBJ_LISTBOX_MULTI : EXC_OBJ_LISTBOX_SINGLE, 4, 2 ); + ::set_flag( nStyle, EXC_OBJ_LISTBOX_FLAT, mbFlatBorder ); + + rStrm.StartRecord( EXC_ID_OBJLBSDATA, 0 ); + + if( const XclTokenArray* pSrcRange = GetSourceRangeTokArr() ) + { + rStrm << static_cast< sal_uInt16 >( (pSrcRange->GetSize() + 7) & 0xFFFE ); + WriteFormula( rStrm, *pSrcRange ); + } + else + rStrm << sal_uInt16( 0 ); + + rStrm << nEntryCount << mnSelEntry << nStyle << sal_uInt16( 0 ); + if( mnObjType == EXC_OBJTYPE_LISTBOX ) + { + if( nEntryCount ) + { + ScfUInt8Vec aSelEx( nEntryCount, 0 ); + for( const auto& rItem : maMultiSel ) + if( rItem < nEntryCount ) + aSelEx[ rItem ] = 1; + rStrm.Write( aSelEx.data(), aSelEx.size() ); + } + } + else if( mnObjType == EXC_OBJTYPE_DROPDOWN ) + { + rStrm << sal_uInt16( 0 ) << mnLineCount << sal_uInt16( 0 ) << sal_uInt16( 0 ); + } + + rStrm.EndRecord(); + } + break; + + // *** Spin buttons, scrollbars *** + + case EXC_OBJTYPE_SPIN: + case EXC_OBJTYPE_SCROLLBAR: + { + // ftSbs subrecord - scroll bars + WriteSbs( rStrm ); + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + // ftSbsFmla subrecord - cell link + WriteCellLinkSubRec( rStrm, EXC_ID_OBJSBSFMLA ); + } + break; + + // *** Group boxes *** + + case EXC_OBJTYPE_GROUPBOX: + { + // ftMacro - macro link + WriteMacroSubRec( rStrm ); + + // ftGboData subrecord - group box properties + sal_uInt16 nStyle = 0; + ::set_flag( nStyle, EXC_OBJ_GROUPBOX_FLAT, mbFlatBorder ); + + rStrm.StartRecord( EXC_ID_OBJGBODATA, 6 ); + rStrm << sal_uInt32( 0 ) + << nStyle; + rStrm.EndRecord(); + } + break; + } +} + +void XclExpTbxControlObj::WriteCellLinkSubRec( XclExpStream& rStrm, sal_uInt16 nSubRecId ) +{ + if( const XclTokenArray* pCellLink = GetCellLinkTokArr() ) + WriteFormulaSubRec( rStrm, nSubRecId, *pCellLink ); +} + +void XclExpTbxControlObj::WriteSbs( XclExpStream& rStrm ) +{ + sal_uInt16 nOrient = 0; + ::set_flag( nOrient, EXC_OBJ_SCROLLBAR_HOR, mbScrollHor ); + sal_uInt16 nStyle = EXC_OBJ_SCROLLBAR_DEFFLAGS; + ::set_flag( nStyle, EXC_OBJ_SCROLLBAR_FLAT, mbFlatButton ); + + rStrm.StartRecord( EXC_ID_OBJSBS, 20 ); + rStrm << sal_uInt32( 0 ) // reserved + << mnScrollValue // thumb position + << mnScrollMin // thumb min pos + << mnScrollMax // thumb max pos + << mnScrollStep // line increment + << mnScrollPage // page increment + << nOrient // 0 = vertical, 1 = horizontal + << sal_uInt16( 15 ) // thumb width + << nStyle; // flags/style + rStrm.EndRecord(); +} + +void XclExpTbxControlObj::setShapeId(sal_Int32 aShapeId) +{ + mnShapeId = aShapeId; +} + +namespace +{ +/// Handles the VML export of form controls (e.g. checkboxes). +class VmlFormControlExporter : public oox::vml::VMLExport +{ + sal_uInt16 m_nObjType; + tools::Rectangle m_aAreaFrom; + tools::Rectangle m_aAreaTo; + OUString m_aLabel; + OUString m_aMacroName; + +public: + VmlFormControlExporter(const sax_fastparser::FSHelperPtr& p, sal_uInt16 nObjType, + const tools::Rectangle& rAreaFrom, const tools::Rectangle& rAreaTo, + const OUString& rLabel, const OUString& rMacroName); + +protected: + using VMLExport::StartShape; + sal_Int32 StartShape() override; + using VMLExport::EndShape; + void EndShape(sal_Int32 nShapeElement) override; +}; + +VmlFormControlExporter::VmlFormControlExporter(const sax_fastparser::FSHelperPtr& p, + sal_uInt16 nObjType, + const tools::Rectangle& rAreaFrom, + const tools::Rectangle& rAreaTo, + const OUString& rLabel, const OUString& rMacroName) + : VMLExport(p) + , m_nObjType(nObjType) + , m_aAreaFrom(rAreaFrom) + , m_aAreaTo(rAreaTo) + , m_aLabel(rLabel) + , m_aMacroName(rMacroName) +{ +} + +sal_Int32 VmlFormControlExporter::StartShape() +{ + // Host control. + AddShapeAttribute(XML_type, "#_x0000_t201"); + return VMLExport::StartShape(); +} + +void VmlFormControlExporter::EndShape(sal_Int32 nShapeElement) +{ + sax_fastparser::FSHelperPtr pVmlDrawing = GetFS(); + + pVmlDrawing->startElement(FSNS(XML_v, XML_textbox)); + pVmlDrawing->startElement(XML_div); + pVmlDrawing->write(m_aLabel); + pVmlDrawing->endElement(XML_div); + pVmlDrawing->endElement(FSNS(XML_v, XML_textbox)); + + OString aObjectType; + switch (m_nObjType) + { + case EXC_OBJTYPE_CHECKBOX: + aObjectType = "Checkbox"; + break; + case EXC_OBJTYPE_BUTTON: + aObjectType = "Button"; + break; + } + pVmlDrawing->startElement(FSNS(XML_x, XML_ClientData), XML_ObjectType, aObjectType); + OString aAnchor + = OString::number(m_aAreaFrom.Left()) + ", " + OString::number(m_aAreaFrom.Top()) + ", " + + OString::number(m_aAreaFrom.Right()) + ", " + OString::number(m_aAreaFrom.Bottom()) + ", " + + OString::number(m_aAreaTo.Left()) + ", " + OString::number(m_aAreaTo.Top()) + ", " + + OString::number(m_aAreaTo.Right()) + ", " + OString::number(m_aAreaTo.Bottom()); + XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_Anchor), aAnchor); + + if (!m_aMacroName.isEmpty()) + { + XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_FmlaMacro), m_aMacroName); + } + + // XclExpOcxControlObj::WriteSubRecs() has the same fixed values. + if (m_nObjType == EXC_OBJTYPE_BUTTON) + { + XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_TextHAlign), "Center"); + } + XclXmlUtils::WriteElement(pVmlDrawing, FSNS(XML_x, XML_TextVAlign), "Center"); + + pVmlDrawing->endElement(FSNS(XML_x, XML_ClientData)); + VMLExport::EndShape(nShapeElement); +} + +} + +/// Save into xl/drawings/vmlDrawing1.vml. +void XclExpTbxControlObj::SaveVml(XclExpXmlStream& rStrm) +{ + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape); + tools::Rectangle aAreaFrom; + tools::Rectangle aAreaTo; + // Unlike XclExpTbxControlObj::SaveXml(), this is not calculated in EMUs. + lcl_GetFromTo(mrRoot, pObj->GetLogicRect(), GetTab(), aAreaFrom, aAreaTo); + VmlFormControlExporter aFormControlExporter(rStrm.GetCurrentStream(), GetObjType(), aAreaFrom, + aAreaTo, msLabel, GetMacroName()); + aFormControlExporter.AddSdrObject(*pObj, /*bIsFollowingTextFlow=*/false, /*eHOri=*/-1, + /*eVOri=*/-1, /*eHRel=*/-1, /*eVRel=*/-1, + /*pWrapAttrList=*/nullptr, /*bOOxmlExport=*/true); +} + +// save into xl\drawings\drawing1.xml +void XclExpTbxControlObj::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& pDrawing = rStrm.GetCurrentStream(); + + pDrawing->startElement(FSNS(XML_mc, XML_AlternateContent), + FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce))); + pDrawing->startElement(FSNS(XML_mc, XML_Choice), + FSNS(XML_xmlns, XML_a14), rStrm.getNamespaceURL(OOX_NS(a14)), + XML_Requires, "a14"); + + pDrawing->startElement(FSNS(XML_xdr, XML_twoCellAnchor), XML_editAs, "oneCell"); + { + pDrawing->startElement(FSNS(XML_xdr, XML_from)); + lcl_WriteAnchorVertex(pDrawing, maAreaFrom); + pDrawing->endElement(FSNS(XML_xdr, XML_from)); + pDrawing->startElement(FSNS(XML_xdr, XML_to)); + lcl_WriteAnchorVertex(pDrawing, maAreaTo); + pDrawing->endElement(FSNS(XML_xdr, XML_to)); + + pDrawing->startElement(FSNS(XML_xdr, XML_sp)); + { + // xdr:nvSpPr + pDrawing->startElement(FSNS(XML_xdr, XML_nvSpPr)); + { + pDrawing->singleElement(FSNS(XML_xdr, XML_cNvPr), + XML_id, OString::number(mnShapeId).getStr(), + XML_name, msCtrlName, // control name + XML_descr, msLabel, // description as alt text + XML_hidden, mbVisible ? "0" : "1"); + pDrawing->singleElement(FSNS(XML_xdr, XML_cNvSpPr)); + } + pDrawing->endElement(FSNS(XML_xdr, XML_nvSpPr)); + + // xdr:spPr + pDrawing->startElement(FSNS(XML_xdr, XML_spPr)); + { + // a:xfrm + pDrawing->startElement(FSNS(XML_a, XML_xfrm)); + { + pDrawing->singleElement(FSNS(XML_a, XML_off), + XML_x, "0", + XML_y, "0"); + pDrawing->singleElement(FSNS(XML_a, XML_ext), + XML_cx, "0", + XML_cy, "0"); + } + pDrawing->endElement(FSNS(XML_a, XML_xfrm)); + + // a:prstGeom + pDrawing->startElement(FSNS(XML_a, XML_prstGeom), XML_prst, "rect"); + { + pDrawing->singleElement(FSNS(XML_a, XML_avLst)); + } + pDrawing->endElement(FSNS(XML_a, XML_prstGeom)); + } + pDrawing->endElement(FSNS(XML_xdr, XML_spPr)); + + // xdr:txBody + { + pDrawing->startElement(FSNS(XML_xdr, XML_txBody)); + +#define DEFLRINS 254 +#define DEFTBINS 127 + sal_Int32 nLeft, nRight, nTop, nBottom; + nLeft = nRight = DEFLRINS; + nTop = nBottom = DEFTBINS; + + // top inset looks a bit different compared to ppt export + // check if something related doesn't work as expected + Reference< XPropertySet > rXPropSet(mxShape, UNO_QUERY); + + try + { + css::uno::Any mAny; + + mAny = rXPropSet->getPropertyValue("TextLeftDistance"); + if (mAny.hasValue()) + mAny >>= nLeft; + + mAny = rXPropSet->getPropertyValue("TextRightDistance"); + if (mAny.hasValue()) + mAny >>= nRight; + + mAny = rXPropSet->getPropertyValue("TextUpperDistance"); + if (mAny.hasValue()) + mAny >>= nTop; + + mAny = rXPropSet->getPropertyValue("TextLowerDistance"); + if (mAny.hasValue()) + mAny >>= nBottom; + } + catch (...) + { + } + + // Specifies the inset of the bounding rectangle. + // Insets are used just as internal margins for text boxes within shapes. + // If this attribute is omitted, then a value of 45720 or 0.05 inches is implied. + pDrawing->startElementNS(XML_a, XML_bodyPr, + XML_lIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nLeft)), nLeft != DEFLRINS), + XML_rIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nRight)), nRight != DEFLRINS), + XML_tIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nTop)), nTop != DEFTBINS), + XML_bIns, sax_fastparser::UseIf(OString::number(oox::drawingml::convertHmmToEmu(nBottom)), nBottom != DEFTBINS), + XML_anchor, "ctr"); + + { + bool bTextAutoGrowHeight = false; + + try + { + css::uno::Any mAny; + + mAny = rXPropSet->getPropertyValue("TextAutoGrowHeight"); + if (mAny.hasValue()) + mAny >>= bTextAutoGrowHeight; + } + catch (...) + { + } + + pDrawing->singleElementNS(XML_a, (bTextAutoGrowHeight ? XML_spAutoFit : XML_noAutofit)); + } + + pDrawing->endElementNS(XML_a, XML_bodyPr); + + { + pDrawing->startElementNS(XML_a, XML_p); + pDrawing->startElementNS(XML_a, XML_r); + pDrawing->startElementNS(XML_a, XML_t); + pDrawing->write(msLabel); + pDrawing->endElementNS(XML_a, XML_t); + pDrawing->endElementNS(XML_a, XML_r); + pDrawing->endElementNS(XML_a, XML_p); + } + + pDrawing->endElement(FSNS(XML_xdr, XML_txBody)); + } + } + pDrawing->endElement(FSNS(XML_xdr, XML_sp)); + pDrawing->singleElement(FSNS(XML_xdr, XML_clientData)); + } + pDrawing->endElement(FSNS(XML_xdr, XML_twoCellAnchor)); + pDrawing->endElement( FSNS( XML_mc, XML_Choice ) ); + pDrawing->endElement( FSNS( XML_mc, XML_AlternateContent ) ); +} + +// output into ctrlProp1.xml +OUString XclExpTbxControlObj::SaveControlPropertiesXml(XclExpXmlStream& rStrm) const +{ + OUString sIdFormControlPr; + + switch (mnObjType) + { + case EXC_OBJTYPE_CHECKBOX: + { + const sal_Int32 nDrawing = DrawingML::getNewDrawingUniqueId(); + sax_fastparser::FSHelperPtr pFormControl = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName( "xl/", "ctrlProps/ctrlProps", nDrawing ), + XclXmlUtils::GetStreamName( "../", "ctrlProps/ctrlProps", nDrawing ), + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.ms-excel.controlproperties+xml", + oox::getRelationship(Relationship::CTRLPROP), + &sIdFormControlPr ); + + rStrm.PushStream( pFormControl ); + // checkbox + // + // + pFormControl->write("write(" checked=\"Checked\""); + + pFormControl->write(" autoLine=\"false\""); + + if (mbPrint) + pFormControl->write(" print=\"true\""); + else + pFormControl->write(" print=\"false\""); + + if (mxCellLinkAddress.IsValid()) + { + OUString aCellLink = mxCellLinkAddress.Format(ScRefFlags::ADDR_ABS, + &GetDoc(), + ScAddress::Details(::formula::FormulaGrammar::CONV_XL_A1)); + + // "Sheet1!$C$5" + pFormControl->write(" fmlaLink=\""); + if (aCellLink.indexOf('!') < 0) + { + pFormControl->write(GetTabInfo().GetScTabName(mxCellLinkAddress.Tab())); + pFormControl->write("!"); + } + pFormControl->write(aCellLink); + pFormControl->write("\""); + } + + pFormControl->write(" lockText=\"1\" noThreeD=\"1\"/>"); + rStrm.PopStream(); + + break; + } + case EXC_OBJTYPE_BUTTON: + { + sal_Int32 nDrawing = DrawingML::getNewDrawingUniqueId(); + sax_fastparser::FSHelperPtr pFormControl = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/", "ctrlProps/ctrlProps", nDrawing), + XclXmlUtils::GetStreamName("../", "ctrlProps/ctrlProps", nDrawing), + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.ms-excel.controlproperties+xml", + oox::getRelationship(Relationship::CTRLPROP), &sIdFormControlPr); + + pFormControl->singleElement(XML_formControlPr, XML_xmlns, + rStrm.getNamespaceURL(OOX_NS(xls14Lst)), XML_objectType, + "Button", XML_lockText, "1"); + break; + } + } + + return sIdFormControlPr; +} + +// output into sheet1.xml +void XclExpTbxControlObj::SaveSheetXml(XclExpXmlStream& rStrm, const OUString& aIdFormControlPr) const +{ + switch (mnObjType) + { + case EXC_OBJTYPE_CHECKBOX: + { + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent), + FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce))); + rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x14"); + + rWorksheet->startElement( + XML_control, + XML_shapeId, OString::number(mnShapeId).getStr(), + FSNS(XML_r, XML_id), aIdFormControlPr, + XML_name, msLabel); // text to display with checkbox button + + rWorksheet->write("write(" print=\"true\""); + else + rWorksheet->write(" print=\"false\""); + + if (!msCtrlName.isEmpty()) + { + rWorksheet->write(" altText=\""); + rWorksheet->write(msCtrlName); // alt text + rWorksheet->write("\""); + } + + rWorksheet->write(">"); + + rWorksheet->startElement(XML_anchor, XML_moveWithCells, "true", XML_sizeWithCells, "false"); + rWorksheet->startElement(XML_from); + lcl_WriteAnchorVertex(rWorksheet, maAreaFrom); + rWorksheet->endElement(XML_from); + rWorksheet->startElement(XML_to); + lcl_WriteAnchorVertex(rWorksheet, maAreaTo); + rWorksheet->endElement(XML_to); + rWorksheet->endElement( XML_anchor ); + + rWorksheet->write(""); + + rWorksheet->endElement(XML_control); + rWorksheet->endElement( FSNS( XML_mc, XML_Choice ) ); + rWorksheet->endElement( FSNS( XML_mc, XML_AlternateContent ) ); + + break; + } + case EXC_OBJTYPE_BUTTON: + { + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement(FSNS(XML_mc, XML_AlternateContent), FSNS(XML_xmlns, XML_mc), + rStrm.getNamespaceURL(OOX_NS(mce))); + rWorksheet->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "x14"); + + rWorksheet->startElement(XML_control, XML_shapeId, OString::number(mnShapeId).getStr(), + FSNS(XML_r, XML_id), aIdFormControlPr, XML_name, msCtrlName); + + OString aMacroName = GetMacroName().toUtf8(); + // Omit the macro attribute if it would be empty. + const char* pMacroName = aMacroName.isEmpty() ? nullptr : aMacroName.getStr(); + rWorksheet->startElement(XML_controlPr, XML_defaultSize, "0", XML_print, + mbPrint ? "true" : "false", XML_autoFill, "0", XML_autoPict, + "0", XML_macro, pMacroName); + + rWorksheet->startElement(XML_anchor, XML_moveWithCells, "true", XML_sizeWithCells, + "false"); + + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape); + tools::Rectangle aAreaFrom; + tools::Rectangle aAreaTo; + lcl_GetFromTo(mrRoot, pObj->GetLogicRect(), GetTab(), aAreaFrom, aAreaTo, + /*bInEMU=*/true); + + rWorksheet->startElement(XML_from); + lcl_WriteAnchorVertex(rWorksheet, aAreaFrom); + rWorksheet->endElement(XML_from); + rWorksheet->startElement(XML_to); + lcl_WriteAnchorVertex(rWorksheet, aAreaTo); + rWorksheet->endElement(XML_to); + rWorksheet->endElement(XML_anchor); + + rWorksheet->endElement(XML_controlPr); + + rWorksheet->endElement(XML_control); + rWorksheet->endElement(FSNS(XML_mc, XML_Choice)); + rWorksheet->endElement(FSNS(XML_mc, XML_AlternateContent)); + break; + } + } +} + +//#endif + +XclExpChartObj::XclExpChartObj( XclExpObjectManager& rObjMgr, Reference< XShape > const & xShape, const tools::Rectangle* pChildAnchor, ScDocument* pDoc ) : + XclObj( rObjMgr, EXC_OBJTYPE_CHART ), + XclExpRoot( rObjMgr.GetRoot() ), mxShape( xShape ), + mpDoc(pDoc) +{ + // create the MSODRAWING record contents for the chart object + mrEscherEx.OpenContainer( ESCHER_SpContainer ); + mrEscherEx.AddShape( ESCHER_ShpInst_HostControl, ShapeFlag::HaveAnchor | ShapeFlag::HaveShapeProperty ); + EscherPropertyContainer aPropOpt; + aPropOpt.AddOpt( ESCHER_Prop_LockAgainstGrouping, 0x01040104 ); + aPropOpt.AddOpt( ESCHER_Prop_FitTextToShape, 0x00080008 ); + aPropOpt.AddOpt( ESCHER_Prop_fillColor, 0x0800004E ); + aPropOpt.AddOpt( ESCHER_Prop_fillBackColor, 0x0800004D ); + aPropOpt.AddOpt( ESCHER_Prop_fNoFillHitTest, 0x00110010 ); + aPropOpt.AddOpt( ESCHER_Prop_lineColor, 0x0800004D ); + aPropOpt.AddOpt( ESCHER_Prop_fNoLineDrawDash, 0x00080008 ); + aPropOpt.AddOpt( ESCHER_Prop_fshadowObscured, 0x00020000 ); + aPropOpt.AddOpt( ESCHER_Prop_fPrint, 0x00080000 ); + aPropOpt.Commit( mrEscherEx.GetStream() ); + + // anchor + SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape( xShape ); + ImplWriteAnchor( pSdrObj, pChildAnchor ); + + // client data (the following OBJ record) + mrEscherEx.AddAtom( 0, ESCHER_ClientData ); + mrEscherEx.CloseContainer(); // ESCHER_SpContainer + mrEscherEx.UpdateDffFragmentEnd(); + + // load the chart OLE object + if( SdrOle2Obj* pSdrOleObj = dynamic_cast< SdrOle2Obj* >( pSdrObj ) ) + svt::EmbeddedObjectRef::TryRunningState( pSdrOleObj->GetObjRef() ); + + // create the chart substream object + ScfPropertySet aShapeProp( xShape ); + css::awt::Rectangle aBoundRect; + aShapeProp.GetProperty( aBoundRect, "BoundRect" ); + tools::Rectangle aChartRect( Point( aBoundRect.X, aBoundRect.Y ), Size( aBoundRect.Width, aBoundRect.Height ) ); + mxChart = std::make_shared(GetRoot(), GetChartDoc(), aChartRect); +} + +XclExpChartObj::~XclExpChartObj() +{ +} + +void XclExpChartObj::Save( XclExpStream& rStrm ) +{ + // content of OBJ record + XclObj::Save( rStrm ); + // chart substream + mxChart->Save( rStrm ); +} + +void XclExpChartObj::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr pDrawing = rStrm.GetCurrentStream(); + + // FIXME: two cell? it seems the two cell anchor is incorrect. + pDrawing->startElement( FSNS( XML_xdr, XML_twoCellAnchor ), // OOXTODO: oneCellAnchor, absoluteAnchor + XML_editAs, "oneCell" ); + Reference< XPropertySet > xPropSet( mxShape, UNO_QUERY ); + if (xPropSet.is()) + { + XclObjAny::WriteFromTo( rStrm, mxShape, GetTab() ); + ChartExport aChartExport(XML_xdr, pDrawing, GetChartDoc(), &rStrm, drawingml::DOCUMENT_XLSX); + auto pURLTransformer = std::make_shared(*mpDoc); + aChartExport.SetURLTranslator(pURLTransformer); + static sal_Int32 nChartCount = 0; + nChartCount++; + sal_Int32 nID = rStrm.GetUniqueId(); + aChartExport.WriteChartObj( mxShape, nID, nChartCount ); + // TODO: get the correcto chart number + } + + pDrawing->singleElement( FSNS( XML_xdr, XML_clientData) + // OOXTODO: XML_fLocksWithSheet + // OOXTODO: XML_fPrintsWithSheet + ); + pDrawing->endElement( FSNS( XML_xdr, XML_twoCellAnchor ) ); +} + +css::uno::Reference XclExpChartObj::GetChartDoc() const +{ + SdrObject* pObj = SdrObject::getSdrObjectFromXShape(mxShape); + if (!pObj || pObj->GetObjIdentifier() != SdrObjKind::OLE2) + return {}; + // May load here - makes sure that we are working with actually loaded OLE object + return css::uno::Reference( + static_cast(pObj)->getXModel(), css::uno::UNO_QUERY); +} + +XclExpNote::XclExpNote(const XclExpRoot& rRoot, const ScAddress& rScPos, + const ScPostIt* pScNote, std::u16string_view rAddText) + : XclExpRecord(EXC_ID_NOTE) + , mrRoot(rRoot) + , maScPos(rScPos) + , mnObjId(EXC_OBJ_INVALID_ID) + , mbVisible(pScNote && pScNote->IsCaptionShown()) + , meTHA(SDRTEXTHORZADJUST_LEFT) + , meTVA(SDRTEXTVERTADJUST_TOP) + , mbAutoScale(false) + , mbLocked(false) + , mbAutoFill(false) + , mbColHidden(false) + , mbRowHidden(false) +{ + // get the main note text + OUString aNoteText; + if( pScNote ) + { + aNoteText = pScNote->GetText(); + const EditTextObject *pEditObj = pScNote->GetEditTextObject(); + if( pEditObj ) + mpNoteContents = XclExpStringHelper::CreateString( rRoot, *pEditObj ); + } + // append additional text + aNoteText = ScGlobal::addToken( aNoteText, rAddText, '\n', 2 ); + + // initialize record dependent on BIFF type + switch( rRoot.GetBiff() ) + { + case EXC_BIFF5: + maNoteText = OUStringToOString(aNoteText, rRoot.GetTextEncoding()); + break; + + case EXC_BIFF8: + { + // TODO: additional text + if( pScNote ) + { + if( SdrCaptionObj* pCaption = pScNote->GetOrCreateCaption( maScPos ) ) + { + lcl_GetFromTo( rRoot, pCaption->GetLogicRect(), maScPos.Tab(), maCommentFrom, maCommentTo ); + if( const OutlinerParaObject* pOPO = pCaption->GetOutlinerParaObject() ) + mnObjId = rRoot.GetObjectManager().AddObj( std::make_unique( rRoot.GetObjectManager(), pCaption->GetLogicRect(), pOPO->GetTextObject(), pCaption, mbVisible, maScPos, maCommentFrom, maCommentTo ) ); + + SfxItemSet aItemSet = pCaption->GetMergedItemSet(); + meTVA = pCaption->GetTextVerticalAdjust(); + meTHA = pCaption->GetTextHorizontalAdjust(); + mbAutoScale = pCaption->GetFitToSize() != drawing::TextFitToSizeType_NONE; + mbLocked = pCaption->IsMoveProtect() || pCaption->IsResizeProtect(); + + // AutoFill style would change if Postit.cxx object creation values are changed + OUString aCol(aItemSet.Get(XATTR_FILLCOLOR).GetValue()); + mbAutoFill = aCol.isEmpty() && (aItemSet.Get(XATTR_FILLSTYLE).GetValue() == drawing::FillStyle_SOLID); + mbRowHidden = (rRoot.GetDoc().RowHidden(maScPos.Row(),maScPos.Tab())); + mbColHidden = (rRoot.GetDoc().ColHidden(maScPos.Col(),maScPos.Tab())); + } + // stAuthor (variable): An XLUnicodeString that specifies the name of the comment + // author. String length MUST be greater than or equal to 1 and less than or equal + // to 54. + if( pScNote->GetAuthor().isEmpty() ) + maAuthor = XclExpString( " " ); + else + maAuthor = XclExpString( pScNote->GetAuthor(), XclStrFlags::NONE, 54 ); + } + + SetRecSize( 9 + maAuthor.GetSize() ); + } + break; + + default: DBG_ERROR_BIFF(); + } +} + +void XclExpNote::Save( XclExpStream& rStrm ) +{ + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF5: + { + // write the NOTE record directly, there may be the need to create more than one + const char* pcBuffer = maNoteText.getStr(); + sal_uInt16 nCharsLeft = static_cast< sal_uInt16 >( maNoteText.getLength() ); + + while( nCharsLeft ) + { + sal_uInt16 nWriteChars = ::std::min( nCharsLeft, EXC_NOTE5_MAXLEN ); + + rStrm.StartRecord( EXC_ID_NOTE, 6 + nWriteChars ); + if( pcBuffer == maNoteText.getStr() ) + { + // first record: row, col, length of complete text + rStrm << static_cast< sal_uInt16 >( maScPos.Row() ) + << static_cast< sal_uInt16 >( maScPos.Col() ) + << nCharsLeft; // still contains full length + } + else + { + // next records: -1, 0, length of current text segment + rStrm << sal_uInt16( 0xFFFF ) + << sal_uInt16( 0 ) + << nWriteChars; + } + rStrm.Write( pcBuffer, nWriteChars ); + rStrm.EndRecord(); + + pcBuffer += nWriteChars; + nCharsLeft = nCharsLeft - nWriteChars; + } + } + break; + + case EXC_BIFF8: + if( mnObjId != EXC_OBJ_INVALID_ID ) + XclExpRecord::Save( rStrm ); + break; + + default: DBG_ERROR_BIFF(); + } +} + +void XclExpNote::WriteBody( XclExpStream& rStrm ) +{ + // BIFF5/BIFF7 is written separately + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ); + + sal_uInt16 nFlags = 0; + ::set_flag( nFlags, EXC_NOTE_VISIBLE, mbVisible ); + + rStrm << static_cast< sal_uInt16 >( maScPos.Row() ) + << static_cast< sal_uInt16 >( maScPos.Col() ) + << nFlags + << mnObjId + << maAuthor + << sal_uInt8( 0 ); +} + +void XclExpNote::WriteXml( sal_Int32 nAuthorId, XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr rComments = rStrm.GetCurrentStream(); + + rComments->startElement( XML_comment, + XML_ref, XclXmlUtils::ToOString(mrRoot.GetDoc(), maScPos), + XML_authorId, OString::number(nAuthorId) + // OOXTODO: XML_guid + ); + rComments->startElement(XML_text); + // OOXTODO: phoneticPr, rPh, r + if( mpNoteContents ) + mpNoteContents->WriteXml( rStrm ); + rComments->endElement( XML_text ); + +/* + Export of commentPr is disabled, since the current (Oct 2010) + version of MSO 2010 doesn't yet support commentPr + */ +#if 1//def XLSX_OOXML_FUTURE + if( rStrm.getVersion() == oox::core::ISOIEC_29500_2008 ) + { + rComments->startElement(FSNS(XML_mc, XML_AlternateContent)); + rComments->startElement(FSNS(XML_mc, XML_Choice), XML_Requires, "v2"); + rComments->startElement( XML_commentPr, + XML_autoFill, ToPsz( mbAutoFill ), + XML_autoScale, ToPsz( mbAutoScale ), + XML_colHidden, ToPsz( mbColHidden ), + XML_locked, ToPsz( mbLocked ), + XML_rowHidden, ToPsz( mbRowHidden ), + XML_textHAlign, ToHorizAlign( meTHA ), + XML_textVAlign, ToVertAlign( meTVA ) ); + rComments->startElement(XML_anchor, XML_moveWithCells, "false", XML_sizeWithCells, "false"); + rComments->startElement(FSNS(XML_xdr, XML_from)); + lcl_WriteAnchorVertex( rComments, maCommentFrom ); + rComments->endElement( FSNS( XML_xdr, XML_from ) ); + rComments->startElement(FSNS(XML_xdr, XML_to)); + lcl_WriteAnchorVertex( rComments, maCommentTo ); + rComments->endElement( FSNS( XML_xdr, XML_to ) ); + rComments->endElement( XML_anchor ); + rComments->endElement( XML_commentPr ); + + rComments->endElement( FSNS( XML_mc, XML_Choice ) ); + rComments->startElement(FSNS(XML_mc, XML_Fallback)); + // Any fallback code ? + rComments->endElement( FSNS( XML_mc, XML_Fallback ) ); + rComments->endElement( FSNS( XML_mc, XML_AlternateContent ) ); + } +#endif + rComments->endElement( XML_comment ); +} + +XclMacroHelper::XclMacroHelper( const XclExpRoot& rRoot ) : + XclExpControlHelper( rRoot ) +{ +} + +XclMacroHelper::~XclMacroHelper() +{ +} + +void XclMacroHelper::WriteMacroSubRec( XclExpStream& rStrm ) +{ + if( mxMacroLink ) + WriteFormulaSubRec( rStrm, EXC_ID_OBJMACRO, *mxMacroLink ); +} + +const OUString& XclMacroHelper::GetMacroName() const { return maMacroName; } + +bool +XclMacroHelper::SetMacroLink( const ScriptEventDescriptor& rEvent, const XclTbxEventType& nEventType ) +{ + maMacroName = XclControlHelper::ExtractFromMacroDescriptor(rEvent, nEventType); + if (!maMacroName.isEmpty()) + { + return SetMacroLink(maMacroName); + } + return false; +} + +bool +XclMacroHelper::SetMacroLink( const OUString& rMacroName ) +{ + // OOXML documents do not store any defined name for VBA macros (while BIFF documents do). + bool bOOXML = GetOutput() == EXC_OUTPUT_XML_2007; + if (!rMacroName.isEmpty() && !bOOXML) + { + sal_uInt16 nExtSheet = GetLocalLinkManager().FindExtSheet( EXC_EXTSH_OWNDOC ); + sal_uInt16 nNameIdx + = GetNameManager().InsertMacroCall(rMacroName, /*bVBasic=*/true, /*bFunc=*/false); + mxMacroLink = GetFormulaCompiler().CreateNameXFormula( nExtSheet, nNameIdx ); + return true; + } + return false; +} + +XclExpShapeObj::XclExpShapeObj( XclExpObjectManager& rRoot, css::uno::Reference< css::drawing::XShape > const & xShape, ScDocument* pDoc ) : + XclObjAny( rRoot, xShape, pDoc ), + XclMacroHelper( rRoot ) +{ + if (SdrObject* pSdrObj = SdrObject::getSdrObjectFromXShape(xShape)) + { + ScMacroInfo* pInfo = ScDrawLayer::GetMacroInfo( pSdrObj ); + if ( pInfo && !pInfo->GetMacro().isEmpty() ) +// FIXME ooo330-m2: XclControlHelper::GetXclMacroName was removed in upstream sources; they started to call XclTools::GetXclMacroName instead; is this enough? it has only one parameter +// SetMacroLink( XclControlHelper::GetXclMacroName( pInfo->GetMacro(), rRoot.GetDocShell() ) ); + SetMacroLink( XclTools::GetXclMacroName( pInfo->GetMacro() ) ); + } +} + +XclExpShapeObj::~XclExpShapeObj() +{ +} + +void XclExpShapeObj::WriteSubRecs( XclExpStream& rStrm ) +{ + XclObjAny::WriteSubRecs( rStrm ); + WriteMacroSubRec( rStrm ); +} + +XclExpComments::XclExpComments( SCTAB nTab, XclExpRecordList< XclExpNote >& rNotes ) + : mnTab( nTab ), mrNotes( rNotes ) +{ +} + +void XclExpComments::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mrNotes.IsEmpty() ) + return; + + sax_fastparser::FSHelperPtr rComments = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName( "xl/", "comments", mnTab + 1 ), + XclXmlUtils::GetStreamName( "../", "comments", mnTab + 1 ), + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml", + oox::getRelationship(Relationship::COMMENTS)); + rStrm.PushStream( rComments ); + + if( rStrm.getVersion() == oox::core::ISOIEC_29500_2008 ) + rComments->startElement( XML_comments, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)), + FSNS(XML_xmlns, XML_mc), rStrm.getNamespaceURL(OOX_NS(mce)), + FSNS(XML_xmlns, XML_xdr), rStrm.getNamespaceURL(OOX_NS(dmlSpreadDr)), + FSNS(XML_xmlns, XML_v2), rStrm.getNamespaceURL(OOX_NS(mceTest)), + FSNS( XML_mc, XML_Ignorable ), "v2" ); + else + rComments->startElement( XML_comments, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)), + FSNS(XML_xmlns, XML_xdr), rStrm.getNamespaceURL(OOX_NS(dmlSpreadDr)) ); + + rComments->startElement(XML_authors); + + typedef std::set Authors; + Authors aAuthors; + + size_t nNotes = mrNotes.GetSize(); + for( size_t i = 0; i < nNotes; ++i ) + { + aAuthors.insert( XclXmlUtils::ToOUString( mrNotes.GetRecord( i )->GetAuthor() ) ); + } + + for( const auto& rAuthor : aAuthors ) + { + rComments->startElement(XML_author); + rComments->writeEscaped( rAuthor ); + rComments->endElement( XML_author ); + } + + rComments->endElement( XML_authors ); + rComments->startElement(XML_commentList); + + Authors::const_iterator aAuthorsBegin = aAuthors.begin(); + for( size_t i = 0; i < nNotes; ++i ) + { + XclExpRecordList< XclExpNote >::RecordRefType xNote = mrNotes.GetRecord( i ); + Authors::const_iterator aAuthor = aAuthors.find( + XclXmlUtils::ToOUString( xNote->GetAuthor() ) ); + sal_Int32 nAuthorId = distance( aAuthorsBegin, aAuthor ); + xNote->WriteXml( nAuthorId, rStrm ); + } + + rComments->endElement( XML_commentList ); + rComments->endElement( XML_comments ); + + rStrm.PopStream(); +} + +// object manager ============================================================= + +XclExpObjectManager::XclExpObjectManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ + InitStream( true ); + mxEscherEx = std::make_shared( GetRoot(), *this, *mxDffStrm ); +} + +XclExpObjectManager::XclExpObjectManager( const XclExpObjectManager& rParent ) : + XclExpRoot( rParent.GetRoot() ) +{ + InitStream( false ); + mxEscherEx = std::make_shared( GetRoot(), *this, *mxDffStrm, rParent.mxEscherEx.get() ); +} + +XclExpObjectManager::~XclExpObjectManager() +{ +} + +XclExpDffAnchorBase* XclExpObjectManager::CreateDffAnchor() const +{ + return new XclExpDffSheetAnchor( GetRoot() ); +} + +rtl::Reference< XclExpRecordBase > XclExpObjectManager::CreateDrawingGroup() +{ + return new XclExpMsoDrawingGroup( *mxEscherEx ); +} + +void XclExpObjectManager::StartSheet() +{ + mxObjList = new XclExpObjList( GetRoot(), *mxEscherEx ); +} + +rtl::Reference< XclExpRecordBase > XclExpObjectManager::ProcessDrawing( const SdrPage* pSdrPage ) +{ + if( pSdrPage ) + mxEscherEx->AddSdrPage( *pSdrPage, GetOutput() != EXC_OUTPUT_BINARY ); + // the first dummy object may still be open + OSL_ENSURE( mxEscherEx->GetGroupLevel() <= 1, "XclExpObjectManager::ProcessDrawing - still groups open?" ); + while( mxEscherEx->GetGroupLevel() ) + mxEscherEx->LeaveGroup(); + mxObjList->EndSheet(); + return mxObjList; +} + +rtl::Reference< XclExpRecordBase > XclExpObjectManager::ProcessDrawing( const Reference< XShapes >& rxShapes ) +{ + if( rxShapes.is() ) + mxEscherEx->AddUnoShapes( rxShapes, GetOutput() != EXC_OUTPUT_BINARY ); + // the first dummy object may still be open + OSL_ENSURE( mxEscherEx->GetGroupLevel() <= 1, "XclExpObjectManager::ProcessDrawing - still groups open?" ); + while( mxEscherEx->GetGroupLevel() ) + mxEscherEx->LeaveGroup(); + mxObjList->EndSheet(); + return mxObjList; +} + +void XclExpObjectManager::EndDocument() +{ + mxEscherEx->EndDocument(); +} + +XclExpMsoDrawing* XclExpObjectManager::GetMsodrawingPerSheet() +{ + return mxObjList->GetMsodrawingPerSheet(); +} + +bool XclExpObjectManager::HasObj() const +{ + return !mxObjList->empty(); +} + +sal_uInt16 XclExpObjectManager::AddObj( std::unique_ptr pObjRec ) +{ + return mxObjList->Add( std::move(pObjRec) ); +} + +std::unique_ptr XclExpObjectManager::RemoveLastObj() +{ + return mxObjList->pop_back(); +} + +void XclExpObjectManager::InitStream( bool bTempFile ) +{ + if( bTempFile ) + { + mxTempFile = std::make_shared<::utl::TempFile>(); + if( mxTempFile->IsValid() ) + { + mxTempFile->EnableKillingFile(); + mxDffStrm = ::utl::UcbStreamHelper::CreateStream( mxTempFile->GetURL(), StreamMode::STD_READWRITE ); + } + } + + if( !mxDffStrm ) + mxDffStrm = std::make_unique(); + + mxDffStrm->SetEndian( SvStreamEndian::LITTLE ); +} + +XclExpEmbeddedObjectManager::XclExpEmbeddedObjectManager( + const XclExpObjectManager& rParent, const Size& rPageSize, sal_Int32 nScaleX, sal_Int32 nScaleY ) : + XclExpObjectManager( rParent ), + maPageSize( rPageSize ), + mnScaleX( nScaleX ), + mnScaleY( nScaleY ) +{ +} + +XclExpDffAnchorBase* XclExpEmbeddedObjectManager::CreateDffAnchor() const +{ + return new XclExpDffEmbeddedAnchor( GetRoot(), maPageSize, mnScaleX, mnScaleY ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeextlst.cxx b/sc/source/filter/excel/xeextlst.cxx new file mode 100644 index 000000000..242f21dbb --- /dev/null +++ b/sc/source/filter/excel/xeextlst.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 + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ::oox; + +XclExpExt::XclExpExt( const XclExpRoot& rRoot ): + XclExpRoot(rRoot) +{ +} + +XclExtLst::XclExtLst( const XclExpRoot& rRoot ): + XclExpRoot(rRoot) +{ +} + +XclExpExtNegativeColor::XclExpExtNegativeColor( const Color& rColor ): + maColor(rColor) +{ +} + +void XclExpExtNegativeColor::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->singleElementNS( XML_x14, XML_negativeFillColor, + XML_rgb, XclXmlUtils::ToOString(maColor) ); +} + +XclExpExtAxisColor::XclExpExtAxisColor( const Color& rColor ): + maAxisColor(rColor) +{ +} + +void XclExpExtAxisColor::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->singleElementNS( XML_x14, XML_axisColor, + XML_rgb, XclXmlUtils::ToOString(maAxisColor) ); +} + +XclExpExtIcon::XclExpExtIcon(const XclExpRoot& rRoot, const std::pair& rCustomEntry): + XclExpRoot(rRoot), + nIndex(rCustomEntry.second) +{ + pIconSetName = ScIconSetFormat::getIconSetName(rCustomEntry.first); +} + +void XclExpExtIcon::SaveXml(XclExpXmlStream& rStrm) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + if (nIndex == -1) + { + nIndex = 0; + pIconSetName = "NoIcons"; + } + + rWorksheet->singleElementNS(XML_x14, XML_cfIcon, + XML_iconSet, pIconSetName, + XML_iconId, OString::number(nIndex)); +} + +XclExpExtCfvo::XclExpExtCfvo( const XclExpRoot& rRoot, const ScColorScaleEntry& rEntry, const ScAddress& rSrcPos, bool bFirst ): + XclExpRoot(rRoot), + meType(rEntry.GetType()), + mbFirst(bFirst) +{ + if( rEntry.GetType() == COLORSCALE_FORMULA ) + { + const ScTokenArray* pArr = rEntry.GetFormula(); + OUString aFormula; + if(pArr) + { + aFormula = XclXmlUtils::ToOUString( GetCompileFormulaContext(), rSrcPos, pArr); + } + maValue = OUStringToOString(aFormula, RTL_TEXTENCODING_UTF8 ); + } + else + maValue = OString::number(rEntry.GetValue()); +} + +namespace { + +const char* getColorScaleType( ScColorScaleEntryType eType, bool bFirst ) +{ + switch(eType) + { + case COLORSCALE_MIN: + return "min"; + case COLORSCALE_MAX: + return "max"; + case COLORSCALE_PERCENT: + return "percent"; + case COLORSCALE_FORMULA: + return "formula"; + case COLORSCALE_AUTO: + if(bFirst) + return "autoMin"; + else + return "autoMax"; + case COLORSCALE_PERCENTILE: + return "percentile"; + default: + break; + } + return "num"; +} + +} + +void XclExpExtCfvo::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElementNS(XML_x14, XML_cfvo, XML_type, getColorScaleType(meType, mbFirst)); + + if (meType == COLORSCALE_FORMULA || + meType == COLORSCALE_PERCENT || + meType == COLORSCALE_PERCENTILE || + meType == COLORSCALE_VALUE) + { + rWorksheet->startElementNS(XML_xm, XML_f); + rWorksheet->writeEscaped(maValue.getStr()); + rWorksheet->endElementNS(XML_xm, XML_f); + } + + rWorksheet->endElementNS(XML_x14, XML_cfvo); +} + +XclExpExtCF::XclExpExtCF( const XclExpRoot& rRoot, const ScCondFormatEntry& rFormat ): + XclExpRoot(rRoot), + mrFormat(rFormat) +{ +} + +namespace { + +bool RequiresFixedFormula(ScConditionMode eMode) +{ + switch (eMode) + { + case ScConditionMode::BeginsWith: + case ScConditionMode::EndsWith: + case ScConditionMode::ContainsText: + case ScConditionMode::NotContainsText: + return true; + default: + break; + } + + return false; +} + +OString GetFixedFormula(ScConditionMode eMode, const ScAddress& rAddress, std::string_view rText) +{ + OStringBuffer aBuffer; + XclXmlUtils::ToOString(aBuffer, rAddress); + OString aPos = aBuffer.makeStringAndClear(); + switch (eMode) + { + case ScConditionMode::BeginsWith: + return OString("LEFT(" + aPos + ",LEN(" + rText + "))=" + rText); + case ScConditionMode::EndsWith: + return OString("RIGHT(" + aPos + ",LEN(" + rText + "))=" + rText); + case ScConditionMode::ContainsText: + return OString(OString::Concat("NOT(ISERROR(SEARCH(") + rText + "," + aPos + ")))"); + case ScConditionMode::NotContainsText: + return OString(OString::Concat("ISERROR(SEARCH(") + rText + "," + aPos + "))"); + default: + break; + } + + return ""; +} + +} + +void XclExpExtCF::SaveXml( XclExpXmlStream& rStrm ) +{ + OUString aStyleName = mrFormat.GetStyle(); + SfxStyleSheetBasePool* pPool = GetDoc().GetStyleSheetPool(); + SfxStyleSheetBase* pStyle = pPool->Find(aStyleName, SfxStyleFamily::Para); + SfxItemSet& rSet = pStyle->GetItemSet(); + + std::unique_ptr pTokenArray(mrFormat.CreateFlatCopiedTokenArray(0)); + aFormula = XclXmlUtils::ToOUString( GetCompileFormulaContext(), mrFormat.GetValidSrcPos(), pTokenArray.get()); + + std::unique_ptr pColor(new XclExpColor); + if(!pColor->FillFromItemSet( rSet )) + pColor.reset(); + + std::unique_ptr pBorder(new XclExpCellBorder); + if (!pBorder->FillFromItemSet( rSet, GetPalette(), GetBiff()) ) + pBorder.reset(); + + std::unique_ptr pAlign(new XclExpCellAlign); + if (!pAlign->FillFromItemSet(*this, rSet, false, GetBiff())) + pAlign.reset(); + + std::unique_ptr pCellProt(new XclExpCellProt); + if (!pCellProt->FillFromItemSet( rSet )) + pCellProt.reset(); + + std::unique_ptr pFont(new XclExpDxfFont(GetRoot(), rSet)); + + std::unique_ptr pNumFormat; + if( const SfxUInt32Item* pPoolItem = rSet.GetItemIfSet( ATTR_VALUE_FORMAT ) ) + { + sal_uInt32 nScNumFmt = pPoolItem->GetValue(); + XclExpNumFmtBuffer& rNumFmtBuffer = GetRoot().GetNumFmtBuffer(); + sal_uInt32 nXclNumFmt = rNumFmtBuffer.Insert(nScNumFmt); + pNumFormat.reset(new XclExpNumFmt(nScNumFmt, nXclNumFmt, rNumFmtBuffer.GetFormatCode(nScNumFmt))); + } + + XclExpDxf rDxf( GetRoot(), + std::move(pAlign), + std::move(pBorder), + std::move(pFont), + std::move(pNumFormat), + std::move(pCellProt), + std::move(pColor) ); + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + ScConditionMode eOperation = mrFormat.GetOperation(); + if (RequiresFixedFormula(eOperation)) + { + ScAddress aFixedFormulaPos = mrFormat.GetValidSrcPos(); + OString aFixedFormulaText = aFormula.toUtf8(); + OString aFixedFormula = GetFixedFormula(eOperation, aFixedFormulaPos, aFixedFormulaText); + rWorksheet->startElementNS( XML_xm, XML_f ); + rWorksheet->writeEscaped(aFixedFormula.getStr()); + rWorksheet->endElementNS( XML_xm, XML_f ); + + rWorksheet->startElementNS( XML_xm, XML_f ); + rWorksheet->writeEscaped( aFormula ); + rWorksheet->endElementNS( XML_xm, XML_f ); + rDxf.SaveXmlExt(rStrm); + } + else + { + rWorksheet->startElementNS(XML_xm, XML_f); + rWorksheet->writeEscaped(aFormula); + rWorksheet->endElementNS(XML_xm, XML_f); + rDxf.SaveXmlExt(rStrm); + } +} + +XclExpExtDataBar::XclExpExtDataBar( const XclExpRoot& rRoot, const ScDataBarFormat& rFormat, const ScAddress& rPos ): + XclExpRoot(rRoot) +{ + const ScDataBarFormatData& rFormatData = *rFormat.GetDataBarData(); + mpLowerLimit.reset(new XclExpExtCfvo(*this, *rFormatData.mpLowerLimit, rPos, true)); + mpUpperLimit.reset(new XclExpExtCfvo(*this, *rFormatData.mpUpperLimit, rPos, false)); + if (rFormatData.mxNegativeColor) + mpNegativeColor.reset(new XclExpExtNegativeColor(*rFormatData.mxNegativeColor)); + else + mpNegativeColor.reset( new XclExpExtNegativeColor( rFormatData.maPositiveColor ) ); + mpAxisColor.reset( new XclExpExtAxisColor( rFormatData.maAxisColor ) ); + + meAxisPosition = rFormatData.meAxisPosition; + mbGradient = rFormatData.mbGradient; + mnMinLength = rFormatData.mnMinLength; + mnMaxLength = rFormatData.mnMaxLength; +} + +namespace { + +const char* getAxisPosition(databar::ScAxisPosition eAxisPosition) +{ + switch(eAxisPosition) + { + case databar::NONE: + return "none"; + case databar::AUTOMATIC: + return "automatic"; + case databar::MIDDLE: + return "middle"; + } + return ""; +} + +const char* GetOperatorString(ScConditionMode eMode) +{ + const char* pRet = nullptr; + switch(eMode) + { + case ScConditionMode::Equal: + pRet = "equal"; + break; + case ScConditionMode::Less: + pRet = "lessThan"; + break; + case ScConditionMode::Greater: + pRet = "greaterThan"; + break; + case ScConditionMode::EqLess: + pRet = "lessThanOrEqual"; + break; + case ScConditionMode::EqGreater: + pRet = "greaterThanOrEqual"; + break; + case ScConditionMode::NotEqual: + pRet = "notEqual"; + break; + case ScConditionMode::Between: + pRet = "between"; + break; + case ScConditionMode::NotBetween: + pRet = "notBetween"; + break; + case ScConditionMode::Duplicate: + pRet = nullptr; + break; + case ScConditionMode::NotDuplicate: + pRet = nullptr; + break; + case ScConditionMode::BeginsWith: + pRet = "beginsWith"; + break; + case ScConditionMode::EndsWith: + pRet = "endsWith"; + break; + case ScConditionMode::ContainsText: + pRet = "containsText"; + break; + case ScConditionMode::NotContainsText: + pRet = "notContains"; + break; + case ScConditionMode::Direct: + break; + case ScConditionMode::NONE: + default: + break; + } + return pRet; +} + +const char* GetTypeString(ScConditionMode eMode) +{ + switch(eMode) + { + case ScConditionMode::Direct: + return "expression"; + case ScConditionMode::BeginsWith: + return "beginsWith"; + case ScConditionMode::EndsWith: + return "endsWith"; + case ScConditionMode::ContainsText: + return "containsText"; + case ScConditionMode::NotContainsText: + return "notContainsText"; + default: + return "cellIs"; + } +} + +} + +void XclExpExtDataBar::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElementNS( XML_x14, XML_dataBar, + XML_minLength, OString::number(mnMinLength), + XML_maxLength, OString::number(mnMaxLength), + XML_axisPosition, getAxisPosition(meAxisPosition), + XML_gradient, ToPsz(mbGradient) ); + + mpLowerLimit->SaveXml( rStrm ); + mpUpperLimit->SaveXml( rStrm ); + mpNegativeColor->SaveXml( rStrm ); + mpAxisColor->SaveXml( rStrm ); + + rWorksheet->endElementNS( XML_x14, XML_dataBar ); +} + +XclExpExtIconSet::XclExpExtIconSet(const XclExpRoot& rRoot, const ScIconSetFormat& rFormat, const ScAddress& rPos): + XclExpRoot(rRoot) +{ + const ScIconSetFormatData& rData = *rFormat.GetIconSetData(); + for (auto const& itr : rData.m_Entries) + { + maCfvos.AppendNewRecord(new XclExpExtCfvo(*this, *itr, rPos, false)); + } + mbCustom = rData.mbCustom; + mbReverse = rData.mbReverse; + mbShowValue = rData.mbShowValue; + mpIconSetName = ScIconSetFormat::getIconSetName(rData.eIconSetType); + + if (mbCustom) + { + for (const auto& rItem : rData.maCustomVector) + { + maCustom.AppendNewRecord(new XclExpExtIcon(*this, rItem)); + } + } +} + +void XclExpExtIconSet::SaveXml(XclExpXmlStream& rStrm) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElementNS(XML_x14, XML_iconSet, + XML_iconSet, mpIconSetName, + XML_custom, sax_fastparser::UseIf(ToPsz10(mbCustom), mbCustom), + XML_reverse, ToPsz10(mbReverse), + XML_showValue, ToPsz10(mbShowValue)); + + maCfvos.SaveXml(rStrm); + + if (mbCustom) + { + maCustom.SaveXml(rStrm); + } + + rWorksheet->endElementNS(XML_x14, XML_iconSet); +} + +XclExpExtCfRule::XclExpExtCfRule( const XclExpRoot& rRoot, const ScFormatEntry& rFormat, const ScAddress& rPos, const OString& rId, sal_Int32 nPriority ): + XclExpRoot(rRoot), + maId(rId), + pType(nullptr), + mnPriority(nPriority), + mOperator(nullptr) +{ + switch (rFormat.GetType()) + { + case ScFormatEntry::Type::Databar: + { + const ScDataBarFormat& rDataBar = static_cast(rFormat); + mxEntry = new XclExpExtDataBar( *this, rDataBar, rPos ); + pType = "dataBar"; + } + break; + case ScFormatEntry::Type::Iconset: + { + const ScIconSetFormat& rIconSet = static_cast(rFormat); + mxEntry = new XclExpExtIconSet(*this, rIconSet, rPos); + pType = "iconSet"; + } + break; + case ScFormatEntry::Type::ExtCondition: + { + const ScCondFormatEntry& rCondFormat = static_cast(rFormat); + mxEntry = new XclExpExtCF(*this, rCondFormat); + pType = GetTypeString(rCondFormat.GetOperation()); + mOperator = GetOperatorString( rCondFormat.GetOperation() ); + } + break; + default: + break; + } +} + +void XclExpExtCfRule::SaveXml( XclExpXmlStream& rStrm ) +{ + if (!mxEntry) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElementNS( XML_x14, XML_cfRule, + XML_type, pType, + XML_priority, sax_fastparser::UseIf(OString::number(mnPriority + 1), mnPriority != -1), + XML_operator, mOperator, + XML_id, maId ); + + mxEntry->SaveXml( rStrm ); + + rWorksheet->endElementNS( XML_x14, XML_cfRule ); + +} + +XclExpExtConditionalFormatting::XclExpExtConditionalFormatting( const XclExpRoot& rRoot, + std::vector& rData, const ScRangeList& rRange): + XclExpRoot(rRoot), + maRange(rRange) +{ + ScAddress aAddr = maRange.front().aStart; + for (const auto& rItem : rData) + { + const ScFormatEntry* pEntry = rItem.pEntry; + switch (pEntry->GetType()) + { + case ScFormatEntry::Type::Iconset: + { + const ScIconSetFormat& rIconSet = static_cast(*pEntry); + bool bNeedsExt = false; + switch (rIconSet.GetIconSetData()->eIconSetType) + { + case IconSet_3Triangles: + case IconSet_3Smilies: + case IconSet_3ColorSmilies: + case IconSet_5Boxes: + case IconSet_3Stars: + bNeedsExt = true; + break; + default: + break; + } + + if (rIconSet.GetIconSetData()->mbCustom) + bNeedsExt = true; + + if (bNeedsExt) + { + maCfRules.AppendNewRecord(new XclExpExtCfRule(*this, *pEntry, aAddr, rItem.aGUID, rItem.nPriority)); + } + } + break; + case ScFormatEntry::Type::Databar: + maCfRules.AppendNewRecord(new XclExpExtCfRule( *this, *pEntry, aAddr, rItem.aGUID, rItem.nPriority)); + break; + case ScFormatEntry::Type::ExtCondition: + maCfRules.AppendNewRecord(new XclExpExtCfRule( *this, *pEntry, aAddr, rItem.aGUID, rItem.nPriority)); + break; + default: + break; + } + } +} + +void XclExpExtConditionalFormatting::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElementNS( XML_x14, XML_conditionalFormatting, + FSNS( XML_xmlns, XML_xm ), rStrm.getNamespaceURL(OOX_NS(xm)) ); + + maCfRules.SaveXml( rStrm ); + rWorksheet->startElementNS(XML_xm, XML_sqref); + rWorksheet->write(XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maRange)); + + rWorksheet->endElementNS( XML_xm, XML_sqref ); + + rWorksheet->endElementNS( XML_x14, XML_conditionalFormatting ); +} + +XclExpExtCalcPr::XclExpExtCalcPr( const XclExpRoot& rRoot, formula::FormulaGrammar::AddressConvention eConv ): + XclExpExt( rRoot ) +{ + maURI = OString("{7626C862-2A13-11E5-B345-FEFF819CDC9F}"); + + switch (eConv) + { + case formula::FormulaGrammar::CONV_OOO: + maSyntax = OString("CalcA1"); + break; + case formula::FormulaGrammar::CONV_XL_A1: + maSyntax = OString("ExcelA1"); + break; + case formula::FormulaGrammar::CONV_XL_R1C1: + maSyntax = OString("ExcelR1C1"); + break; + case formula::FormulaGrammar::CONV_A1_XL_A1: + maSyntax = OString("CalcA1ExcelA1"); + break; + case formula::FormulaGrammar::CONV_UNSPECIFIED: + case formula::FormulaGrammar::CONV_ODF: + case formula::FormulaGrammar::CONV_XL_OOX: + case formula::FormulaGrammar::CONV_LOTUS_A1: + case formula::FormulaGrammar::CONV_LAST: + maSyntax = OString("Unspecified"); + break; + } +} + +void XclExpExtCalcPr::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_ext, + FSNS(XML_xmlns, XML_loext), rStrm.getNamespaceURL(OOX_NS(loext)), + XML_uri, maURI ); + + rWorksheet->singleElementNS(XML_loext, XML_extCalcPr, XML_stringRefSyntax, maSyntax); + + rWorksheet->endElement( XML_ext ); +} + +XclExpExtCondFormat::XclExpExtCondFormat( const XclExpRoot& rRoot ): + XclExpExt( rRoot ) +{ + maURI = OString("{78C0D931-6437-407d-A8EE-F0AAD7539E65}"); +} + +void XclExpExtCondFormat::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_ext, + FSNS(XML_xmlns, XML_x14), rStrm.getNamespaceURL(OOX_NS(xls14Lst)), + XML_uri, maURI ); + + rWorksheet->startElementNS(XML_x14, XML_conditionalFormattings); + + maCF.SaveXml( rStrm ); + + rWorksheet->endElementNS( XML_x14, XML_conditionalFormattings ); + rWorksheet->endElement( XML_ext ); +} + +void XclExpExtCondFormat::AddRecord( XclExpExtConditionalFormatting* pEntry ) +{ + maCF.AppendRecord( pEntry ); +} + +void XclExtLst::SaveXml( XclExpXmlStream& rStrm ) +{ + if(maExtEntries.IsEmpty()) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_extLst); + + maExtEntries.SaveXml(rStrm); + + rWorksheet->endElement( XML_extLst ); +} + +void XclExtLst::AddRecord( XclExpExt* pEntry ) +{ + maExtEntries.AppendRecord( pEntry ); +} + +XclExpExt* XclExtLst::GetItem( XclExpExtType eType ) +{ + size_t n = maExtEntries.GetSize(); + for( size_t i = 0; i < n; ++i ) + { + if (maExtEntries.GetRecord( i )->GetType() == eType) + return maExtEntries.GetRecord( i ); + } + + return nullptr; +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeformula.cxx b/sc/source/filter/excel/xeformula.cxx new file mode 100644 index 000000000..e6eabd69c --- /dev/null +++ b/sc/source/filter/excel/xeformula.cxx @@ -0,0 +1,2709 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::formula; + +// External reference log ===================================================== + +XclExpRefLogEntry::XclExpRefLogEntry() : + mpUrl( nullptr ), + mpFirstTab( nullptr ), + mpLastTab( nullptr ), + mnFirstXclTab( EXC_TAB_DELETED ), + mnLastXclTab( EXC_TAB_DELETED ) +{ +} + +// Formula compiler =========================================================== + +namespace { + +/** Wrapper structure for a processed Calc formula token with additional + settings (whitespaces). */ +struct XclExpScToken +{ + const FormulaToken* mpScToken; /// Currently processed Calc token. + sal_uInt8 mnSpaces; /// Number of spaces before the Calc token. + + explicit XclExpScToken() : mpScToken( nullptr ), mnSpaces( 0 ) {} + bool Is() const { return mpScToken != nullptr; } + StackVar GetType() const { return mpScToken ? mpScToken->GetType() : svUnknown; } + OpCode GetOpCode() const { return mpScToken ? mpScToken->GetOpCode() : ocNone; } +}; + +/** Effective token class conversion types. */ +enum XclExpClassConv +{ + EXC_CLASSCONV_ORG, /// Keep original class of the token. + EXC_CLASSCONV_VAL, /// Convert ARR tokens to VAL class (REF remains unchanged). + EXC_CLASSCONV_ARR /// Convert VAL tokens to ARR class (REF remains unchanged). +}; + +/** Token class conversion and position of a token in the token array. */ +struct XclExpTokenConvInfo +{ + sal_uInt16 mnTokPos; /// Position of the token in the token array. + XclFuncParamConv meConv; /// Token class conversion type. + bool mbValType; /// Data type (false = REFTYPE, true = VALTYPE). +}; + +/** Vector of token position and conversion for all operands of an operator, + or for all parameters of a function. */ +struct XclExpOperandList : public std::vector< XclExpTokenConvInfo > +{ + explicit XclExpOperandList() { reserve( 2 ); } + void AppendOperand( sal_uInt16 nTokPos, XclFuncParamConv eConv, bool bValType ); +}; + +void XclExpOperandList::AppendOperand( sal_uInt16 nTokPos, XclFuncParamConv eConv, bool bValType ) +{ + resize( size() + 1 ); + XclExpTokenConvInfo& rConvInfo = back(); + rConvInfo.mnTokPos = nTokPos; + rConvInfo.meConv = eConv; + rConvInfo.mbValType = bValType; +} + +typedef std::shared_ptr< XclExpOperandList > XclExpOperandListRef; + +/** Encapsulates all data needed for a call to an external function (macro, add-in). */ +struct XclExpExtFuncData +{ + OUString maFuncName; /// Name of the function. + bool mbVBasic; /// True = Visual Basic macro call. + bool mbHidden; /// True = Create hidden defined name. + + explicit XclExpExtFuncData() : mbVBasic( false ), mbHidden( false ) {} + void Set( const OUString& rFuncName, bool bVBasic, bool bHidden ); +}; + +void XclExpExtFuncData::Set( const OUString& rFuncName, bool bVBasic, bool bHidden ) +{ + maFuncName = rFuncName; + mbVBasic = bVBasic; + mbHidden = bHidden; +} + +/** Encapsulates all data needed to process an entire function. */ +class XclExpFuncData +{ +public: + explicit XclExpFuncData( + const XclExpScToken& rTokData, + const XclFunctionInfo& rFuncInfo, + const XclExpExtFuncData& rExtFuncData ); + + const FormulaToken& GetScToken() const { return *mrTokData.mpScToken; } + OpCode GetOpCode() const { return mrFuncInfo.meOpCode; } + sal_uInt16 GetXclFuncIdx() const { return mrFuncInfo.mnXclFunc; } + bool IsVolatile() const { return mrFuncInfo.IsVolatile(); } + bool IsFixedParamCount() const { return mrFuncInfo.IsFixedParamCount(); } + bool IsAddInEquivalent() const { return mrFuncInfo.IsAddInEquivalent(); } + bool IsMacroFunc() const { return mrFuncInfo.IsMacroFunc(); } + sal_uInt8 GetSpaces() const { return mrTokData.mnSpaces; } + const XclExpExtFuncData& GetExtFuncData() const { return maExtFuncData; } + sal_uInt8 GetReturnClass() const { return mrFuncInfo.mnRetClass; } + + const XclFuncParamInfo& GetParamInfo() const; + bool IsCalcOnlyParam() const; + bool IsExcelOnlyParam() const; + void IncParamInfoIdx(); + + sal_uInt8 GetMinParamCount() const { return mrFuncInfo.mnMinParamCount; } + sal_uInt8 GetMaxParamCount() const { return mrFuncInfo.mnMaxParamCount; } + sal_uInt8 GetParamCount() const { return static_cast< sal_uInt8 >( mxOperands->size() ); } + void FinishParam( sal_uInt16 nTokPos ); + const XclExpOperandListRef& GetOperandList() const { return mxOperands; } + + ScfUInt16Vec& GetAttrPosVec() { return maAttrPosVec; } + void AppendAttrPos( sal_uInt16 nPos ) { maAttrPosVec.push_back( nPos ); } + +private: + ScfUInt16Vec maAttrPosVec; /// Token array positions of tAttr tokens. + const XclExpScToken& mrTokData; /// Data about processed function name token. + const XclFunctionInfo& mrFuncInfo; /// Constant data about processed function. + XclExpExtFuncData maExtFuncData; /// Data for external functions (macro, add-in). + XclExpOperandListRef mxOperands; /// Class conversion and position of all parameters. + const XclFuncParamInfo* mpParamInfo; /// Information for current parameter. +}; + +XclExpFuncData::XclExpFuncData( const XclExpScToken& rTokData, + const XclFunctionInfo& rFuncInfo, const XclExpExtFuncData& rExtFuncData ) : + mrTokData( rTokData ), + mrFuncInfo( rFuncInfo ), + maExtFuncData( rExtFuncData ), + mxOperands( std::make_shared() ), + mpParamInfo( rFuncInfo.mpParamInfos ) +{ + OSL_ENSURE( mrTokData.mpScToken, "XclExpFuncData::XclExpFuncData - missing core token" ); + // set name of an add-in function + if( (maExtFuncData.maFuncName.isEmpty()) && dynamic_cast< const FormulaExternalToken* >( mrTokData.mpScToken ) ) + maExtFuncData.Set( GetScToken().GetExternal(), true, false ); +} + +const XclFuncParamInfo& XclExpFuncData::GetParamInfo() const +{ + static const XclFuncParamInfo saInvalidInfo = { EXC_PARAM_NONE, EXC_PARAMCONV_ORG, false }; + return mpParamInfo ? *mpParamInfo : saInvalidInfo; +} + +bool XclExpFuncData::IsCalcOnlyParam() const +{ + return mpParamInfo && (mpParamInfo->meValid == EXC_PARAM_CALCONLY); +} + +bool XclExpFuncData::IsExcelOnlyParam() const +{ + return mpParamInfo && (mpParamInfo->meValid == EXC_PARAM_EXCELONLY); +} + +void XclExpFuncData::IncParamInfoIdx() +{ + if( !mpParamInfo ) + return; + + // move pointer to next entry, if something explicit follows + if( (o3tl::make_unsigned( mpParamInfo - mrFuncInfo.mpParamInfos + 1 ) < EXC_FUNCINFO_PARAMINFO_COUNT) && (mpParamInfo[ 1 ].meValid != EXC_PARAM_NONE) ) + ++mpParamInfo; + // if last parameter type is 'Excel-only' or 'Calc-only', do not repeat it + else if( IsExcelOnlyParam() || IsCalcOnlyParam() ) + mpParamInfo = nullptr; + // points to last info, but parameter pairs expected, move to previous info + else if( mrFuncInfo.IsParamPairs() ) + --mpParamInfo; + // otherwise: repeat last parameter class +} + +void XclExpFuncData::FinishParam( sal_uInt16 nTokPos ) +{ + // write token class conversion info for this parameter + const XclFuncParamInfo& rParamInfo = GetParamInfo(); + mxOperands->AppendOperand( nTokPos, rParamInfo.meConv, rParamInfo.mbValType ); + // move to next parameter info structure + IncParamInfoIdx(); +} + +// compiler configuration ----------------------------------------------------- + +/** Type of token class handling. */ +enum XclExpFmlaClassType +{ + EXC_CLASSTYPE_CELL, /// Cell formula, shared formula. + EXC_CLASSTYPE_ARRAY, /// Array formula, conditional formatting, data validation. + EXC_CLASSTYPE_NAME /// Defined name, range list. +}; + +/** Configuration data of the formula compiler. */ +struct XclExpCompConfig +{ + XclFormulaType meType; /// Type of the formula to be created. + XclExpFmlaClassType meClassType; /// Token class handling type. + bool mbLocalLinkMgr; /// True = local (per-sheet) link manager, false = global. + bool mbFromCell; /// True = Any kind of cell formula (cell, array, shared). + bool mb3DRefOnly; /// True = Only 3D references allowed (e.g. names). + bool mbAllowArrays; /// True = Allow inline arrays. +}; + +/** The table containing configuration data for all formula types. */ +const XclExpCompConfig spConfigTable[] = +{ + // formula type token class type lclLM inCell 3dOnly allowArray + { EXC_FMLATYPE_CELL, EXC_CLASSTYPE_CELL, true, true, false, true }, + { EXC_FMLATYPE_SHARED, EXC_CLASSTYPE_CELL, true, true, false, true }, + { EXC_FMLATYPE_MATRIX, EXC_CLASSTYPE_ARRAY, true, true, false, true }, + { EXC_FMLATYPE_CONDFMT, EXC_CLASSTYPE_ARRAY, true, false, false, false }, + { EXC_FMLATYPE_DATAVAL, EXC_CLASSTYPE_ARRAY, true, false, false, false }, + { EXC_FMLATYPE_NAME, EXC_CLASSTYPE_NAME, false, false, true, true }, + { EXC_FMLATYPE_CHART, EXC_CLASSTYPE_NAME, true, false, true, true }, + { EXC_FMLATYPE_CONTROL, EXC_CLASSTYPE_NAME, true, false, false, false }, + { EXC_FMLATYPE_WQUERY, EXC_CLASSTYPE_NAME, true, false, true, false }, + { EXC_FMLATYPE_LISTVAL, EXC_CLASSTYPE_NAME, true, false, false, false } +}; + +/** Working data of the formula compiler. Used to push onto a stack for recursive calls. */ +struct XclExpCompData +{ + typedef std::shared_ptr< ScTokenArray > ScTokenArrayRef; + + const XclExpCompConfig& mrCfg; /// Configuration for current formula type. + ScTokenArrayRef mxOwnScTokArr; /// Own clone of a Calc token array. + XclTokenArrayIterator maTokArrIt; /// Iterator in Calc token array. + XclExpLinkManager* mpLinkMgr; /// Link manager for current context (local/global). + XclExpRefLog* mpRefLog; /// Log for external references. + const ScAddress* mpScBasePos; /// Current cell position of the formula. + + ScfUInt8Vec maTokVec; /// Byte vector containing token data. + ScfUInt8Vec maExtDataVec; /// Byte vector containing extended data (arrays, stacked NLRs). + std::vector< XclExpOperandListRef > + maOpListVec; /// Formula structure, maps operators to their operands. + ScfUInt16Vec maOpPosStack; /// Stack with positions of operand tokens waiting for an operator. + bool mbStopAtSep; /// True = Stop subexpression creation at an ocSep token. + bool mbVolatile; /// True = Formula contains volatile function. + bool mbOk; /// Current state of the compiler. + + explicit XclExpCompData( const XclExpCompConfig* pCfg ); +}; + +XclExpCompData::XclExpCompData( const XclExpCompConfig* pCfg ) : + mrCfg( pCfg ? *pCfg : spConfigTable[ 0 ] ), + mpLinkMgr( nullptr ), + mpRefLog( nullptr ), + mpScBasePos( nullptr ), + mbStopAtSep( false ), + mbVolatile( false ), + mbOk( pCfg != nullptr ) +{ + OSL_ENSURE( pCfg, "XclExpFmlaCompImpl::Init - unknown formula type" ); +} + +} // namespace + +/** Implementation class of the export formula compiler. */ +class XclExpFmlaCompImpl : protected XclExpRoot, protected XclTokenArrayHelper +{ +public: + explicit XclExpFmlaCompImpl( const XclExpRoot& rRoot ); + + /** Creates an Excel token array from the passed Calc token array. */ + XclTokenArrayRef CreateFormula( + XclFormulaType eType, const ScTokenArray& rScTokArr, + const ScAddress* pScBasePos = nullptr, XclExpRefLog* pRefLog = nullptr ); + /** Creates a single error token containing the passed error code. */ + XclTokenArrayRef CreateErrorFormula( sal_uInt8 nErrCode ); + /** Creates a single token for a special cell reference. */ + XclTokenArrayRef CreateSpecialRefFormula( sal_uInt8 nTokenId, const XclAddress& rXclPos ); + /** Creates a single tNameXR token for a reference to an external name. */ + XclTokenArrayRef CreateNameXFormula( sal_uInt16 nExtSheet, sal_uInt16 nExtName ); + + /** Returns true, if the passed formula type allows 3D references only. */ + bool Is3DRefOnly( XclFormulaType eType ) const; + + bool IsRef2D( const ScSingleRefData& rRefData, bool bCheck3DFlag ) const; + bool IsRef2D( const ScComplexRefData& rRefData, bool bCheck3DFlag ) const; + +private: + const XclExpCompConfig* GetConfigForType( XclFormulaType eType ) const; + sal_uInt16 GetSize() const { return static_cast< sal_uInt16 >( mxData->maTokVec.size() ); } + + void Init( XclFormulaType eType ); + void Init( XclFormulaType eType, const ScTokenArray& rScTokArr, + const ScAddress* pScBasePos, XclExpRefLog* pRefLog ); + + void RecalcTokenClasses(); + void RecalcTokenClass( const XclExpTokenConvInfo& rConvInfo, XclFuncParamConv ePrevConv, XclExpClassConv ePrevClassConv, bool bWasRefClass ); + + void FinalizeFormula(); + XclTokenArrayRef CreateTokenArray(); + + // compiler --------------------------------------------------------------- + // XclExpScToken: pass-by-value and return-by-value is intended + + const FormulaToken* GetNextRawToken(); + const FormulaToken* PeekNextRawToken() const; + + bool GetNextToken( XclExpScToken& rTokData ); + XclExpScToken GetNextToken(); + + XclExpScToken Expression( XclExpScToken aTokData, bool bInParentheses, bool bStopAtSep ); + XclExpScToken SkipExpression( XclExpScToken aTokData, bool bStopAtSep ); + + XclExpScToken OrTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken AndTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken CompareTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken ConcatTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken AddSubTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken MulDivTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken PowTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken UnaryPostTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken UnaryPreTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken ListTerm( XclExpScToken aTokData, bool bInParentheses ); + XclExpScToken IntersectTerm( XclExpScToken aTokData, bool& rbHasRefOp ); + XclExpScToken RangeTerm( XclExpScToken aTokData, bool& rbHasRefOp ); + XclExpScToken Factor( XclExpScToken aTokData ); + + // formula structure ------------------------------------------------------ + + void ProcessDouble( const XclExpScToken& rTokData ); + void ProcessString( const XclExpScToken& rTokData ); + void ProcessMissing( const XclExpScToken& rTokData ); + void ProcessBad( const XclExpScToken& rTokData ); + void ProcessParentheses( const XclExpScToken& rTokData ); + void ProcessBoolean( const XclExpScToken& rTokData ); + void ProcessDdeLink( const XclExpScToken& rTokData ); + void ProcessExternal( const XclExpScToken& rTokData ); + void ProcessMatrix( const XclExpScToken& rTokData ); + + void ProcessFunction( const XclExpScToken& rTokData ); + void PrepareFunction( const XclExpFuncData& rFuncData ); + void FinishFunction( XclExpFuncData& rFuncData, sal_uInt8 nCloseSpaces ); + void FinishIfFunction( XclExpFuncData& rFuncData ); + void FinishChooseFunction( XclExpFuncData& rFuncData ); + + XclExpScToken ProcessParam( XclExpScToken aTokData, XclExpFuncData& rFuncData ); + void PrepareParam( XclExpFuncData& rFuncData ); + void FinishParam( XclExpFuncData& rFuncData ); + void AppendDefaultParam( XclExpFuncData& rFuncData ); + void AppendTrailingParam( XclExpFuncData& rFuncData ); + + // reference handling ----------------------------------------------------- + + SCTAB GetScTab( const ScSingleRefData& rRefData ) const; + + void ConvertRefData( ScSingleRefData& rRefData, XclAddress& rXclPos, + bool bNatLangRef, bool bTruncMaxCol, bool bTruncMaxRow ) const; + void ConvertRefData( ScComplexRefData& rRefData, XclRange& rXclRange, + bool bNatLangRef ) const; + + XclExpRefLogEntry* GetNewRefLogEntry(); + void ProcessCellRef( const XclExpScToken& rTokData ); + void ProcessRangeRef( const XclExpScToken& rTokData ); + void ProcessExternalCellRef( const XclExpScToken& rTokData ); + void ProcessExternalRangeRef( const XclExpScToken& rTokData ); + void ProcessDefinedName( const XclExpScToken& rTokData ); + void ProcessExternalName( const XclExpScToken& rTokData ); + + // token vector ----------------------------------------------------------- + + void PushOperandPos( sal_uInt16 nTokPos ); + void PushOperatorPos( sal_uInt16 nTokPos, const XclExpOperandListRef& rxOperands ); + sal_uInt16 PopOperandPos(); + + void Append( sal_uInt8 nData ); + void Append( sal_uInt8 nData, size_t nCount ); + void Append( sal_uInt16 nData ); + void Append( sal_uInt32 nData ); + void Append( double fData ); + void Append( const OUString& rString ); + + void AppendAddress( const XclAddress& rXclPos ); + void AppendRange( const XclRange& rXclRange ); + + void AppendSpaceToken( sal_uInt8 nType, sal_uInt8 nCount ); + + void AppendOperandTokenId( sal_uInt8 nTokenId, sal_uInt8 nSpaces = 0 ); + void AppendIntToken( sal_uInt16 nValue, sal_uInt8 nSpaces = 0 ); + void AppendNumToken( double fValue, sal_uInt8 nSpaces = 0 ); + void AppendBoolToken( bool bValue, sal_uInt8 nSpaces = 0 ); + void AppendErrorToken( sal_uInt8 nErrCode, sal_uInt8 nSpaces = 0 ); + void AppendMissingToken( sal_uInt8 nSpaces = 0 ); + void AppendNameToken( sal_uInt16 nNameIdx, sal_uInt8 nSpaces = 0 ); + void AppendMissingNameToken( const OUString& rName, sal_uInt8 nSpaces = 0 ); + void AppendNameXToken( sal_uInt16 nExtSheet, sal_uInt16 nExtName, sal_uInt8 nSpaces = 0 ); + void AppendMacroCallToken( const XclExpExtFuncData& rExtFuncData ); + void AppendAddInCallToken( const XclExpExtFuncData& rExtFuncData ); + void AppendEuroToolCallToken( const XclExpExtFuncData& rExtFuncData ); + + void AppendOperatorTokenId( sal_uInt8 nTokenId, const XclExpOperandListRef& rxOperands, sal_uInt8 nSpaces = 0 ); + void AppendUnaryOperatorToken( sal_uInt8 nTokenId, sal_uInt8 nSpaces = 0 ); + void AppendBinaryOperatorToken( sal_uInt8 nTokenId, bool bValType, sal_uInt8 nSpaces = 0 ); + void AppendLogicalOperatorToken( sal_uInt16 nXclFuncIdx, sal_uInt8 nOpCount ); + void AppendFuncToken( const XclExpFuncData& rFuncData ); + + void AppendParenToken( sal_uInt8 nOpenSpaces = 0, sal_uInt8 nCloseSpaces = 0 ); + void AppendJumpToken( XclExpFuncData& rFuncData, sal_uInt8 nAttrType ); + + void InsertZeros( sal_uInt16 nInsertPos, sal_uInt16 nInsertSize ); + void Overwrite( sal_uInt16 nWriteToPos, sal_uInt16 nOffset ); + + void UpdateAttrGoto( sal_uInt16 nAttrPos ); + + bool IsSpaceToken( sal_uInt16 nPos ) const; + void RemoveTrailingParen(); + + void AppendExt( sal_uInt8 nData ); + void AppendExt( sal_uInt8 nData, size_t nCount ); + void AppendExt( sal_uInt16 nData ); + void AppendExt( double fData ); + void AppendExt( const OUString& rString ); + +private: + typedef std::map< XclFormulaType, XclExpCompConfig > XclExpCompConfigMap; + typedef std::shared_ptr< XclExpCompData > XclExpCompDataRef; + + XclExpCompConfigMap maCfgMap; /// Compiler configuration map for all formula types. + XclFunctionProvider maFuncProv; /// Excel function data provider. + XclExpCompDataRef mxData; /// Working data for current formula. + std::vector< XclExpCompDataRef > + maDataStack; /// Stack for working data, when compiler is called recursively. + const XclBiff meBiff; /// Cached BIFF version to save GetBiff() calls. + const SCCOL mnMaxAbsCol; /// Maximum column index. + const SCROW mnMaxAbsRow; /// Maximum row index. + const SCCOL mnMaxScCol; /// Maximum column index in Calc itself. + const SCROW mnMaxScRow; /// Maximum row index in Calc itself. + const sal_uInt16 mnMaxColMask; /// Mask to delete invalid bits in column fields. + const sal_uInt32 mnMaxRowMask; /// Mask to delete invalid bits in row fields. +}; + +XclExpFmlaCompImpl::XclExpFmlaCompImpl( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + maFuncProv( rRoot ), + meBiff( rRoot.GetBiff() ), + mnMaxAbsCol( rRoot.GetXclMaxPos().Col() ), + mnMaxAbsRow( rRoot.GetXclMaxPos().Row() ), + mnMaxScCol( rRoot.GetScMaxPos().Col() ), + mnMaxScRow( rRoot.GetScMaxPos().Row() ), + mnMaxColMask( static_cast< sal_uInt16 >( rRoot.GetXclMaxPos().Col() ) ), + mnMaxRowMask( static_cast< sal_uInt32 >( rRoot.GetXclMaxPos().Row() ) ) +{ + // build the configuration map + for(auto const &rEntry : spConfigTable) + maCfgMap[ rEntry.meType ] = rEntry; +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateFormula( XclFormulaType eType, + const ScTokenArray& rScTokArr, const ScAddress* pScBasePos, XclExpRefLog* pRefLog ) +{ + // initialize the compiler + Init( eType, rScTokArr, pScBasePos, pRefLog ); + + // start compilation, if initialization didn't fail + if( mxData->mbOk ) + { + XclExpScToken aTokData( GetNextToken() ); + FormulaError nScError = rScTokArr.GetCodeError(); + if( (nScError != FormulaError::NONE) && (!aTokData.Is() || (aTokData.GetOpCode() == ocStop)) ) + { + // #i50253# convert simple ocStop token to error code formula (e.g. =#VALUE!) + AppendErrorToken( XclTools::GetXclErrorCode( nScError ), aTokData.mnSpaces ); + } + else if( aTokData.Is() ) + { + aTokData = Expression( aTokData, false, false ); + } + else + { + OSL_FAIL( "XclExpFmlaCompImpl::CreateFormula - empty token array" ); + mxData->mbOk = false; + } + + if( mxData->mbOk ) + { + // #i44907# auto-generated SUBTOTAL formula cells have trailing ocStop token + mxData->mbOk = !aTokData.Is() || (aTokData.GetOpCode() == ocStop); + OSL_ENSURE( mxData->mbOk, "XclExpFmlaCompImpl::CreateFormula - unknown garbage behind formula" ); + } + } + + // finalize (add tAttrVolatile token, calculate all token classes) + RecalcTokenClasses(); + FinalizeFormula(); + + // leave recursive call, create and return the final token array + return CreateTokenArray(); +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateErrorFormula( sal_uInt8 nErrCode ) +{ + Init( EXC_FMLATYPE_NAME ); + AppendErrorToken( nErrCode ); + return CreateTokenArray(); +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateSpecialRefFormula( sal_uInt8 nTokenId, const XclAddress& rXclPos ) +{ + Init( EXC_FMLATYPE_NAME ); + AppendOperandTokenId( nTokenId ); + Append( static_cast(rXclPos.mnRow) ); + Append( rXclPos.mnCol ); // do not use AppendAddress(), we always need 16-bit column here + return CreateTokenArray(); +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateNameXFormula( sal_uInt16 nExtSheet, sal_uInt16 nExtName ) +{ + Init( EXC_FMLATYPE_NAME ); + AppendNameXToken( nExtSheet, nExtName ); + return CreateTokenArray(); +} + +bool XclExpFmlaCompImpl::Is3DRefOnly( XclFormulaType eType ) const +{ + const XclExpCompConfig* pCfg = GetConfigForType( eType ); + return pCfg && pCfg->mb3DRefOnly; +} + +// private -------------------------------------------------------------------- + +const XclExpCompConfig* XclExpFmlaCompImpl::GetConfigForType( XclFormulaType eType ) const +{ + XclExpCompConfigMap::const_iterator aIt = maCfgMap.find( eType ); + OSL_ENSURE( aIt != maCfgMap.end(), "XclExpFmlaCompImpl::GetConfigForType - unknown formula type" ); + return (aIt == maCfgMap.end()) ? nullptr : &aIt->second; +} + +void XclExpFmlaCompImpl::Init( XclFormulaType eType ) +{ + // compiler invoked recursively? - store old working data + if( mxData ) + maDataStack.push_back( mxData ); + // new compiler working data structure + mxData = std::make_shared( GetConfigForType( eType ) ); +} + +void XclExpFmlaCompImpl::Init( XclFormulaType eType, const ScTokenArray& rScTokArr, + const ScAddress* pScBasePos, XclExpRefLog* pRefLog ) +{ + // common initialization + Init( eType ); + + // special initialization + if( mxData->mbOk ) switch( mxData->mrCfg.meType ) + { + case EXC_FMLATYPE_CELL: + case EXC_FMLATYPE_MATRIX: + case EXC_FMLATYPE_CHART: + mxData->mbOk = pScBasePos != nullptr; + OSL_ENSURE( mxData->mbOk, "XclExpFmlaCompImpl::Init - missing cell address" ); + mxData->mpScBasePos = pScBasePos; + break; + case EXC_FMLATYPE_SHARED: + mxData->mbOk = pScBasePos != nullptr; + assert(mxData->mbOk && "XclExpFmlaCompImpl::Init - missing cell address"); + if (mxData->mbOk) + { + // clone the passed token array, convert references relative to current cell position + mxData->mxOwnScTokArr = rScTokArr.Clone(); + ScCompiler::MoveRelWrap( *mxData->mxOwnScTokArr, GetDoc(), *pScBasePos, GetDoc().MaxCol(), GetDoc().MaxRow() ); + // don't remember pScBasePos in mxData->mpScBasePos, shared formulas use real relative refs + } + break; + default:; + } + + if( mxData->mbOk ) + { + // link manager to be used + mxData->mpLinkMgr = mxData->mrCfg.mbLocalLinkMgr ? &GetLocalLinkManager() : &GetGlobalLinkManager(); + + // token array iterator (use cloned token array if present) + mxData->maTokArrIt.Init( mxData->mxOwnScTokArr ? *mxData->mxOwnScTokArr : rScTokArr, false ); + mxData->mpRefLog = pRefLog; + // Only for OOXML + if (GetOutput() == EXC_OUTPUT_XML_2007) + mxData->mpScBasePos = pScBasePos; + } +} + +void XclExpFmlaCompImpl::RecalcTokenClasses() +{ + if( !mxData->mbOk ) + return; + + mxData->mbOk = mxData->maOpPosStack.size() == 1; + OSL_ENSURE( mxData->mbOk, "XclExpFmlaCompImpl::RecalcTokenClasses - position of root token expected on stack" ); + if( mxData->mbOk ) + { + /* Cell and array formulas start with VAL conversion and VALTYPE + parameter type, defined names start with ARR conversion and + REFTYPE parameter type for the root token. */ + bool bNameFmla = mxData->mrCfg.meClassType == EXC_CLASSTYPE_NAME; + XclFuncParamConv eParamConv = bNameFmla ? EXC_PARAMCONV_ARR : EXC_PARAMCONV_VAL; + XclExpClassConv eClassConv = bNameFmla ? EXC_CLASSCONV_ARR : EXC_CLASSCONV_VAL; + XclExpTokenConvInfo aConvInfo = { PopOperandPos(), eParamConv, !bNameFmla }; + RecalcTokenClass( aConvInfo, eParamConv, eClassConv, bNameFmla ); + } + + // clear operand vectors (calls to the expensive InsertZeros() may follow) + mxData->maOpListVec.clear(); + mxData->maOpPosStack.clear(); +} + +void XclExpFmlaCompImpl::RecalcTokenClass( const XclExpTokenConvInfo& rConvInfo, + XclFuncParamConv ePrevConv, XclExpClassConv ePrevClassConv, bool bWasRefClass ) +{ + OSL_ENSURE( rConvInfo.mnTokPos < GetSize(), "XclExpFmlaCompImpl::RecalcTokenClass - invalid token position" ); + sal_uInt8& rnTokenId = mxData->maTokVec[ rConvInfo.mnTokPos ]; + sal_uInt8 nTokClass = GetTokenClass( rnTokenId ); + + // REF tokens in VALTYPE parameters behave like VAL tokens + if( rConvInfo.mbValType && (nTokClass == EXC_TOKCLASS_REF) ) + { + nTokClass = EXC_TOKCLASS_VAL; + ChangeTokenClass( rnTokenId, nTokClass ); + } + + // replace RPO conversion of operator with parent conversion + XclFuncParamConv eConv = (rConvInfo.meConv == EXC_PARAMCONV_RPO) ? ePrevConv : rConvInfo.meConv; + + // find the effective token class conversion to be performed for this token + XclExpClassConv eClassConv = EXC_CLASSCONV_ORG; + switch( eConv ) + { + case EXC_PARAMCONV_ORG: + // conversion is forced independent of parent conversion + eClassConv = EXC_CLASSCONV_ORG; + break; + case EXC_PARAMCONV_VAL: + // conversion is forced independent of parent conversion + eClassConv = EXC_CLASSCONV_VAL; + break; + case EXC_PARAMCONV_ARR: + // conversion is forced independent of parent conversion + eClassConv = EXC_CLASSCONV_ARR; + break; + case EXC_PARAMCONV_RPT: + switch( ePrevConv ) + { + case EXC_PARAMCONV_ORG: + case EXC_PARAMCONV_VAL: + case EXC_PARAMCONV_ARR: + /* If parent token has REF class (REF token in REFTYPE + function parameter), then RPT does not repeat the + previous explicit ORG or ARR conversion, but always + falls back to VAL conversion. */ + eClassConv = bWasRefClass ? EXC_CLASSCONV_VAL : ePrevClassConv; + break; + case EXC_PARAMCONV_RPT: + // nested RPT repeats the previous effective conversion + eClassConv = ePrevClassConv; + break; + case EXC_PARAMCONV_RPX: + /* If parent token has REF class (REF token in REFTYPE + function parameter), then RPX repeats the previous + effective conversion (which will be either ORG or ARR, + but never VAL), otherwise falls back to ORG conversion. */ + eClassConv = bWasRefClass ? ePrevClassConv : EXC_CLASSCONV_ORG; + break; + case EXC_PARAMCONV_RPO: // does not occur + break; + } + break; + case EXC_PARAMCONV_RPX: + /* If current token still has REF class, set previous effective + conversion as current conversion. This will not have an effect + on the REF token but is needed for RPT parameters of this + function that want to repeat this conversion type. If current + token is VAL or ARR class, the previous ARR conversion will be + repeated on the token, but VAL conversion will not. */ + eClassConv = ((nTokClass == EXC_TOKCLASS_REF) || (ePrevClassConv == EXC_CLASSCONV_ARR)) ? + ePrevClassConv : EXC_CLASSCONV_ORG; + break; + case EXC_PARAMCONV_RPO: // does not occur (see above) + break; + } + + // do the token class conversion + switch( eClassConv ) + { + case EXC_CLASSCONV_ORG: + /* Cell formulas: leave the current token class. Cell formulas + are the only type of formulas where all tokens can keep + their original token class. + Array and defined name formulas: convert VAL to ARR. */ + if( (mxData->mrCfg.meClassType != EXC_CLASSTYPE_CELL) && (nTokClass == EXC_TOKCLASS_VAL) ) + { + nTokClass = EXC_TOKCLASS_ARR; + ChangeTokenClass( rnTokenId, nTokClass ); + } + break; + case EXC_CLASSCONV_VAL: + // convert ARR to VAL + if( nTokClass == EXC_TOKCLASS_ARR ) + { + nTokClass = EXC_TOKCLASS_VAL; + ChangeTokenClass( rnTokenId, nTokClass ); + } + break; + case EXC_CLASSCONV_ARR: + // convert VAL to ARR + if( nTokClass == EXC_TOKCLASS_VAL ) + { + nTokClass = EXC_TOKCLASS_ARR; + ChangeTokenClass( rnTokenId, nTokClass ); + } + break; + } + + // do conversion for nested operands, if token is an operator or function + if( rConvInfo.mnTokPos < mxData->maOpListVec.size() ) + if( const XclExpOperandList* pOperands = mxData->maOpListVec[ rConvInfo.mnTokPos ].get() ) + for( const auto& rOperand : *pOperands ) + RecalcTokenClass( rOperand, eConv, eClassConv, nTokClass == EXC_TOKCLASS_REF ); +} + +void XclExpFmlaCompImpl::FinalizeFormula() +{ + if( mxData->mbOk ) + { + // Volatile? Add a tAttrVolatile token at the beginning of the token array. + if( mxData->mbVolatile ) + { + // tAttrSpace token can be extended with volatile flag + if( !IsSpaceToken( 0 ) ) + { + InsertZeros( 0, 4 ); + mxData->maTokVec[ 0 ] = EXC_TOKID_ATTR; + } + mxData->maTokVec[ 1 ] |= EXC_TOK_ATTR_VOLATILE; + } + + // Token array too long? -> error + mxData->mbOk = mxData->maTokVec.size() <= EXC_TOKARR_MAXLEN; + } + + if( !mxData->mbOk ) + { + // Any unrecoverable error? -> Create a =#NA formula. + mxData->maTokVec.clear(); + mxData->maExtDataVec.clear(); + mxData->mbVolatile = false; + AppendErrorToken( EXC_ERR_NA ); + } +} + +XclTokenArrayRef XclExpFmlaCompImpl::CreateTokenArray() +{ + // create the Excel token array from working data before resetting mxData + OSL_ENSURE( mxData->mrCfg.mbAllowArrays || mxData->maExtDataVec.empty(), "XclExpFmlaCompImpl::CreateTokenArray - unexpected extended data" ); + if( !mxData->mrCfg.mbAllowArrays ) + mxData->maExtDataVec.clear(); + XclTokenArrayRef xTokArr = std::make_shared( mxData->maTokVec, mxData->maExtDataVec, mxData->mbVolatile ); + mxData.reset(); + + // compiler invoked recursively? - restore old working data + if( !maDataStack.empty() ) + { + mxData = maDataStack.back(); + maDataStack.pop_back(); + } + + return xTokArr; +} + +// compiler ------------------------------------------------------------------- + +const FormulaToken* XclExpFmlaCompImpl::GetNextRawToken() +{ + const FormulaToken* pScToken = mxData->maTokArrIt.Get(); + ++mxData->maTokArrIt; + return pScToken; +} + +const FormulaToken* XclExpFmlaCompImpl::PeekNextRawToken() const +{ + /* Returns pointer to next raw token in the token array. The token array + iterator already points to the next token (A call to GetNextToken() + always increases the iterator), so this function just returns the token + the iterator points to. To skip space tokens, a copy of the iterator is + created and set to the passed skip-spaces mode. If spaces have to be + skipped, and the iterator currently points to a space token, the + constructor will move it to the next non-space token. */ + XclTokenArrayIterator aTempIt( mxData->maTokArrIt, true/*bSkipSpaces*/ ); + return aTempIt.Get(); +} + +bool XclExpFmlaCompImpl::GetNextToken( XclExpScToken& rTokData ) +{ + rTokData.mpScToken = GetNextRawToken(); + rTokData.mnSpaces = 0; + /* TODO: handle ocWhitespace characters? */ + while (rTokData.GetOpCode() == ocSpaces || rTokData.GetOpCode() == ocWhitespace) + { + rTokData.mnSpaces += rTokData.mpScToken->GetByte(); + rTokData.mpScToken = GetNextRawToken(); + } + return rTokData.Is(); +} + +XclExpScToken XclExpFmlaCompImpl::GetNextToken() +{ + XclExpScToken aTokData; + GetNextToken( aTokData ); + return aTokData; +} + +namespace { + +/** Returns the Excel token ID of a comparison operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetCompareTokenId( OpCode eOpCode ) +{ + switch( eOpCode ) + { + case ocLess: return EXC_TOKID_LT; + case ocLessEqual: return EXC_TOKID_LE; + case ocEqual: return EXC_TOKID_EQ; + case ocGreaterEqual: return EXC_TOKID_GE; + case ocGreater: return EXC_TOKID_GT; + case ocNotEqual: return EXC_TOKID_NE; + default:; + } + return EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a string concatenation operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetConcatTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocAmpersand) ? EXC_TOKID_CONCAT : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of an addition/subtraction operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetAddSubTokenId( OpCode eOpCode ) +{ + switch( eOpCode ) + { + case ocAdd: return EXC_TOKID_ADD; + case ocSub: return EXC_TOKID_SUB; + default:; + } + return EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a multiplication/division operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetMulDivTokenId( OpCode eOpCode ) +{ + switch( eOpCode ) + { + case ocMul: return EXC_TOKID_MUL; + case ocDiv: return EXC_TOKID_DIV; + default:; + } + return EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a power operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetPowTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocPow) ? EXC_TOKID_POWER : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a trailing unary operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetUnaryPostTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocPercentSign) ? EXC_TOKID_PERCENT : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a leading unary operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetUnaryPreTokenId( OpCode eOpCode ) +{ + switch( eOpCode ) + { + case ocAdd: return EXC_TOKID_UPLUS; // +(1) + case ocNeg: return EXC_TOKID_UMINUS; // NEG(1) + case ocNegSub: return EXC_TOKID_UMINUS; // -(1) + default:; + } + return EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a reference list operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetListTokenId( OpCode eOpCode, bool bStopAtSep ) +{ + return ((eOpCode == ocUnion) || (!bStopAtSep && (eOpCode == ocSep))) ? EXC_TOKID_LIST : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a reference intersection operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetIntersectTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocIntersect) ? EXC_TOKID_ISECT : EXC_TOKID_NONE; +} + +/** Returns the Excel token ID of a reference range operator or EXC_TOKID_NONE. */ +sal_uInt8 lclGetRangeTokenId( OpCode eOpCode ) +{ + return (eOpCode == ocRange) ? EXC_TOKID_RANGE : EXC_TOKID_NONE; +} + +} // namespace + +XclExpScToken XclExpFmlaCompImpl::Expression( XclExpScToken aTokData, bool bInParentheses, bool bStopAtSep ) +{ + if( mxData->mbOk && aTokData.Is() ) + { + // remember old stop-at-ocSep mode, restored below + bool bOldStopAtSep = mxData->mbStopAtSep; + mxData->mbStopAtSep = bStopAtSep; + // start compilation of the subexpression + aTokData = OrTerm( aTokData, bInParentheses ); + // restore old stop-at-ocSep mode + mxData->mbStopAtSep = bOldStopAtSep; + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::SkipExpression( XclExpScToken aTokData, bool bStopAtSep ) +{ + while( mxData->mbOk && aTokData.Is() && (aTokData.GetOpCode() != ocClose) && (!bStopAtSep || (aTokData.GetOpCode() != ocSep)) ) + { + if( aTokData.GetOpCode() == ocOpen ) + { + aTokData = SkipExpression( GetNextToken(), false ); + if( mxData->mbOk ) mxData->mbOk = aTokData.GetOpCode() == ocClose; + } + aTokData = GetNextToken(); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::OrTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = AndTerm( aTokData, bInParentheses ); + sal_uInt8 nParamCount = 1; + while( mxData->mbOk && (aTokData.GetOpCode() == ocOr) ) + { + RemoveTrailingParen(); + aTokData = AndTerm( GetNextToken(), bInParentheses ); + RemoveTrailingParen(); + ++nParamCount; + if( mxData->mbOk ) mxData->mbOk = nParamCount <= EXC_FUNC_MAXPARAM; + } + if( mxData->mbOk && (nParamCount > 1) ) + AppendLogicalOperatorToken( EXC_FUNCID_OR, nParamCount ); + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::AndTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = CompareTerm( aTokData, bInParentheses ); + sal_uInt8 nParamCount = 1; + while( mxData->mbOk && (aTokData.GetOpCode() == ocAnd) ) + { + RemoveTrailingParen(); + aTokData = CompareTerm( GetNextToken(), bInParentheses ); + RemoveTrailingParen(); + ++nParamCount; + if( mxData->mbOk ) mxData->mbOk = nParamCount <= EXC_FUNC_MAXPARAM; + } + if( mxData->mbOk && (nParamCount > 1) ) + AppendLogicalOperatorToken( EXC_FUNCID_AND, nParamCount ); + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::CompareTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = ConcatTerm( aTokData, bInParentheses ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetConcatTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = ConcatTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::ConcatTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = AddSubTerm( aTokData, bInParentheses ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetCompareTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = AddSubTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::AddSubTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = MulDivTerm( aTokData, bInParentheses ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetAddSubTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = MulDivTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::MulDivTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = PowTerm( aTokData, bInParentheses ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetMulDivTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = PowTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::PowTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = UnaryPostTerm( aTokData, bInParentheses ); + sal_uInt8 nOpTokenId = EXC_TOKID_NONE; + while( mxData->mbOk ) + { + nOpTokenId = lclGetPowTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = UnaryPostTerm( GetNextToken(), bInParentheses ); + AppendBinaryOperatorToken( nOpTokenId, true, nSpaces ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::UnaryPostTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + aTokData = UnaryPreTerm( aTokData, bInParentheses ); + sal_uInt8 nOpTokenId = EXC_TOKID_NONE; + while( mxData->mbOk ) + { + nOpTokenId = lclGetUnaryPostTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + AppendUnaryOperatorToken( nOpTokenId, aTokData.mnSpaces ); + GetNextToken( aTokData ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::UnaryPreTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + sal_uInt8 nOpTokenId = mxData->mbOk ? lclGetUnaryPreTokenId( aTokData.GetOpCode() ) : EXC_TOKID_NONE; + if( nOpTokenId != EXC_TOKID_NONE ) + { + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = UnaryPreTerm( GetNextToken(), bInParentheses ); + AppendUnaryOperatorToken( nOpTokenId, nSpaces ); + } + else + { + aTokData = ListTerm( aTokData, bInParentheses ); + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::ListTerm( XclExpScToken aTokData, bool bInParentheses ) +{ + sal_uInt16 nSubExprPos = GetSize(); + bool bHasAnyRefOp = false; + bool bHasListOp = false; + aTokData = IntersectTerm( aTokData, bHasAnyRefOp ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetListTokenId( aTokData.GetOpCode(), mxData->mbStopAtSep ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = IntersectTerm( GetNextToken(), bHasAnyRefOp ); + AppendBinaryOperatorToken( nOpTokenId, false, nSpaces ); + bHasAnyRefOp = bHasListOp = true; + } + if( bHasAnyRefOp ) + { + // add a tMemFunc token enclosing the entire reference subexpression + sal_uInt16 nSubExprSize = GetSize() - nSubExprPos; + InsertZeros( nSubExprPos, 3 ); + mxData->maTokVec[ nSubExprPos ] = GetTokenId( EXC_TOKID_MEMFUNC, EXC_TOKCLASS_REF ); + Overwrite( nSubExprPos + 1, nSubExprSize ); + // update the operand/operator stack (set the list expression as operand of the tMemFunc) + XclExpOperandListRef xOperands = std::make_shared(); + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_VAL, false ); + PushOperatorPos( nSubExprPos, xOperands ); + } + // #i86439# enclose list operator into parentheses, e.g. Calc's =AREAS(A1~A2) to Excel's =AREAS((A1;A2)) + if( bHasListOp && !bInParentheses ) + AppendParenToken(); + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::IntersectTerm( XclExpScToken aTokData, bool& rbHasRefOp ) +{ + aTokData = RangeTerm( aTokData, rbHasRefOp ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetIntersectTokenId( aTokData.GetOpCode() ); + if (nOpTokenId ==EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = RangeTerm( GetNextToken(), rbHasRefOp ); + AppendBinaryOperatorToken( nOpTokenId, false, nSpaces ); + rbHasRefOp = true; + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::RangeTerm( XclExpScToken aTokData, bool& rbHasRefOp ) +{ + aTokData = Factor( aTokData ); + while( mxData->mbOk ) + { + sal_uInt8 nOpTokenId = lclGetRangeTokenId( aTokData.GetOpCode() ); + if (nOpTokenId == EXC_TOKID_NONE) + break; + sal_uInt8 nSpaces = aTokData.mnSpaces; + aTokData = Factor( GetNextToken() ); + AppendBinaryOperatorToken( nOpTokenId, false, nSpaces ); + rbHasRefOp = true; + } + return aTokData; +} + +XclExpScToken XclExpFmlaCompImpl::Factor( XclExpScToken aTokData ) +{ + if( !mxData->mbOk || !aTokData.Is() ) return XclExpScToken(); + + switch( aTokData.GetType() ) + { + case svUnknown: mxData->mbOk = false; break; + case svDouble: ProcessDouble( aTokData ); break; + case svString: ProcessString( aTokData ); break; + case svSingleRef: ProcessCellRef( aTokData ); break; + case svDoubleRef: ProcessRangeRef( aTokData ); break; + case svExternalSingleRef: ProcessExternalCellRef( aTokData ); break; + case svExternalDoubleRef: ProcessExternalRangeRef( aTokData ); break; + case svExternalName: ProcessExternalName( aTokData ); break; + case svMatrix: ProcessMatrix( aTokData ); break; + case svExternal: ProcessExternal( aTokData ); break; + + default: switch( aTokData.GetOpCode() ) + { + case ocNone: /* do nothing */ break; + case ocMissing: ProcessMissing( aTokData ); break; + case ocBad: ProcessBad( aTokData ); break; + case ocOpen: ProcessParentheses( aTokData ); break; + case ocName: ProcessDefinedName( aTokData ); break; + case ocFalse: + case ocTrue: ProcessBoolean( aTokData ); break; + case ocDde: ProcessDdeLink( aTokData ); break; + default: ProcessFunction( aTokData ); + } + } + + return GetNextToken(); +} + +// formula structure ---------------------------------------------------------- + +void XclExpFmlaCompImpl::ProcessDouble( const XclExpScToken& rTokData ) +{ + double fValue = rTokData.mpScToken->GetDouble(); + double fInt; + double fFrac = modf( fValue, &fInt ); + if( (fFrac == 0.0) && (0.0 <= fInt) && (fInt <= 65535.0) ) + AppendIntToken( static_cast< sal_uInt16 >( fInt ), rTokData.mnSpaces ); + else + AppendNumToken( fValue, rTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessString( const XclExpScToken& rTokData ) +{ + AppendOperandTokenId( EXC_TOKID_STR, rTokData.mnSpaces ); + Append( rTokData.mpScToken->GetString().getString() ); +} + +void XclExpFmlaCompImpl::ProcessMissing( const XclExpScToken& rTokData ) +{ + AppendMissingToken( rTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessBad( const XclExpScToken& rTokData ) +{ + AppendErrorToken( EXC_ERR_NA, rTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessParentheses( const XclExpScToken& rTokData ) +{ + XclExpScToken aTokData = Expression( GetNextToken(), true, false ); + mxData->mbOk = aTokData.GetOpCode() == ocClose; + AppendParenToken( rTokData.mnSpaces, aTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessBoolean( const XclExpScToken& rTokData ) +{ + mxData->mbOk = GetNextToken().GetOpCode() == ocOpen; + if( mxData->mbOk ) mxData->mbOk = GetNextToken().GetOpCode() == ocClose; + if( mxData->mbOk ) + AppendBoolToken( rTokData.GetOpCode() == ocTrue, rTokData.mnSpaces ); +} + +namespace { + +bool lclGetTokenString( OUString& rString, const XclExpScToken& rTokData ) +{ + bool bIsStr = (rTokData.GetType() == svString) && (rTokData.GetOpCode() == ocPush); + if( bIsStr ) + rString = rTokData.mpScToken->GetString().getString(); + return bIsStr; +} + +} // namespace + +void XclExpFmlaCompImpl::ProcessDdeLink( const XclExpScToken& rTokData ) +{ + OUString aApplic, aTopic, aItem; + + mxData->mbOk = GetNextToken().GetOpCode() == ocOpen; + if( mxData->mbOk ) mxData->mbOk = lclGetTokenString( aApplic, GetNextToken() ); + if( mxData->mbOk ) mxData->mbOk = GetNextToken().GetOpCode() == ocSep; + if( mxData->mbOk ) mxData->mbOk = lclGetTokenString( aTopic, GetNextToken() ); + if( mxData->mbOk ) mxData->mbOk = GetNextToken().GetOpCode() == ocSep; + if( mxData->mbOk ) mxData->mbOk = lclGetTokenString( aItem, GetNextToken() ); + if( mxData->mbOk ) mxData->mbOk = GetNextToken().GetOpCode() == ocClose; + if( mxData->mbOk ) mxData->mbOk = !aApplic.isEmpty() && !aTopic.isEmpty() && !aItem.isEmpty(); + if( mxData->mbOk ) + { + sal_uInt16 nExtSheet(0), nExtName(0); + if( mxData->mpLinkMgr && mxData->mpLinkMgr->InsertDde( nExtSheet, nExtName, aApplic, aTopic, aItem ) ) + AppendNameXToken( nExtSheet, nExtName, rTokData.mnSpaces ); + else + AppendErrorToken( EXC_ERR_NA, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessExternal( const XclExpScToken& rTokData ) +{ + /* #i47228# Excel import generates svExternal/ocMacro tokens for invalid + names and for external/invalid function calls. This function looks for + the next token in the token array. If it is an opening parenthesis, the + token is processed as external function call, otherwise as undefined name. */ + const FormulaToken* pNextScToken = PeekNextRawToken(); + if( !pNextScToken || (pNextScToken->GetOpCode() != ocOpen) ) + AppendMissingNameToken( rTokData.mpScToken->GetExternal(), rTokData.mnSpaces ); + else + ProcessFunction( rTokData ); +} + +void XclExpFmlaCompImpl::ProcessMatrix( const XclExpScToken& rTokData ) +{ + const ScMatrix* pMatrix = rTokData.mpScToken->GetMatrix(); + if( pMatrix && mxData->mrCfg.mbAllowArrays ) + { + SCSIZE nScCols, nScRows; + pMatrix->GetDimensions( nScCols, nScRows ); + OSL_ENSURE( (nScCols > 0) && (nScRows > 0), "XclExpFmlaCompImpl::ProcessMatrix - invalid matrix size" ); + sal_uInt16 nCols = ::limit_cast< sal_uInt16 >( nScCols, 0, 256 ); + sal_uInt16 nRows = ::limit_cast< sal_uInt16 >( nScRows, 0, 1024 ); + + // create the tArray token + AppendOperandTokenId( GetTokenId( EXC_TOKID_ARRAY, EXC_TOKCLASS_ARR ), rTokData.mnSpaces ); + Append( static_cast< sal_uInt8 >( (meBiff == EXC_BIFF8) ? (nCols - 1) : nCols ) ); + Append( static_cast< sal_uInt16 >( (meBiff == EXC_BIFF8) ? (nRows - 1) : nRows ) ); + Append( static_cast< sal_uInt32 >( 0 ) ); + + // create the extended data containing the array values + AppendExt( static_cast< sal_uInt8 >( (meBiff == EXC_BIFF8) ? (nCols - 1) : nCols ) ); + AppendExt( static_cast< sal_uInt16 >( (meBiff == EXC_BIFF8) ? (nRows - 1) : nRows ) ); + for( SCSIZE nScRow = 0; nScRow < nScRows; ++nScRow ) + { + for( SCSIZE nScCol = 0; nScCol < nScCols; ++nScCol ) + { + ScMatrixValue nMatVal = pMatrix->Get( nScCol, nScRow ); + if( ScMatrix::IsValueType( nMatVal.nType ) ) // value, boolean, or error + { + FormulaError nErr; + if( ScMatrix::IsBooleanType( nMatVal.nType ) ) + { + AppendExt( EXC_CACHEDVAL_BOOL ); + AppendExt( static_cast< sal_uInt8 >( nMatVal.GetBoolean() ? 1 : 0 ) ); + AppendExt( 0, 7 ); + } + else if( (nErr = nMatVal.GetError()) != FormulaError::NONE ) + { + AppendExt( EXC_CACHEDVAL_ERROR ); + AppendExt( XclTools::GetXclErrorCode( nErr ) ); + AppendExt( 0, 7 ); + } + else + { + AppendExt( EXC_CACHEDVAL_DOUBLE ); + AppendExt( nMatVal.fVal ); + } + } + else // string or empty + { + const OUString aStr( nMatVal.GetString().getString()); + if( aStr.isEmpty() ) + { + AppendExt( EXC_CACHEDVAL_EMPTY ); + AppendExt( 0, 8 ); + } + else + { + AppendExt( EXC_CACHEDVAL_STRING ); + AppendExt( aStr ); + } + } + } + } + } + else + { + // array in places that do not allow it (cond fmts, data validation) + AppendErrorToken( EXC_ERR_NA, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessFunction( const XclExpScToken& rTokData ) +{ + OpCode eOpCode = rTokData.GetOpCode(); + const XclFunctionInfo* pFuncInfo = maFuncProv.GetFuncInfoFromOpCode( eOpCode ); + + XclExpExtFuncData aExtFuncData; + + // no exportable function found - try to create an external macro call + if( !pFuncInfo && (eOpCode >= SC_OPCODE_START_NO_PAR) ) + { + const OUString& rFuncName = ScCompiler::GetNativeSymbol( eOpCode ); + if( !rFuncName.isEmpty() ) + { + aExtFuncData.Set( rFuncName, true, false ); + pFuncInfo = maFuncProv.GetFuncInfoFromOpCode( ocMacro ); + } + } + + mxData->mbOk = pFuncInfo != nullptr; + if( !mxData->mbOk ) return; + + // internal functions equivalent to an existing add-in + if( pFuncInfo->IsAddInEquivalent() ) + aExtFuncData.Set( pFuncInfo->GetAddInEquivalentFuncName(), true, false ); + // functions simulated by a macro call in file format + else if( pFuncInfo->IsMacroFunc() ) + aExtFuncData.Set( pFuncInfo->GetMacroFuncName(), false, true ); + + XclExpFuncData aFuncData( rTokData, *pFuncInfo, aExtFuncData ); + XclExpScToken aTokData; + + // preparations for special functions, before function processing starts + PrepareFunction( aFuncData ); + + enum { STATE_START, STATE_OPEN, STATE_PARAM, STATE_SEP, STATE_CLOSE, STATE_END } + eState = STATE_START; + while( eState != STATE_END ) switch( eState ) + { + case STATE_START: + mxData->mbOk = GetNextToken( aTokData ) && (aTokData.GetOpCode() == ocOpen); + eState = mxData->mbOk ? STATE_OPEN : STATE_END; + break; + case STATE_OPEN: + mxData->mbOk = GetNextToken( aTokData ); + eState = mxData->mbOk ? ((aTokData.GetOpCode() == ocClose) ? STATE_CLOSE : STATE_PARAM) : STATE_END; + break; + case STATE_PARAM: + aTokData = ProcessParam( aTokData, aFuncData ); + switch( aTokData.GetOpCode() ) + { + case ocSep: eState = STATE_SEP; break; + case ocClose: eState = STATE_CLOSE; break; + default: mxData->mbOk = false; + } + if( !mxData->mbOk ) eState = STATE_END; + break; + case STATE_SEP: + mxData->mbOk = (aFuncData.GetParamCount() < EXC_FUNC_MAXPARAM) && GetNextToken( aTokData ); + eState = mxData->mbOk ? STATE_PARAM : STATE_END; + break; + case STATE_CLOSE: + FinishFunction( aFuncData, aTokData.mnSpaces ); + eState = STATE_END; + break; + default:; + } +} + +void XclExpFmlaCompImpl::PrepareFunction( const XclExpFuncData& rFuncData ) +{ + // For OOXML these are not rewritten anymore. + if (GetOutput() == EXC_OUTPUT_XML_2007) + return; + + switch( rFuncData.GetOpCode() ) + { + case ocCosecant: // simulate CSC(x) by (1/SIN(x)) + case ocSecant: // simulate SEC(x) by (1/COS(x)) + case ocCot: // simulate COT(x) by (1/TAN(x)) + case ocCosecantHyp: // simulate CSCH(x) by (1/SINH(x)) + case ocSecantHyp: // simulate SECH(x) by (1/COSH(x)) + case ocCotHyp: // simulate COTH(x) by (1/TANH(x)) + AppendIntToken( 1 ); + break; + case ocArcCot: // simulate ACOT(x) by (PI/2-ATAN(x)) + AppendNumToken( M_PI_2 ); + break; + default:; + } +} + +void XclExpFmlaCompImpl::FinishFunction( XclExpFuncData& rFuncData, sal_uInt8 nCloseSpaces ) +{ + // append missing parameters required in Excel, may modify param count + AppendTrailingParam( rFuncData ); + + // check if parameter count fits into the limits of the function + sal_uInt8 nParamCount = rFuncData.GetParamCount(); + if( (rFuncData.GetMinParamCount() <= nParamCount) && (nParamCount <= rFuncData.GetMaxParamCount()) ) + { + // first put the tAttrSpace tokens, they must not be included in tAttrGoto handling + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP_CLOSE, nCloseSpaces ); + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP, rFuncData.GetSpaces() ); + + // add tAttrGoto tokens for IF or CHOOSE functions + switch( rFuncData.GetOpCode() ) + { + case ocIf: + case ocChoose: + AppendJumpToken( rFuncData, EXC_TOK_ATTR_GOTO ); + break; + default:; + } + + // put the tFunc or tFuncVar token (or another special token, e.g. tAttrSum) + AppendFuncToken( rFuncData ); + + // update volatile flag - is set if at least one used function is volatile + mxData->mbVolatile |= rFuncData.IsVolatile(); + + // update jump tokens for specific functions, add additional tokens + switch( rFuncData.GetOpCode() ) + { + case ocIf: + FinishIfFunction( rFuncData ); + break; + case ocChoose: + FinishChooseFunction( rFuncData ); + break; + + case ocCosecant: // simulate CSC(x) by (1/SIN(x)) + case ocSecant: // simulate SEC(x) by (1/COS(x)) + case ocCot: // simulate COT(x) by (1/TAN(x)) + case ocCosecantHyp: // simulate CSCH(x) by (1/SINH(x)) + case ocSecantHyp: // simulate SECH(x) by (1/COSH(x)) + case ocCotHyp: // simulate COTH(x) by (1/TANH(x)) + // For OOXML not rewritten anymore. + if (GetOutput() != EXC_OUTPUT_XML_2007) + { + AppendBinaryOperatorToken( EXC_TOKID_DIV, true ); + AppendParenToken(); + } + break; + case ocArcCot: // simulate ACOT(x) by (PI/2-ATAN(x)) + // For OOXML not rewritten anymore. + if (GetOutput() != EXC_OUTPUT_XML_2007) + { + AppendBinaryOperatorToken( EXC_TOKID_SUB, true ); + AppendParenToken(); + } + break; + + default:; + } + } + else + mxData->mbOk = false; +} + +void XclExpFmlaCompImpl::FinishIfFunction( XclExpFuncData& rFuncData ) +{ + sal_uInt16 nParamCount = rFuncData.GetParamCount(); + OSL_ENSURE( (nParamCount == 2) || (nParamCount == 3), "XclExpFmlaCompImpl::FinishIfFunction - wrong parameter count" ); + const ScfUInt16Vec& rAttrPos = rFuncData.GetAttrPosVec(); + OSL_ENSURE( nParamCount == rAttrPos.size(), "XclExpFmlaCompImpl::FinishIfFunction - wrong number of tAttr tokens" ); + // update tAttrIf token following the condition parameter + Overwrite( rAttrPos[ 0 ] + 2, static_cast< sal_uInt16 >( rAttrPos[ 1 ] - rAttrPos[ 0 ] ) ); + // update the tAttrGoto tokens following true and false parameters + UpdateAttrGoto( rAttrPos[ 1 ] ); + if( nParamCount == 3 ) + UpdateAttrGoto( rAttrPos[ 2 ] ); +} + +void XclExpFmlaCompImpl::FinishChooseFunction( XclExpFuncData& rFuncData ) +{ + sal_uInt16 nParamCount = rFuncData.GetParamCount(); + ScfUInt16Vec& rAttrPos = rFuncData.GetAttrPosVec(); + OSL_ENSURE( nParamCount == rAttrPos.size(), "XclExpFmlaCompImpl::FinishChooseFunction - wrong number of tAttr tokens" ); + // number of choices is parameter count minus 1 + sal_uInt16 nChoices = nParamCount - 1; + // tAttrChoose token contains number of choices + Overwrite( rAttrPos[ 0 ] + 2, nChoices ); + // cache position of the jump table (follows number of choices in tAttrChoose token) + sal_uInt16 nJumpArrPos = rAttrPos[ 0 ] + 4; + // size of jump table: number of choices, plus 1 for error position + sal_uInt16 nJumpArrSize = 2 * (nChoices + 1); + // insert the jump table into the tAttrChoose token + InsertZeros( nJumpArrPos, nJumpArrSize ); + // update positions of tAttrGoto tokens after jump table insertion + sal_uInt16 nIdx; + for( nIdx = 1; nIdx < nParamCount; ++nIdx ) + rAttrPos[ nIdx ] = rAttrPos[ nIdx ] + nJumpArrSize; + // update the tAttrGoto tokens (they contain a value one-less to real distance) + for( nIdx = 1; nIdx < nParamCount; ++nIdx ) + UpdateAttrGoto( rAttrPos[ nIdx ] ); + // update the distances in the jump table + Overwrite( nJumpArrPos, nJumpArrSize ); + for( nIdx = 1; nIdx < nParamCount; ++nIdx ) + Overwrite( nJumpArrPos + 2 * nIdx, static_cast< sal_uInt16 >( rAttrPos[ nIdx ] + 4 - nJumpArrPos ) ); +} + +XclExpScToken XclExpFmlaCompImpl::ProcessParam( XclExpScToken aTokData, XclExpFuncData& rFuncData ) +{ + if( rFuncData.IsCalcOnlyParam() ) + { + // skip Calc-only parameter, stop at next ocClose or ocSep + aTokData = SkipExpression( aTokData, true ); + rFuncData.IncParamInfoIdx(); + } + else + { + // insert Excel-only parameters, modifies param count and class in rFuncData + while( rFuncData.IsExcelOnlyParam() ) + AppendDefaultParam( rFuncData ); + + // process the parameter, stop at next ocClose or ocSep + PrepareParam( rFuncData ); + /* #i37355# insert tMissArg token for missing parameters -- + Excel import filter adds ocMissing token (handled in Factor()), + but Calc itself does not do this if a new formula is entered. */ + switch( aTokData.GetOpCode() ) + { + case ocSep: + case ocClose: AppendMissingToken(); break; // empty parameter + default: aTokData = Expression( aTokData, false, true ); + } + // finalize the parameter and add special tokens, e.g. for IF or CHOOSE parameters + if( mxData->mbOk ) FinishParam( rFuncData ); + } + return aTokData; +} + +void XclExpFmlaCompImpl::PrepareParam( XclExpFuncData& rFuncData ) +{ + // index of this parameter is equal to number of already finished parameters + sal_uInt8 nParamIdx = rFuncData.GetParamCount(); + + switch( rFuncData.GetOpCode() ) + { + case ocIf: + switch( nParamIdx ) + { + // add a tAttrIf token before true-parameter (second parameter) + case 1: AppendJumpToken( rFuncData, EXC_TOK_ATTR_IF ); break; + // add a tAttrGoto token before false-parameter (third parameter) + case 2: AppendJumpToken( rFuncData, EXC_TOK_ATTR_GOTO ); break; + } + break; + + case ocChoose: + switch( nParamIdx ) + { + // do nothing for first parameter + case 0: break; + // add a tAttrChoose token before first value parameter (second parameter) + case 1: AppendJumpToken( rFuncData, EXC_TOK_ATTR_CHOOSE ); break; + // add a tAttrGoto token before other value parameters + default: AppendJumpToken( rFuncData, EXC_TOK_ATTR_GOTO ); + } + break; + + case ocArcCotHyp: // simulate ACOTH(x) by ATANH(1/(x)) + if( nParamIdx == 0 ) + AppendIntToken( 1 ); + break; + default:; + } +} + +void XclExpFmlaCompImpl::FinishParam( XclExpFuncData& rFuncData ) +{ + // increase parameter count, update operand stack + rFuncData.FinishParam( PopOperandPos() ); + + // append more tokens for parameters of some special functions + sal_uInt8 nParamIdx = rFuncData.GetParamCount() - 1; + switch( rFuncData.GetOpCode() ) + { + case ocArcCotHyp: // simulate ACOTH(x) by ATANH(1/(x)) + if( nParamIdx == 0 ) + { + AppendParenToken(); + AppendBinaryOperatorToken( EXC_TOKID_DIV, true ); + } + break; + default:; + } +} + +void XclExpFmlaCompImpl::AppendDefaultParam( XclExpFuncData& rFuncData ) +{ + // prepare parameters of some special functions + PrepareParam( rFuncData ); + + switch( rFuncData.GetOpCode() ) + { + case ocExternal: + AppendAddInCallToken( rFuncData.GetExtFuncData() ); + break; + case ocEuroConvert: + AppendEuroToolCallToken( rFuncData.GetExtFuncData() ); + break; + case ocMacro: + // Do not write the OOXML element. + if (GetOutput() == EXC_OUTPUT_XML_2007) + AppendNameToken( 0 ); // dummy to keep parameter count valid + else + AppendMacroCallToken( rFuncData.GetExtFuncData() ); + break; + default: + { + if( rFuncData.IsAddInEquivalent() ) + { + AppendAddInCallToken( rFuncData.GetExtFuncData() ); + } + else if( rFuncData.IsMacroFunc() ) + { + // Do not write the OOXML element for new _xlfn. + // prefixed functions. + if (GetOutput() == EXC_OUTPUT_XML_2007) + AppendNameToken( 0 ); // dummy to keep parameter count valid + else + AppendMacroCallToken( rFuncData.GetExtFuncData() ); + } + else + { + SAL_WARN( "sc.filter", "XclExpFmlaCompImpl::AppendDefaultParam - unknown opcode" ); + AppendMissingToken(); // to keep parameter count valid + } + } + } + + // update parameter count, add special parameter tokens + FinishParam( rFuncData ); +} + +void XclExpFmlaCompImpl::AppendTrailingParam( XclExpFuncData& rFuncData ) +{ + sal_uInt8 nParamCount = rFuncData.GetParamCount(); + switch( rFuncData.GetOpCode() ) + { + case ocIf: + if( nParamCount == 1 ) + { + // Excel needs at least two parameters in IF function + PrepareParam( rFuncData ); + AppendBoolToken( true ); + FinishParam( rFuncData ); + } + break; + + case ocRound: + case ocRoundUp: + case ocRoundDown: + if( nParamCount == 1 ) + { + // ROUND, ROUNDUP, ROUNDDOWN functions are fixed to 2 parameters in Excel + PrepareParam( rFuncData ); + AppendIntToken( 0 ); + FinishParam( rFuncData ); + } + break; + + case ocIndex: + if( nParamCount == 1 ) + { + // INDEX function needs at least 2 parameters in Excel + PrepareParam( rFuncData ); + AppendMissingToken(); + FinishParam( rFuncData ); + } + break; + + case ocExternal: + case ocMacro: + // external or macro call without parameters needs the external name reference + if( nParamCount == 0 ) + AppendDefaultParam( rFuncData ); + break; + + case ocGammaDist: + if( nParamCount == 3 ) + { + // GAMMADIST function needs 4 parameters in Excel + PrepareParam( rFuncData ); + AppendIntToken( 1 ); + FinishParam( rFuncData ); + } + break; + + case ocPoissonDist: + if( nParamCount == 2 ) + { + // POISSON function needs 3 parameters in Excel + PrepareParam( rFuncData ); + AppendIntToken( 1 ); + FinishParam( rFuncData ); + } + break; + + case ocNormDist: + if( nParamCount == 3 ) + { + // NORMDIST function needs 4 parameters in Excel + PrepareParam( rFuncData ); + AppendBoolToken( true ); + FinishParam( rFuncData ); + } + break; + + case ocLogNormDist: + case ocLogInv: + switch( nParamCount ) + { + // LOGNORMDIST function needs 3 parameters in Excel + case 1: + PrepareParam( rFuncData ); + AppendIntToken( 0 ); + FinishParam( rFuncData ); + [[fallthrough]]; // add next default parameter + case 2: + PrepareParam( rFuncData ); + AppendIntToken( 1 ); + FinishParam( rFuncData ); + break; + default:; + } + + break; + + default: + // #i108420# function without parameters stored as macro call needs the external name reference + if( (nParamCount == 0) && rFuncData.IsMacroFunc() ) + AppendDefaultParam( rFuncData ); + + } +} + +// reference handling --------------------------------------------------------- + +namespace { + +bool lclIsRefRel2D( const ScSingleRefData& rRefData ) +{ + return rRefData.IsColRel() || rRefData.IsRowRel(); +} + +bool lclIsRefDel2D( const ScSingleRefData& rRefData ) +{ + return rRefData.IsColDeleted() || rRefData.IsRowDeleted(); +} + +bool lclIsRefRel2D( const ScComplexRefData& rRefData ) +{ + return lclIsRefRel2D( rRefData.Ref1 ) || lclIsRefRel2D( rRefData.Ref2 ); +} + +bool lclIsRefDel2D( const ScComplexRefData& rRefData ) +{ + return lclIsRefDel2D( rRefData.Ref1 ) || lclIsRefDel2D( rRefData.Ref2 ); +} + +} // namespace + +SCTAB XclExpFmlaCompImpl::GetScTab( const ScSingleRefData& rRefData ) const +{ + if (rRefData.IsTabDeleted()) + return SCTAB_INVALID; + + if (!rRefData.IsTabRel()) + // absolute address + return rRefData.Tab(); + + if (!mxData->mpScBasePos) + return SCTAB_INVALID; + + return rRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos).Tab(); +} + +bool XclExpFmlaCompImpl::IsRef2D( const ScSingleRefData& rRefData, bool bCheck3DFlag ) const +{ + /* rRefData.IsFlag3D() determines if sheet name is always visible, even on + the own sheet. If 3D references are allowed, the passed reference does + not count as 2D reference. */ + + // conditional formatting does not allow 3D refs in xls + if (mxData && mxData->mrCfg.meType == EXC_FMLATYPE_CONDFMT) + return true; + + if (bCheck3DFlag && rRefData.IsFlag3D()) + return false; + + if (rRefData.IsTabDeleted()) + return false; + + if (rRefData.IsTabRel()) + return rRefData.Tab() == 0; + else + return rRefData.Tab() == GetCurrScTab(); +} + +bool XclExpFmlaCompImpl::IsRef2D( const ScComplexRefData& rRefData, bool bCheck3DFlag ) const +{ + return IsRef2D(rRefData.Ref1, bCheck3DFlag) && IsRef2D(rRefData.Ref2, bCheck3DFlag); +} + +void XclExpFmlaCompImpl::ConvertRefData( + ScSingleRefData& rRefData, XclAddress& rXclPos, + bool bNatLangRef, bool bTruncMaxCol, bool bTruncMaxRow ) const +{ + if( mxData->mpScBasePos ) + { + // *** reference position exists (cell, matrix) - convert to absolute *** + ScAddress aAbs = rRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos); + + // convert column index + if (bTruncMaxCol && (aAbs.Col() == mnMaxScCol)) + aAbs.SetCol(mnMaxAbsCol); + else if ((aAbs.Col() < 0) || (aAbs.Col() > mnMaxAbsCol)) + rRefData.SetColDeleted(true); + rXclPos.mnCol = static_cast(aAbs.Col()) & mnMaxColMask; + + // convert row index + if (bTruncMaxRow && (aAbs.Row() == mnMaxScRow)) + aAbs.SetRow(mnMaxAbsRow); + else if ((aAbs.Row() < 0) || (aAbs.Row() > mnMaxAbsRow)) + rRefData.SetRowDeleted(true); + rXclPos.mnRow = static_cast(aAbs.Row()) & mnMaxRowMask; + + // Update the reference. + rRefData.SetAddress(GetRoot().GetDoc().GetSheetLimits(), aAbs, *mxData->mpScBasePos); + } + else + { + // *** no reference position (shared, names, condfmt) - use relative values *** + + // convert column index (2-step-cast ScsCOL->sal_Int16->sal_uInt16 to get all bits correctly) + sal_Int16 nXclRelCol = static_cast(rRefData.Col()); + rXclPos.mnCol = static_cast< sal_uInt16 >( nXclRelCol ) & mnMaxColMask; + + // convert row index (2-step-cast ScsROW->sal_Int32->sal_uInt32 to get all bits correctly) + sal_Int32 nXclRelRow = static_cast(rRefData.Row()); + rXclPos.mnRow = static_cast< sal_uInt32 >( nXclRelRow ) & mnMaxRowMask; + } + + // flags for relative column and row + if( bNatLangRef ) + { + OSL_ENSURE( meBiff == EXC_BIFF8, "XclExpFmlaCompImpl::ConvertRefData - NLRs only for BIFF8" ); + // Calc does not support absolute reference mode in natural language references + ::set_flag( rXclPos.mnCol, EXC_TOK_NLR_REL ); + } + else + { + sal_uInt16 rnRelRow = rXclPos.mnRow; + sal_uInt16& rnRelField = (meBiff <= EXC_BIFF5) ? rnRelRow : rXclPos.mnCol; + ::set_flag( rnRelField, EXC_TOK_REF_COLREL, rRefData.IsColRel() ); + ::set_flag( rnRelField, EXC_TOK_REF_ROWREL, rRefData.IsRowRel() ); + } +} + +void XclExpFmlaCompImpl::ConvertRefData( + ScComplexRefData& rRefData, XclRange& rXclRange, bool bNatLangRef ) const +{ + // convert start and end of the range + ConvertRefData( rRefData.Ref1, rXclRange.maFirst, bNatLangRef, false, false ); + bool bTruncMaxCol = !rRefData.Ref1.IsColDeleted() && (rXclRange.maFirst.mnCol == 0); + bool bTruncMaxRow = !rRefData.Ref1.IsRowDeleted() && (rXclRange.maFirst.mnRow == 0); + ConvertRefData( rRefData.Ref2, rXclRange.maLast, bNatLangRef, bTruncMaxCol, bTruncMaxRow ); +} + +XclExpRefLogEntry* XclExpFmlaCompImpl::GetNewRefLogEntry() +{ + if( mxData->mpRefLog ) + { + mxData->mpRefLog->emplace_back(); + return &mxData->mpRefLog->back(); + } + return nullptr; +} + +void XclExpFmlaCompImpl::ProcessCellRef( const XclExpScToken& rTokData ) +{ + // get the Excel address components, adjust internal data in aRefData + bool bNatLangRef = (meBiff == EXC_BIFF8) && mxData->mpScBasePos && (rTokData.GetOpCode() == ocColRowName); + ScSingleRefData aRefData = *rTokData.mpScToken->GetSingleRef(); + XclAddress aXclPos( ScAddress::UNINITIALIZED ); + ConvertRefData( aRefData, aXclPos, bNatLangRef, false, false ); + + if( bNatLangRef ) + { + OSL_ENSURE( aRefData.IsColRel() != aRefData.IsRowRel(), + "XclExpFmlaCompImpl::ProcessCellRef - broken natural language reference" ); + // create tNlr token for natural language reference + sal_uInt8 nSubId = aRefData.IsColRel() ? EXC_TOK_NLR_COLV : EXC_TOK_NLR_ROWV; + AppendOperandTokenId( EXC_TOKID_NLR, rTokData.mnSpaces ); + Append( nSubId ); + AppendAddress( aXclPos ); + } + else + { + // store external cell contents in CRN records + if( mxData->mrCfg.mbFromCell && mxData->mpLinkMgr && mxData->mpScBasePos ) + mxData->mpLinkMgr->StoreCell(aRefData, *mxData->mpScBasePos); + + // create the tRef, tRefErr, tRefN, tRef3d, or tRefErr3d token + if (!mxData->mrCfg.mb3DRefOnly && IsRef2D(aRefData, mxData->mpLinkMgr != nullptr)) + { + // 2D reference (not in defined names, but allowed in range lists) + sal_uInt8 nBaseId = (!mxData->mpScBasePos && lclIsRefRel2D( aRefData )) ? EXC_TOKID_REFN : + (lclIsRefDel2D( aRefData ) ? EXC_TOKID_REFERR : EXC_TOKID_REF); + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + AppendAddress( aXclPos ); + } + else if( mxData->mpLinkMgr ) // 3D reference + { + // 1-based EXTERNSHEET index and 0-based Excel sheet index + sal_uInt16 nExtSheet, nXclTab; + mxData->mpLinkMgr->FindExtSheet( nExtSheet, nXclTab, GetScTab( aRefData ), GetNewRefLogEntry() ); + // write the token + sal_uInt8 nBaseId = lclIsRefDel2D( aRefData ) ? EXC_TOKID_REFERR3D : EXC_TOKID_REF3D; + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + { + Append( 0, 8 ); + Append( nXclTab ); + Append( nXclTab ); + } + AppendAddress( aXclPos ); + } + else + { + // 3D ref in cond. format, or 2D ref in name + AppendErrorToken( EXC_ERR_REF, rTokData.mnSpaces ); + } + } +} + +void XclExpFmlaCompImpl::ProcessRangeRef( const XclExpScToken& rTokData ) +{ + // get the Excel address components, adjust internal data in aRefData + ScComplexRefData aRefData = *rTokData.mpScToken->GetDoubleRef(); + XclRange aXclRange( ScAddress::UNINITIALIZED ); + ConvertRefData( aRefData, aXclRange, false ); + + // store external cell contents in CRN records + if( mxData->mrCfg.mbFromCell && mxData->mpLinkMgr && mxData->mpScBasePos ) + mxData->mpLinkMgr->StoreCellRange(aRefData, *mxData->mpScBasePos); + + // create the tArea, tAreaErr, tAreaN, tArea3d, or tAreaErr3d token + if (!mxData->mrCfg.mb3DRefOnly && IsRef2D(aRefData, mxData->mpLinkMgr != nullptr)) + { + // 2D reference (not in name formulas, but allowed in range lists) + sal_uInt8 nBaseId = (!mxData->mpScBasePos && lclIsRefRel2D( aRefData )) ? EXC_TOKID_AREAN : + (lclIsRefDel2D( aRefData ) ? EXC_TOKID_AREAERR : EXC_TOKID_AREA); + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + AppendRange( aXclRange ); + } + else if( mxData->mpLinkMgr ) // 3D reference + { + // 1-based EXTERNSHEET index and 0-based Excel sheet indexes + sal_uInt16 nExtSheet, nFirstXclTab, nLastXclTab; + mxData->mpLinkMgr->FindExtSheet( nExtSheet, nFirstXclTab, nLastXclTab, + GetScTab( aRefData.Ref1 ), GetScTab( aRefData.Ref2 ), GetNewRefLogEntry() ); + // write the token + sal_uInt8 nBaseId = lclIsRefDel2D( aRefData ) ? EXC_TOKID_AREAERR3D : EXC_TOKID_AREA3D; + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + { + Append( 0, 8 ); + Append( nFirstXclTab ); + Append( nLastXclTab ); + } + AppendRange( aXclRange ); + } + else + { + // 3D ref in cond. format, or 2D ref in name + AppendErrorToken( EXC_ERR_REF, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessExternalCellRef( const XclExpScToken& rTokData ) +{ + if( mxData->mpLinkMgr ) + { + // get the Excel address components, adjust internal data in aRefData + ScSingleRefData aRefData = *rTokData.mpScToken->GetSingleRef(); + XclAddress aXclPos( ScAddress::UNINITIALIZED ); + ConvertRefData( aRefData, aXclPos, false, false, false ); + + // store external cell contents in CRN records + sal_uInt16 nFileId = rTokData.mpScToken->GetIndex(); + OUString aTabName = rTokData.mpScToken->GetString().getString(); + if( mxData->mrCfg.mbFromCell && mxData->mpScBasePos ) + mxData->mpLinkMgr->StoreCell(nFileId, aTabName, aRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos)); + + // 1-based EXTERNSHEET index and 0-based Excel sheet indexes + sal_uInt16 nExtSheet, nFirstSBTab, nLastSBTab; + mxData->mpLinkMgr->FindExtSheet( nFileId, aTabName, 1, nExtSheet, nFirstSBTab, nLastSBTab, GetNewRefLogEntry() ); + // write the token + sal_uInt8 nBaseId = lclIsRefDel2D( aRefData ) ? EXC_TOKID_REFERR3D : EXC_TOKID_REF3D; + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + { + Append( 0, 8 ); + Append( nFirstSBTab ); + Append( nLastSBTab ); + } + AppendAddress( aXclPos ); + } + else + { + AppendErrorToken( EXC_ERR_REF, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessExternalRangeRef( const XclExpScToken& rTokData ) +{ + if( mxData->mpLinkMgr ) + { + // get the Excel address components, adjust internal data in aRefData + ScComplexRefData aRefData = *rTokData.mpScToken->GetDoubleRef(); + XclRange aXclRange( ScAddress::UNINITIALIZED ); + ConvertRefData( aRefData, aXclRange, false ); + + // store external cell contents in CRN records + sal_uInt16 nFileId = rTokData.mpScToken->GetIndex(); + OUString aTabName = rTokData.mpScToken->GetString().getString(); + if( mxData->mrCfg.mbFromCell && mxData->mpScBasePos ) + mxData->mpLinkMgr->StoreCellRange(nFileId, aTabName, aRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos)); + + // 1-based EXTERNSHEET index and 0-based Excel sheet indexes + sal_uInt16 nExtSheet, nFirstSBTab, nLastSBTab; + sal_uInt16 nTabSpan = static_cast(aRefData.Ref2.Tab() - aRefData.Ref1.Tab() + 1); + mxData->mpLinkMgr->FindExtSheet( + nFileId, aTabName, nTabSpan, nExtSheet, nFirstSBTab, nLastSBTab, GetNewRefLogEntry()); + // write the token + sal_uInt8 nBaseId = lclIsRefDel2D( aRefData ) ? EXC_TOKID_AREAERR3D : EXC_TOKID_AREA3D; + AppendOperandTokenId( GetTokenId( nBaseId, EXC_TOKCLASS_REF ), rTokData.mnSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + { + Append( 0, 8 ); + Append( nFirstSBTab ); + Append( nLastSBTab ); + } + AppendRange( aXclRange ); + } + else + { + AppendErrorToken( EXC_ERR_REF, rTokData.mnSpaces ); + } +} + +void XclExpFmlaCompImpl::ProcessDefinedName( const XclExpScToken& rTokData ) +{ + sal_Int16 nSheet = rTokData.mpScToken->GetSheet(); + SCTAB nTab = (nSheet < 0 ? SCTAB_GLOBAL : nSheet); + + XclExpNameManager& rNameMgr = GetNameManager(); + sal_uInt16 nNameIdx = rNameMgr.InsertName(nTab, rTokData.mpScToken->GetIndex(), GetCurrScTab()); + if( nNameIdx != 0 ) + { + // global names always with tName token, local names dependent on config + SCTAB nScTab = rNameMgr.GetScTab( nNameIdx ); + if( (nScTab == SCTAB_GLOBAL) || (!mxData->mrCfg.mb3DRefOnly && (nScTab == GetCurrScTab())) ) + { + AppendNameToken( nNameIdx, rTokData.mnSpaces ); + } + else if( mxData->mpLinkMgr ) + { + // use the same special EXTERNNAME to refer to any local name + sal_uInt16 nExtSheet = mxData->mpLinkMgr->FindExtSheet( EXC_EXTSH_OWNDOC ); + AppendNameXToken( nExtSheet, nNameIdx, rTokData.mnSpaces ); + } + else + AppendErrorToken( EXC_ERR_NAME, rTokData.mnSpaces ); + // volatile names (containing volatile functions) + mxData->mbVolatile |= rNameMgr.IsVolatile( nNameIdx ); + } + else + AppendErrorToken( EXC_ERR_NAME, rTokData.mnSpaces ); +} + +void XclExpFmlaCompImpl::ProcessExternalName( const XclExpScToken& rTokData ) +{ + if( mxData->mpLinkMgr ) + { + ScExternalRefManager& rExtRefMgr = *GetDoc().GetExternalRefManager(); + sal_uInt16 nFileId = rTokData.mpScToken->GetIndex(); + OUString aName = rTokData.mpScToken->GetString().getString(); + ScExternalRefCache::TokenArrayRef xArray = rExtRefMgr.getRangeNameTokens( nFileId, aName ); + if( xArray ) + { + // store external cell contents in CRN records + if( mxData->mpScBasePos ) + { + FormulaTokenArrayPlainIterator aIter(*xArray); + for( FormulaToken* pScToken = aIter.First(); pScToken; pScToken = aIter.Next() ) + { + if( pScToken->IsExternalRef() ) + { + switch( pScToken->GetType() ) + { + case svExternalSingleRef: + { + ScSingleRefData aRefData = *pScToken->GetSingleRef(); + mxData->mpLinkMgr->StoreCell( + nFileId, pScToken->GetString().getString(), aRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos)); + } + break; + case svExternalDoubleRef: + { + ScComplexRefData aRefData = *pScToken->GetDoubleRef(); + mxData->mpLinkMgr->StoreCellRange( + nFileId, pScToken->GetString().getString(), aRefData.toAbs(GetRoot().GetDoc(), *mxData->mpScBasePos)); + } + break; + default: + ; // nothing, avoid compiler warning + } + } + } + } + + // insert the new external name and create the tNameX token + sal_uInt16 nExtSheet = 0, nExtName = 0; + const OUString* pFile = rExtRefMgr.getExternalFileName( nFileId ); + if( pFile && mxData->mpLinkMgr->InsertExtName( nExtSheet, nExtName, *pFile, aName, xArray ) ) + { + AppendNameXToken( nExtSheet, nExtName, rTokData.mnSpaces ); + return; + } + } + } + + // on any error: create a #NAME? error + AppendErrorToken( EXC_ERR_NAME, rTokData.mnSpaces ); +} + +// token vector --------------------------------------------------------------- + +void XclExpFmlaCompImpl::PushOperandPos( sal_uInt16 nTokPos ) +{ + mxData->maOpPosStack.push_back( nTokPos ); +} + +void XclExpFmlaCompImpl::PushOperatorPos( sal_uInt16 nTokPos, const XclExpOperandListRef& rxOperands ) +{ + PushOperandPos( nTokPos ); + OSL_ENSURE( rxOperands, "XclExpFmlaCompImpl::AppendOperatorTokenId - missing operand list" ); + if( mxData->maOpListVec.size() <= nTokPos ) + mxData->maOpListVec.resize( nTokPos + 1, XclExpOperandListRef() ); + mxData->maOpListVec[ nTokPos ] = rxOperands; +} + +sal_uInt16 XclExpFmlaCompImpl::PopOperandPos() +{ + OSL_ENSURE( !mxData->mbOk || !mxData->maOpPosStack.empty(), "XclExpFmlaCompImpl::PopOperandPos - token stack broken" ); + mxData->mbOk &= !mxData->maOpPosStack.empty(); + if( mxData->mbOk ) + { + sal_uInt16 nTokPos = mxData->maOpPosStack.back(); + mxData->maOpPosStack.pop_back(); + return nTokPos; + } + return 0; +} + +namespace { + +void lclAppend( ScfUInt8Vec& orVector, sal_uInt16 nData ) +{ + orVector.resize( orVector.size() + 2 ); + ShortToSVBT16( nData, &*(orVector.end() - 2) ); +} + +void lclAppend( ScfUInt8Vec& orVector, sal_uInt32 nData ) +{ + orVector.resize( orVector.size() + 4 ); + UInt32ToSVBT32( nData, &*(orVector.end() - 4) ); +} + +void lclAppend( ScfUInt8Vec& orVector, double fData ) +{ + orVector.resize( orVector.size() + 8 ); + DoubleToSVBT64( fData, &*(orVector.end() - 8) ); +} + +void lclAppend( ScfUInt8Vec& orVector, const XclExpRoot& rRoot, const OUString& rString, XclStrFlags nStrFlags ) +{ + XclExpStringRef xXclStr = XclExpStringHelper::CreateString( rRoot, rString, nStrFlags, EXC_TOK_STR_MAXLEN ); + size_t nSize = orVector.size(); + orVector.resize( nSize + xXclStr->GetSize() ); + xXclStr->WriteToMem( &orVector[ nSize ] ); +} + +} // namespace + +void XclExpFmlaCompImpl::Append( sal_uInt8 nData ) +{ + mxData->maTokVec.push_back( nData ); +} + +void XclExpFmlaCompImpl::Append( sal_uInt8 nData, size_t nCount ) +{ + mxData->maTokVec.resize( mxData->maTokVec.size() + nCount, nData ); +} + +void XclExpFmlaCompImpl::Append( sal_uInt16 nData ) +{ + lclAppend( mxData->maTokVec, nData ); +} + +void XclExpFmlaCompImpl::Append( sal_uInt32 nData ) +{ + lclAppend( mxData->maTokVec, nData ); +} + +void XclExpFmlaCompImpl::Append( double fData ) +{ + lclAppend( mxData->maTokVec, fData ); +} + +void XclExpFmlaCompImpl::Append( const OUString& rString ) +{ + lclAppend( mxData->maTokVec, GetRoot(), rString, XclStrFlags::EightBitLength ); +} + +void XclExpFmlaCompImpl::AppendAddress( const XclAddress& rXclPos ) +{ + Append( static_cast(rXclPos.mnRow) ); + if( meBiff <= EXC_BIFF5 ) + Append( static_cast< sal_uInt8 >( rXclPos.mnCol ) ); + else + Append( rXclPos.mnCol ); +} + +void XclExpFmlaCompImpl::AppendRange( const XclRange& rXclRange ) +{ + Append( static_cast(rXclRange.maFirst.mnRow) ); + Append( static_cast(rXclRange.maLast.mnRow) ); + if( meBiff <= EXC_BIFF5 ) + { + Append( static_cast< sal_uInt8 >( rXclRange.maFirst.mnCol ) ); + Append( static_cast< sal_uInt8 >( rXclRange.maLast.mnCol ) ); + } + else + { + Append( rXclRange.maFirst.mnCol ); + Append( rXclRange.maLast.mnCol ); + } +} + +void XclExpFmlaCompImpl::AppendSpaceToken( sal_uInt8 nType, sal_uInt8 nCount ) +{ + if( nCount > 0 ) + { + Append( EXC_TOKID_ATTR ); + Append( EXC_TOK_ATTR_SPACE ); + Append( nType ); + Append( nCount ); + } +} + +void XclExpFmlaCompImpl::AppendOperandTokenId( sal_uInt8 nTokenId, sal_uInt8 nSpaces ) +{ + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP, nSpaces ); + PushOperandPos( GetSize() ); + Append( nTokenId ); +} + +void XclExpFmlaCompImpl::AppendIntToken( sal_uInt16 nValue, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_INT, nSpaces ); + Append( nValue ); +} + +void XclExpFmlaCompImpl::AppendNumToken( double fValue, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_NUM, nSpaces ); + Append( fValue ); +} + +void XclExpFmlaCompImpl::AppendBoolToken( bool bValue, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_BOOL, nSpaces ); + Append( bValue ? EXC_TOK_BOOL_TRUE : EXC_TOK_BOOL_FALSE ); +} + +void XclExpFmlaCompImpl::AppendErrorToken( sal_uInt8 nErrCode, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_ERR, nSpaces ); + Append( nErrCode ); +} + +void XclExpFmlaCompImpl::AppendMissingToken( sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( EXC_TOKID_MISSARG, nSpaces ); +} + +void XclExpFmlaCompImpl::AppendNameToken( sal_uInt16 nNameIdx, sal_uInt8 nSpaces ) +{ + if( nNameIdx > 0 ) + { + AppendOperandTokenId( GetTokenId( EXC_TOKID_NAME, EXC_TOKCLASS_REF ), nSpaces ); + Append( nNameIdx ); + Append( 0, (meBiff <= EXC_BIFF5) ? 12 : 2 ); + } + else + AppendErrorToken( EXC_ERR_NAME ); +} + +void XclExpFmlaCompImpl::AppendMissingNameToken( const OUString& rName, sal_uInt8 nSpaces ) +{ + sal_uInt16 nNameIdx = GetNameManager().InsertRawName( rName ); + AppendNameToken( nNameIdx, nSpaces ); +} + +void XclExpFmlaCompImpl::AppendNameXToken( sal_uInt16 nExtSheet, sal_uInt16 nExtName, sal_uInt8 nSpaces ) +{ + AppendOperandTokenId( GetTokenId( EXC_TOKID_NAMEX, EXC_TOKCLASS_REF ), nSpaces ); + Append( nExtSheet ); + if( meBiff <= EXC_BIFF5 ) + Append( 0, 8 ); + Append( nExtName ); + Append( 0, (meBiff <= EXC_BIFF5) ? 12 : 2 ); +} + +void XclExpFmlaCompImpl::AppendMacroCallToken( const XclExpExtFuncData& rExtFuncData ) +{ + sal_uInt16 nNameIdx = GetNameManager().InsertMacroCall( rExtFuncData.maFuncName, rExtFuncData.mbVBasic, true, rExtFuncData.mbHidden ); + AppendNameToken( nNameIdx ); +} + +void XclExpFmlaCompImpl::AppendAddInCallToken( const XclExpExtFuncData& rExtFuncData ) +{ + OUString aXclFuncName; + if( mxData->mpLinkMgr && ScGlobal::GetAddInCollection()->GetExcelName( rExtFuncData.maFuncName, GetUILanguage(), aXclFuncName ) ) + { + sal_uInt16 nExtSheet, nExtName; + if( mxData->mpLinkMgr->InsertAddIn( nExtSheet, nExtName, aXclFuncName ) ) + { + AppendNameXToken( nExtSheet, nExtName ); + return; + } + } + AppendMacroCallToken( rExtFuncData ); +} + +void XclExpFmlaCompImpl::AppendEuroToolCallToken( const XclExpExtFuncData& rExtFuncData ) +{ + sal_uInt16 nExtSheet(0), nExtName(0); + if( mxData->mpLinkMgr && mxData->mpLinkMgr->InsertEuroTool( nExtSheet, nExtName, rExtFuncData.maFuncName ) ) + AppendNameXToken( nExtSheet, nExtName ); + else + AppendMacroCallToken( rExtFuncData ); +} + +void XclExpFmlaCompImpl::AppendOperatorTokenId( sal_uInt8 nTokenId, const XclExpOperandListRef& rxOperands, sal_uInt8 nSpaces ) +{ + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP, nSpaces ); + PushOperatorPos( GetSize(), rxOperands ); + Append( nTokenId ); +} + +void XclExpFmlaCompImpl::AppendUnaryOperatorToken( sal_uInt8 nTokenId, sal_uInt8 nSpaces ) +{ + XclExpOperandListRef xOperands = std::make_shared(); + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_RPO, true ); + AppendOperatorTokenId( nTokenId, xOperands, nSpaces ); +} + +void XclExpFmlaCompImpl::AppendBinaryOperatorToken( sal_uInt8 nTokenId, bool bValType, sal_uInt8 nSpaces ) +{ + XclExpOperandListRef xOperands = std::make_shared(); + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_RPO, bValType ); + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_RPO, bValType ); + AppendOperatorTokenId( nTokenId, xOperands, nSpaces ); +} + +void XclExpFmlaCompImpl::AppendLogicalOperatorToken( sal_uInt16 nXclFuncIdx, sal_uInt8 nOpCount ) +{ + XclExpOperandListRef xOperands = std::make_shared(); + for( sal_uInt8 nOpIdx = 0; nOpIdx < nOpCount; ++nOpIdx ) + xOperands->AppendOperand( PopOperandPos(), EXC_PARAMCONV_RPX, false ); + AppendOperatorTokenId( GetTokenId( EXC_TOKID_FUNCVAR, EXC_TOKCLASS_VAL ), xOperands ); + Append( nOpCount ); + Append( nXclFuncIdx ); +} + +void XclExpFmlaCompImpl::AppendFuncToken( const XclExpFuncData& rFuncData ) +{ + sal_uInt16 nXclFuncIdx = rFuncData.GetXclFuncIdx(); + sal_uInt8 nParamCount = rFuncData.GetParamCount(); + sal_uInt8 nRetClass = rFuncData.GetReturnClass(); + + if( (nXclFuncIdx == EXC_FUNCID_SUM) && (nParamCount == 1) ) + { + // SUM with only one parameter + AppendOperatorTokenId( EXC_TOKID_ATTR, rFuncData.GetOperandList() ); + Append( EXC_TOK_ATTR_SUM ); + Append( sal_uInt16( 0 ) ); + } + else if( rFuncData.IsFixedParamCount() ) + { + // fixed number of parameters + AppendOperatorTokenId( GetTokenId( EXC_TOKID_FUNC, nRetClass ), rFuncData.GetOperandList() ); + Append( nXclFuncIdx ); + } + else + { + // variable number of parameters + AppendOperatorTokenId( GetTokenId( EXC_TOKID_FUNCVAR, nRetClass ), rFuncData.GetOperandList() ); + Append( nParamCount ); + Append( nXclFuncIdx ); + } +} + +void XclExpFmlaCompImpl::AppendParenToken( sal_uInt8 nOpenSpaces, sal_uInt8 nCloseSpaces ) +{ + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP_OPEN, nOpenSpaces ); + AppendSpaceToken( EXC_TOK_ATTR_SPACE_SP_CLOSE, nCloseSpaces ); + Append( EXC_TOKID_PAREN ); +} + +void XclExpFmlaCompImpl::AppendJumpToken( XclExpFuncData& rFuncData, sal_uInt8 nAttrType ) +{ + // store the start position of the token + rFuncData.AppendAttrPos( GetSize() ); + // create the tAttr token + Append( EXC_TOKID_ATTR ); + Append( nAttrType ); + Append( sal_uInt16( 0 ) ); // placeholder that will be updated later +} + +void XclExpFmlaCompImpl::InsertZeros( sal_uInt16 nInsertPos, sal_uInt16 nInsertSize ) +{ + // insert zeros into the token array + OSL_ENSURE( nInsertPos < mxData->maTokVec.size(), "XclExpFmlaCompImpl::Insert - invalid position" ); + mxData->maTokVec.insert( mxData->maTokVec.begin() + nInsertPos, nInsertSize, 0 ); + + // update positions of operands waiting for an operator + for( auto& rOpPos : mxData->maOpPosStack ) + if( nInsertPos <= rOpPos ) + rOpPos += nInsertSize; + + // update operand lists of all operator tokens + if( nInsertPos < mxData->maOpListVec.size() ) + mxData->maOpListVec.insert( mxData->maOpListVec.begin() + nInsertPos, nInsertSize, XclExpOperandListRef() ); + for( auto& rxOpList : mxData->maOpListVec ) + if( rxOpList ) + for( auto& rOp : *rxOpList ) + if( nInsertPos <= rOp.mnTokPos ) + rOp.mnTokPos += nInsertSize; +} + +void XclExpFmlaCompImpl::Overwrite( sal_uInt16 nWriteToPos, sal_uInt16 nOffset ) +{ + OSL_ENSURE( o3tl::make_unsigned( nWriteToPos + 1 ) < mxData->maTokVec.size(), "XclExpFmlaCompImpl::Overwrite - invalid position" ); + ShortToSVBT16( nOffset, &mxData->maTokVec[ nWriteToPos ] ); +} + +void XclExpFmlaCompImpl::UpdateAttrGoto( sal_uInt16 nAttrPos ) +{ + /* tAttrGoto contains distance from end of tAttr token to position behind + the function token (for IF or CHOOSE function), which is currently at + the end of the token array. Additionally this distance is decreased by + one, for whatever reason. So we have to subtract 4 and 1 from the + distance between the tAttr token start and the end of the token array. */ + Overwrite( nAttrPos + 2, static_cast< sal_uInt16 >( GetSize() - nAttrPos - 5 ) ); +} + +bool XclExpFmlaCompImpl::IsSpaceToken( sal_uInt16 nPos ) const +{ + return + (o3tl::make_unsigned( nPos + 4 ) <= mxData->maTokVec.size()) && + (mxData->maTokVec[ nPos ] == EXC_TOKID_ATTR) && + (mxData->maTokVec[ nPos + 1 ] == EXC_TOK_ATTR_SPACE); +} + +void XclExpFmlaCompImpl::RemoveTrailingParen() +{ + // remove trailing tParen token + if( !mxData->maTokVec.empty() && (mxData->maTokVec.back() == EXC_TOKID_PAREN) ) + mxData->maTokVec.pop_back(); + // remove remaining tAttrSpace tokens + while( (mxData->maTokVec.size() >= 4) && IsSpaceToken( GetSize() - 4 ) ) + mxData->maTokVec.erase( mxData->maTokVec.end() - 4, mxData->maTokVec.end() ); +} + +void XclExpFmlaCompImpl::AppendExt( sal_uInt8 nData ) +{ + mxData->maExtDataVec.push_back( nData ); +} + +void XclExpFmlaCompImpl::AppendExt( sal_uInt8 nData, size_t nCount ) +{ + mxData->maExtDataVec.resize( mxData->maExtDataVec.size() + nCount, nData ); +} + +void XclExpFmlaCompImpl::AppendExt( sal_uInt16 nData ) +{ + lclAppend( mxData->maExtDataVec, nData ); +} + +void XclExpFmlaCompImpl::AppendExt( double fData ) +{ + lclAppend( mxData->maExtDataVec, fData ); +} + +void XclExpFmlaCompImpl::AppendExt( const OUString& rString ) +{ + lclAppend( mxData->maExtDataVec, GetRoot(), rString, (meBiff == EXC_BIFF8) ? XclStrFlags::NONE : XclStrFlags::EightBitLength ); +} + +namespace { + +void lclInitOwnTab( ScSingleRefData& rRef, const ScAddress& rScPos, SCTAB nCurrScTab, bool b3DRefOnly ) +{ + if( b3DRefOnly ) + { + // no reduction to 2D reference, if global link manager is used + rRef.SetFlag3D(true); + } + else if( rScPos.Tab() == nCurrScTab ) + { + rRef.SetRelTab(0); + } +} + +void lclPutCellToTokenArray( ScTokenArray& rScTokArr, const ScAddress& rScPos, SCTAB nCurrScTab, bool b3DRefOnly ) +{ + ScSingleRefData aRef; + aRef.InitAddress( rScPos ); + lclInitOwnTab( aRef, rScPos, nCurrScTab, b3DRefOnly ); + rScTokArr.AddSingleReference( aRef ); +} + +void lclPutRangeToTokenArray( ScTokenArray& rScTokArr, const ScRange& rScRange, SCTAB nCurrScTab, bool b3DRefOnly ) +{ + if( rScRange.aStart == rScRange.aEnd ) + { + lclPutCellToTokenArray( rScTokArr, rScRange.aStart, nCurrScTab, b3DRefOnly ); + } + else + { + ScComplexRefData aRef; + aRef.InitRange( rScRange ); + lclInitOwnTab( aRef.Ref1, rScRange.aStart, nCurrScTab, b3DRefOnly ); + lclInitOwnTab( aRef.Ref2, rScRange.aEnd, nCurrScTab, b3DRefOnly ); + rScTokArr.AddDoubleReference( aRef ); + } +} + +} // namespace + +XclExpFormulaCompiler::XclExpFormulaCompiler( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mxImpl( std::make_shared( rRoot ) ) +{ +} + +XclExpFormulaCompiler::~XclExpFormulaCompiler() +{ +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateFormula( + XclFormulaType eType, const ScTokenArray& rScTokArr, + const ScAddress* pScBasePos, XclExpRefLog* pRefLog ) +{ + return mxImpl->CreateFormula( eType, rScTokArr, pScBasePos, pRefLog ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateFormula( XclFormulaType eType, const ScAddress& rScPos ) +{ + ScTokenArray aScTokArr(GetRoot().GetDoc()); + lclPutCellToTokenArray( aScTokArr, rScPos, GetCurrScTab(), mxImpl->Is3DRefOnly( eType ) ); + return mxImpl->CreateFormula( eType, aScTokArr ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateFormula( XclFormulaType eType, const ScRange& rScRange ) +{ + ScTokenArray aScTokArr(GetRoot().GetDoc()); + lclPutRangeToTokenArray( aScTokArr, rScRange, GetCurrScTab(), mxImpl->Is3DRefOnly( eType ) ); + return mxImpl->CreateFormula( eType, aScTokArr ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateFormula( XclFormulaType eType, const ScRangeList& rScRanges ) +{ + size_t nCount = rScRanges.size(); + if( nCount == 0 ) + return XclTokenArrayRef(); + + ScTokenArray aScTokArr(GetRoot().GetDoc()); + SCTAB nCurrScTab = GetCurrScTab(); + bool b3DRefOnly = mxImpl->Is3DRefOnly( eType ); + for( size_t nIdx = 0; nIdx < nCount; ++nIdx ) + { + if( nIdx > 0 ) + aScTokArr.AddOpCode( ocUnion ); + lclPutRangeToTokenArray( aScTokArr, rScRanges[ nIdx ], nCurrScTab, b3DRefOnly ); + } + return mxImpl->CreateFormula( eType, aScTokArr ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateErrorFormula( sal_uInt8 nErrCode ) +{ + return mxImpl->CreateErrorFormula( nErrCode ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateSpecialRefFormula( + sal_uInt8 nTokenId, const XclAddress& rXclPos ) +{ + return mxImpl->CreateSpecialRefFormula( nTokenId, rXclPos ); +} + +XclTokenArrayRef XclExpFormulaCompiler::CreateNameXFormula( + sal_uInt16 nExtSheet, sal_uInt16 nExtName ) +{ + return mxImpl->CreateNameXFormula( nExtSheet, nExtName ); +} + +bool XclExpFormulaCompiler::IsRef2D( const ScSingleRefData& rRefData ) const +{ + return mxImpl->IsRef2D(rRefData, true); +} + +bool XclExpFormulaCompiler::IsRef2D( const ScComplexRefData& rRefData ) const +{ + return mxImpl->IsRef2D(rRefData, true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xehelper.cxx b/sc/source/filter/excel/xehelper.cxx new file mode 100644 index 000000000..038ec8f71 --- /dev/null +++ b/sc/source/filter/excel/xehelper.cxx @@ -0,0 +1,1071 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::i18n::XBreakIterator; + +// Export progress bar ======================================================== + +XclExpProgressBar::XclExpProgressBar( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mxProgress( new ScfProgressBar( rRoot.GetDocShell(), STR_SAVE_DOC ) ), + mpSubProgress( nullptr ), + mpSubRowCreate( nullptr ), + mpSubRowFinal( nullptr ), + mnSegRowFinal( SCF_INV_SEGMENT ), + mnRowCount( 0 ) +{ +} + +XclExpProgressBar::~XclExpProgressBar() +{ +} + +void XclExpProgressBar::Initialize() +{ + const ScDocument& rDoc = GetDoc(); + const XclExpTabInfo& rTabInfo = GetTabInfo(); + SCTAB nScTabCount = rTabInfo.GetScTabCount(); + + // *** segment: creation of ROW records *** ------------------------------- + + sal_Int32 nSegRowCreate = mxProgress->AddSegment( 2000 ); + mpSubRowCreate = &mxProgress->GetSegmentProgressBar( nSegRowCreate ); + maSubSegRowCreate.resize( nScTabCount, SCF_INV_SEGMENT ); + + for( SCTAB nScTab = 0; nScTab < nScTabCount; ++nScTab ) + { + if( rTabInfo.IsExportTab( nScTab ) ) + { + SCCOL nLastUsedScCol; + SCROW nLastUsedScRow; + rDoc.GetTableArea( nScTab, nLastUsedScCol, nLastUsedScRow ); + std::size_t nSegSize = static_cast< std::size_t >( nLastUsedScRow + 1 ); + maSubSegRowCreate[ nScTab ] = mpSubRowCreate->AddSegment( nSegSize ); + } + } + + // *** segment: writing all ROW records *** ------------------------------- + + mnSegRowFinal = mxProgress->AddSegment( 1000 ); + // sub progress bar and segment are created later in ActivateFinalRowsSegment() +} + +void XclExpProgressBar::IncRowRecordCount() +{ + ++mnRowCount; +} + +void XclExpProgressBar::ActivateCreateRowsSegment() +{ + OSL_ENSURE( (0 <= GetCurrScTab()) && (GetCurrScTab() < GetTabInfo().GetScTabCount()), + "XclExpProgressBar::ActivateCreateRowsSegment - invalid sheet" ); + sal_Int32 nSeg = maSubSegRowCreate[ GetCurrScTab() ]; + OSL_ENSURE( nSeg != SCF_INV_SEGMENT, "XclExpProgressBar::ActivateCreateRowsSegment - invalid segment" ); + if( nSeg != SCF_INV_SEGMENT ) + { + mpSubProgress = mpSubRowCreate; + mpSubProgress->ActivateSegment( nSeg ); + } + else + mpSubProgress = nullptr; +} + +void XclExpProgressBar::ActivateFinalRowsSegment() +{ + if( !mpSubRowFinal && (mnRowCount > 0) ) + { + mpSubRowFinal = &mxProgress->GetSegmentProgressBar( mnSegRowFinal ); + mpSubRowFinal->AddSegment( mnRowCount ); + } + mpSubProgress = mpSubRowFinal; + if( mpSubProgress ) + mpSubProgress->Activate(); +} + +void XclExpProgressBar::Progress() +{ + if( mpSubProgress && !mpSubProgress->IsFull() ) + mpSubProgress->Progress(); +} + +// Calc->Excel cell address/range conversion ================================== + +namespace { + +/** Fills the passed Excel address with the passed Calc cell coordinates without checking any limits. */ +void lclFillAddress( XclAddress& rXclPos, SCCOL nScCol, SCROW nScRow ) +{ + rXclPos.mnCol = static_cast< sal_uInt16 >( nScCol ); + rXclPos.mnRow = static_cast< sal_uInt32 >( nScRow ); +} + +} // namespace + +XclExpAddressConverter::XclExpAddressConverter( const XclExpRoot& rRoot ) : + XclAddressConverterBase( rRoot.GetTracer(), rRoot.GetXclMaxPos() ) +{ +} + +// cell address --------------------------------------------------------------- + +bool XclExpAddressConverter::CheckAddress( const ScAddress& rScPos, bool bWarn ) +{ + // ScAddress::operator<=() doesn't do what we want here + bool bValidCol = (0 <= rScPos.Col()) && (rScPos.Col() <= maMaxPos.Col()); + bool bValidRow = (0 <= rScPos.Row()) && (rScPos.Row() <= maMaxPos.Row()); + bool bValidTab = (0 <= rScPos.Tab()) && (rScPos.Tab() <= maMaxPos.Tab()); + + bool bValid = bValidCol && bValidRow && bValidTab; + if( !bValid ) + { + mbColTrunc |= !bValidCol; + mbRowTrunc |= !bValidRow; + } + if( !bValid && bWarn ) + { + mbTabTrunc |= (rScPos.Tab() > maMaxPos.Tab()); // do not warn for deleted refs + mrTracer.TraceInvalidAddress( rScPos, maMaxPos ); + } + return bValid; +} + +bool XclExpAddressConverter::ConvertAddress( XclAddress& rXclPos, + const ScAddress& rScPos, bool bWarn ) +{ + bool bValid = CheckAddress( rScPos, bWarn ); + if( bValid ) + lclFillAddress( rXclPos, rScPos.Col(), rScPos.Row() ); + return bValid; +} + +XclAddress XclExpAddressConverter::CreateValidAddress( const ScAddress& rScPos, bool bWarn ) +{ + XclAddress aXclPos( ScAddress::UNINITIALIZED ); + if( !ConvertAddress( aXclPos, rScPos, bWarn ) ) + lclFillAddress( aXclPos, ::std::min( rScPos.Col(), maMaxPos.Col() ), ::std::min( rScPos.Row(), maMaxPos.Row() ) ); + return aXclPos; +} + +// cell range ----------------------------------------------------------------- + +bool XclExpAddressConverter::CheckRange( const ScRange& rScRange, bool bWarn ) +{ + return CheckAddress( rScRange.aStart, bWarn ) && CheckAddress( rScRange.aEnd, bWarn ); +} + +bool XclExpAddressConverter::ValidateRange( ScRange& rScRange, bool bWarn ) +{ + rScRange.PutInOrder(); + + // check start position + bool bValidStart = CheckAddress( rScRange.aStart, bWarn ); + if( bValidStart ) + { + // check & correct end position + ScAddress& rScEnd = rScRange.aEnd; + if( !CheckAddress( rScEnd, bWarn ) ) + { + rScEnd.SetCol( ::std::min( rScEnd.Col(), maMaxPos.Col() ) ); + rScEnd.SetRow( ::std::min( rScEnd.Row(), maMaxPos.Row() ) ); + rScEnd.SetTab( ::std::min( rScEnd.Tab(), maMaxPos.Tab() ) ); + } + } + + return bValidStart; +} + +bool XclExpAddressConverter::ConvertRange( XclRange& rXclRange, + const ScRange& rScRange, bool bWarn ) +{ + // check start position + bool bValidStart = CheckAddress( rScRange.aStart, bWarn ); + if( bValidStart ) + { + lclFillAddress( rXclRange.maFirst, rScRange.aStart.Col(), rScRange.aStart.Row() ); + + // check & correct end position + SCCOL nScCol2 = rScRange.aEnd.Col(); + SCROW nScRow2 = rScRange.aEnd.Row(); + if( !CheckAddress( rScRange.aEnd, bWarn ) ) + { + nScCol2 = ::std::min( nScCol2, maMaxPos.Col() ); + nScRow2 = ::std::min( nScRow2, maMaxPos.Row() ); + } + lclFillAddress( rXclRange.maLast, nScCol2, nScRow2 ); + } + return bValidStart; +} + +// cell range list ------------------------------------------------------------ + +void XclExpAddressConverter::ValidateRangeList( ScRangeList& rScRanges, bool bWarn ) +{ + for ( size_t nRange = rScRanges.size(); nRange > 0; ) + { + ScRange & rScRange = rScRanges[ --nRange ]; + if( !CheckRange( rScRange, bWarn ) ) + rScRanges.Remove(nRange); + } +} + +void XclExpAddressConverter::ConvertRangeList( XclRangeList& rXclRanges, + const ScRangeList& rScRanges, bool bWarn ) +{ + rXclRanges.clear(); + for( size_t nPos = 0, nCount = rScRanges.size(); nPos < nCount; ++nPos ) + { + const ScRange & rScRange = rScRanges[ nPos ]; + XclRange aXclRange( ScAddress::UNINITIALIZED ); + if( ConvertRange( aXclRange, rScRange, bWarn ) ) + rXclRanges.push_back( aXclRange ); + } +} + +// EditEngine->String conversion ============================================== + +namespace { + +OUString lclGetUrlRepresentation( const SvxURLField& rUrlField ) +{ + const OUString& aRepr = rUrlField.GetRepresentation(); + // no representation -> use URL + return aRepr.isEmpty() ? rUrlField.GetURL() : aRepr; +} + +} // namespace + +XclExpHyperlinkHelper::XclExpHyperlinkHelper( const XclExpRoot& rRoot, const ScAddress& rScPos ) : + XclExpRoot( rRoot ), + maScPos( rScPos ), + mbMultipleUrls( false ) +{ +} + +XclExpHyperlinkHelper::~XclExpHyperlinkHelper() +{ +} + +OUString XclExpHyperlinkHelper::ProcessUrlField( const SvxURLField& rUrlField ) +{ + OUString aUrlRepr; + + if( GetBiff() == EXC_BIFF8 ) // no HLINK records in BIFF2-BIFF7 + { + // there was/is already a HLINK record + mbMultipleUrls = static_cast< bool >(mxLinkRec); + + mxLinkRec = new XclExpHyperlink( GetRoot(), rUrlField, maScPos ); + + if( const OUString* pRepr = mxLinkRec->GetRepr() ) + aUrlRepr = *pRepr; + + // add URL to note text + maUrlList = ScGlobal::addToken( maUrlList, rUrlField.GetURL(), '\n' ); + } + + // no hyperlink representation from Excel HLINK record -> use it from text field + return aUrlRepr.isEmpty() ? lclGetUrlRepresentation(rUrlField) : aUrlRepr; +} + +bool XclExpHyperlinkHelper::HasLinkRecord() const +{ + return !mbMultipleUrls && mxLinkRec; +} + +XclExpHyperlinkHelper::XclExpHyperlinkRef XclExpHyperlinkHelper::GetLinkRecord() const +{ + if( HasLinkRecord() ) + return mxLinkRec; + return XclExpHyperlinkRef(); +} + +namespace { + +/** Creates a new formatted string from the passed unformatted string. + + Creates a Unicode string or a byte string, depending on the current BIFF + version contained in the passed XclExpRoot object. May create a formatted + string object, if the text contains different script types. + + @param pCellAttr + Cell attributes used for font formatting. + @param nFlags + Modifiers for string export. + @param nMaxLen + The maximum number of characters to store in this string. + @return + The new string object. + */ +XclExpStringRef lclCreateFormattedString( + const XclExpRoot& rRoot, const OUString& rText, const ScPatternAttr* pCellAttr, + XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + /* Create an empty Excel string object with correctly initialized BIFF mode, + because this function only uses Append() functions that require this. */ + XclExpStringRef xString = XclExpStringHelper::CreateString( rRoot, OUString(), nFlags, nMaxLen ); + + // script type handling + Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator(); + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + // #i63255# get script type for leading weak characters + sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( rRoot, rText ); + + // font buffer and cell item set + XclExpFontBuffer& rFontBuffer = rRoot.GetFontBuffer(); + const SfxItemSet& rItemSet = pCellAttr ? pCellAttr->GetItemSet() : rRoot.GetDoc().GetDefPattern()->GetItemSet(); + + // process all script portions + sal_Int32 nPortionPos = 0; + sal_Int32 nTextLen = rText.getLength(); + while( nPortionPos < nTextLen ) + { + // get script type and end position of next script portion + sal_Int16 nScript = xBreakIt->getScriptType( rText, nPortionPos ); + sal_Int32 nPortionEnd = xBreakIt->endOfScript( rText, nPortionPos, nScript ); + + // reuse previous script for following weak portions + if( nScript == ApiScriptType::WEAK ) + nScript = nLastScript; + + // construct font from current text portion + SvxFont aFont( XclExpFontHelper::GetFontFromItemSet( rRoot, rItemSet, nScript ) ); + + // Excel start position of this portion + sal_Int32 nXclPortionStart = xString->Len(); + // add portion text to Excel string + XclExpStringHelper::AppendString( *xString, rRoot, rText.subView( nPortionPos, nPortionEnd - nPortionPos ) ); + if( nXclPortionStart < xString->Len() ) + { + // insert font into buffer + sal_uInt16 nFontIdx = rFontBuffer.Insert( aFont, EXC_COLOR_CELLTEXT ); + // insert font index into format run vector + xString->AppendFormat( nXclPortionStart, nFontIdx ); + } + + // go to next script portion + nLastScript = nScript; + nPortionPos = nPortionEnd; + } + + return xString; +} + +/** Creates a new formatted string from an edit engine text object. + + Creates a Unicode string or a byte string, depending on the current BIFF + version contained in the passed XclExpRoot object. + + @param rEE + The edit engine in use. The text object must already be set. + @param nFlags + Modifiers for string export. + @param nMaxLen + The maximum number of characters to store in this string. + @return + The new string object. + */ +XclExpStringRef lclCreateFormattedString( + const XclExpRoot& rRoot, EditEngine& rEE, XclExpHyperlinkHelper* pLinkHelper, + XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + /* Create an empty Excel string object with correctly initialized BIFF mode, + because this function only uses Append() functions that require this. */ + XclExpStringRef xString = XclExpStringHelper::CreateString( rRoot, OUString(), nFlags, nMaxLen ); + + // font buffer and helper item set for edit engine -> Calc item conversion + XclExpFontBuffer& rFontBuffer = rRoot.GetFontBuffer(); + SfxItemSetFixed aItemSet( *rRoot.GetDoc().GetPool() ); + + // script type handling + Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator(); + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + // #i63255# get script type for leading weak characters + sal_Int16 nLastScript = XclExpStringHelper::GetLeadingScriptType( rRoot, rEE.GetText() ); + + // process all paragraphs + sal_Int32 nParaCount = rEE.GetParagraphCount(); + for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara ) + { + ESelection aSel( nPara, 0 ); + OUString aParaText( rEE.GetText( nPara ) ); + + std::vector aPosList; + rEE.GetPortions( nPara, aPosList ); + + // process all portions in the paragraph + for( const auto& rPos : aPosList ) + { + aSel.nEndPos = rPos; + OUString aXclPortionText = aParaText.copy( aSel.nStartPos, aSel.nEndPos - aSel.nStartPos ); + + aItemSet.ClearItem(); + SfxItemSet aEditSet( rEE.GetAttribs( aSel ) ); + ScPatternAttr::GetFromEditItemSet( aItemSet, aEditSet ); + + // get escapement value + short nEsc = aEditSet.Get( EE_CHAR_ESCAPEMENT ).GetEsc(); + + // process text fields + bool bIsHyperlink = false; + if( aSel.nStartPos + 1 == aSel.nEndPos ) + { + // test if the character is a text field + if( const SvxFieldItem* pItem = aEditSet.GetItemIfSet( EE_FEATURE_FIELD, false ) ) + { + const SvxFieldData* pField = pItem->GetField(); + if( const SvxURLField* pUrlField = dynamic_cast( pField ) ) + { + // convert URL field to string representation + aXclPortionText = pLinkHelper ? + pLinkHelper->ProcessUrlField( *pUrlField ) : + lclGetUrlRepresentation( *pUrlField ); + bIsHyperlink = true; + } + else + { + OSL_FAIL( "lclCreateFormattedString - unknown text field" ); + aXclPortionText.clear(); + } + } + } + + // Excel start position of this portion + sal_Int32 nXclPortionStart = xString->Len(); + // add portion text to Excel string + XclExpStringHelper::AppendString( *xString, rRoot, aXclPortionText ); + if( (nXclPortionStart < xString->Len()) || (aParaText.isEmpty()) ) + { + /* Construct font from current edit engine text portion. Edit engine + creates different portions for different script types, no need to loop. */ + sal_Int16 nScript = xBreakIt->getScriptType( aXclPortionText, 0 ); + if( nScript == ApiScriptType::WEAK ) + nScript = nLastScript; + SvxFont aFont( XclExpFontHelper::GetFontFromItemSet( rRoot, aItemSet, nScript ) ); + nLastScript = nScript; + + // add escapement + aFont.SetEscapement( nEsc ); + // modify automatic font color for hyperlinks + if( bIsHyperlink && aItemSet.Get( ATTR_FONT_COLOR ).GetValue() == COL_AUTO) + aFont.SetColor( COL_LIGHTBLUE ); + + // insert font into buffer + sal_uInt16 nFontIdx = rFontBuffer.Insert( aFont, EXC_COLOR_CELLTEXT ); + // insert font index into format run vector + xString->AppendFormat( nXclPortionStart, nFontIdx ); + } + + aSel.nStartPos = aSel.nEndPos; + } + + // add trailing newline (important for correct character index calculation) + if( nPara + 1 < nParaCount ) + XclExpStringHelper::AppendChar( *xString, rRoot, '\n' ); + } + + return xString; +} + +} // namespace + +XclExpStringRef XclExpStringHelper::CreateString( + const XclExpRoot& rRoot, const OUString& rString, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + XclExpStringRef xString = std::make_shared(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + xString->Assign( rString, nFlags, nMaxLen ); + else + xString->AssignByte( rString, rRoot.GetTextEncoding(), nFlags, nMaxLen ); + return xString; +} + +XclExpStringRef XclExpStringHelper::CreateString( + const XclExpRoot& rRoot, sal_Unicode cChar, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + XclExpStringRef xString = CreateString( rRoot, OUString(), nFlags, nMaxLen ); + AppendChar( *xString, rRoot, cChar ); + return xString; +} + +void XclExpStringHelper::AppendString( XclExpString& rXclString, const XclExpRoot& rRoot, std::u16string_view rString ) +{ + if( rRoot.GetBiff() == EXC_BIFF8 ) + rXclString.Append( rString ); + else + rXclString.AppendByte( rString, rRoot.GetTextEncoding() ); +} + +void XclExpStringHelper::AppendChar( XclExpString& rXclString, const XclExpRoot& rRoot, sal_Unicode cChar ) +{ + if( rRoot.GetBiff() == EXC_BIFF8 ) + rXclString.Append( rtl::OUStringChar(cChar) ); + else + rXclString.AppendByte( cChar, rRoot.GetTextEncoding() ); +} + +XclExpStringRef XclExpStringHelper::CreateCellString( + const XclExpRoot& rRoot, const OUString& rString, const ScPatternAttr* pCellAttr, + XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + return lclCreateFormattedString(rRoot, rString, pCellAttr, nFlags, nMaxLen); +} + +XclExpStringRef XclExpStringHelper::CreateCellString( + const XclExpRoot& rRoot, const EditTextObject& rEditText, const ScPatternAttr* pCellAttr, + XclExpHyperlinkHelper& rLinkHelper, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + XclExpStringRef xString; + + // formatted cell + ScEditEngineDefaulter& rEE = rRoot.GetEditEngine(); + bool bOldUpdateMode = rEE.SetUpdateLayout( true ); + + // default items + const SfxItemSet& rItemSet = pCellAttr ? pCellAttr->GetItemSet() : rRoot.GetDoc().GetDefPattern()->GetItemSet(); + auto pEEItemSet = std::make_unique( rEE.GetEmptyItemSet() ); + ScPatternAttr::FillToEditItemSet( *pEEItemSet, rItemSet ); + rEE.SetDefaults( std::move(pEEItemSet) ); // edit engine takes ownership + + // create the string + rEE.SetTextCurrentDefaults(rEditText); + xString = lclCreateFormattedString( rRoot, rEE, &rLinkHelper, nFlags, nMaxLen ); + rEE.SetUpdateLayout( bOldUpdateMode ); + + return xString; +} + +XclExpStringRef XclExpStringHelper::CreateString( + const XclExpRoot& rRoot, const SdrTextObj& rTextObj, + XclStrFlags nFlags ) +{ + XclExpStringRef xString; + if( const OutlinerParaObject* pParaObj = rTextObj.GetOutlinerParaObject() ) + { + EditEngine& rEE = rRoot.GetDrawEditEngine(); + bool bOldUpdateMode = rEE.SetUpdateLayout( true ); + // create the string + rEE.SetText( pParaObj->GetTextObject() ); + xString = lclCreateFormattedString( rRoot, rEE, nullptr, nFlags, EXC_STR_MAXLEN ); + rEE.SetUpdateLayout( bOldUpdateMode ); + // limit formats - TODO: BIFF dependent + if( !xString->IsEmpty() ) + { + xString->LimitFormatCount( EXC_MAXRECSIZE_BIFF8 / 8 - 1 ); + xString->AppendTrailingFormat( EXC_FONT_APP ); + } + } + else + { + OSL_FAIL( "XclExpStringHelper::CreateString - textbox without para object" ); + // create BIFF dependent empty Excel string + xString = CreateString( rRoot, OUString(), nFlags ); + } + return xString; +} + +XclExpStringRef XclExpStringHelper::CreateString( + const XclExpRoot& rRoot, const EditTextObject& rEditObj, + XclStrFlags nFlags ) +{ + XclExpStringRef xString; + EditEngine& rEE = rRoot.GetDrawEditEngine(); + bool bOldUpdateMode = rEE.SetUpdateLayout( true ); + rEE.SetText( rEditObj ); + xString = lclCreateFormattedString( rRoot, rEE, nullptr, nFlags, EXC_STR_MAXLEN ); + rEE.SetUpdateLayout( bOldUpdateMode ); + // limit formats - TODO: BIFF dependent + if( !xString->IsEmpty() ) + { + xString->LimitFormatCount( EXC_MAXRECSIZE_BIFF8 / 8 - 1 ); + xString->AppendTrailingFormat( EXC_FONT_APP ); + } + return xString; +} + +sal_Int16 XclExpStringHelper::GetLeadingScriptType( const XclExpRoot& rRoot, const OUString& rString ) +{ + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + Reference< XBreakIterator > xBreakIt = rRoot.GetDoc().GetBreakIterator(); + sal_Int32 nStrPos = 0; + sal_Int32 nStrLen = rString.getLength(); + sal_Int16 nScript = ApiScriptType::WEAK; + while( (nStrPos < nStrLen) && (nScript == ApiScriptType::WEAK) ) + { + nScript = xBreakIt->getScriptType( rString, nStrPos ); + nStrPos = xBreakIt->endOfScript( rString, nStrPos, nScript ); + } + return (nScript == ApiScriptType::WEAK) ? rRoot.GetDefApiScript() : nScript; +} + +// Header/footer conversion =================================================== + +XclExpHFConverter::XclExpHFConverter( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mrEE( rRoot.GetHFEditEngine() ), + mnTotalHeight( 0 ) +{ +} + +void XclExpHFConverter::GenerateString( + const EditTextObject* pLeftObj, + const EditTextObject* pCenterObj, + const EditTextObject* pRightObj ) +{ + maHFString.clear(); + mnTotalHeight = 0; + AppendPortion( pLeftObj, 'L' ); + AppendPortion( pCenterObj, 'C' ); + AppendPortion( pRightObj, 'R' ); +} + +void XclExpHFConverter::AppendPortion( const EditTextObject* pTextObj, sal_Unicode cPortionCode ) +{ + if( !pTextObj ) return; + + OUString aText; + sal_Int32 nHeight = 0; + SfxItemSetFixed aItemSet( *GetDoc().GetPool() ); + + // edit engine + bool bOldUpdateMode = mrEE.SetUpdateLayout( true ); + mrEE.SetText( *pTextObj ); + + // font information + XclFontData aFontData, aNewData; + if( const XclExpFont* pFirstFont = GetFontBuffer().GetFont( EXC_FONT_APP ) ) + { + aFontData = pFirstFont->GetFontData(); + aFontData.mnHeight = (aFontData.mnHeight + 10) / 20; // using pt here, not twips + } + else + aFontData.mnHeight = 10; + + const FontList* pFontList = nullptr; + if( SfxObjectShell* pDocShell = GetDocShell() ) + { + if( const SvxFontListItem* pInfoItem = static_cast< const SvxFontListItem* >( + pDocShell->GetItem( SID_ATTR_CHAR_FONTLIST ) ) ) + pFontList = pInfoItem->GetFontList(); + } + + sal_Int32 nParaCount = mrEE.GetParagraphCount(); + for( sal_Int32 nPara = 0; nPara < nParaCount; ++nPara ) + { + ESelection aSel( nPara, 0 ); + OUStringBuffer aParaText; + sal_Int32 nParaHeight = 0; + std::vector aPosList; + mrEE.GetPortions( nPara, aPosList ); + + for( const auto& rPos : aPosList ) + { + aSel.nEndPos = rPos; + if( aSel.nStartPos < aSel.nEndPos ) + { + +// --- font attributes --- + + vcl::Font aFont; + aItemSet.ClearItem(); + SfxItemSet aEditSet( mrEE.GetAttribs( aSel ) ); + ScPatternAttr::GetFromEditItemSet( aItemSet, aEditSet ); + ScPatternAttr::GetFont( aFont, aItemSet, SC_AUTOCOL_RAW ); + + // font name and style + aNewData.maName = XclTools::GetXclFontName( aFont.GetFamilyName() ); + aNewData.mnWeight = (aFont.GetWeight() > WEIGHT_NORMAL) ? EXC_FONTWGHT_BOLD : EXC_FONTWGHT_NORMAL; + aNewData.mbItalic = (aFont.GetItalic() != ITALIC_NONE); + bool bNewFont = (aFontData.maName != aNewData.maName); + bool bNewStyle = (aFontData.mnWeight != aNewData.mnWeight) || + (aFontData.mbItalic != aNewData.mbItalic); + if( bNewFont || (bNewStyle && pFontList) ) + { + aParaText.append("&\"" + aNewData.maName); + if( pFontList ) + { + FontMetric aFontMetric( pFontList->Get( + aNewData.maName, + (aNewData.mnWeight > EXC_FONTWGHT_NORMAL) ? WEIGHT_BOLD : WEIGHT_NORMAL, + aNewData.mbItalic ? ITALIC_NORMAL : ITALIC_NONE ) ); + aNewData.maStyle = pFontList->GetStyleName( aFontMetric ); + if( !aNewData.maStyle.isEmpty() ) + aParaText.append("," + aNewData.maStyle); + } + aParaText.append("\""); + } + + // height + // is calculated wrong in ScPatternAttr::GetFromEditItemSet, because already in twips and not 100thmm + // -> get it directly from edit engine item set + aNewData.mnHeight = ulimit_cast< sal_uInt16 >( aEditSet.Get( EE_CHAR_FONTHEIGHT ).GetHeight() ); + aNewData.mnHeight = (aNewData.mnHeight + 10) / 20; + bool bFontHtChanged = (aFontData.mnHeight != aNewData.mnHeight); + if( bFontHtChanged ) + aParaText.append("&" + OUString::number(aNewData.mnHeight)); + // update maximum paragraph height, convert to twips + nParaHeight = ::std::max< sal_Int32 >( nParaHeight, aNewData.mnHeight * 20 ); + + // underline + aNewData.mnUnderline = EXC_FONTUNDERL_NONE; + switch( aFont.GetUnderline() ) + { + case LINESTYLE_NONE: aNewData.mnUnderline = EXC_FONTUNDERL_NONE; break; + case LINESTYLE_SINGLE: aNewData.mnUnderline = EXC_FONTUNDERL_SINGLE; break; + case LINESTYLE_DOUBLE: aNewData.mnUnderline = EXC_FONTUNDERL_DOUBLE; break; + default: aNewData.mnUnderline = EXC_FONTUNDERL_SINGLE; + } + if( aFontData.mnUnderline != aNewData.mnUnderline ) + { + sal_uInt8 nTmpUnderl = (aNewData.mnUnderline == EXC_FONTUNDERL_NONE) ? + aFontData.mnUnderline : aNewData.mnUnderline; + (nTmpUnderl == EXC_FONTUNDERL_SINGLE)? aParaText.append("&U") : aParaText.append("&E"); + } + + // font color + aNewData.maColor = aFont.GetColor(); + if ( !aFontData.maColor.IsRGBEqual( aNewData.maColor ) ) + { + aParaText.append("&K" + aNewData.maColor.AsRGBHexString()); + } + + // strikeout + aNewData.mbStrikeout = (aFont.GetStrikeout() != STRIKEOUT_NONE); + if( aFontData.mbStrikeout != aNewData.mbStrikeout ) + aParaText.append("&S"); + + // super/sub script + const SvxEscapementItem& rEscapeItem = aEditSet.Get( EE_CHAR_ESCAPEMENT ); + aNewData.SetScEscapement( rEscapeItem.GetEsc() ); + if( aFontData.mnEscapem != aNewData.mnEscapem ) + { + switch(aNewData.mnEscapem) + { + // close the previous super/sub script. + case EXC_FONTESC_NONE: (aFontData.mnEscapem == EXC_FONTESC_SUPER) ? aParaText.append("&X") : aParaText.append("&Y"); break; + case EXC_FONTESC_SUPER: aParaText.append("&X"); break; + case EXC_FONTESC_SUB: aParaText.append("&Y"); break; + default: break; + } + } + + aFontData = aNewData; + +// --- text content or text fields --- + + const SvxFieldItem* pItem; + if( (aSel.nStartPos + 1 == aSel.nEndPos) && // fields are single characters + (pItem = aEditSet.GetItemIfSet( EE_FEATURE_FIELD, false )) ) + { + if( const SvxFieldData* pFieldData = pItem->GetField() ) + { + if( dynamic_cast( pFieldData) != nullptr ) + aParaText.append("&P"); + else if( dynamic_cast( pFieldData) != nullptr ) + aParaText.append("&N"); + else if( dynamic_cast( pFieldData) != nullptr ) + aParaText.append("&D"); + else if( dynamic_cast( pFieldData) != nullptr || dynamic_cast( pFieldData) != nullptr ) + aParaText.append("&T"); + else if( dynamic_cast( pFieldData) != nullptr ) + aParaText.append("&A"); + else if( dynamic_cast( pFieldData) != nullptr ) // title -> file name + aParaText.append("&F"); + else if( const SvxExtFileField* pFileField = dynamic_cast( pFieldData ) ) + { + switch( pFileField->GetFormat() ) + { + case SvxFileFormat::NameAndExt: + case SvxFileFormat::NameOnly: + aParaText.append("&F"); + break; + case SvxFileFormat::PathOnly: + aParaText.append("&Z"); + break; + case SvxFileFormat::PathFull: + aParaText.append("&Z&F"); + break; + default: + OSL_FAIL( "XclExpHFConverter::AppendPortion - unknown file field" ); + } + } + } + } + else + { + OUString aPortionText( mrEE.GetText( aSel ) ); + aPortionText = aPortionText.replaceAll( "&", "&&" ); + // #i17440# space between font height and numbers in text + if( bFontHtChanged && aParaText.getLength() && !aPortionText.isEmpty() ) + { + sal_Unicode cLast = aParaText[ aParaText.getLength() - 1 ]; + sal_Unicode cFirst = aPortionText[0]; + if( ('0' <= cLast) && (cLast <= '9') && ('0' <= cFirst) && (cFirst <= '9') ) + aParaText.append(" "); + } + aParaText.append(aPortionText); + } + } + + aSel.nStartPos = aSel.nEndPos; + } + + aText = ScGlobal::addToken( aText, aParaText, '\n' ); + aParaText.setLength(0); + if( nParaHeight == 0 ) + nParaHeight = aFontData.mnHeight * 20; // points -> twips + nHeight += nParaHeight; + } + + mrEE.SetUpdateLayout( bOldUpdateMode ); + + if( !aText.isEmpty() ) + { + maHFString += "&" + OUStringChar(cPortionCode) + aText; + mnTotalHeight = ::std::max( mnTotalHeight, nHeight ); + } +} + +// URL conversion ============================================================= + +namespace { + +/** Encodes special parts of the URL, i.e. directory separators and volume names. + @param pTableName Pointer to a table name to be encoded in this URL, or 0. */ +OUString lclEncodeDosUrl( + XclBiff eBiff, const OUString& rUrl, std::u16string_view rBase, const OUString* pTableName) +{ + OUStringBuffer aBuf; + + if (!rUrl.isEmpty()) + { + OUString aOldUrl = rUrl; + aBuf.append(EXC_URLSTART_ENCODED); + + if ( aOldUrl.getLength() > 2 && aOldUrl.startsWith("\\\\") ) + { + // UNC + aBuf.append(EXC_URL_DOSDRIVE).append('@'); + aOldUrl = aOldUrl.copy(2); + } + else if ( aOldUrl.getLength() > 2 && aOldUrl.match(":\\", 1) ) + { + // drive letter + sal_Unicode cThisDrive = rBase.empty() ? ' ' : rBase[0]; + sal_Unicode cDrive = aOldUrl[0]; + if (cThisDrive == cDrive) + // This document and the referenced document are under the same drive. + aBuf.append(EXC_URL_DRIVEROOT); + else + aBuf.append(EXC_URL_DOSDRIVE).append(cDrive); + aOldUrl = aOldUrl.copy(3); + } + else + { + // URL probably points to a document on a Unix-like file system + aBuf.append(EXC_URL_DRIVEROOT); + } + + // directories + sal_Int32 nPos = -1; + while((nPos = aOldUrl.indexOf('\\')) != -1) + { + if ( aOldUrl.startsWith("..") ) + // parent dir (NOTE: the MS-XLS spec doesn't mention this, and + // Excel seems confused by this token). + aBuf.append(EXC_URL_PARENTDIR); + else + aBuf.append(aOldUrl.subView(0,nPos)).append(EXC_URL_SUBDIR); + + aOldUrl = aOldUrl.copy(nPos + 1); + } + + // file name + if (pTableName) // enclose file name in brackets if table name follows + aBuf.append('[').append(aOldUrl).append(']'); + else + aBuf.append(aOldUrl); + } + else // empty URL -> self reference + { + switch( eBiff ) + { + case EXC_BIFF5: + aBuf.append(pTableName ? EXC_URLSTART_SELFENCODED : EXC_URLSTART_SELF); + break; + case EXC_BIFF8: + DBG_ASSERT( pTableName, "lclEncodeDosUrl - sheet name required for BIFF8" ); + aBuf.append(EXC_URLSTART_SELF); + break; + default: + DBG_ERROR_BIFF(); + } + } + + // table name + if (pTableName) + aBuf.append(*pTableName); + + // VirtualPath must be shorter than 255 chars ([MS-XLS].pdf 2.5.277) + // It's better to truncate, than generate invalid file that Excel cannot open. + if (aBuf.getLength() > 255) + aBuf.setLength(255); + + return aBuf.makeStringAndClear(); +} + +} // namespace + +OUString XclExpUrlHelper::EncodeUrl( const XclExpRoot& rRoot, std::u16string_view rAbsUrl, const OUString* pTableName ) +{ + OUString aDosUrl = INetURLObject(rAbsUrl).getFSysPath(FSysStyle::Dos); + OUString aDosBase = INetURLObject(rRoot.GetBasePath()).getFSysPath(FSysStyle::Dos); + return lclEncodeDosUrl(rRoot.GetBiff(), aDosUrl, aDosBase, pTableName); +} + +OUString XclExpUrlHelper::EncodeDde( std::u16string_view rApplic, std::u16string_view rTopic ) +{ + return rApplic + OUStringChar(EXC_DDE_DELIM) + rTopic; +} + +// Cached Value Lists ========================================================= + +XclExpCachedMatrix::XclExpCachedMatrix( const ScMatrix& rMatrix ) + : mrMatrix( rMatrix ) +{ + mrMatrix.IncRef(); +} +XclExpCachedMatrix::~XclExpCachedMatrix() +{ + mrMatrix.DecRef(); +} + +void XclExpCachedMatrix::GetDimensions( SCSIZE & nCols, SCSIZE & nRows ) const +{ + mrMatrix.GetDimensions( nCols, nRows ); + + OSL_ENSURE( nCols && nRows, "XclExpCachedMatrix::GetDimensions - empty matrix" ); + OSL_ENSURE( nCols <= 256, "XclExpCachedMatrix::GetDimensions - too many columns" ); +} + +std::size_t XclExpCachedMatrix::GetSize() const +{ + SCSIZE nCols, nRows; + + GetDimensions( nCols, nRows ); + + /* The returned size may be wrong if the matrix contains strings. The only + effect is that the export stream has to update a wrong record size which is + faster than to iterate through all cached values and calculate their sizes. */ + return 3 + 9 * (nCols * nRows); +} + +void XclExpCachedMatrix::Save( XclExpStream& rStrm ) const +{ + SCSIZE nCols, nRows; + + GetDimensions( nCols, nRows ); + + if( rStrm.GetRoot().GetBiff() <= EXC_BIFF5 ) + // in BIFF2-BIFF7: 256 columns represented by 0 columns + rStrm << static_cast< sal_uInt8 >( nCols ) << static_cast< sal_uInt16 >( nRows ); + else + // in BIFF8: columns and rows decreased by 1 + rStrm << static_cast< sal_uInt8 >( nCols - 1 ) << static_cast< sal_uInt16 >( nRows - 1 ); + + for( SCSIZE nRow = 0; nRow < nRows; ++nRow ) + { + for( SCSIZE nCol = 0; nCol < nCols; ++nCol ) + { + ScMatrixValue nMatVal = mrMatrix.Get( nCol, nRow ); + + FormulaError nScError; + if( ScMatValType::Empty == nMatVal.nType ) + { + rStrm.SetSliceSize( 9 ); + rStrm << EXC_CACHEDVAL_EMPTY; + rStrm.WriteZeroBytes( 8 ); + } + else if( ScMatrix::IsNonValueType( nMatVal.nType ) ) + { + XclExpString aStr( nMatVal.GetString().getString(), XclStrFlags::NONE ); + rStrm.SetSliceSize( 6 ); + rStrm << EXC_CACHEDVAL_STRING << aStr; + } + else if( ScMatValType::Boolean == nMatVal.nType ) + { + sal_Int8 nBool = sal_Int8(nMatVal.GetBoolean()); + rStrm.SetSliceSize( 9 ); + rStrm << EXC_CACHEDVAL_BOOL << nBool; + rStrm.WriteZeroBytes( 7 ); + } + else if( (nScError = nMatVal.GetError()) != FormulaError::NONE ) + { + sal_Int8 nError ( XclTools::GetXclErrorCode( nScError ) ); + rStrm.SetSliceSize( 9 ); + rStrm << EXC_CACHEDVAL_ERROR << nError; + rStrm.WriteZeroBytes( 7 ); + } + else + { + rStrm.SetSliceSize( 9 ); + rStrm << EXC_CACHEDVAL_DOUBLE << nMatVal.fVal; + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xelink.cxx b/sc/source/filter/excel/xelink.cxx new file mode 100644 index 000000000..4bdc24335 --- /dev/null +++ b/sc/source/filter/excel/xelink.cxx @@ -0,0 +1,2660 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using ::std::unique_ptr; +using ::std::vector; +using ::com::sun::star::uno::Any; + +using namespace oox; + +// *** Helper classes *** + +// External names ============================================================= + +namespace { + +/** This is a base class for any external name (i.e. add-in names or DDE links). + @descr Derived classes implement creation and export of the external names. */ +class XclExpExtNameBase : public XclExpRecord, protected XclExpRoot +{ +public: + /** @param nFlags The flags to export. */ + explicit XclExpExtNameBase( const XclExpRoot& rRoot, + const OUString& rName, sal_uInt16 nFlags = 0 ); + + /** Returns the name string of the external name. */ + const OUString& GetName() const { return maName; } + +private: + /** Writes the start of the record that is equal in all EXTERNNAME records and calls WriteAddData(). */ + virtual void WriteBody( XclExpStream& rStrm ) override; + /** Called to write additional data following the common record contents. + @descr Derived classes should overwrite this function to write their data. */ + virtual void WriteAddData( XclExpStream& rStrm ); + +protected: + OUString maName; /// Calc name (title) of the external name. + XclExpStringRef mxName; /// Excel name (title) of the external name. + sal_uInt16 mnFlags; /// Flags for record export. +}; + +/** Represents an EXTERNNAME record for an add-in function name. */ +class XclExpExtNameAddIn : public XclExpExtNameBase +{ +public: + explicit XclExpExtNameAddIn( const XclExpRoot& rRoot, const OUString& rName ); + +private: + /** Writes additional record contents. */ + virtual void WriteAddData( XclExpStream& rStrm ) override; +}; + +/** Represents an EXTERNNAME record for a DDE link. */ +class XclExpExtNameDde : public XclExpExtNameBase +{ +public: + explicit XclExpExtNameDde( const XclExpRoot& rRoot, const OUString& rName, + sal_uInt16 nFlags, const ScMatrix* pResults = nullptr ); + +private: + /** Writes additional record contents. */ + virtual void WriteAddData( XclExpStream& rStrm ) override; + +private: + typedef std::shared_ptr< XclExpCachedMatrix > XclExpCachedMatRef; + XclExpCachedMatRef mxMatrix; /// Cached results of the DDE link. +}; + +class XclExpSupbook; + +class XclExpExtName : public XclExpExtNameBase +{ +public: + explicit XclExpExtName( const XclExpRoot& rRoot, const XclExpSupbook& rSupbook, const OUString& rName, + const ScExternalRefCache::TokenArrayRef& rArray ); + + virtual void SaveXml(XclExpXmlStream& rStrm) override; + +private: + /** Writes additional record contents. */ + virtual void WriteAddData( XclExpStream& rStrm ) override; + +private: + const XclExpSupbook& mrSupbook; + unique_ptr mpArray; +}; + +// List of external names ===================================================== + +/** List of all external names of a sheet. */ +class XclExpExtNameBuffer : public XclExpRecordBase, protected XclExpRoot +{ +public: + explicit XclExpExtNameBuffer( const XclExpRoot& rRoot ); + + /** Inserts an add-in function name + @return The 1-based (Excel-like) list index of the name. */ + sal_uInt16 InsertAddIn( const OUString& rName ); + /** InsertEuroTool */ + sal_uInt16 InsertEuroTool( const OUString& rName ); + /** Inserts a DDE link. + @return The 1-based (Excel-like) list index of the DDE link. */ + sal_uInt16 InsertDde( std::u16string_view rApplic, std::u16string_view rTopic, const OUString& rItem ); + + sal_uInt16 InsertExtName( const XclExpSupbook& rSupbook, const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ); + + /** Writes the EXTERNNAME record list. */ + virtual void Save( XclExpStream& rStrm ) override; + + virtual void SaveXml(XclExpXmlStream& rStrm) override; + +private: + /** Returns the 1-based (Excel-like) list index of the external name or 0, if not found. */ + sal_uInt16 GetIndex( std::u16string_view rName ) const; + /** Appends the passed newly crested external name. + @return The 1-based (Excel-like) list index of the appended name. */ + sal_uInt16 AppendNew( XclExpExtNameBase* pExtName ); + + XclExpRecordList< XclExpExtNameBase > maNameList; /// The list with all EXTERNNAME records. +}; + +// Cached external cells ====================================================== + +/** Stores the contents of a consecutive row of external cells (record CRN). */ +class XclExpCrn : public XclExpRecord +{ +public: + explicit XclExpCrn( SCCOL nScCol, SCROW nScRow, const Any& rValue ); + + /** Returns true, if the passed value could be appended to this record. */ + bool InsertValue( SCCOL nScCol, SCROW nScRow, const Any& rValue ); + + /** Writes the row and child elements. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + virtual void WriteBody( XclExpStream& rStrm ) override; + + static void WriteBool( XclExpStream& rStrm, bool bValue ); + static void WriteDouble( XclExpStream& rStrm, double fValue ); + static void WriteString( XclExpStream& rStrm, const OUString& rValue ); + static void WriteError( XclExpStream& rStrm, sal_uInt8 nErrCode ); + static void WriteEmpty( XclExpStream& rStrm ); + +private: + typedef ::std::vector< Any > CachedValues; + + CachedValues maValues; /// All cached values. + SCCOL mnScCol; /// Column index of the first external cell. + SCROW mnScRow; /// Row index of the external cells. +}; + +class XclExpCrnList; + +/** Represents the record XCT which is the header record of a CRN record list. + */ +class XclExpXct : public XclExpRecordBase, protected XclExpRoot +{ +public: + explicit XclExpXct( const XclExpRoot& rRoot, + const OUString& rTabName, sal_uInt16 nSBTab, + ScExternalRefCache::TableTypeRef const & xCacheTable ); + + /** Returns the external sheet name. */ + const XclExpString& GetTabName() const { return maTabName; } + + /** Stores all cells in the given range in the CRN list. */ + void StoreCellRange( const ScRange& rRange ); + + void StoreCell_( const ScAddress& rCell ); + void StoreCellRange_( const ScRange& rRange ); + + /** Writes the XCT and all CRN records. */ + virtual void Save( XclExpStream& rStrm ) override; + + /** Writes the sheetDataSet and child elements. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + ScExternalRefCache::TableTypeRef mxCacheTable; + ScMarkData maUsedCells; /// Contains addresses of all stored cells. + ScRange maBoundRange; /// Bounding box of maUsedCells. + XclExpString maTabName; /// Sheet name of the external sheet. + sal_uInt16 mnSBTab; /// Referred sheet index in SUPBOOK record. + + /** Build the internal representation of records to be saved as BIFF or OOXML. */ + bool BuildCrnList( XclExpCrnList& rCrnRecs ); +}; + +// External documents (EXTERNSHEET/SUPBOOK), base class ======================= + +/** Base class for records representing external sheets/documents. + + In BIFF5/BIFF7, this record is the EXTERNSHEET record containing one sheet + of the own or an external document. In BIFF8, this record is the SUPBOOK + record representing the entire own or external document with all referenced + sheets. + */ +class XclExpExternSheetBase : public XclExpRecord, protected XclExpRoot +{ +public: + explicit XclExpExternSheetBase( const XclExpRoot& rRoot, + sal_uInt16 nRecId, sal_uInt32 nRecSize = 0 ); + +protected: + /** Creates and returns the list of EXTERNNAME records. */ + XclExpExtNameBuffer& GetExtNameBuffer(); + /** Writes the list of EXTERNNAME records. */ + void WriteExtNameBuffer( XclExpStream& rStrm ); + /** Writes the list of externalName elements. */ + void WriteExtNameBufferXml( XclExpXmlStream& rStrm ); + +protected: + typedef std::shared_ptr< XclExpExtNameBuffer > XclExpExtNameBfrRef; + XclExpExtNameBfrRef mxExtNameBfr; /// List of EXTERNNAME records. +}; + +// External documents (EXTERNSHEET, BIFF5/BIFF7) ============================== + +/** Represents an EXTERNSHEET record containing the URL and sheet name of a sheet. + @descr This class is used up to BIFF7 only, writing a BIFF8 EXTERNSHEET + record is implemented directly in the link manager. */ +class XclExpExternSheet : public XclExpExternSheetBase +{ +public: + /** Creates an EXTERNSHEET record containing a special code (i.e. own document or sheet). */ + explicit XclExpExternSheet( const XclExpRoot& rRoot, sal_Unicode cCode ); + /** Creates an EXTERNSHEET record referring to an internal sheet. */ + explicit XclExpExternSheet( const XclExpRoot& rRoot, std::u16string_view rTabName ); + + /** Finds or inserts an EXTERNNAME record for add-ins. + @return The 1-based EXTERNNAME record index; or 0, if the record list is full. */ + sal_uInt16 InsertAddIn( const OUString& rName ); + + /** Writes the EXTERNSHEET and all EXTERNNAME, XCT and CRN records. */ + virtual void Save( XclExpStream& rStrm ) override; + +private: + /** Initializes the record data with the passed encoded URL. */ + void Init( std::u16string_view rEncUrl ); + /** Writes the contents of the EXTERNSHEET record. */ + virtual void WriteBody( XclExpStream& rStrm ) override; + +private: + XclExpString maTabName; /// The name of the sheet. +}; + +// External documents (SUPBOOK, BIFF8) ======================================== + +/** The SUPBOOK record contains data for an external document (URL, sheet names, external values). */ +class XclExpSupbook : public XclExpExternSheetBase +{ +public: + /** Creates a SUPBOOK record for internal references. */ + explicit XclExpSupbook( const XclExpRoot& rRoot, sal_uInt16 nXclTabCount ); + /** Creates a SUPBOOK record for add-in functions. */ + explicit XclExpSupbook( const XclExpRoot& rRoot ); + /** EUROTOOL SUPBOOK */ + explicit XclExpSupbook( const XclExpRoot& rRoot, const OUString& rUrl, XclSupbookType ); + /** Creates a SUPBOOK record for an external document. */ + explicit XclExpSupbook( const XclExpRoot& rRoot, const OUString& rUrl ); + /** Creates a SUPBOOK record for a DDE link. */ + explicit XclExpSupbook( const XclExpRoot& rRoot, const OUString& rApplic, const OUString& rTopic ); + + /** Returns true, if this SUPBOOK contains the passed URL of an external document. */ + bool IsUrlLink( std::u16string_view rUrl ) const; + /** Returns true, if this SUPBOOK contains the passed DDE link. */ + bool IsDdeLink( std::u16string_view rApplic, std::u16string_view rTopic ) const; + /** Fills the passed reference log entry with the URL and sheet names. */ + void FillRefLogEntry( XclExpRefLogEntry& rRefLogEntry, + sal_uInt16 nFirstSBTab, sal_uInt16 nLastSBTab ) const; + + /** Stores all cells in the given range in the CRN list of the specified SUPBOOK sheet. */ + void StoreCellRange( const ScRange& rRange, sal_uInt16 nSBTab ); + + void StoreCell_( sal_uInt16 nSBTab, const ScAddress& rCell ); + void StoreCellRange_( sal_uInt16 nSBTab, const ScRange& rRange ); + + sal_uInt16 GetTabIndex( const OUString& rTabName ) const; + sal_uInt16 GetTabCount() const; + + /** Inserts a new sheet name into the SUPBOOK and returns the SUPBOOK internal sheet index. */ + sal_uInt16 InsertTabName( const OUString& rTabName, ScExternalRefCache::TableTypeRef const & xCacheTable ); + /** Finds or inserts an EXTERNNAME record for add-ins. + @return The 1-based EXTERNNAME record index; or 0, if the record list is full. */ + sal_uInt16 InsertAddIn( const OUString& rName ); + /** InsertEuroTool */ + sal_uInt16 InsertEuroTool( const OUString& rName ); + /** Finds or inserts an EXTERNNAME record for DDE links. + @return The 1-based EXTERNNAME record index; or 0, if the record list is full. */ + sal_uInt16 InsertDde( const OUString& rItem ); + + sal_uInt16 InsertExtName( const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ); + + /** Get the type of record. */ + XclSupbookType GetType() const; + + /** For references to an external document, 1-based OOXML file ID. */ + sal_uInt16 GetFileId() const; + + /** For references to an external document. */ + const OUString& GetUrl() const; + + /** Writes the SUPBOOK and all EXTERNNAME, XCT and CRN records. */ + virtual void Save( XclExpStream& rStrm ) override; + + /** Writes the externalBook and all child elements. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + /** Returns the sheet name inside of this SUPBOOK. */ + const XclExpString* GetTabName( sal_uInt16 nSBTab ) const; + + /** Writes the SUPBOOK record contents. */ + virtual void WriteBody( XclExpStream& rStrm ) override; + +private: + typedef XclExpRecordList< XclExpXct > XclExpXctList; + typedef XclExpXctList::RecordRefType XclExpXctRef; + + XclExpXctList maXctList; /// List of XCT records (which contain CRN records). + OUString maUrl; /// URL of the external document or application name for DDE. + OUString maDdeTopic; /// Topic of a DDE link. + XclExpString maUrlEncoded; /// Document name encoded for Excel. + XclSupbookType meType; /// Type of this SUPBOOK record. + sal_uInt16 mnXclTabCount; /// Number of internal sheets. + sal_uInt16 mnFileId; /// 1-based external reference file ID for OOXML +}; + +// All SUPBOOKS in a document ================================================= + +/** This struct contains a sheet index range for 3D references. + @descr This reference consists of an index to a SUPBOOK record and indexes + to SUPBOOK sheet names. */ +struct XclExpXti +{ + sal_uInt16 mnSupbook; /// Index to SUPBOOK record. + sal_uInt16 mnFirstSBTab; /// Index to the first sheet of the range in the SUPBOOK. + sal_uInt16 mnLastSBTab; /// Index to the last sheet of the range in the SUPBOOK. + + explicit XclExpXti() : mnSupbook( 0 ), mnFirstSBTab( 0 ), mnLastSBTab( 0 ) {} + explicit XclExpXti( sal_uInt16 nSupbook, sal_uInt16 nFirstSBTab, sal_uInt16 nLastSBTab ) : + mnSupbook( nSupbook ), mnFirstSBTab( nFirstSBTab ), mnLastSBTab( nLastSBTab ) {} + + /** Writes this XTI structure (inside of the EXTERNSHEET record). */ + void Save( XclExpStream& rStrm ) const + { rStrm << mnSupbook << mnFirstSBTab << mnLastSBTab; } +}; + +bool operator==( const XclExpXti& rLeft, const XclExpXti& rRight ) +{ + return + (rLeft.mnSupbook == rRight.mnSupbook) && + (rLeft.mnFirstSBTab == rRight.mnFirstSBTab) && + (rLeft.mnLastSBTab == rRight.mnLastSBTab); +} + +/** Contains a list of all SUPBOOK records and index arrays of external sheets. */ +class XclExpSupbookBuffer : public XclExpRecordBase, protected XclExpRoot +{ +public: + explicit XclExpSupbookBuffer( const XclExpRoot& rRoot ); + + /** Finds SUPBOOK index and SUPBOOK sheet range from given Excel sheet range. + @return An XTI structure containing SUPBOOK and sheet indexes. */ + XclExpXti GetXti( sal_uInt16 nFirstXclTab, sal_uInt16 nLastXclTab, + XclExpRefLogEntry* pRefLogEntry = nullptr ) const; + + /** Stores all cells in the given range in a CRN record list. */ + void StoreCellRange( const ScRange& rRange ); + + void StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell ); + void StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ); + + /** Finds or inserts an EXTERNNAME record for an add-in function name. + @param rnSupbook Returns the index of the SUPBOOK record which contains the add-in function name. + @param rnExtName Returns the 1-based EXTERNNAME record index. */ + bool InsertAddIn( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, + const OUString& rName ); + /** InsertEuroTool */ + bool InsertEuroTool( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, + const OUString& rName ); + /** Finds or inserts an EXTERNNAME record for DDE links. + @param rnSupbook Returns the index of the SUPBOOK record which contains the DDE link. + @param rnExtName Returns the 1-based EXTERNNAME record index. */ + bool InsertDde( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ); + + bool InsertExtName( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ); + + XclExpXti GetXti( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + XclExpRefLogEntry* pRefLogEntry ); + + /** Writes all SUPBOOK records with their sub records. */ + virtual void Save( XclExpStream& rStrm ) override; + + /** Writes all externalBook elements with their child elements to OOXML. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + + /** Whether we need to write externalReferences or not. */ + bool HasExternalReferences() const; + + struct XclExpSBIndex + { + sal_uInt16 mnSupbook; /// SUPBOOK index for an Excel sheet. + sal_uInt16 mnSBTab; /// Sheet name index in SUPBOOK for an Excel sheet. + void Set( sal_uInt16 nSupbook, sal_uInt16 nSBTab ) + { mnSupbook = nSupbook; mnSBTab = nSBTab; } + }; + +private: + typedef XclExpRecordList< XclExpSupbook > XclExpSupbookList; + typedef XclExpSupbookList::RecordRefType XclExpSupbookRef; + +private: + /** Searches for the SUPBOOK record containing the passed document URL. + @param rxSupbook (out-param) Returns a reference to the SUPBOOK record, or 0. + @param rnIndex (out-param) Returns the list index, if the SUPBOOK exists. + @return True, if the SUPBOOK record exists (out-parameters are valid). */ + bool GetSupbookUrl( XclExpSupbookRef& rxSupbook, sal_uInt16& rnIndex, + std::u16string_view rUrl ) const; + /** Searches for the SUPBOOK record containing the passed DDE link. + @param rxSupbook (out-param) Returns a reference to the SUPBOOK record, or 0. + @param rnIndex (out-param) Returns the list index, if the SUPBOOK exists. + @return True, if the SUPBOOK record exists (out-parameters are valid). */ + bool GetSupbookDde( XclExpSupbookRef& rxSupbook, sal_uInt16& rnIndex, + std::u16string_view rApplic, std::u16string_view rTopic ) const; + + /** Appends a new SUPBOOK to the list. + @return The list index of the SUPBOOK record. */ + sal_uInt16 Append( XclExpSupbookRef const & xSupbook ); + +private: + XclExpSupbookList maSupbookList; /// List of all SUPBOOK records. + std::vector< XclExpSBIndex > + maSBIndexVec; /// SUPBOOK and sheet name index for each Excel sheet. + sal_uInt16 mnOwnDocSB; /// Index to SUPBOOK for own document. + sal_uInt16 mnAddInSB; /// Index to add-in SUPBOOK. +}; + +} + +// Export link manager ======================================================== + +/** Abstract base class for implementation classes of the link manager. */ +class XclExpLinkManagerImpl : protected XclExpRoot +{ +public: + /** Derived classes search for an EXTSHEET structure for the given Calc sheet range. */ + virtual void FindExtSheet( sal_uInt16& rnExtSheet, + sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, + XclExpRefLogEntry* pRefLogEntry ) = 0; + /** Derived classes search for a special EXTERNSHEET index for the own document. */ + virtual sal_uInt16 FindExtSheet( sal_Unicode cCode ) = 0; + + virtual void FindExtSheet( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) = 0; + + /** Derived classes store all cells in the given range in a CRN record list. */ + virtual void StoreCellRange( const ScSingleRefData& rRef1, const ScSingleRefData& rRef2, const ScAddress& rPos ) = 0; + + virtual void StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) = 0; + virtual void StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) = 0; + + /** Derived classes find or insert an EXTERNNAME record for an add-in function name. */ + virtual bool InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) = 0; + /** InsertEuroTool */ + virtual bool InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) = 0; + + /** Derived classes find or insert an EXTERNNAME record for DDE links. */ + virtual bool InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) = 0; + + virtual bool InsertExtName( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) = 0; + + /** Derived classes write the entire link table to the passed stream. */ + virtual void Save( XclExpStream& rStrm ) = 0; + + /** Derived classes write the entire link table to the passed OOXML stream. */ + virtual void SaveXml( XclExpXmlStream& rStrm ) = 0; + +protected: + explicit XclExpLinkManagerImpl( const XclExpRoot& rRoot ); +}; + +namespace { + +/** Implementation of the link manager for BIFF5/BIFF7. */ +class XclExpLinkManagerImpl5 : public XclExpLinkManagerImpl +{ +public: + explicit XclExpLinkManagerImpl5( const XclExpRoot& rRoot ); + + virtual void FindExtSheet( sal_uInt16& rnExtSheet, + sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, + XclExpRefLogEntry* pRefLogEntry ) override; + virtual sal_uInt16 FindExtSheet( sal_Unicode cCode ) override; + + virtual void FindExtSheet( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) override; + + virtual void StoreCellRange( const ScSingleRefData& rRef1, const ScSingleRefData& rRef2, const ScAddress& rPos ) override; + + virtual void StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) override; + virtual void StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) override; + + virtual bool InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) override; + + /** InsertEuroTool */ + virtual bool InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) override; + + virtual bool InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) override; + + virtual bool InsertExtName( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) override; + + virtual void Save( XclExpStream& rStrm ) override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + typedef XclExpRecordList< XclExpExternSheet > XclExpExtSheetList; + typedef XclExpExtSheetList::RecordRefType XclExpExtSheetRef; + typedef ::std::map< SCTAB, sal_uInt16 > XclExpIntTabMap; + typedef ::std::map< sal_Unicode, sal_uInt16 > XclExpCodeMap; + +private: + /** Returns the number of EXTERNSHEET records. */ + sal_uInt16 GetExtSheetCount() const; + + /** Appends an internal EXTERNSHEET record and returns the one-based index. */ + sal_uInt16 AppendInternal( XclExpExtSheetRef const & xExtSheet ); + /** Creates all EXTERNSHEET records for internal sheets on first call. */ + void CreateInternal(); + + /** Returns the specified internal EXTERNSHEET record. */ + XclExpExtSheetRef GetInternal( sal_uInt16 nExtSheet ); + /** Returns the EXTERNSHEET index of an internal Calc sheet, or a deleted reference. */ + XclExpExtSheetRef FindInternal( sal_uInt16& rnExtSheet, sal_uInt16& rnXclTab, SCTAB nScTab ); + /** Finds or creates the EXTERNSHEET index of an internal special EXTERNSHEET. */ + XclExpExtSheetRef FindInternal( sal_uInt16& rnExtSheet, sal_Unicode cCode ); + +private: + XclExpExtSheetList maExtSheetList; /// List with EXTERNSHEET records. + XclExpIntTabMap maIntTabMap; /// Maps internal Calc sheets to EXTERNSHEET records. + XclExpCodeMap maCodeMap; /// Maps special external codes to EXTERNSHEET records. +}; + +/** Implementation of the link manager for BIFF8 and OOXML. */ +class XclExpLinkManagerImpl8 : public XclExpLinkManagerImpl +{ +public: + explicit XclExpLinkManagerImpl8( const XclExpRoot& rRoot ); + + virtual void FindExtSheet( sal_uInt16& rnExtSheet, + sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, + XclExpRefLogEntry* pRefLogEntry ) override; + virtual sal_uInt16 FindExtSheet( sal_Unicode cCode ) override; + + virtual void FindExtSheet( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) override; + + virtual void StoreCellRange( const ScSingleRefData& rRef1, const ScSingleRefData& rRef2, const ScAddress& rPos ) override; + + virtual void StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) override; + virtual void StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) override; + + virtual bool InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) override; + /** InsertEuroTool */ + virtual bool InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rName ) override; + + virtual bool InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) override; + + virtual bool InsertExtName( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) override; + + virtual void Save( XclExpStream& rStrm ) override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + /** Searches for or inserts a new XTI structure. + @return The 0-based list index of the XTI structure. */ + sal_uInt16 InsertXti( const XclExpXti& rXti ); + +private: + + XclExpSupbookBuffer maSBBuffer; /// List of all SUPBOOK records. + std::vector< XclExpXti > maXtiVec; /// List of XTI structures for the EXTERNSHEET record. +}; + +} + +// *** Implementation *** + +// Excel sheet indexes ======================================================== + + +XclExpTabInfo::XclExpTabInfo( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnScCnt( 0 ), + mnXclCnt( 0 ), + mnXclExtCnt( 0 ), + mnXclSelCnt( 0 ), + mnDisplXclTab( 0 ), + mnFirstVisXclTab( 0 ) +{ + ScDocument& rDoc = GetDoc(); + ScExtDocOptions& rDocOpt = GetExtDocOptions(); + + mnScCnt = rDoc.GetTableCount(); + + SCTAB nScTab; + SCTAB nFirstVisScTab = SCTAB_INVALID; // first visible sheet + SCTAB nFirstExpScTab = SCTAB_INVALID; // first exported sheet + + // --- initialize the flags in the index buffer --- + + maTabInfoVec.resize( mnScCnt ); + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + // ignored sheets (skipped by export, with invalid Excel sheet index) + if( rDoc.IsScenario( nScTab ) ) + { + SetFlag( nScTab, ExcTabBufFlags::Ignore ); + } + + // external sheets (skipped, but with valid Excel sheet index for ref's) + else if( rDoc.GetLinkMode( nScTab ) == ScLinkMode::VALUE ) + { + SetFlag( nScTab, ExcTabBufFlags::Extern ); + } + + // exported sheets + else + { + // sheet name + rDoc.GetName( nScTab, maTabInfoVec[ nScTab ].maScName ); + + // remember first exported sheet + if( nFirstExpScTab == SCTAB_INVALID ) + nFirstExpScTab = nScTab; + // remember first visible exported sheet + if( (nFirstVisScTab == SCTAB_INVALID) && rDoc.IsVisible( nScTab ) ) + nFirstVisScTab = nScTab; + + // sheet visible (only exported sheets) + SetFlag( nScTab, ExcTabBufFlags::Visible, rDoc.IsVisible( nScTab ) ); + + // sheet selected (only exported sheets) + if( const ScExtTabSettings* pTabSett = rDocOpt.GetTabSettings( nScTab ) ) + SetFlag( nScTab, ExcTabBufFlags::Selected, pTabSett->mbSelected ); + + // sheet mirrored (only exported sheets) + SetFlag( nScTab, ExcTabBufFlags::Mirrored, rDoc.IsLayoutRTL( nScTab ) ); + } + } + + // --- visible/selected sheets --- + + SCTAB nDisplScTab = rDocOpt.GetDocSettings().mnDisplTab; + + // missing viewdata at embedded XLSX OLE objects + if (nDisplScTab == -1 ) + nDisplScTab = rDoc.GetVisibleTab(); + + // find first visible exported sheet + if( (nFirstVisScTab == SCTAB_INVALID) || !IsExportTab( nFirstVisScTab ) ) + { + // no exportable visible sheet -> use first exportable sheet + nFirstVisScTab = nFirstExpScTab; + if( (nFirstVisScTab == SCTAB_INVALID) || !IsExportTab( nFirstVisScTab ) ) + { + // no exportable sheet at all -> use active sheet and export it + nFirstVisScTab = nDisplScTab; + SetFlag( nFirstVisScTab, ExcTabBufFlags::SkipMask, false ); // clear skip flags + } + SetFlag( nFirstVisScTab, ExcTabBufFlags::Visible ); // must be visible, even if originally hidden + } + + // find currently displayed sheet + if( !IsExportTab( nDisplScTab ) ) // selected sheet not exported (i.e. scenario) -> use first visible + nDisplScTab = nFirstVisScTab; + SetFlag( nDisplScTab, ExcTabBufFlags::Visible | ExcTabBufFlags::Selected ); + + // number of selected sheets + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + if( IsSelectedTab( nScTab ) ) + ++mnXclSelCnt; + + // --- calculate resulting Excel sheet indexes --- + + CalcXclIndexes(); + mnFirstVisXclTab = GetXclTab( nFirstVisScTab ); + mnDisplXclTab = GetXclTab( nDisplScTab ); + + // --- sorted vectors for index lookup --- + + CalcSortedIndexes(); +} + +bool XclExpTabInfo::IsExportTab( SCTAB nScTab ) const +{ + /* Check sheet index before to avoid assertion in GetFlag(). */ + return (nScTab < mnScCnt && nScTab >= 0) && !GetFlag( nScTab, ExcTabBufFlags::SkipMask ); +} + +bool XclExpTabInfo::IsExternalTab( SCTAB nScTab ) const +{ + /* Check sheet index before to avoid assertion (called from formula + compiler also for deleted references). */ + return (nScTab < mnScCnt && nScTab >= 0) && GetFlag( nScTab, ExcTabBufFlags::Extern ); +} + +bool XclExpTabInfo::IsVisibleTab( SCTAB nScTab ) const +{ + return GetFlag( nScTab, ExcTabBufFlags::Visible ); +} + +bool XclExpTabInfo::IsSelectedTab( SCTAB nScTab ) const +{ + return GetFlag( nScTab, ExcTabBufFlags::Selected ); +} + +bool XclExpTabInfo::IsDisplayedTab( SCTAB nScTab ) const +{ + OSL_ENSURE( nScTab < mnScCnt && nScTab >= 0, "XclExpTabInfo::IsActiveTab - sheet out of range" ); + return GetXclTab( nScTab ) == mnDisplXclTab; +} + +bool XclExpTabInfo::IsMirroredTab( SCTAB nScTab ) const +{ + return GetFlag( nScTab, ExcTabBufFlags::Mirrored ); +} + +OUString XclExpTabInfo::GetScTabName( SCTAB nScTab ) const +{ + OSL_ENSURE( nScTab < mnScCnt && nScTab >= 0, "XclExpTabInfo::IsActiveTab - sheet out of range" ); + return (nScTab < mnScCnt && nScTab >= 0) ? maTabInfoVec[ nScTab ].maScName : OUString(); +} + +sal_uInt16 XclExpTabInfo::GetXclTab( SCTAB nScTab ) const +{ + return (nScTab < mnScCnt && nScTab >= 0) ? maTabInfoVec[ nScTab ].mnXclTab : EXC_TAB_DELETED; +} + +SCTAB XclExpTabInfo::GetRealScTab( SCTAB nSortedScTab ) const +{ + OSL_ENSURE( nSortedScTab < mnScCnt && nSortedScTab >= 0, "XclExpTabInfo::GetRealScTab - sheet out of range" ); + return (nSortedScTab < mnScCnt && nSortedScTab >= 0) ? maFromSortedVec[ nSortedScTab ] : SCTAB_INVALID; +} + +bool XclExpTabInfo::GetFlag( SCTAB nScTab, ExcTabBufFlags nFlags ) const +{ + OSL_ENSURE( nScTab < mnScCnt && nScTab >= 0, "XclExpTabInfo::GetFlag - sheet out of range" ); + return (nScTab < mnScCnt && nScTab >= 0) && (maTabInfoVec[ nScTab ].mnFlags & nFlags); +} + +void XclExpTabInfo::SetFlag( SCTAB nScTab, ExcTabBufFlags nFlags, bool bSet ) +{ + OSL_ENSURE( nScTab < mnScCnt && nScTab >= 0, "XclExpTabInfo::SetFlag - sheet out of range" ); + if( nScTab < mnScCnt && nScTab >= 0 ) + { + if (bSet) + maTabInfoVec[ nScTab ].mnFlags |= nFlags; + else + maTabInfoVec[ nScTab ].mnFlags &= ~nFlags; + } +} + +void XclExpTabInfo::CalcXclIndexes() +{ + sal_uInt16 nXclTab = 0; + SCTAB nScTab = 0; + + // --- pass 1: process regular sheets --- + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + if( IsExportTab( nScTab ) ) + { + maTabInfoVec[ nScTab ].mnXclTab = nXclTab; + ++nXclTab; + } + else + maTabInfoVec[ nScTab ].mnXclTab = EXC_TAB_DELETED; + } + mnXclCnt = nXclTab; + + // --- pass 2: process external sheets (nXclTab continues) --- + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + if( IsExternalTab( nScTab ) ) + { + maTabInfoVec[ nScTab ].mnXclTab = nXclTab; + ++nXclTab; + ++mnXclExtCnt; + } + } + + // result: first occur all exported sheets, followed by all external sheets +} + +typedef ::std::pair< OUString, SCTAB > XclExpTabName; + +namespace { + +struct XclExpTabNameSort { + bool operator ()( const XclExpTabName& rArg1, const XclExpTabName& rArg2 ) + { + // compare the sheet names only + return ScGlobal::GetCollator().compareString( rArg1.first, rArg2.first ) < 0; + } +}; + +} + +void XclExpTabInfo::CalcSortedIndexes() +{ + ScDocument& rDoc = GetDoc(); + ::std::vector< XclExpTabName > aVec( mnScCnt ); + SCTAB nScTab; + + // fill with sheet names + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + rDoc.GetName( nScTab, aVec[ nScTab ].first ); + aVec[ nScTab ].second = nScTab; + } + ::std::sort( aVec.begin(), aVec.end(), XclExpTabNameSort() ); + + // fill index vectors from sorted sheet name vector + maFromSortedVec.resize( mnScCnt ); + maToSortedVec.resize( mnScCnt ); + for( nScTab = 0; nScTab < mnScCnt; ++nScTab ) + { + maFromSortedVec[ nScTab ] = aVec[ nScTab ].second; + maToSortedVec[ aVec[ nScTab ].second ] = nScTab; + } +} + +// External names ============================================================= + +XclExpExtNameBase::XclExpExtNameBase( + const XclExpRoot& rRoot, const OUString& rName, sal_uInt16 nFlags ) : + XclExpRecord( EXC_ID_EXTERNNAME ), + XclExpRoot( rRoot ), + maName( rName ), + mxName( XclExpStringHelper::CreateString( rRoot, rName, XclStrFlags::EightBitLength ) ), + mnFlags( nFlags ) +{ + OSL_ENSURE( maName.getLength() <= 255, "XclExpExtNameBase::XclExpExtNameBase - string too long" ); + SetRecSize( 6 + mxName->GetSize() ); +} + +void XclExpExtNameBase::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnFlags + << sal_uInt32( 0 ) + << *mxName; + WriteAddData( rStrm ); +} + +void XclExpExtNameBase::WriteAddData( XclExpStream& /*rStrm*/ ) +{ +} + +XclExpExtNameAddIn::XclExpExtNameAddIn( const XclExpRoot& rRoot, const OUString& rName ) : + XclExpExtNameBase( rRoot, rName ) +{ + AddRecSize( 4 ); +} + +void XclExpExtNameAddIn::WriteAddData( XclExpStream& rStrm ) +{ + // write a #REF! error formula + rStrm << sal_uInt16( 2 ) << EXC_TOKID_ERR << EXC_ERR_REF; +} + +XclExpExtNameDde::XclExpExtNameDde( const XclExpRoot& rRoot, + const OUString& rName, sal_uInt16 nFlags, const ScMatrix* pResults ) : + XclExpExtNameBase( rRoot, rName, nFlags ) +{ + if( pResults ) + { + mxMatrix = std::make_shared( *pResults ); + AddRecSize( mxMatrix->GetSize() ); + } +} + +void XclExpExtNameDde::WriteAddData( XclExpStream& rStrm ) +{ + if( mxMatrix ) + mxMatrix->Save( rStrm ); +} + +XclExpExtName::XclExpExtName( const XclExpRoot& rRoot, const XclExpSupbook& rSupbook, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) : + XclExpExtNameBase( rRoot, rName ), + mrSupbook(rSupbook), + mpArray(rArray->Clone()) +{ +} + +void XclExpExtName::WriteAddData( XclExpStream& rStrm ) +{ + // Write only if it only has a single token that is either a cell or cell + // range address. Excel just writes '02 00 1C 17' for all the other types + // of external names. + + using namespace ::formula; + do + { + if (mpArray->GetLen() != 1) + break; + + const formula::FormulaToken* p = mpArray->FirstToken(); + if (!p->IsExternalRef()) + break; + + switch (p->GetType()) + { + case svExternalSingleRef: + { + const ScSingleRefData& rRef = *p->GetSingleRef(); + if (rRef.IsTabRel()) + break; + + bool bColRel = rRef.IsColRel(); + bool bRowRel = rRef.IsRowRel(); + sal_uInt16 nCol = static_cast(rRef.Col()); + sal_uInt16 nRow = static_cast(rRef.Row()); + if (bColRel) nCol |= 0x4000; + if (bRowRel) nCol |= 0x8000; + + OUString aTabName = p->GetString().getString(); + sal_uInt16 nSBTab = mrSupbook.GetTabIndex(aTabName); + + // size is always 9 + rStrm << static_cast(9); + // operator token (3A for cell reference) + rStrm << static_cast(0x3A); + // cell address (Excel's address has 2 sheet IDs.) + rStrm << nSBTab << nSBTab << nRow << nCol; + return; + } + case svExternalDoubleRef: + { + const ScComplexRefData& rRef = *p->GetDoubleRef(); + const ScSingleRefData& r1 = rRef.Ref1; + const ScSingleRefData& r2 = rRef.Ref2; + if (r1.IsTabRel() || r2.IsTabRel()) + break; + + sal_uInt16 nTab1 = r1.Tab(); + sal_uInt16 nTab2 = r2.Tab(); + bool bCol1Rel = r1.IsColRel(); + bool bRow1Rel = r1.IsRowRel(); + bool bCol2Rel = r2.IsColRel(); + bool bRow2Rel = r2.IsRowRel(); + + sal_uInt16 nCol1 = static_cast(r1.Col()); + sal_uInt16 nCol2 = static_cast(r2.Col()); + sal_uInt16 nRow1 = static_cast(r1.Row()); + sal_uInt16 nRow2 = static_cast(r2.Row()); + if (bCol1Rel) nCol1 |= 0x4000; + if (bRow1Rel) nCol1 |= 0x8000; + if (bCol2Rel) nCol2 |= 0x4000; + if (bRow2Rel) nCol2 |= 0x8000; + + OUString aTabName = p->GetString().getString(); + sal_uInt16 nSBTab = mrSupbook.GetTabIndex(aTabName); + + // size is always 13 (0x0D) + rStrm << static_cast(13); + // operator token (3B for area reference) + rStrm << static_cast(0x3B); + // range (area) address + sal_uInt16 nSBTab2 = nSBTab + nTab2 - nTab1; + rStrm << nSBTab << nSBTab2 << nRow1 << nRow2 << nCol1 << nCol2; + return; + } + default: + ; // nothing + } + } + while (false); + + // special value for #REF! (02 00 1C 17) + rStrm << static_cast(2) << EXC_TOKID_ERR << EXC_ERR_REF; +} + +void XclExpExtName::SaveXml(XclExpXmlStream& rStrm) +{ + sax_fastparser::FSHelperPtr pExternalLink = rStrm.GetCurrentStream(); + + /* TODO: mpArray contains external references. It doesn't cause any problems, but it's enough + to export it without the external document identifier. */ + if (mpArray->GetLen()) + { + const OUString aFormula = XclXmlUtils::ToOUString(GetCompileFormulaContext(), ScAddress(0, 0, 0), mpArray.get()); + pExternalLink->startElement(XML_definedName, + XML_name, maName.toUtf8(), + XML_refersTo, aFormula.toUtf8(), + XML_sheetId, nullptr); + } + else + { + pExternalLink->startElement(XML_definedName, + XML_name, maName.toUtf8(), + XML_refersTo, nullptr, + XML_sheetId, nullptr); + } + + pExternalLink->endElement(XML_definedName); +} + +// List of external names ===================================================== + +XclExpExtNameBuffer::XclExpExtNameBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +sal_uInt16 XclExpExtNameBuffer::InsertAddIn( const OUString& rName ) +{ + sal_uInt16 nIndex = GetIndex( rName ); + return nIndex ? nIndex : AppendNew( new XclExpExtNameAddIn( GetRoot(), rName ) ); +} + +sal_uInt16 XclExpExtNameBuffer::InsertEuroTool( const OUString& rName ) +{ + sal_uInt16 nIndex = GetIndex( rName ); + return nIndex ? nIndex : AppendNew( new XclExpExtNameBase( GetRoot(), rName ) ); +} + +sal_uInt16 XclExpExtNameBuffer::InsertDde( + std::u16string_view rApplic, std::u16string_view rTopic, const OUString& rItem ) +{ + sal_uInt16 nIndex = GetIndex( rItem ); + if( nIndex == 0 ) + { + size_t nPos; + if( GetDoc().FindDdeLink( rApplic, rTopic, rItem, SC_DDE_IGNOREMODE, nPos ) ) + { + // create the leading 'StdDocumentName' EXTERNNAME record + if( maNameList.IsEmpty() ) + AppendNew( new XclExpExtNameDde( + GetRoot(), "StdDocumentName", EXC_EXTN_EXPDDE_STDDOC ) ); + + // try to find DDE result array, but create EXTERNNAME record without them too + const ScMatrix* pScMatrix = GetDoc().GetDdeLinkResultMatrix( nPos ); + nIndex = AppendNew( new XclExpExtNameDde( GetRoot(), rItem, EXC_EXTN_EXPDDE, pScMatrix ) ); + } + } + return nIndex; +} + +sal_uInt16 XclExpExtNameBuffer::InsertExtName( const XclExpSupbook& rSupbook, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) +{ + sal_uInt16 nIndex = GetIndex( rName ); + return nIndex ? nIndex : AppendNew( new XclExpExtName( GetRoot(), rSupbook, rName, rArray ) ); +} + +void XclExpExtNameBuffer::Save( XclExpStream& rStrm ) +{ + maNameList.Save( rStrm ); +} + +void XclExpExtNameBuffer::SaveXml(XclExpXmlStream& rStrm) +{ + maNameList.SaveXml(rStrm); +} + +sal_uInt16 XclExpExtNameBuffer::GetIndex( std::u16string_view rName ) const +{ + for( size_t nPos = 0, nSize = maNameList.GetSize(); nPos < nSize; ++nPos ) + if( maNameList.GetRecord( nPos )->GetName() == rName ) + return static_cast< sal_uInt16 >( nPos + 1 ); + return 0; +} + +sal_uInt16 XclExpExtNameBuffer::AppendNew( XclExpExtNameBase* pExtName ) +{ + size_t nSize = maNameList.GetSize(); + if( nSize < 0x7FFF ) + { + maNameList.AppendRecord( pExtName ); + return static_cast< sal_uInt16 >( nSize + 1 ); + } + return 0; +} + +// Cached external cells ====================================================== + +XclExpCrn::XclExpCrn( SCCOL nScCol, SCROW nScRow, const Any& rValue ) : + XclExpRecord( EXC_ID_CRN, 4 ), + mnScCol( nScCol ), + mnScRow( nScRow ) +{ + maValues.push_back( rValue ); +} + +bool XclExpCrn::InsertValue( SCCOL nScCol, SCROW nScRow, const Any& rValue ) +{ + if( (nScRow != mnScRow) || (nScCol != static_cast< SCCOL >( mnScCol + maValues.size() )) ) + return false; + maValues.push_back( rValue ); + return true; +} + +void XclExpCrn::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast< sal_uInt8 >( mnScCol + maValues.size() - 1 ) + << static_cast< sal_uInt8 >( mnScCol ) + << static_cast< sal_uInt16 >( mnScRow ); + for( const auto& rValue : maValues ) + { + if( rValue.has< bool >() ) + WriteBool( rStrm, rValue.get< bool >() ); + else if( rValue.has< double >() ) + WriteDouble( rStrm, rValue.get< double >() ); + else if( rValue.has< OUString >() ) + WriteString( rStrm, rValue.get< OUString >() ); + else + WriteEmpty( rStrm ); + } +} + +void XclExpCrn::WriteBool( XclExpStream& rStrm, bool bValue ) +{ + rStrm << EXC_CACHEDVAL_BOOL << sal_uInt8( bValue ? 1 : 0); + rStrm.WriteZeroBytes( 7 ); +} + +void XclExpCrn::WriteDouble( XclExpStream& rStrm, double fValue ) +{ + if( !std::isfinite( fValue ) ) + { + FormulaError nScError = GetDoubleErrorValue(fValue); + WriteError( rStrm, XclTools::GetXclErrorCode( nScError ) ); + } + else + { + rStrm << EXC_CACHEDVAL_DOUBLE << fValue; + } +} + +void XclExpCrn::WriteString( XclExpStream& rStrm, const OUString& rValue ) +{ + rStrm << EXC_CACHEDVAL_STRING << XclExpString( rValue ); +} + +void XclExpCrn::WriteError( XclExpStream& rStrm, sal_uInt8 nErrCode ) +{ + rStrm << EXC_CACHEDVAL_ERROR << nErrCode; + rStrm.WriteZeroBytes( 7 ); +} + +void XclExpCrn::WriteEmpty( XclExpStream& rStrm ) +{ + rStrm << EXC_CACHEDVAL_EMPTY; + rStrm.WriteZeroBytes( 8 ); +} + +void XclExpCrn::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr pFS = rStrm.GetCurrentStream(); + + pFS->startElement(XML_row, XML_r, OString::number(mnScRow + 1)); + + ScAddress aAdr( mnScCol, mnScRow, 0); // Tab number doesn't matter + for( const auto& rValue : maValues ) + { + bool bCloseCell = true; + if( rValue.has< double >() ) + { + double fVal = rValue.get< double >(); + if (std::isfinite( fVal)) + { + // t='n' is omitted + pFS->startElement(XML_cell, XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aAdr)); + pFS->startElement(XML_v); + pFS->write( fVal ); + } + else + { + pFS->startElement(XML_cell, XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aAdr), XML_t, "e"); + pFS->startElement(XML_v); + pFS->write( "#VALUE!" ); // OOXTODO: support other error values + } + } + else if( rValue.has< OUString >() ) + { + pFS->startElement(XML_cell, XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aAdr), XML_t, "str"); + pFS->startElement(XML_v); + pFS->write( rValue.get< OUString >() ); + } + else if( rValue.has< bool >() ) + { + pFS->startElement(XML_cell, XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aAdr), XML_t, "b"); + pFS->startElement(XML_v); + pFS->write( rValue.get< bool >() ? "1" : "0" ); + } + // OOXTODO: error type cell t='e' + else + { + // Empty/blank cell not stored, only aAdr is incremented. + bCloseCell = false; + } + if (bCloseCell) + { + pFS->endElement(XML_v); + pFS->endElement(XML_cell); + } + aAdr.IncCol(); + } + + pFS->endElement( XML_row); +} + +// Cached cells of a sheet ==================================================== + +XclExpXct::XclExpXct( const XclExpRoot& rRoot, const OUString& rTabName, + sal_uInt16 nSBTab, ScExternalRefCache::TableTypeRef const & xCacheTable ) : + XclExpRoot( rRoot ), + mxCacheTable( xCacheTable ), + maUsedCells( rRoot.GetDoc().GetSheetLimits() ), + maBoundRange( ScAddress::INITIALIZE_INVALID ), + maTabName( rTabName ), + mnSBTab( nSBTab ) +{ +} + +void XclExpXct::StoreCellRange( const ScRange& rRange ) +{ + // #i70418# restrict size of external range to prevent memory overflow + if( (rRange.aEnd.Col() - rRange.aStart.Col()) * (rRange.aEnd.Row() - rRange.aStart.Row()) > 1024 ) + return; + + maUsedCells.SetMultiMarkArea( rRange ); + maBoundRange.ExtendTo( rRange ); +} + +void XclExpXct::StoreCell_( const ScAddress& rCell ) +{ + maUsedCells.SetMultiMarkArea( ScRange( rCell ) ); + maBoundRange.ExtendTo( ScRange( rCell ) ); +} + +void XclExpXct::StoreCellRange_( const ScRange& rRange ) +{ + maUsedCells.SetMultiMarkArea( rRange ); + maBoundRange.ExtendTo( rRange ); +} + +namespace { + +class XclExpCrnList : public XclExpRecordList< XclExpCrn > +{ +public: + /** Inserts the passed value into an existing or new CRN record. + @return True = value inserted successfully, false = CRN list is full. */ + bool InsertValue( SCCOL nScCol, SCROW nScRow, const Any& rValue ); +}; + +bool XclExpCrnList::InsertValue( SCCOL nScCol, SCROW nScRow, const Any& rValue ) +{ + RecordRefType xLastRec = GetLastRecord(); + if( xLastRec && xLastRec->InsertValue( nScCol, nScRow, rValue ) ) + return true; + if( GetSize() == SAL_MAX_UINT16 ) + return false; + AppendNewRecord( new XclExpCrn( nScCol, nScRow, rValue ) ); + return true; +} + +} // namespace + +bool XclExpXct::BuildCrnList( XclExpCrnList& rCrnRecs ) +{ + if( !mxCacheTable ) + return false; + + /* Get the range of used rows in the cache table. This may help to + optimize building the CRN record list if the cache table does not + contain all referred cells, e.g. if big empty ranges are used in the + formulas. */ + ::std::pair< SCROW, SCROW > aRowRange = mxCacheTable->getRowRange(); + if( aRowRange.first >= aRowRange.second ) + return false; + + /* Crop the bounding range of used cells in this table to Excel limits. + Return if there is no external cell inside these limits. */ + if( !GetAddressConverter().ValidateRange( maBoundRange, false ) ) + return false; + + /* Find the resulting row range that needs to be processed. */ + SCROW nScRow1 = ::std::max( aRowRange.first, maBoundRange.aStart.Row() ); + SCROW nScRow2 = ::std::min( aRowRange.second - 1, maBoundRange.aEnd.Row() ); + if( nScRow1 > nScRow2 ) + return false; + + /* Build and collect all CRN records before writing the XCT record. This + is needed to determine the total number of CRN records which must be + known when writing the XCT record (possibly encrypted, so seeking the + output stream back after writing the CRN records is not an option). */ + SvNumberFormatter& rFormatter = GetFormatter(); + bool bValid = true; + for( SCROW nScRow = nScRow1; bValid && (nScRow <= nScRow2); ++nScRow ) + { + ::std::pair< SCCOL, SCCOL > aColRange = mxCacheTable->getColRange( nScRow ); + const SCCOL nScEnd = ::std::min( aColRange.second, GetDoc().GetSheetLimits().GetMaxColCount() ); + for( SCCOL nScCol = aColRange.first; bValid && (nScCol < nScEnd); ++nScCol ) + { + if( maUsedCells.IsCellMarked( nScCol, nScRow, true ) ) + { + sal_uInt32 nScNumFmt = 0; + ScExternalRefCache::TokenRef xToken = mxCacheTable->getCell( nScCol, nScRow, &nScNumFmt ); + using namespace ::formula; + if( xToken ) + switch( xToken->GetType() ) + { + case svDouble: + bValid = (rFormatter.GetType( nScNumFmt ) == SvNumFormatType::LOGICAL) ? + rCrnRecs.InsertValue( nScCol, nScRow, Any( xToken->GetDouble() != 0 ) ) : + rCrnRecs.InsertValue( nScCol, nScRow, Any( xToken->GetDouble() ) ); + break; + case svString: + // do not save empty strings (empty cells) to cache + if( !xToken->GetString().isEmpty() ) + bValid = rCrnRecs.InsertValue( nScCol, nScRow, Any( xToken->GetString().getString() ) ); + break; + default: + break; + } + } + } + } + return true; +} + +void XclExpXct::Save( XclExpStream& rStrm ) +{ + XclExpCrnList aCrnRecs; + if (!BuildCrnList( aCrnRecs)) + return; + + // write the XCT record and the list of CRN records + rStrm.StartRecord( EXC_ID_XCT, 4 ); + rStrm << static_cast< sal_uInt16 >( aCrnRecs.GetSize() ) << mnSBTab; + rStrm.EndRecord(); + aCrnRecs.Save( rStrm ); +} + +void XclExpXct::SaveXml( XclExpXmlStream& rStrm ) +{ + XclExpCrnList aCrnRecs; + + sax_fastparser::FSHelperPtr pFS = rStrm.GetCurrentStream(); + + bool bValid = BuildCrnList( aCrnRecs); + pFS->startElement(XML_sheetData, XML_sheetId, OString::number(mnSBTab)); + if (bValid) + { + // row elements + aCrnRecs.SaveXml( rStrm ); + } + pFS->endElement( XML_sheetData); +} + +// External documents (EXTERNSHEET/SUPBOOK), base class ======================= + +XclExpExternSheetBase::XclExpExternSheetBase( const XclExpRoot& rRoot, sal_uInt16 nRecId, sal_uInt32 nRecSize ) : + XclExpRecord( nRecId, nRecSize ), + XclExpRoot( rRoot ) +{ +} + +XclExpExtNameBuffer& XclExpExternSheetBase::GetExtNameBuffer() +{ + if( !mxExtNameBfr ) + mxExtNameBfr = std::make_shared( GetRoot() ); + return *mxExtNameBfr; +} + +void XclExpExternSheetBase::WriteExtNameBuffer( XclExpStream& rStrm ) +{ + if( mxExtNameBfr ) + mxExtNameBfr->Save( rStrm ); +} + +void XclExpExternSheetBase::WriteExtNameBufferXml( XclExpXmlStream& rStrm ) +{ + if( mxExtNameBfr ) + mxExtNameBfr->SaveXml( rStrm ); +} + +// External documents (EXTERNSHEET, BIFF5/BIFF7) ============================== + +XclExpExternSheet::XclExpExternSheet( const XclExpRoot& rRoot, sal_Unicode cCode ) : + XclExpExternSheetBase( rRoot, EXC_ID_EXTERNSHEET ) +{ + Init( OUStringChar(cCode) ); +} + +XclExpExternSheet::XclExpExternSheet( const XclExpRoot& rRoot, std::u16string_view rTabName ) : + XclExpExternSheetBase( rRoot, EXC_ID_EXTERNSHEET ) +{ + // reference to own sheet: \03 + Init(OUStringConcatenation(OUStringChar(EXC_EXTSH_TABNAME) + rTabName)); +} + +void XclExpExternSheet::Save( XclExpStream& rStrm ) +{ + // EXTERNSHEET record + XclExpRecord::Save( rStrm ); + // EXTERNNAME records + WriteExtNameBuffer( rStrm ); +} + +void XclExpExternSheet::Init( std::u16string_view rEncUrl ) +{ + OSL_ENSURE_BIFF( GetBiff() <= EXC_BIFF5 ); + maTabName.AssignByte( rEncUrl, GetTextEncoding(), XclStrFlags::EightBitLength ); + SetRecSize( maTabName.GetSize() ); +} + +sal_uInt16 XclExpExternSheet::InsertAddIn( const OUString& rName ) +{ + return GetExtNameBuffer().InsertAddIn( rName ); +} + +void XclExpExternSheet::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt8 nNameSize = static_cast< sal_uInt8 >( maTabName.Len() ); + // special case: reference to own sheet (starting with '\03') needs wrong string length + if( maTabName.GetChar( 0 ) == EXC_EXTSH_TABNAME ) + --nNameSize; + rStrm << nNameSize; + maTabName.WriteBuffer( rStrm ); +} + +// External document (SUPBOOK, BIFF8) ========================================= + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot, sal_uInt16 nXclTabCount ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK, 4 ), + meType( XclSupbookType::Self ), + mnXclTabCount( nXclTabCount ), + mnFileId( 0 ) +{ +} + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK, 4 ), + meType( XclSupbookType::Addin ), + mnXclTabCount( 1 ), + mnFileId( 0 ) +{ +} + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot, const OUString& rUrl, XclSupbookType ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK ), + maUrl( rUrl ), + maUrlEncoded( rUrl ), + meType( XclSupbookType::Eurotool ), + mnXclTabCount( 0 ), + mnFileId( 0 ) +{ + SetRecSize( 2 + maUrlEncoded.GetSize() ); +} + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot, const OUString& rUrl ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK ), + maUrl( rUrl ), + maUrlEncoded( XclExpUrlHelper::EncodeUrl( rRoot, rUrl ) ), + meType( XclSupbookType::Extern ), + mnXclTabCount( 0 ), + mnFileId( 0 ) +{ + SetRecSize( 2 + maUrlEncoded.GetSize() ); + + // We need to create all tables up front to ensure the correct table order. + ScExternalRefManager* pRefMgr = rRoot.GetDoc().GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId( rUrl ); + mnFileId = nFileId + 1; + ScfStringVec aTabNames; + pRefMgr->getAllCachedTableNames( nFileId, aTabNames ); + size_t nTabIndex = 0; + for( const auto& rTabName : aTabNames ) + { + InsertTabName( rTabName, pRefMgr->getCacheTable( nFileId, nTabIndex ) ); + ++nTabIndex; + } +} + +XclExpSupbook::XclExpSupbook( const XclExpRoot& rRoot, const OUString& rApplic, const OUString& rTopic ) : + XclExpExternSheetBase( rRoot, EXC_ID_SUPBOOK, 4 ), + maUrl( rApplic ), + maDdeTopic( rTopic ), + maUrlEncoded( XclExpUrlHelper::EncodeDde( rApplic, rTopic ) ), + meType( XclSupbookType::Special ), + mnXclTabCount( 0 ), + mnFileId( 0 ) +{ + SetRecSize( 2 + maUrlEncoded.GetSize() ); +} + +bool XclExpSupbook::IsUrlLink( std::u16string_view rUrl ) const +{ + return (meType == XclSupbookType::Extern || meType == XclSupbookType::Eurotool) && (maUrl == rUrl); +} + +bool XclExpSupbook::IsDdeLink( std::u16string_view rApplic, std::u16string_view rTopic ) const +{ + return (meType == XclSupbookType::Special) && (maUrl == rApplic) && (maDdeTopic == rTopic); +} + +void XclExpSupbook::FillRefLogEntry( XclExpRefLogEntry& rRefLogEntry, + sal_uInt16 nFirstSBTab, sal_uInt16 nLastSBTab ) const +{ + rRefLogEntry.mpUrl = maUrlEncoded.IsEmpty() ? nullptr : &maUrlEncoded; + rRefLogEntry.mpFirstTab = GetTabName( nFirstSBTab ); + rRefLogEntry.mpLastTab = GetTabName( nLastSBTab ); +} + +void XclExpSupbook::StoreCellRange( const ScRange& rRange, sal_uInt16 nSBTab ) +{ + if( XclExpXct* pXct = maXctList.GetRecord( nSBTab ) ) + pXct->StoreCellRange( rRange ); +} + +void XclExpSupbook::StoreCell_( sal_uInt16 nSBTab, const ScAddress& rCell ) +{ + if( XclExpXct* pXct = maXctList.GetRecord( nSBTab ) ) + pXct->StoreCell_( rCell ); +} + +void XclExpSupbook::StoreCellRange_( sal_uInt16 nSBTab, const ScRange& rRange ) +{ + // multi-table range is not allowed! + if( rRange.aStart.Tab() == rRange.aEnd.Tab() ) + if( XclExpXct* pXct = maXctList.GetRecord( nSBTab ) ) + pXct->StoreCellRange_( rRange ); +} + +sal_uInt16 XclExpSupbook::GetTabIndex( const OUString& rTabName ) const +{ + XclExpString aXclName(rTabName); + size_t nSize = maXctList.GetSize(); + for (size_t i = 0; i < nSize; ++i) + { + XclExpXctRef aRec = maXctList.GetRecord(i); + if (aXclName == aRec->GetTabName()) + return ulimit_cast(i); + } + return EXC_NOTAB; +} + +sal_uInt16 XclExpSupbook::GetTabCount() const +{ + return ulimit_cast(maXctList.GetSize()); +} + +sal_uInt16 XclExpSupbook::InsertTabName( const OUString& rTabName, ScExternalRefCache::TableTypeRef const & xCacheTable ) +{ + SAL_WARN_IF( meType != XclSupbookType::Extern, "sc.filter", "Don't insert sheet names here" ); + sal_uInt16 nSBTab = ulimit_cast< sal_uInt16 >( maXctList.GetSize() ); + XclExpXctRef xXct = new XclExpXct( GetRoot(), rTabName, nSBTab, xCacheTable ); + AddRecSize( xXct->GetTabName().GetSize() ); + maXctList.AppendRecord( xXct ); + return nSBTab; +} + +sal_uInt16 XclExpSupbook::InsertAddIn( const OUString& rName ) +{ + return GetExtNameBuffer().InsertAddIn( rName ); +} + +sal_uInt16 XclExpSupbook::InsertEuroTool( const OUString& rName ) +{ + return GetExtNameBuffer().InsertEuroTool( rName ); +} + +sal_uInt16 XclExpSupbook::InsertDde( const OUString& rItem ) +{ + return GetExtNameBuffer().InsertDde( maUrl, maDdeTopic, rItem ); +} + +sal_uInt16 XclExpSupbook::InsertExtName( const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) +{ + return GetExtNameBuffer().InsertExtName(*this, rName, rArray); +} + +XclSupbookType XclExpSupbook::GetType() const +{ + return meType; +} + +sal_uInt16 XclExpSupbook::GetFileId() const +{ + return mnFileId; +} + +const OUString& XclExpSupbook::GetUrl() const +{ + return maUrl; +} + +void XclExpSupbook::Save( XclExpStream& rStrm ) +{ + // SUPBOOK record + XclExpRecord::Save( rStrm ); + // XCT record, CRN records + maXctList.Save( rStrm ); + // EXTERNNAME records + WriteExtNameBuffer( rStrm ); +} + +void XclExpSupbook::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr pExternalLink = rStrm.GetCurrentStream(); + + // Add relation for this stream, e.g. xl/externalLinks/_rels/externalLink1.xml.rels + sal_uInt16 nLevel = 0; + bool bRel = true; + + // BuildFileName delete ../ and convert them to nLevel + // but addrelation needs ../ instead of nLevel, so we have to convert it back + OUString sFile = XclExpHyperlink::BuildFileName(nLevel, bRel, maUrl, GetRoot(), true); + while (nLevel-- > 0) + sFile = "../" + sFile; + + OUString sId = rStrm.addRelation( pExternalLink->getOutputStream(), + oox::getRelationship(Relationship::EXTERNALLINKPATH), sFile, true ); + + pExternalLink->startElement( XML_externalLink, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8()); + + pExternalLink->startElement( XML_externalBook, + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + FSNS(XML_r, XML_id), sId.toUtf8()); + + if (!maXctList.IsEmpty()) + { + pExternalLink->startElement(XML_sheetNames); + for (size_t nPos = 0, nSize = maXctList.GetSize(); nPos < nSize; ++nPos) + { + pExternalLink->singleElement(XML_sheetName, + XML_val, XclXmlUtils::ToOString(maXctList.GetRecord(nPos)->GetTabName())); + } + pExternalLink->endElement( XML_sheetNames); + + } + + if (mxExtNameBfr) + { + pExternalLink->startElement(XML_definedNames); + // externalName elements + WriteExtNameBufferXml( rStrm ); + pExternalLink->endElement(XML_definedNames); + } + + if (!maXctList.IsEmpty()) + { + pExternalLink->startElement(XML_sheetDataSet); + + // sheetData elements + maXctList.SaveXml( rStrm ); + + pExternalLink->endElement( XML_sheetDataSet); + + } + pExternalLink->endElement( XML_externalBook); + pExternalLink->endElement( XML_externalLink); +} + +const XclExpString* XclExpSupbook::GetTabName( sal_uInt16 nSBTab ) const +{ + XclExpXctRef xXct = maXctList.GetRecord( nSBTab ); + return xXct ? &xXct->GetTabName() : nullptr; +} + +void XclExpSupbook::WriteBody( XclExpStream& rStrm ) +{ + switch( meType ) + { + case XclSupbookType::Self: + rStrm << mnXclTabCount << EXC_SUPB_SELF; + break; + case XclSupbookType::Extern: + case XclSupbookType::Special: + case XclSupbookType::Eurotool: + { + sal_uInt16 nCount = ulimit_cast< sal_uInt16 >( maXctList.GetSize() ); + rStrm << nCount << maUrlEncoded; + + for( size_t nPos = 0, nSize = maXctList.GetSize(); nPos < nSize; ++nPos ) + rStrm << maXctList.GetRecord( nPos )->GetTabName(); + } + break; + case XclSupbookType::Addin: + rStrm << mnXclTabCount << EXC_SUPB_ADDIN; + break; + default: + SAL_WARN( "sc.filter", "Unhandled SUPBOOK type " << meType); + } +} + +// All SUPBOOKS in a document ================================================= + +XclExpSupbookBuffer::XclExpSupbookBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnOwnDocSB( SAL_MAX_UINT16 ), + mnAddInSB( SAL_MAX_UINT16 ) +{ + XclExpTabInfo& rTabInfo = GetTabInfo(); + sal_uInt16 nXclCnt = rTabInfo.GetXclTabCount(); + sal_uInt16 nCodeCnt = static_cast< sal_uInt16 >( GetExtDocOptions().GetCodeNameCount() ); + size_t nCount = nXclCnt + rTabInfo.GetXclExtTabCount(); + + OSL_ENSURE( nCount > 0, "XclExpSupbookBuffer::XclExpSupbookBuffer - no sheets to export" ); + if( nCount ) + { + maSBIndexVec.resize( nCount ); + + // self-ref SUPBOOK first of list + XclExpSupbookRef xSupbook = new XclExpSupbook( GetRoot(), ::std::max( nXclCnt, nCodeCnt ) ); + mnOwnDocSB = Append( xSupbook ); + for( sal_uInt16 nXclTab = 0; nXclTab < nXclCnt; ++nXclTab ) + maSBIndexVec[ nXclTab ].Set( mnOwnDocSB, nXclTab ); + } +} + +XclExpXti XclExpSupbookBuffer::GetXti( sal_uInt16 nFirstXclTab, sal_uInt16 nLastXclTab, + XclExpRefLogEntry* pRefLogEntry ) const +{ + XclExpXti aXti; + size_t nSize = maSBIndexVec.size(); + if( (nFirstXclTab < nSize) && (nLastXclTab < nSize) ) + { + // index of the SUPBOOK record + aXti.mnSupbook = maSBIndexVec[ nFirstXclTab ].mnSupbook; + + // all sheets in the same supbook? + bool bSameSB = true; + for( sal_uInt16 nXclTab = nFirstXclTab + 1; bSameSB && (nXclTab <= nLastXclTab); ++nXclTab ) + { + bSameSB = maSBIndexVec[ nXclTab ].mnSupbook == aXti.mnSupbook; + if( !bSameSB ) + nLastXclTab = nXclTab - 1; + } + aXti.mnFirstSBTab = maSBIndexVec[ nFirstXclTab ].mnSBTab; + aXti.mnLastSBTab = maSBIndexVec[ nLastXclTab ].mnSBTab; + + // fill external reference log entry (for change tracking) + if( pRefLogEntry ) + { + pRefLogEntry->mnFirstXclTab = nFirstXclTab; + pRefLogEntry->mnLastXclTab = nLastXclTab; + XclExpSupbookRef xSupbook = maSupbookList.GetRecord( aXti.mnSupbook ); + if( xSupbook ) + xSupbook->FillRefLogEntry( *pRefLogEntry, aXti.mnFirstSBTab, aXti.mnLastSBTab ); + } + } + else + { + // special range, i.e. for deleted sheets or add-ins + aXti.mnSupbook = mnOwnDocSB; + aXti.mnFirstSBTab = nFirstXclTab; + aXti.mnLastSBTab = nLastXclTab; + } + + return aXti; +} + +void XclExpSupbookBuffer::StoreCellRange( const ScRange& rRange ) +{ + sal_uInt16 nXclTab = GetTabInfo().GetXclTab( rRange.aStart.Tab() ); + if( nXclTab < maSBIndexVec.size() ) + { + const XclExpSBIndex& rSBIndex = maSBIndexVec[ nXclTab ]; + XclExpSupbookRef xSupbook = maSupbookList.GetRecord( rSBIndex.mnSupbook ); + OSL_ENSURE( xSupbook , "XclExpSupbookBuffer::StoreCellRange - missing SUPBOOK record" ); + if( xSupbook ) + xSupbook->StoreCellRange( rRange, rSBIndex.mnSBTab ); + } +} + +namespace { + +class FindSBIndexEntry +{ +public: + explicit FindSBIndexEntry(sal_uInt16 nSupbookId, sal_uInt16 nTabId) : + mnSupbookId(nSupbookId), mnTabId(nTabId) {} + + bool operator()(const XclExpSupbookBuffer::XclExpSBIndex& r) const + { + return mnSupbookId == r.mnSupbook && mnTabId == r.mnSBTab; + } + +private: + sal_uInt16 mnSupbookId; + sal_uInt16 mnTabId; +}; + +} + +void XclExpSupbookBuffer::StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell ) +{ + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + const OUString* pUrl = pRefMgr->getExternalFileName(nFileId); + if (!pUrl) + return; + + XclExpSupbookRef xSupbook; + sal_uInt16 nSupbookId; + if (!GetSupbookUrl(xSupbook, nSupbookId, *pUrl)) + { + xSupbook = new XclExpSupbook(GetRoot(), *pUrl); + nSupbookId = Append(xSupbook); + } + + sal_uInt16 nSheetId = xSupbook->GetTabIndex(rTabName); + if (nSheetId == EXC_NOTAB) + // specified table name not found in this SUPBOOK. + return; + + FindSBIndexEntry f(nSupbookId, nSheetId); + if (::std::none_of(maSBIndexVec.begin(), maSBIndexVec.end(), f)) + { + maSBIndexVec.emplace_back(); + XclExpSBIndex& r = maSBIndexVec.back(); + r.mnSupbook = nSupbookId; + r.mnSBTab = nSheetId; + } + + xSupbook->StoreCell_(nSheetId, rCell); +} + +void XclExpSupbookBuffer::StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) +{ + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + const OUString* pUrl = pRefMgr->getExternalFileName(nFileId); + if (!pUrl) + return; + + XclExpSupbookRef xSupbook; + sal_uInt16 nSupbookId; + if (!GetSupbookUrl(xSupbook, nSupbookId, *pUrl)) + { + xSupbook = new XclExpSupbook(GetRoot(), *pUrl); + nSupbookId = Append(xSupbook); + } + + SCTAB nTabCount = rRange.aEnd.Tab() - rRange.aStart.Tab() + 1; + + // If this is a multi-table range, get token for each table. + using namespace ::formula; + SCTAB aMatrixListSize = 0; + + // This is a new'ed instance, so we must manage its life cycle here. + ScExternalRefCache::TokenArrayRef pArray = pRefMgr->getDoubleRefTokens(nFileId, rTabName, rRange, nullptr); + if (!pArray) + return; + + FormulaTokenArrayPlainIterator aIter(*pArray); + for (FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + if (p->GetType() == svMatrix) + ++aMatrixListSize; + else if (p->GetOpCode() != ocSep) + { + // This is supposed to be ocSep!!! + return; + } + } + + if (aMatrixListSize != nTabCount) + { + // matrix size mismatch! + return; + } + + sal_uInt16 nFirstSheetId = xSupbook->GetTabIndex(rTabName); + + ScRange aRange(rRange); + aRange.aStart.SetTab(0); + aRange.aEnd.SetTab(0); + for (SCTAB nTab = 0; nTab < nTabCount; ++nTab) + { + sal_uInt16 nSheetId = nFirstSheetId + static_cast(nTab); + FindSBIndexEntry f(nSupbookId, nSheetId); + if (::std::none_of(maSBIndexVec.begin(), maSBIndexVec.end(), f)) + { + maSBIndexVec.emplace_back(); + XclExpSBIndex& r = maSBIndexVec.back(); + r.mnSupbook = nSupbookId; + r.mnSBTab = nSheetId; + } + + xSupbook->StoreCellRange_(nSheetId, aRange); + } +} + +bool XclExpSupbookBuffer::InsertAddIn( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, const OUString& rName ) +{ + XclExpSupbookRef xSupbook; + if( mnAddInSB == SAL_MAX_UINT16 ) + { + xSupbook = new XclExpSupbook( GetRoot() ); + mnAddInSB = Append( xSupbook ); + } + else + xSupbook = maSupbookList.GetRecord( mnAddInSB ); + OSL_ENSURE( xSupbook, "XclExpSupbookBuffer::InsertAddin - missing add-in supbook" ); + rnSupbook = mnAddInSB; + rnExtName = xSupbook->InsertAddIn( rName ); + return rnExtName > 0; +} + +bool XclExpSupbookBuffer::InsertEuroTool( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, const OUString& rName ) +{ + XclExpSupbookRef xSupbook; + OUString aUrl( "\001\010EUROTOOL.XLA" ); + if( !GetSupbookUrl( xSupbook, rnSupbook, aUrl ) ) + { + xSupbook = new XclExpSupbook( GetRoot(), aUrl, XclSupbookType::Eurotool ); + rnSupbook = Append( xSupbook ); + } + rnExtName = xSupbook->InsertEuroTool( rName ); + return rnExtName > 0; +} + +bool XclExpSupbookBuffer::InsertDde( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) +{ + XclExpSupbookRef xSupbook; + if( !GetSupbookDde( xSupbook, rnSupbook, rApplic, rTopic ) ) + { + xSupbook = new XclExpSupbook( GetRoot(), rApplic, rTopic ); + rnSupbook = Append( xSupbook ); + } + rnExtName = xSupbook->InsertDde( rItem ); + return rnExtName > 0; +} + +bool XclExpSupbookBuffer::InsertExtName( + sal_uInt16& rnSupbook, sal_uInt16& rnExtName, const OUString& rUrl, + const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) +{ + XclExpSupbookRef xSupbook; + if (!GetSupbookUrl(xSupbook, rnSupbook, rUrl)) + { + xSupbook = new XclExpSupbook(GetRoot(), rUrl); + rnSupbook = Append(xSupbook); + } + rnExtName = xSupbook->InsertExtName(rName, rArray); + return rnExtName > 0; +} + +XclExpXti XclExpSupbookBuffer::GetXti( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + XclExpRefLogEntry* pRefLogEntry ) +{ + XclExpXti aXti(0, EXC_NOTAB, EXC_NOTAB); + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + const OUString* pUrl = pRefMgr->getExternalFileName(nFileId); + if (!pUrl) + return aXti; + + XclExpSupbookRef xSupbook; + sal_uInt16 nSupbookId; + if (!GetSupbookUrl(xSupbook, nSupbookId, *pUrl)) + { + xSupbook = new XclExpSupbook(GetRoot(), *pUrl); + nSupbookId = Append(xSupbook); + } + aXti.mnSupbook = nSupbookId; + + sal_uInt16 nFirstSheetId = xSupbook->GetTabIndex(rTabName); + if (nFirstSheetId == EXC_NOTAB) + { + // first sheet not found in SUPBOOK. + return aXti; + } + sal_uInt16 nSheetCount = xSupbook->GetTabCount(); + for (sal_uInt16 i = 0; i < nXclTabSpan; ++i) + { + sal_uInt16 nSheetId = nFirstSheetId + i; + if (nSheetId >= nSheetCount) + return aXti; + + FindSBIndexEntry f(nSupbookId, nSheetId); + if (::std::none_of(maSBIndexVec.begin(), maSBIndexVec.end(), f)) + { + maSBIndexVec.emplace_back(); + XclExpSBIndex& r = maSBIndexVec.back(); + r.mnSupbook = nSupbookId; + r.mnSBTab = nSheetId; + } + if (i == 0) + aXti.mnFirstSBTab = nSheetId; + if (i == nXclTabSpan - 1) + aXti.mnLastSBTab = nSheetId; + } + + if (pRefLogEntry) + { + pRefLogEntry->mnFirstXclTab = 0; + pRefLogEntry->mnLastXclTab = 0; + if (xSupbook) + xSupbook->FillRefLogEntry(*pRefLogEntry, aXti.mnFirstSBTab, aXti.mnLastSBTab); + } + + return aXti; +} + +void XclExpSupbookBuffer::Save( XclExpStream& rStrm ) +{ + maSupbookList.Save( rStrm ); +} + +void XclExpSupbookBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + // Unused external references are not saved, only kept in memory. + // Those that are saved must be indexed from 1, so indexes must be reordered + ScExternalRefManager* pRefMgr = GetDoc().GetExternalRefManager(); + vector aExternFileIds; + for (size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos) + { + XclExpSupbookRef xRef(maSupbookList.GetRecord(nPos)); + // fileIDs are indexed from 1 in xlsx, and from 0 in ScExternalRefManager + // converting between them require a -1 or +1 + if (xRef->GetType() == XclSupbookType::Extern) + aExternFileIds.push_back(xRef->GetFileId() - 1); + } + if (aExternFileIds.size() > 0) + pRefMgr->setSkipUnusedFileIds(aExternFileIds); + + ::std::map< sal_uInt16, OUString > aMap; + for (size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos) + { + XclExpSupbookRef xRef( maSupbookList.GetRecord( nPos)); + if (xRef->GetType() != XclSupbookType::Extern) + continue; // handle only external reference (for now?) + + sal_uInt16 nId = xRef->GetFileId(); + sal_uInt16 nUsedId = pRefMgr->convertFileIdToUsedFileId(nId - 1) + 1; + const OUString& rUrl = xRef->GetUrl(); + ::std::pair< ::std::map< sal_uInt16, OUString >::iterator, bool > aInsert( + aMap.insert( ::std::make_pair( nId, rUrl))); + if (!aInsert.second) + { + SAL_WARN( "sc.filter", "XclExpSupbookBuffer::SaveXml: file ID already used: " << nId << + " wanted for " << rUrl << " and is " << (*aInsert.first).second << + (rUrl == (*aInsert.first).second ? " multiple Supbook not supported" : "")); + continue; + } + OUString sId; + sax_fastparser::FSHelperPtr pExternalLink = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName( "xl/", "externalLinks/externalLink", nUsedId), + XclXmlUtils::GetStreamName( nullptr, "externalLinks/externalLink", nUsedId), + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml", + CREATE_OFFICEDOC_RELATION_TYPE("externalLink"), + &sId ); + + // externalReference entry in workbook externalReferences + rStrm.GetCurrentStream()->singleElement( XML_externalReference, + FSNS(XML_r, XML_id), sId.toUtf8() ); + + // Each externalBook in a separate stream. + rStrm.PushStream( pExternalLink ); + xRef->SaveXml( rStrm ); + rStrm.PopStream(); + } +} + +bool XclExpSupbookBuffer::HasExternalReferences() const +{ + for (size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos) + { + if (maSupbookList.GetRecord( nPos)->GetType() == XclSupbookType::Extern) + return true; + } + return false; +} + +bool XclExpSupbookBuffer::GetSupbookUrl( + XclExpSupbookRef& rxSupbook, sal_uInt16& rnIndex, std::u16string_view rUrl ) const +{ + for( size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos ) + { + rxSupbook = maSupbookList.GetRecord( nPos ); + if( rxSupbook->IsUrlLink( rUrl ) ) + { + rnIndex = ulimit_cast< sal_uInt16 >( nPos ); + return true; + } + } + return false; +} + +bool XclExpSupbookBuffer::GetSupbookDde( XclExpSupbookRef& rxSupbook, + sal_uInt16& rnIndex, std::u16string_view rApplic, std::u16string_view rTopic ) const +{ + for( size_t nPos = 0, nSize = maSupbookList.GetSize(); nPos < nSize; ++nPos ) + { + rxSupbook = maSupbookList.GetRecord( nPos ); + if( rxSupbook->IsDdeLink( rApplic, rTopic ) ) + { + rnIndex = ulimit_cast< sal_uInt16 >( nPos ); + return true; + } + } + return false; +} + +sal_uInt16 XclExpSupbookBuffer::Append( XclExpSupbookRef const & xSupbook ) +{ + maSupbookList.AppendRecord( xSupbook ); + return ulimit_cast< sal_uInt16 >( maSupbookList.GetSize() - 1 ); +} + +// Export link manager ======================================================== + +XclExpLinkManagerImpl::XclExpLinkManagerImpl( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +XclExpLinkManagerImpl5::XclExpLinkManagerImpl5( const XclExpRoot& rRoot ) : + XclExpLinkManagerImpl( rRoot ) +{ +} + +void XclExpLinkManagerImpl5::FindExtSheet( + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, XclExpRefLogEntry* pRefLogEntry ) +{ + FindInternal( rnExtSheet, rnFirstXclTab, nFirstScTab ); + if( (rnFirstXclTab == EXC_TAB_DELETED) || (nFirstScTab == nLastScTab) ) + { + rnLastXclTab = rnFirstXclTab; + } + else + { + sal_uInt16 nDummyExtSheet; + FindInternal( nDummyExtSheet, rnLastXclTab, nLastScTab ); + } + + OSL_ENSURE( !pRefLogEntry, "XclExpLinkManagerImpl5::FindExtSheet - fill reflog entry not implemented" ); +} + +sal_uInt16 XclExpLinkManagerImpl5::FindExtSheet( sal_Unicode cCode ) +{ + sal_uInt16 nExtSheet; + FindInternal( nExtSheet, cCode ); + return nExtSheet; +} + +void XclExpLinkManagerImpl5::FindExtSheet( + sal_uInt16 /*nFileId*/, const OUString& /*rTabName*/, sal_uInt16 /*nXclTabSpan*/, + sal_uInt16& /*rnExtSheet*/, sal_uInt16& /*rnFirstSBTab*/, sal_uInt16& /*rnLastSBTab*/, + XclExpRefLogEntry* /*pRefLogEntry*/ ) +{ + // not implemented +} + +void XclExpLinkManagerImpl5::StoreCellRange( const ScSingleRefData& /*rRef1*/, const ScSingleRefData& /*rRef2*/, const ScAddress& /*rPos*/ ) +{ + // not implemented +} + +void XclExpLinkManagerImpl5::StoreCell( sal_uInt16 /*nFileId*/, const OUString& /*rTabName*/, const ScAddress& /*rPos*/ ) +{ + // not implemented +} + +void XclExpLinkManagerImpl5::StoreCellRange( sal_uInt16 /*nFileId*/, const OUString& /*rTabName*/, const ScRange& /*rRange*/ ) +{ + // not implemented +} + +bool XclExpLinkManagerImpl5::InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + XclExpExtSheetRef xExtSheet = FindInternal( rnExtSheet, EXC_EXTSH_ADDIN ); + if( xExtSheet ) + { + rnExtName = xExtSheet->InsertAddIn( rName ); + return rnExtName > 0; + } + return false; +} + +bool XclExpLinkManagerImpl5::InsertEuroTool( + sal_uInt16& /*rnExtSheet*/, sal_uInt16& /*rnExtName*/, const OUString& /*rName*/ ) +{ + return false; +} + +bool XclExpLinkManagerImpl5::InsertDde( + sal_uInt16& /*rnExtSheet*/, sal_uInt16& /*rnExtName*/, + const OUString& /*rApplic*/, const OUString& /*rTopic*/, const OUString& /*rItem*/ ) +{ + // not implemented + return false; +} + +bool XclExpLinkManagerImpl5::InsertExtName( + sal_uInt16& /*rnExtSheet*/, sal_uInt16& /*rnExtName*/, const OUString& /*rUrl*/, + const OUString& /*rName*/, const ScExternalRefCache::TokenArrayRef& /*rArray*/ ) +{ + // not implemented + return false; +} + +void XclExpLinkManagerImpl5::Save( XclExpStream& rStrm ) +{ + if( sal_uInt16 nExtSheetCount = GetExtSheetCount() ) + { + // EXTERNCOUNT record + XclExpUInt16Record( EXC_ID_EXTERNCOUNT, nExtSheetCount ).Save( rStrm ); + // list of EXTERNSHEET records with EXTERNNAME, XCT, CRN records + maExtSheetList.Save( rStrm ); + } +} + +void XclExpLinkManagerImpl5::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ + // not applicable +} + +sal_uInt16 XclExpLinkManagerImpl5::GetExtSheetCount() const +{ + return static_cast< sal_uInt16 >( maExtSheetList.GetSize() ); +} + +sal_uInt16 XclExpLinkManagerImpl5::AppendInternal( XclExpExtSheetRef const & xExtSheet ) +{ + if( GetExtSheetCount() < 0x7FFF ) + { + maExtSheetList.AppendRecord( xExtSheet ); + // return negated one-based EXTERNSHEET index (i.e. 0xFFFD for 3rd record) + return static_cast< sal_uInt16 >( -GetExtSheetCount() ); + } + return 0; +} + +void XclExpLinkManagerImpl5::CreateInternal() +{ + if( !maIntTabMap.empty() ) + return; + + // create EXTERNSHEET records for all internal exported sheets + XclExpTabInfo& rTabInfo = GetTabInfo(); + for( SCTAB nScTab = 0, nScCnt = rTabInfo.GetScTabCount(); nScTab < nScCnt; ++nScTab ) + { + if( rTabInfo.IsExportTab( nScTab ) ) + { + XclExpExtSheetRef xRec; + if( nScTab == GetCurrScTab() ) + xRec = new XclExpExternSheet( GetRoot(), EXC_EXTSH_OWNTAB ); + else + xRec = new XclExpExternSheet( GetRoot(), rTabInfo.GetScTabName( nScTab ) ); + maIntTabMap[ nScTab ] = AppendInternal( xRec ); + } + } +} + +XclExpLinkManagerImpl5::XclExpExtSheetRef XclExpLinkManagerImpl5::GetInternal( sal_uInt16 nExtSheet ) +{ + return maExtSheetList.GetRecord( static_cast< sal_uInt16 >( -nExtSheet - 1 ) ); +} + +XclExpLinkManagerImpl5::XclExpExtSheetRef XclExpLinkManagerImpl5::FindInternal( + sal_uInt16& rnExtSheet, sal_uInt16& rnXclTab, SCTAB nScTab ) +{ + // create internal EXTERNSHEET records on demand + CreateInternal(); + + // try to find an EXTERNSHEET record - if not, return a "deleted sheet" reference + XclExpExtSheetRef xExtSheet; + XclExpIntTabMap::const_iterator aIt = maIntTabMap.find( nScTab ); + if( aIt == maIntTabMap.end() ) + { + xExtSheet = FindInternal( rnExtSheet, EXC_EXTSH_OWNDOC ); + rnXclTab = EXC_TAB_DELETED; + } + else + { + rnExtSheet = aIt->second; + xExtSheet = GetInternal( rnExtSheet ); + rnXclTab = GetTabInfo().GetXclTab( nScTab ); + } + return xExtSheet; +} + +XclExpLinkManagerImpl5::XclExpExtSheetRef XclExpLinkManagerImpl5::FindInternal( + sal_uInt16& rnExtSheet, sal_Unicode cCode ) +{ + XclExpExtSheetRef xExtSheet; + XclExpCodeMap::const_iterator aIt = maCodeMap.find( cCode ); + if( aIt == maCodeMap.end() ) + { + xExtSheet = new XclExpExternSheet( GetRoot(), cCode ); + rnExtSheet = maCodeMap[ cCode ] = AppendInternal( xExtSheet ); + } + else + { + rnExtSheet = aIt->second; + xExtSheet = GetInternal( rnExtSheet ); + } + return xExtSheet; +} + +XclExpLinkManagerImpl8::XclExpLinkManagerImpl8( const XclExpRoot& rRoot ) : + XclExpLinkManagerImpl( rRoot ), + maSBBuffer( rRoot ) +{ +} + +void XclExpLinkManagerImpl8::FindExtSheet( + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, XclExpRefLogEntry* pRefLogEntry ) +{ + XclExpTabInfo& rTabInfo = GetTabInfo(); + rnFirstXclTab = rTabInfo.GetXclTab( nFirstScTab ); + rnLastXclTab = rTabInfo.GetXclTab( nLastScTab ); + rnExtSheet = InsertXti( maSBBuffer.GetXti( rnFirstXclTab, rnLastXclTab, pRefLogEntry ) ); +} + +sal_uInt16 XclExpLinkManagerImpl8::FindExtSheet( sal_Unicode cCode ) +{ + OSL_ENSURE( (cCode == EXC_EXTSH_OWNDOC) || (cCode == EXC_EXTSH_ADDIN), + "XclExpLinkManagerImpl8::FindExtSheet - unknown externsheet code" ); + return InsertXti( maSBBuffer.GetXti( EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); +} + +void XclExpLinkManagerImpl8::FindExtSheet( + sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) +{ + XclExpXti aXti = maSBBuffer.GetXti(nFileId, rTabName, nXclTabSpan, pRefLogEntry); + rnExtSheet = InsertXti(aXti); + rnFirstSBTab = aXti.mnFirstSBTab; + rnLastSBTab = aXti.mnLastSBTab; +} + +void XclExpLinkManagerImpl8::StoreCellRange( const ScSingleRefData& rRef1, const ScSingleRefData& rRef2, const ScAddress& rPos ) +{ + ScAddress aAbs1 = rRef1.toAbs(GetRoot().GetDoc(), rPos); + ScAddress aAbs2 = rRef2.toAbs(GetRoot().GetDoc(), rPos); + if (!(!rRef1.IsDeleted() && !rRef2.IsDeleted() && (aAbs1.Tab() >= 0) && (aAbs2.Tab() >= 0))) + return; + + const XclExpTabInfo& rTabInfo = GetTabInfo(); + SCTAB nFirstScTab = aAbs1.Tab(); + SCTAB nLastScTab = aAbs2.Tab(); + ScRange aRange(aAbs1.Col(), aAbs1.Row(), 0, aAbs2.Col(), aAbs2.Row(), 0); + for (SCTAB nScTab = nFirstScTab; nScTab <= nLastScTab; ++nScTab) + { + if( rTabInfo.IsExternalTab( nScTab ) ) + { + aRange.aStart.SetTab( nScTab ); + aRange.aEnd.SetTab( nScTab ); + maSBBuffer.StoreCellRange( aRange ); + } + } +} + +void XclExpLinkManagerImpl8::StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) +{ + maSBBuffer.StoreCell(nFileId, rTabName, rPos); +} + +void XclExpLinkManagerImpl8::StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) +{ + maSBBuffer.StoreCellRange(nFileId, rTabName, rRange); +} + +bool XclExpLinkManagerImpl8::InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + sal_uInt16 nSupbook; + if( maSBBuffer.InsertAddIn( nSupbook, rnExtName, rName ) ) + { + rnExtSheet = InsertXti( XclExpXti( nSupbook, EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); + return true; + } + return false; +} + +bool XclExpLinkManagerImpl8::InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + sal_uInt16 nSupbook; + if( maSBBuffer.InsertEuroTool( nSupbook, rnExtName, rName ) ) + { + rnExtSheet = InsertXti( XclExpXti( nSupbook, EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); + return true; + } + return false; +} + +bool XclExpLinkManagerImpl8::InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) +{ + sal_uInt16 nSupbook; + if( maSBBuffer.InsertDde( nSupbook, rnExtName, rApplic, rTopic, rItem ) ) + { + rnExtSheet = InsertXti( XclExpXti( nSupbook, EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); + return true; + } + return false; +} + +bool XclExpLinkManagerImpl8::InsertExtName( sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rUrl, const OUString& rName, const ScExternalRefCache::TokenArrayRef& rArray ) +{ + sal_uInt16 nSupbook; + if( maSBBuffer.InsertExtName( nSupbook, rnExtName, rUrl, rName, rArray ) ) + { + rnExtSheet = InsertXti( XclExpXti( nSupbook, EXC_TAB_EXTERNAL, EXC_TAB_EXTERNAL ) ); + return true; + } + return false; +} + +void XclExpLinkManagerImpl8::Save( XclExpStream& rStrm ) +{ + if( maXtiVec.empty() ) + return; + + // SUPBOOKs, XCTs, CRNs, EXTERNNAMEs + maSBBuffer.Save( rStrm ); + + // EXTERNSHEET + sal_uInt16 nCount = ulimit_cast< sal_uInt16 >( maXtiVec.size() ); + rStrm.StartRecord( EXC_ID_EXTERNSHEET, 2 + 6 * nCount ); + rStrm << nCount; + rStrm.SetSliceSize( 6 ); + for( const auto& rXti : maXtiVec ) + rXti.Save( rStrm ); + rStrm.EndRecord(); +} + +void XclExpLinkManagerImpl8::SaveXml( XclExpXmlStream& rStrm ) +{ + if (maSBBuffer.HasExternalReferences()) + { + sax_fastparser::FSHelperPtr pWorkbook = rStrm.GetCurrentStream(); + pWorkbook->startElement(XML_externalReferences); + + // externalLink, externalBook, sheetNames, sheetDataSet, externalName + maSBBuffer.SaveXml( rStrm ); + + pWorkbook->endElement( XML_externalReferences); + } + + // TODO: equivalent for EXTERNSHEET in OOXML? +#if 0 + if( !maXtiVec.empty() ) + { + for( const auto& rXti : maXtiVec ) + rXti.SaveXml( rStrm ); + } +#endif +} + +sal_uInt16 XclExpLinkManagerImpl8::InsertXti( const XclExpXti& rXti ) +{ + auto aIt = std::find(maXtiVec.begin(), maXtiVec.end(), rXti); + if (aIt != maXtiVec.end()) + return ulimit_cast< sal_uInt16 >( std::distance(maXtiVec.begin(), aIt) ); + maXtiVec.push_back( rXti ); + return ulimit_cast< sal_uInt16 >( maXtiVec.size() - 1 ); +} + +XclExpLinkManager::XclExpLinkManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ + switch( GetBiff() ) + { + case EXC_BIFF5: + mxImpl = std::make_shared( rRoot ); + break; + case EXC_BIFF8: + mxImpl = std::make_shared( rRoot ); + break; + default: + DBG_ERROR_BIFF(); + } +} + +XclExpLinkManager::~XclExpLinkManager() +{ +} + +void XclExpLinkManager::FindExtSheet( + sal_uInt16& rnExtSheet, sal_uInt16& rnXclTab, + SCTAB nScTab, XclExpRefLogEntry* pRefLogEntry ) +{ + mxImpl->FindExtSheet( rnExtSheet, rnXclTab, rnXclTab, nScTab, nScTab, pRefLogEntry ); +} + +void XclExpLinkManager::FindExtSheet( + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstXclTab, sal_uInt16& rnLastXclTab, + SCTAB nFirstScTab, SCTAB nLastScTab, XclExpRefLogEntry* pRefLogEntry ) +{ + mxImpl->FindExtSheet( rnExtSheet, rnFirstXclTab, rnLastXclTab, nFirstScTab, nLastScTab, pRefLogEntry ); +} + +sal_uInt16 XclExpLinkManager::FindExtSheet( sal_Unicode cCode ) +{ + return mxImpl->FindExtSheet( cCode ); +} + +void XclExpLinkManager::FindExtSheet( sal_uInt16 nFileId, const OUString& rTabName, sal_uInt16 nXclTabSpan, + sal_uInt16& rnExtSheet, sal_uInt16& rnFirstSBTab, sal_uInt16& rnLastSBTab, + XclExpRefLogEntry* pRefLogEntry ) +{ + mxImpl->FindExtSheet( nFileId, rTabName, nXclTabSpan, rnExtSheet, rnFirstSBTab, rnLastSBTab, pRefLogEntry ); +} + +void XclExpLinkManager::StoreCell( const ScSingleRefData& rRef, const ScAddress& rPos ) +{ + mxImpl->StoreCellRange(rRef, rRef, rPos); +} + +void XclExpLinkManager::StoreCellRange( const ScComplexRefData& rRef, const ScAddress& rPos ) +{ + mxImpl->StoreCellRange(rRef.Ref1, rRef.Ref2, rPos); +} + +void XclExpLinkManager::StoreCell( sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rPos ) +{ + mxImpl->StoreCell(nFileId, rTabName, rPos); +} + +void XclExpLinkManager::StoreCellRange( sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange ) +{ + mxImpl->StoreCellRange(nFileId, rTabName, rRange); +} + +bool XclExpLinkManager::InsertAddIn( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + return mxImpl->InsertAddIn( rnExtSheet, rnExtName, rName ); +} + +bool XclExpLinkManager::InsertEuroTool( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rName ) +{ + return mxImpl->InsertEuroTool( rnExtSheet, rnExtName, rName ); +} + +bool XclExpLinkManager::InsertDde( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, + const OUString& rApplic, const OUString& rTopic, const OUString& rItem ) +{ + return mxImpl->InsertDde( rnExtSheet, rnExtName, rApplic, rTopic, rItem ); +} + +bool XclExpLinkManager::InsertExtName( + sal_uInt16& rnExtSheet, sal_uInt16& rnExtName, const OUString& rUrl, const OUString& rName, + const ScExternalRefCache::TokenArrayRef& rArray ) +{ + return mxImpl->InsertExtName(rnExtSheet, rnExtName, rUrl, rName, rArray); +} + +void XclExpLinkManager::Save( XclExpStream& rStrm ) +{ + mxImpl->Save( rStrm ); +} + +void XclExpLinkManager::SaveXml( XclExpXmlStream& rStrm ) +{ + mxImpl->SaveXml( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xename.cxx b/sc/source/filter/excel/xename.cxx new file mode 100644 index 000000000..c8fd0ed37 --- /dev/null +++ b/sc/source/filter/excel/xename.cxx @@ -0,0 +1,870 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace ::oox; + +// *** Helper classes *** + +namespace { + +/** Represents an internal defined name, supports writing it to a NAME record. */ +class XclExpName : public XclExpRecord, protected XclExpRoot +{ +public: + /** Creates a standard defined name. */ + explicit XclExpName( const XclExpRoot& rRoot, const OUString& rName ); + /** Creates a built-in defined name. */ + explicit XclExpName( const XclExpRoot& rRoot, sal_Unicode cBuiltIn ); + + /** Sets a token array containing the definition of this name. */ + void SetTokenArray( const XclTokenArrayRef& xTokArr ); + /** Changes this defined name to be local on the specified Calc sheet. */ + void SetLocalTab( SCTAB nScTab ); + /** Hides or unhides the defined name. */ + void SetHidden( bool bHidden = true ); + /** Changes this name to be the call to a VB macro function or procedure. + @param bVBasic true = Visual Basic macro, false = Sheet macro. + @param bFunc true = Macro function; false = Macro procedure. */ + void SetMacroCall( bool bVBasic, bool bFunc ); + + /** Sets the name's symbol value + @param sValue the name's symbolic value */ + void SetSymbol( const OUString& rValue ); + + /** Returns the original name (title) of this defined name. */ + const OUString& GetOrigName() const { return maOrigName; } + /** Returns the Excel built-in name index of this defined name. + @return The built-in name index or EXC_BUILTIN_UNKNOWN for user-defined names. */ + sal_Unicode GetBuiltInName() const { return mcBuiltIn; } + + /** Returns the symbol value for this defined name. */ + const OUString& GetSymbol() const { return msSymbol; } + + /** Returns true, if this is a document-global defined name. */ + bool IsGlobal() const { return mnXclTab == EXC_NAME_GLOBAL; } + /** Returns the Calc sheet of a local defined name. */ + SCTAB GetScTab() const { return mnScTab; } + + /** Returns true, if this defined name is volatile. */ + bool IsVolatile() const; + /** Returns true, if this defined name describes a macro call. + @param bFunc true = Macro function; false = Macro procedure. */ + bool IsMacroCall( bool bVBasic, bool bFunc ) const; + + /** Writes the entire NAME record to the passed stream. */ + virtual void Save( XclExpStream& rStrm ) override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + +private: + /** Writes the body of the NAME record to the passed stream. */ + virtual void WriteBody( XclExpStream& rStrm ) override; + /** Convert localized range separators */ + OUString GetWithDefaultRangeSeparator( const OUString& rSymbol ) const; + +private: + OUString maOrigName; /// The original user-defined name. + OUString msSymbol; /// The value of the symbol + XclExpStringRef mxName; /// The name as Excel string object. + XclTokenArrayRef mxTokArr; /// The definition of the defined name. + sal_Unicode mcBuiltIn; /// The built-in index for built-in names. + SCTAB mnScTab; /// The Calc sheet index for local names. + sal_uInt16 mnFlags; /// Additional flags for this defined name. + sal_uInt16 mnExtSheet; /// The 1-based index to a global EXTERNSHEET record. + sal_uInt16 mnXclTab; /// The 1-based Excel sheet index for local names. +}; + +} + +/** Implementation class of the name manager. */ +class XclExpNameManagerImpl : protected XclExpRoot +{ +public: + explicit XclExpNameManagerImpl( const XclExpRoot& rRoot ); + + /** Creates NAME records for built-in and user defined names. */ + void Initialize(); + + /** Inserts the Calc name with the passed index and returns the Excel NAME index. */ + sal_uInt16 InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab ); + + /** Inserts a new built-in defined name. */ + sal_uInt16 InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, SCTAB nScTab, const ScRangeList& aRangeList ); + sal_uInt16 InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, const ScRange& aRange ); + /** Inserts a new defined name. Sets another unused name, if rName already exists. */ + sal_uInt16 InsertUniqueName( const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab ); + /** Returns index of an existing name, or creates a name without definition. */ + sal_uInt16 InsertRawName( const OUString& rName ); + /** Searches or inserts a defined name describing a macro name. + @param bVBasic true = Visual Basic macro; false = Sheet macro. + @param bFunc true = Macro function; false = Macro procedure. */ + sal_uInt16 InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden ); + + /** Returns the NAME record at the specified position or 0 on error. */ + const XclExpName* GetName( sal_uInt16 nNameIdx ) const; + + /** Writes the entire list of NAME records. + @descr In BIFF7 and lower, writes the entire global link table, which + consists of an EXTERNCOUNT record, several EXTERNSHEET records, and + the list of NAME records. */ + void Save( XclExpStream& rStrm ); + + void SaveXml( XclExpXmlStream& rStrm ); + +private: + typedef XclExpRecordList< XclExpName > XclExpNameList; + typedef XclExpNameList::RecordRefType XclExpNameRef; + + typedef ::std::map< ::std::pair, sal_uInt16> NamedExpMap; + +private: + /** + * @param nTab 0-based table index, or SCTAB_GLOBAL for global names. + * @param nScIdx calc's name index. + * + * @return excel's name index. + */ + sal_uInt16 FindNamedExp( SCTAB nTab, OUString sName ); + + /** Returns the index of an existing built-in NAME record with the passed definition, otherwise 0. */ + sal_uInt16 FindBuiltInNameIdx( const OUString& rName, + const OUString& sSymbol ) const; + /** Returns an unused name for the passed name. */ + OUString GetUnusedName( const OUString& rName ) const; + + /** Appends a new NAME record to the record list. + @return The 1-based NAME record index used elsewhere in the Excel file. */ + sal_uInt16 Append( XclExpName* pName ); + sal_uInt16 Append( XclExpNameRef const & rxName ) { return Append(rxName.get()); } + /** Creates a new NAME record for the passed user-defined name. + @return The 1-based NAME record index used elsewhere in the Excel file. */ + sal_uInt16 CreateName( SCTAB nTab, const ScRangeData& rRangeData ); + + /** Creates NAME records for all built-in names in the document. */ + void CreateBuiltInNames(); + /** Creates NAME records for all user-defined names in the document. */ + void CreateUserNames(); + +private: + /** + * Maps Calc's named range to Excel's NAME records. Global names use + * -1 as their table index, whereas sheet-local names have 0-based table + * index. + */ + NamedExpMap maNamedExpMap; + XclExpNameList maNameList; /// List of NAME records. + size_t mnFirstUserIdx; /// List index of first user-defined NAME record. +}; + +// *** Implementation *** + +XclExpName::XclExpName( const XclExpRoot& rRoot, const OUString& rName ) : + XclExpRecord( EXC_ID_NAME ), + XclExpRoot( rRoot ), + maOrigName( rName ), + mxName( XclExpStringHelper::CreateString( rRoot, rName, XclStrFlags::EightBitLength ) ), + mcBuiltIn( EXC_BUILTIN_UNKNOWN ), + mnScTab( SCTAB_GLOBAL ), + mnFlags( EXC_NAME_DEFAULT ), + mnExtSheet( EXC_NAME_GLOBAL ), + mnXclTab( EXC_NAME_GLOBAL ) +{ +} + +XclExpName::XclExpName( const XclExpRoot& rRoot, sal_Unicode cBuiltIn ) : + XclExpRecord( EXC_ID_NAME ), + XclExpRoot( rRoot ), + mcBuiltIn( cBuiltIn ), + mnScTab( SCTAB_GLOBAL ), + mnFlags( EXC_NAME_DEFAULT ), + mnExtSheet( EXC_NAME_GLOBAL ), + mnXclTab( EXC_NAME_GLOBAL ) +{ + // filter source range is hidden in Excel + if( cBuiltIn == EXC_BUILTIN_FILTERDATABASE ) + SetHidden(); + + // special case for BIFF5/7 filter source range - name appears as plain text without built-in flag + if( (GetBiff() <= EXC_BIFF5) && (cBuiltIn == EXC_BUILTIN_FILTERDATABASE) ) + { + OUString aName( XclTools::GetXclBuiltInDefName( EXC_BUILTIN_FILTERDATABASE ) ); + mxName = XclExpStringHelper::CreateString( rRoot, aName, XclStrFlags::EightBitLength ); + maOrigName = XclTools::GetXclBuiltInDefName( cBuiltIn ); + } + else + { + maOrigName = XclTools::GetBuiltInDefNameXml( cBuiltIn ) ; + mxName = XclExpStringHelper::CreateString( rRoot, cBuiltIn, XclStrFlags::EightBitLength ); + ::set_flag( mnFlags, EXC_NAME_BUILTIN ); + } +} + +void XclExpName::SetTokenArray( const XclTokenArrayRef& xTokArr ) +{ + mxTokArr = xTokArr; +} + +void XclExpName::SetLocalTab( SCTAB nScTab ) +{ + OSL_ENSURE( GetTabInfo().IsExportTab( nScTab ), "XclExpName::SetLocalTab - invalid sheet index" ); + if( !GetTabInfo().IsExportTab( nScTab ) ) + return; + + mnScTab = nScTab; + GetGlobalLinkManager().FindExtSheet( mnExtSheet, mnXclTab, nScTab ); + + // special handling for NAME record + switch( GetBiff() ) + { + case EXC_BIFF5: // EXTERNSHEET index is positive in NAME record + mnExtSheet = ~mnExtSheet + 1; + break; + case EXC_BIFF8: // EXTERNSHEET index not used, but must be created in link table + mnExtSheet = 0; + break; + default: DBG_ERROR_BIFF(); + } + + // Excel sheet index is 1-based + ++mnXclTab; +} + +void XclExpName::SetHidden( bool bHidden ) +{ + ::set_flag( mnFlags, EXC_NAME_HIDDEN, bHidden ); +} + +void XclExpName::SetMacroCall( bool bVBasic, bool bFunc ) +{ + ::set_flag( mnFlags, EXC_NAME_PROC ); + ::set_flag( mnFlags, EXC_NAME_VB, bVBasic ); + ::set_flag( mnFlags, EXC_NAME_FUNC, bFunc ); +} + +void XclExpName::SetSymbol( const OUString& rSymbol ) +{ + msSymbol = rSymbol; +} + +bool XclExpName::IsVolatile() const +{ + return mxTokArr && mxTokArr->IsVolatile(); +} + +bool XclExpName::IsMacroCall( bool bVBasic, bool bFunc ) const +{ + return + (::get_flag( mnFlags, EXC_NAME_VB ) == bVBasic) && + (::get_flag( mnFlags, EXC_NAME_FUNC ) == bFunc); +} + +void XclExpName::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( mxName && (mxName->Len() > 0), "XclExpName::Save - missing name" ); + OSL_ENSURE( !(IsGlobal() && ::get_flag( mnFlags, EXC_NAME_BUILTIN )), "XclExpName::Save - global built-in name" ); + SetRecSize( 11 + mxName->GetSize() + (mxTokArr ? mxTokArr->GetSize() : 2) ); + XclExpRecord::Save( rStrm ); +} + +OUString XclExpName::GetWithDefaultRangeSeparator( const OUString& rSymbol ) const +{ + sal_Int32 nPos = rSymbol.indexOf(';'); + if ( nPos > -1 ) + { + // convert with validation + ScRange aRange; + ScAddress::Details detailsXL( ::formula::FormulaGrammar::CONV_XL_A1 ); + ScRefFlags nRes = aRange.Parse( rSymbol.copy(0, nPos), GetDoc(), detailsXL ); + if ( nRes & ScRefFlags::VALID ) + { + nRes = aRange.Parse( rSymbol.copy(nPos+1), GetDoc(), detailsXL ); + if ( nRes & ScRefFlags::VALID ) + { + return rSymbol.replaceFirst(";", ","); + } + } + } + return rSymbol; +} + +void XclExpName::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorkbook = rStrm.GetCurrentStream(); + rWorkbook->startElement( XML_definedName, + // OOXTODO: XML_comment, "", + // OOXTODO: XML_customMenu, "", + // OOXTODO: XML_description, "", + XML_function, ToPsz( ::get_flag( mnFlags, EXC_NAME_VB ) ), + // OOXTODO: XML_functionGroupId, "", + // OOXTODO: XML_help, "", + XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_NAME_HIDDEN ) ), + XML_localSheetId, mnScTab == SCTAB_GLOBAL ? nullptr : OString::number( mnScTab ).getStr(), + XML_name, maOrigName.toUtf8(), + // OOXTODO: XML_publishToServer, "", + // OOXTODO: XML_shortcutKey, "", + // OOXTODO: XML_statusBar, "", + XML_vbProcedure, ToPsz( ::get_flag( mnFlags, EXC_NAME_VB ) ) + // OOXTODO: XML_workbookParameter, "", + // OOXTODO: XML_xlm, "" + ); + rWorkbook->writeEscaped( GetWithDefaultRangeSeparator( msSymbol ) ); + rWorkbook->endElement( XML_definedName ); +} + +void XclExpName::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt16 nFmlaSize = mxTokArr ? mxTokArr->GetSize() : 0; + + rStrm << mnFlags // flags + << sal_uInt8( 0 ); // keyboard shortcut + mxName->WriteLenField( rStrm ); // length of name + rStrm << nFmlaSize // size of token array + << mnExtSheet // BIFF5/7: EXTSHEET index, BIFF8: not used + << mnXclTab // 1-based sheet index for local names + << sal_uInt32( 0 ); // length of menu/descr/help/status text + mxName->WriteFlagField( rStrm ); // BIFF8 flag field (no-op in <=BIFF7) + mxName->WriteBuffer( rStrm ); // character array of the name + if( mxTokArr ) + mxTokArr->WriteArray( rStrm ); // token array without size +} + +/** Returns true (needed fixing) if FormulaToken was not absolute and 3D. + So, regardless of whether the fix was successful or not, true is still returned since a fix was required.*/ +static bool lcl_EnsureAbs3DToken( const SCTAB nTab, formula::FormulaToken* pTok, const bool bFix = true ) +{ + bool bFixRequired = false; + if ( !pTok || ( pTok->GetType() != formula::svSingleRef && pTok->GetType() != formula::svDoubleRef ) ) + return bFixRequired; + + ScSingleRefData* pRef1 = pTok->GetSingleRef(); + if ( !pRef1 ) + return bFixRequired; + + ScSingleRefData* pRef2 = nullptr; + if ( pTok->GetType() == formula::svDoubleRef ) + pRef2 = pTok->GetSingleRef2(); + + if ( pRef1->IsTabRel() || !pRef1->IsFlag3D() ) + { + bFixRequired = true; + if ( bFix ) + { + if ( pRef1->IsTabRel() && nTab != SCTAB_GLOBAL ) + pRef1->SetAbsTab( nTab + pRef1->Tab() ); //XLS requirement + if ( !pRef1->IsTabRel() ) + { + pRef1->SetFlag3D( true ); //XLSX requirement + if ( pRef2 && !pRef2->IsTabRel() ) + pRef2->SetFlag3D( pRef2->Tab() != pRef1->Tab() ); + } + } + } + + if ( pRef2 && pRef2->IsTabRel() && !pRef1->IsTabRel() ) + { + bFixRequired = true; + if ( bFix && nTab != SCTAB_GLOBAL ) + { + pRef2->SetAbsTab( nTab + pRef2->Tab() ); + pRef2->SetFlag3D( pRef2->Tab() != pRef1->Tab() ); + } + } + return bFixRequired; +} + +XclExpNameManagerImpl::XclExpNameManagerImpl( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnFirstUserIdx( 0 ) +{ +} + +void XclExpNameManagerImpl::Initialize() +{ + CreateBuiltInNames(); + mnFirstUserIdx = maNameList.GetSize(); + CreateUserNames(); +} + +sal_uInt16 XclExpNameManagerImpl::InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab ) +{ + sal_uInt16 nNameIdx = 0; + const ScRangeData* pData = nullptr; + ScRangeName* pRN = (nTab == SCTAB_GLOBAL) ? GetDoc().GetRangeName() : GetDoc().GetRangeName(nTab); + if (pRN) + pData = pRN->findByIndex(nScNameIdx); + + if (pData) + { + bool bEmulateGlobalRelativeTable = false; + const ScTokenArray* pCode = pData->GetCode(); + if ( pCode + && nTab == SCTAB_GLOBAL + && (pData->HasType( ScRangeData::Type::AbsPos ) || pData->HasType( ScRangeData::Type::AbsArea )) ) + { + bEmulateGlobalRelativeTable = lcl_EnsureAbs3DToken( nTab, pCode->FirstToken(), /*bFix=*/false ); + } + nNameIdx = FindNamedExp( bEmulateGlobalRelativeTable ? nCurrTab : nTab, pData->GetName() ); + if (!nNameIdx) + nNameIdx = CreateName(nTab, *pData); + } + + return nNameIdx; +} + +sal_uInt16 XclExpNameManagerImpl::InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, const ScRange& aRange ) +{ + XclExpNameRef xName = new XclExpName( GetRoot(), cBuiltIn ); + xName->SetTokenArray( xTokArr ); + xName->SetLocalTab( aRange.aStart.Tab() ); + OUString sSymbol(aRange.Format(GetDoc(), ScRefFlags::RANGE_ABS_3D, ScAddress::Details( ::formula::FormulaGrammar::CONV_XL_A1))); + xName->SetSymbol( sSymbol ); + return Append( xName ); +} + +sal_uInt16 XclExpNameManagerImpl::InsertBuiltInName( sal_Unicode cBuiltIn, const XclTokenArrayRef& xTokArr, SCTAB nScTab, const ScRangeList& rRangeList ) +{ + XclExpNameRef xName = new XclExpName( GetRoot(), cBuiltIn ); + xName->SetTokenArray( xTokArr ); + xName->SetLocalTab( nScTab ); + OUString sSymbol; + rRangeList.Format( sSymbol, ScRefFlags::RANGE_ABS_3D, GetDoc(), ::formula::FormulaGrammar::CONV_XL_A1 ); + xName->SetSymbol( sSymbol ); + return Append( xName ); +} + +sal_uInt16 XclExpNameManagerImpl::InsertUniqueName( + const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab ) +{ + OSL_ENSURE( !rName.isEmpty(), "XclExpNameManagerImpl::InsertUniqueName - empty name" ); + XclExpNameRef xName = new XclExpName( GetRoot(), GetUnusedName( rName ) ); + xName->SetTokenArray( xTokArr ); + xName->SetLocalTab( nScTab ); + return Append( xName ); +} + +sal_uInt16 XclExpNameManagerImpl::InsertRawName( const OUString& rName ) +{ + // empty name? may occur in broken external Calc tokens + if( rName.isEmpty() ) + return 0; + + // try to find an existing NAME record, regardless of its type + for( size_t nListIdx = mnFirstUserIdx, nListSize = maNameList.GetSize(); nListIdx < nListSize; ++nListIdx ) + { + XclExpNameRef xName = maNameList.GetRecord( nListIdx ); + if( xName->IsGlobal() && (xName->GetOrigName() == rName) ) + return static_cast< sal_uInt16 >( nListIdx + 1 ); + } + + // create a new NAME record + XclExpNameRef xName = new XclExpName( GetRoot(), rName ); + return Append( xName ); +} + +sal_uInt16 XclExpNameManagerImpl::InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden ) +{ + // empty name? may occur in broken external Calc tokens + if( rMacroName.isEmpty() ) + return 0; + + // try to find an existing NAME record + for( size_t nListIdx = mnFirstUserIdx, nListSize = maNameList.GetSize(); nListIdx < nListSize; ++nListIdx ) + { + XclExpNameRef xName = maNameList.GetRecord( nListIdx ); + if( xName->IsMacroCall( bVBasic, bFunc ) && (xName->GetOrigName() == rMacroName) ) + return static_cast< sal_uInt16 >( nListIdx + 1 ); + } + + // create a new NAME record + XclExpNameRef xName = new XclExpName( GetRoot(), rMacroName ); + xName->SetMacroCall( bVBasic, bFunc ); + xName->SetHidden( bHidden ); + + // for sheet macros, add a #NAME! error + if( !bVBasic ) + xName->SetTokenArray( GetFormulaCompiler().CreateErrorFormula( EXC_ERR_NAME ) ); + + return Append( xName ); +} + +const XclExpName* XclExpNameManagerImpl::GetName( sal_uInt16 nNameIdx ) const +{ + OSL_ENSURE( maNameList.HasRecord( nNameIdx - 1 ), "XclExpNameManagerImpl::GetName - wrong record index" ); + return maNameList.GetRecord( nNameIdx - 1 ); +} + +void XclExpNameManagerImpl::Save( XclExpStream& rStrm ) +{ + maNameList.Save( rStrm ); +} + +void XclExpNameManagerImpl::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maNameList.IsEmpty() ) + return; + sax_fastparser::FSHelperPtr& rWorkbook = rStrm.GetCurrentStream(); + rWorkbook->startElement(XML_definedNames); + maNameList.SaveXml( rStrm ); + rWorkbook->endElement( XML_definedNames ); +} + +// private -------------------------------------------------------------------- + +sal_uInt16 XclExpNameManagerImpl::FindNamedExp( SCTAB nTab, OUString sName ) +{ + NamedExpMap::key_type key(nTab, sName); + NamedExpMap::const_iterator itr = maNamedExpMap.find(key); + return (itr == maNamedExpMap.end()) ? 0 : itr->second; +} + +sal_uInt16 XclExpNameManagerImpl::FindBuiltInNameIdx( + const OUString& rName, const OUString& sSymbol ) const +{ + /* Get built-in index from the name. Special case: the database range + 'unnamed' will be mapped to Excel's built-in '_FilterDatabase' name. */ + sal_Unicode cBuiltIn = XclTools::GetBuiltInDefNameIndex( rName ); + + if( cBuiltIn < EXC_BUILTIN_UNKNOWN ) + { + // try to find the record in existing built-in NAME record list + for( size_t nPos = 0; nPos < mnFirstUserIdx; ++nPos ) + { + XclExpNameRef xName = maNameList.GetRecord( nPos ); + if( xName->GetBuiltInName() == cBuiltIn && xName->GetSymbol().replace(';', ',') == sSymbol.replace(';', ',') ) + { + // tdf#112567 restore the original built-in names with non-localized separators + // TODO: support more localizations, if needed + if ( xName->GetSymbol() != sSymbol ) + { + xName->SetSymbol(xName->GetSymbol().replace(';', ',')); + } + return static_cast< sal_uInt16 >( nPos + 1 ); + } + } + } + return 0; +} + +OUString XclExpNameManagerImpl::GetUnusedName( const OUString& rName ) const +{ + OUString aNewName( rName ); + sal_Int32 nAppIdx = 0; + bool bExist = true; + while( bExist ) + { + // search the list of user-defined names + bExist = false; + for( size_t nPos = mnFirstUserIdx, nSize = maNameList.GetSize(); !bExist && (nPos < nSize); ++nPos ) + { + XclExpNameRef xName = maNameList.GetRecord( nPos ); + bExist = xName->GetOrigName() == aNewName; + // name exists -> create a new name "_" + if( bExist ) + aNewName = rName + "_" + OUString::number( ++nAppIdx ); + } + } + return aNewName; +} + +sal_uInt16 XclExpNameManagerImpl::Append( XclExpName* pName ) +{ + if( maNameList.GetSize() == 0xFFFF ) + return 0; + maNameList.AppendRecord( pName ); + return static_cast< sal_uInt16 >( maNameList.GetSize() ); // 1-based +} + +sal_uInt16 XclExpNameManagerImpl::CreateName( SCTAB nTab, const ScRangeData& rRangeData ) +{ + const OUString& rName = rRangeData.GetName(); + + /* #i38821# recursive names: first insert the (empty) name object, + otherwise a recursive call of this function from the formula compiler + with the same defined name will not find it and will create it again. */ + size_t nOldListSize = maNameList.GetSize(); + XclExpNameRef xName = new XclExpName( GetRoot(), rName ); + if (nTab != SCTAB_GLOBAL) + xName->SetLocalTab(nTab); + sal_uInt16 nNameIdx = Append( xName ); + // store the index of the NAME record in the lookup map + NamedExpMap::key_type key(nTab, rRangeData.GetName()); + maNamedExpMap[key] = nNameIdx; + + /* Create the definition formula. + This may cause recursive creation of other defined names. */ + if( const ScTokenArray* pScTokArr = const_cast< ScRangeData& >( rRangeData ).GetCode() ) + { + XclTokenArrayRef xTokArr; + OUString sSymbol; + // MSO requires named ranges to have absolute sheet references + if ( rRangeData.HasType( ScRangeData::Type::AbsPos ) || rRangeData.HasType( ScRangeData::Type::AbsArea ) ) + { + // Don't modify the actual document; use a temporary copy to create the export formulas. + ScTokenArray aTokenCopy( pScTokArr->CloneValue() ); + lcl_EnsureAbs3DToken(nTab, aTokenCopy.FirstToken()); + + xTokArr = GetFormulaCompiler().CreateFormula(EXC_FMLATYPE_NAME, aTokenCopy); + if ( GetOutput() != EXC_OUTPUT_BINARY ) + { + ScCompiler aComp(GetDoc(), rRangeData.GetPos(), aTokenCopy, + formula::FormulaGrammar::GRAM_OOXML); + aComp.CreateStringFromTokenArray( sSymbol ); + } + } + else + { + bool bOOXML = GetOutput() == EXC_OUTPUT_XML_2007; + xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, *pScTokArr, bOOXML ? + &rRangeData.GetPos() : nullptr ); + sSymbol = rRangeData.GetSymbol( (GetOutput() == EXC_OUTPUT_BINARY) ? + formula::FormulaGrammar::GRAM_ENGLISH_XL_A1 : formula::FormulaGrammar::GRAM_OOXML); + } + xName->SetTokenArray( xTokArr ); + xName->SetSymbol( sSymbol ); + + /* Try to replace by existing built-in name - complete token array is + needed for comparison, and due to the recursion problem above this + cannot be done earlier. If a built-in name is found, the created NAME + record for this name and all following records in the list must be + deleted, otherwise they may contain wrong name list indexes. */ + sal_uInt16 nBuiltInIdx = FindBuiltInNameIdx( rName, sSymbol ); + if( nBuiltInIdx != 0 ) + { + // delete the new NAME records + while( maNameList.GetSize() > nOldListSize ) + maNameList.RemoveRecord( maNameList.GetSize() - 1 ); + // use index of the found built-in NAME record + key = NamedExpMap::key_type(nTab, rRangeData.GetName()); + maNamedExpMap[key] = nNameIdx = nBuiltInIdx; + } + } + + return nNameIdx; +} + +void XclExpNameManagerImpl::CreateBuiltInNames() +{ + ScDocument& rDoc = GetDoc(); + XclExpTabInfo& rTabInfo = GetTabInfo(); + + /* #i2394# built-in defined names must be sorted by the name of the + containing sheet. Example: SheetA!Print_Range must be stored *before* + SheetB!Print_Range, regardless of the position of SheetA in the document! */ + for( SCTAB nScTabIdx = 0, nScTabCount = rTabInfo.GetScTabCount(); nScTabIdx < nScTabCount; ++nScTabIdx ) + { + // find real sheet index from the nScTabIdx counter + SCTAB nScTab = rTabInfo.GetRealScTab( nScTabIdx ); + // create NAME records for all built-in names of this sheet + if( rTabInfo.IsExportTab( nScTab ) ) + { + // *** 1) print ranges *** ---------------------------------------- + + if( rDoc.HasPrintRange() ) + { + ScRangeList aRangeList; + for( sal_uInt16 nIdx = 0, nCount = rDoc.GetPrintRangeCount( nScTab ); nIdx < nCount; ++nIdx ) + { + const ScRange* pPrintRange = rDoc.GetPrintRange( nScTab, nIdx ); + if (!pPrintRange) + continue; + ScRange aRange( *pPrintRange ); + // Calc document does not care about sheet index in print ranges + aRange.aStart.SetTab( nScTab ); + aRange.aEnd.SetTab( nScTab ); + aRange.PutInOrder(); + aRangeList.push_back( aRange ); + } + // create the NAME record (do not warn if ranges are shrunken) + GetAddressConverter().ValidateRangeList( aRangeList, false ); + if( !aRangeList.empty() ) + GetNameManager().InsertBuiltInName( EXC_BUILTIN_PRINTAREA, aRangeList ); + } + + // *** 2) print titles *** ---------------------------------------- + + ScRangeList aTitleList; + // repeated columns + if( std::optional oColRange = rDoc.GetRepeatColRange( nScTab ) ) + aTitleList.push_back( ScRange( + oColRange->aStart.Col(), 0, nScTab, + oColRange->aEnd.Col(), GetXclMaxPos().Row(), nScTab ) ); + // repeated rows + if( std::optional oRowRange = rDoc.GetRepeatRowRange( nScTab ) ) + aTitleList.push_back( ScRange( + 0, oRowRange->aStart.Row(), nScTab, + GetXclMaxPos().Col(), oRowRange->aEnd.Row(), nScTab ) ); + // create the NAME record + GetAddressConverter().ValidateRangeList( aTitleList, false ); + if( !aTitleList.empty() ) + GetNameManager().InsertBuiltInName( EXC_BUILTIN_PRINTTITLES, aTitleList ); + + // *** 3) filter ranges *** --------------------------------------- + + if( GetBiff() == EXC_BIFF8 ) + GetFilterManager().InitTabFilter( nScTab ); + } + } +} + +void XclExpNameManagerImpl::CreateUserNames() +{ + std::vector vEmulateAsLocalRange; + const ScRangeName& rNamedRanges = GetNamedRanges(); + for (const auto& rEntry : rNamedRanges) + { + // skip definitions of shared formulas + if (!FindNamedExp(SCTAB_GLOBAL, rEntry.second->GetName())) + { + const ScTokenArray* pCode = rEntry.second->GetCode(); + if ( pCode + && (rEntry.second->HasType( ScRangeData::Type::AbsPos ) || rEntry.second->HasType( ScRangeData::Type::AbsArea )) + && lcl_EnsureAbs3DToken( SCTAB_GLOBAL, pCode->FirstToken(), /*bFix=*/false ) ) + { + vEmulateAsLocalRange.emplace_back(rEntry.second.get()); + } + else + CreateName(SCTAB_GLOBAL, *rEntry.second); + } + } + //look at sheets containing local range names + ScRangeName::TabNameCopyMap rLocalNames; + GetDoc().GetAllTabRangeNames(rLocalNames); + for (const auto& [rTab, pRangeName] : rLocalNames) + { + for (const auto& rEntry : *pRangeName) + { + // skip definitions of shared formulas + if (!FindNamedExp(rTab, rEntry.second->GetName())) + CreateName(rTab, *rEntry.second); + } + } + + // Emulate relative global variables by creating a copy in each local range. + // Creating AFTER true local range names so that conflicting global names will be ignored. + for ( SCTAB nTab = 0; nTab < GetDoc().GetTableCount(); ++nTab ) + { + for ( auto rangeDataItr : vEmulateAsLocalRange ) + { + if ( !FindNamedExp(nTab, rangeDataItr->GetName()) ) + CreateName(nTab, *rangeDataItr ); + } + } +} + +XclExpNameManager::XclExpNameManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mxImpl( std::make_shared( rRoot ) ) +{ +} + +XclExpNameManager::~XclExpNameManager() +{ +} + +void XclExpNameManager::Initialize() +{ + mxImpl->Initialize(); +} + +sal_uInt16 XclExpNameManager::InsertName( SCTAB nTab, sal_uInt16 nScNameIdx, SCTAB nCurrTab ) +{ + return mxImpl->InsertName( nTab, nScNameIdx, nCurrTab ); +} + +sal_uInt16 XclExpNameManager::InsertBuiltInName( sal_Unicode cBuiltIn, const ScRange& rRange ) +{ + XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, rRange ); + return mxImpl->InsertBuiltInName( cBuiltIn, xTokArr, rRange ); +} + +sal_uInt16 XclExpNameManager::InsertBuiltInName( sal_Unicode cBuiltIn, const ScRangeList& rRangeList ) +{ + sal_uInt16 nNameIdx = 0; + if( !rRangeList.empty() ) + { + XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_NAME, rRangeList ); + nNameIdx = mxImpl->InsertBuiltInName( cBuiltIn, xTokArr, rRangeList.front().aStart.Tab(), rRangeList ); + } + return nNameIdx; +} + +sal_uInt16 XclExpNameManager::InsertUniqueName( + const OUString& rName, const XclTokenArrayRef& xTokArr, SCTAB nScTab ) +{ + return mxImpl->InsertUniqueName( rName, xTokArr, nScTab ); +} + +sal_uInt16 XclExpNameManager::InsertRawName( const OUString& rName ) +{ + return mxImpl->InsertRawName( rName ); +} + +sal_uInt16 XclExpNameManager::InsertMacroCall( const OUString& rMacroName, bool bVBasic, bool bFunc, bool bHidden ) +{ + return mxImpl->InsertMacroCall( rMacroName, bVBasic, bFunc, bHidden ); +} + +OUString XclExpNameManager::GetOrigName( sal_uInt16 nNameIdx ) const +{ + const XclExpName* pName = mxImpl->GetName( nNameIdx ); + return pName ? pName->GetOrigName() : OUString(); +} + +SCTAB XclExpNameManager::GetScTab( sal_uInt16 nNameIdx ) const +{ + const XclExpName* pName = mxImpl->GetName( nNameIdx ); + return pName ? pName->GetScTab() : SCTAB_GLOBAL; +} + +bool XclExpNameManager::IsVolatile( sal_uInt16 nNameIdx ) const +{ + const XclExpName* pName = mxImpl->GetName( nNameIdx ); + return pName && pName->IsVolatile(); +} + +void XclExpNameManager::Save( XclExpStream& rStrm ) +{ + mxImpl->Save( rStrm ); +} + +void XclExpNameManager::SaveXml( XclExpXmlStream& rStrm ) +{ + mxImpl->SaveXml( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xepage.cxx b/sc/source/filter/excel/xepage.cxx new file mode 100644 index 000000000..56ecd2d6b --- /dev/null +++ b/sc/source/filter/excel/xepage.cxx @@ -0,0 +1,512 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ::oox; + +using ::std::set; +using ::std::numeric_limits; + +// Page settings records ====================================================== + +// Header/footer -------------------------------------------------------------- + +XclExpHeaderFooter::XclExpHeaderFooter( sal_uInt16 nRecId, const OUString& rHdrString ) : + XclExpRecord( nRecId ), + maHdrString( rHdrString ) +{ +} + +void XclExpHeaderFooter::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + sal_Int32 nElement; + switch(GetRecId()) { + case EXC_ID_HEADER_FIRST: nElement = XML_firstHeader; break; + case EXC_ID_FOOTER_FIRST: nElement = XML_firstFooter; break; + case EXC_ID_HEADER_EVEN: nElement = XML_evenHeader; break; + case EXC_ID_FOOTER_EVEN: nElement = XML_evenFooter; break; + case EXC_ID_HEADER: nElement = XML_oddHeader; break; + case EXC_ID_FOOTER: + default: nElement = XML_oddFooter; + } + rWorksheet->startElement(nElement); + rWorksheet->writeEscaped( maHdrString ); + rWorksheet->endElement( nElement ); +} + +void XclExpHeaderFooter::WriteBody( XclExpStream& rStrm ) +{ + if( !maHdrString.isEmpty() ) + { + XclExpString aExString; + if( rStrm.GetRoot().GetBiff() <= EXC_BIFF5 ) + aExString.AssignByte( maHdrString, rStrm.GetRoot().GetTextEncoding(), XclStrFlags::EightBitLength ); + else + aExString.Assign( maHdrString, XclStrFlags::NONE, 255 ); // 16-bit length, but max 255 chars + rStrm << aExString; + } +} + +// General page settings ------------------------------------------------------ + +XclExpSetup::XclExpSetup( const XclPageData& rPageData ) : + XclExpRecord( EXC_ID_SETUP, 34 ), + mrData( rPageData ) +{ +} + +void XclExpSetup::SaveXml( XclExpXmlStream& rStrm ) +{ + rtl::Reference pAttrList = sax_fastparser::FastSerializerHelper::createAttrList(); + if( rStrm.getVersion() != oox::core::ISOIEC_29500_2008 || + mrData.mnStrictPaperSize != EXC_PAPERSIZE_USER ) + { + pAttrList->add( XML_paperSize, OString::number( mrData.mnPaperSize ).getStr() ); + } + else + { + pAttrList->add( XML_paperWidth, OString::number( mrData.mnPaperWidth ) + "mm" ); + pAttrList->add( XML_paperHeight, OString::number( mrData.mnPaperHeight ) + "mm" ); + // pAttrList->add( XML_paperUnits, "mm" ); + } + pAttrList->add( XML_scale, OString::number( mrData.mnScaling ).getStr() ); + pAttrList->add( XML_fitToWidth, OString::number( mrData.mnFitToWidth ).getStr() ); + pAttrList->add( XML_fitToHeight, OString::number( mrData.mnFitToHeight ).getStr() ); + pAttrList->add( XML_pageOrder, mrData.mbPrintInRows ? "overThenDown" : "downThenOver" ); + pAttrList->add( XML_orientation, mrData.mbPortrait ? "portrait" : "landscape" ); // OOXTODO: "default"? + // tdf#48767 if XML_usePrinterDefaults field is exist, then XML_orientation is always "portrait" in MS Excel + // To resolve that import issue, if XML_usePrinterDefaults has default value (false) then XML_usePrinterDefaults is not added. + if ( !mrData.mbValid ) + pAttrList->add( XML_usePrinterDefaults, ToPsz( !mrData.mbValid ) ); + pAttrList->add( XML_blackAndWhite, ToPsz( mrData.mbBlackWhite ) ); + pAttrList->add( XML_draft, ToPsz( mrData.mbDraftQuality ) ); + pAttrList->add( XML_cellComments, mrData.mbPrintNotes ? "atEnd" : "none" ); // OOXTODO: "asDisplayed"? + + if ( mrData.mbManualStart ) + { + pAttrList->add( XML_firstPageNumber, OString::number( mrData.mnStartPage ).getStr() ); + pAttrList->add( XML_useFirstPageNumber, ToPsz( mrData.mbManualStart ) ); + } + // OOXTODO: XML_errors, // == displayed|blank|dash|NA + pAttrList->add( XML_horizontalDpi, OString::number( mrData.mnHorPrintRes ).getStr() ); + pAttrList->add( XML_verticalDpi, OString::number( mrData.mnVerPrintRes ).getStr() ); + pAttrList->add( XML_copies, OString::number( mrData.mnCopies ).getStr() ); + // OOXTODO: devMode settings part RelationshipId: FSNS( XML_r, XML_id ), + + rStrm.GetCurrentStream()->singleElement( XML_pageSetup, pAttrList ); +} + +void XclExpSetup::WriteBody( XclExpStream& rStrm ) +{ + XclBiff eBiff = rStrm.GetRoot().GetBiff(); + + sal_uInt16 nFlags = 0; + ::set_flag( nFlags, EXC_SETUP_INROWS, mrData.mbPrintInRows ); + ::set_flag( nFlags, EXC_SETUP_PORTRAIT, mrData.mbPortrait ); + ::set_flag( nFlags, EXC_SETUP_INVALID, !mrData.mbValid ); + ::set_flag( nFlags, EXC_SETUP_BLACKWHITE, mrData.mbBlackWhite ); + if( eBiff >= EXC_BIFF5 ) + { + ::set_flag( nFlags, EXC_SETUP_DRAFT, mrData.mbDraftQuality ); + /* Set the Comments/Notes to "At end of sheet" if Print Notes is true. + We don't currently support "as displayed on sheet". Thus this value + will be re-interpreted to "At end of sheet". */ + const sal_uInt16 nNotes = EXC_SETUP_PRINTNOTES | EXC_SETUP_NOTES_END; + ::set_flag( nFlags, nNotes, mrData.mbPrintNotes ); + ::set_flag( nFlags, EXC_SETUP_STARTPAGE, mrData.mbManualStart ); + } + + rStrm << mrData.mnPaperSize << mrData.mnScaling << mrData.mnStartPage + << mrData.mnFitToWidth << mrData.mnFitToHeight << nFlags; + if( eBiff >= EXC_BIFF5 ) + { + rStrm << mrData.mnHorPrintRes << mrData.mnVerPrintRes + << mrData.mfHeaderMargin << mrData.mfFooterMargin << mrData.mnCopies; + } +} + +// Manual page breaks --------------------------------------------------------- + +XclExpPageBreaks::XclExpPageBreaks( sal_uInt16 nRecId, const ScfUInt16Vec& rPageBreaks, sal_uInt16 nMaxPos ) : + XclExpRecord( nRecId ), + mrPageBreaks( rPageBreaks ), + mnMaxPos( nMaxPos ) +{ +} + +void XclExpPageBreaks::Save( XclExpStream& rStrm ) +{ + if( !mrPageBreaks.empty() ) + { + SetRecSize( 2 + ((rStrm.GetRoot().GetBiff() <= EXC_BIFF5) ? 2 : 6) * mrPageBreaks.size() ); + XclExpRecord::Save( rStrm ); + } +} + +void XclExpPageBreaks::WriteBody( XclExpStream& rStrm ) +{ + bool bWriteRange = (rStrm.GetRoot().GetBiff() == EXC_BIFF8); + + rStrm << static_cast< sal_uInt16 >( mrPageBreaks.size() ); + for( const auto& rPageBreak : mrPageBreaks ) + { + rStrm << rPageBreak; + if( bWriteRange ) + rStrm << sal_uInt16( 0 ) << mnMaxPos; + } +} + +void XclExpPageBreaks::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mrPageBreaks.empty() ) + return; + + sal_Int32 nElement = GetRecId() == EXC_ID_HORPAGEBREAKS ? XML_rowBreaks : XML_colBreaks; + sax_fastparser::FSHelperPtr& pWorksheet = rStrm.GetCurrentStream(); + OString sNumPageBreaks = OString::number( mrPageBreaks.size() ); + pWorksheet->startElement( nElement, + XML_count, sNumPageBreaks, + XML_manualBreakCount, sNumPageBreaks ); + for( const auto& rPageBreak : mrPageBreaks ) + { + pWorksheet->singleElement( XML_brk, + XML_id, OString::number(rPageBreak), + XML_man, "true", + XML_max, OString::number(mnMaxPos), + XML_min, "0" + // OOXTODO: XML_pt, "" + ); + } + pWorksheet->endElement( nElement ); +} + +// Page settings ============================================================== + +XclExpPageSettings::XclExpPageSettings( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ + ScDocument& rDoc = GetDoc(); + SCTAB nScTab = GetCurrScTab(); + + if( SfxStyleSheetBase* pStyleSheet = GetStyleSheetPool().Find( rDoc.GetPageStyle( nScTab ), SfxStyleFamily::Page ) ) + { + const SfxItemSet& rItemSet = pStyleSheet->GetItemSet(); + maData.mbValid = true; + + // *** page settings *** + + maData.mbPrintInRows = ! rItemSet.Get( ATTR_PAGE_TOPDOWN ).GetValue(); + maData.mbHorCenter = rItemSet.Get( ATTR_PAGE_HORCENTER ).GetValue(); + maData.mbVerCenter = rItemSet.Get( ATTR_PAGE_VERCENTER ).GetValue(); + maData.mbPrintHeadings = rItemSet.Get( ATTR_PAGE_HEADERS ).GetValue(); + maData.mbPrintGrid = rItemSet.Get( ATTR_PAGE_GRID ).GetValue(); + maData.mbPrintNotes = rItemSet.Get( ATTR_PAGE_NOTES ).GetValue(); + + maData.mnStartPage = rItemSet.Get( ATTR_PAGE_FIRSTPAGENO ).GetValue(); + maData.mbManualStart = maData.mnStartPage && (!nScTab || rDoc.NeedPageResetAfterTab( nScTab - 1 )); + + const SvxLRSpaceItem& rLRItem = rItemSet.Get( ATTR_LRSPACE ); + maData.mfLeftMargin = XclTools::GetInchFromTwips( rLRItem.GetLeft() ); + maData.mfRightMargin = XclTools::GetInchFromTwips( rLRItem.GetRight() ); + const SvxULSpaceItem& rULItem = rItemSet.Get( ATTR_ULSPACE ); + maData.mfTopMargin = XclTools::GetInchFromTwips( rULItem.GetUpper() ); + maData.mfBottomMargin = XclTools::GetInchFromTwips( rULItem.GetLower() ); + + const SvxPageItem& rPageItem = rItemSet.Get( ATTR_PAGE ); + const SvxSizeItem& rSizeItem = rItemSet.Get( ATTR_PAGE_SIZE ); + maData.SetScPaperSize( rSizeItem.GetSize(), !rPageItem.IsLandscape() ); + + const ScPageScaleToItem& rScaleToItem = rItemSet.Get( ATTR_PAGE_SCALETO ); + sal_uInt16 nPages = rItemSet.Get( ATTR_PAGE_SCALETOPAGES ).GetValue(); + sal_uInt16 nScale = rItemSet.Get( ATTR_PAGE_SCALE ).GetValue(); + + if( ScfTools::CheckItem( rItemSet, ATTR_PAGE_SCALETO, false ) && rScaleToItem.IsValid() ) + { + maData.mnFitToWidth = rScaleToItem.GetWidth(); + maData.mnFitToHeight = rScaleToItem.GetHeight(); + maData.mbFitToPages = true; + } + else if( ScfTools::CheckItem( rItemSet, ATTR_PAGE_SCALETOPAGES, false ) && nPages ) + { + maData.mnFitToWidth = 1; + maData.mnFitToHeight = nPages; + maData.mbFitToPages = true; + } + else if( nScale ) + { + maData.mnScaling = nScale; + maData.mbFitToPages = false; + } + + maData.mxBrushItem.reset( new SvxBrushItem( rItemSet.Get( ATTR_BACKGROUND ) ) ); + maData.mbUseEvenHF = false; + maData.mbUseFirstHF = false; + + // *** header and footer *** + + XclExpHFConverter aHFConv( GetRoot() ); + + // header + const SfxItemSet& rHdrItemSet = rItemSet.Get( ATTR_PAGE_HEADERSET ).GetItemSet(); + if( rHdrItemSet.Get( ATTR_PAGE_ON ).GetValue() ) + { + const ScPageHFItem& rHFItem = rItemSet.Get( ATTR_PAGE_HEADERRIGHT ); + aHFConv.GenerateString( rHFItem.GetLeftArea(), rHFItem.GetCenterArea(), rHFItem.GetRightArea() ); + maData.maHeader = aHFConv.GetHFString(); + if ( rHdrItemSet.HasItem(ATTR_PAGE_SHARED) && !rHdrItemSet.Get(ATTR_PAGE_SHARED).GetValue()) + { + const ScPageHFItem& rHFItemLeft = rItemSet.Get( ATTR_PAGE_HEADERLEFT ); + aHFConv.GenerateString( rHFItemLeft.GetLeftArea(), rHFItemLeft.GetCenterArea(), rHFItemLeft.GetRightArea() ); + maData.maHeaderEven = aHFConv.GetHFString(); + maData.mbUseEvenHF = true; + } + else + { + // If maData.mbUseEvenHF become true, then we will need a copy of maHeader in maHeaderEven. + maData.maHeaderEven = maData.maHeader; + } + if (rHdrItemSet.HasItem(ATTR_PAGE_SHARED_FIRST) && !rHdrItemSet.Get(ATTR_PAGE_SHARED_FIRST).GetValue()) + { + const ScPageHFItem& rHFItemFirst = rItemSet.Get( ATTR_PAGE_HEADERFIRST ); + aHFConv.GenerateString( rHFItemFirst.GetLeftArea(), rHFItemFirst.GetCenterArea(), rHFItemFirst.GetRightArea() ); + maData.maHeaderFirst = aHFConv.GetHFString(); + maData.mbUseFirstHF = true; + } + else + { + maData.maHeaderFirst = maData.maHeader; + } + // header height (Excel excludes header from top margin) + sal_Int32 nHdrHeight = rHdrItemSet.Get( ATTR_PAGE_DYNAMIC ).GetValue() ? + // dynamic height: calculate header height, add header <-> sheet area distance + (aHFConv.GetTotalHeight() + rHdrItemSet.Get( ATTR_ULSPACE ).GetLower()) : + // static height: ATTR_PAGE_SIZE already includes header <-> sheet area distance + static_cast< sal_Int32 >( rHdrItemSet.Get( ATTR_PAGE_SIZE ).GetSize().Height() ); + maData.mfHeaderMargin = maData.mfTopMargin; + maData.mfTopMargin += XclTools::GetInchFromTwips( nHdrHeight ); + } + + // footer + const SfxItemSet& rFtrItemSet = rItemSet.Get( ATTR_PAGE_FOOTERSET ).GetItemSet(); + if( rFtrItemSet.Get( ATTR_PAGE_ON ).GetValue() ) + { + const ScPageHFItem& rHFItem = rItemSet.Get( ATTR_PAGE_FOOTERRIGHT ); + aHFConv.GenerateString( rHFItem.GetLeftArea(), rHFItem.GetCenterArea(), rHFItem.GetRightArea() ); + maData.maFooter = aHFConv.GetHFString(); + if (rFtrItemSet.HasItem(ATTR_PAGE_SHARED) && !rFtrItemSet.Get(ATTR_PAGE_SHARED).GetValue()) + { + const ScPageHFItem& rHFItemLeft = rItemSet.Get( ATTR_PAGE_FOOTERLEFT ); + aHFConv.GenerateString( rHFItemLeft.GetLeftArea(), rHFItemLeft.GetCenterArea(), rHFItemLeft.GetRightArea() ); + maData.maFooterEven = aHFConv.GetHFString(); + maData.mbUseEvenHF = true; + } + else + { + maData.maFooterEven = maData.maFooter; + } + if (rFtrItemSet.HasItem(ATTR_PAGE_SHARED_FIRST) && !rFtrItemSet.Get(ATTR_PAGE_SHARED_FIRST).GetValue()) + { + const ScPageHFItem& rHFItemFirst = rItemSet.Get( ATTR_PAGE_FOOTERFIRST ); + aHFConv.GenerateString( rHFItemFirst.GetLeftArea(), rHFItemFirst.GetCenterArea(), rHFItemFirst.GetRightArea() ); + maData.maFooterFirst = aHFConv.GetHFString(); + maData.mbUseFirstHF = true; + } + else + { + maData.maFooterFirst = maData.maFooter; + } + // footer height (Excel excludes footer from bottom margin) + sal_Int32 nFtrHeight = rFtrItemSet.Get( ATTR_PAGE_DYNAMIC ).GetValue() ? + // dynamic height: calculate footer height, add sheet area <-> footer distance + (aHFConv.GetTotalHeight() + rFtrItemSet.Get( ATTR_ULSPACE ).GetUpper()) : + // static height: ATTR_PAGE_SIZE already includes sheet area <-> footer distance + static_cast< sal_Int32 >( rFtrItemSet.Get( ATTR_PAGE_SIZE ).GetSize().Height() ); + maData.mfFooterMargin = maData.mfBottomMargin; + maData.mfBottomMargin += XclTools::GetInchFromTwips( nFtrHeight ); + } + } + + // *** page breaks *** + + set aRowBreaks; + rDoc.GetAllRowBreaks(aRowBreaks, nScTab, false, true); + + SCROW const nMaxRow = numeric_limits::max(); + for (const SCROW nRow : aRowBreaks) + { + if (nRow > nMaxRow) + break; + + maData.maHorPageBreaks.push_back(nRow); + } + + if (maData.maHorPageBreaks.size() > 1026) + { + // Excel allows only up to 1026 page breaks. Trim any excess page breaks. + ScfUInt16Vec::iterator itr = maData.maHorPageBreaks.begin(); + ::std::advance(itr, 1026); + maData.maHorPageBreaks.erase(itr, maData.maHorPageBreaks.end()); + } + + set aColBreaks; + rDoc.GetAllColBreaks(aColBreaks, nScTab, false, true); + for (const auto& rColBreak : aColBreaks) + maData.maVerPageBreaks.push_back(rColBreak); +} + +namespace { + +class XclExpXmlStartHeaderFooterElementRecord : public XclExpXmlElementRecord +{ +public: + explicit XclExpXmlStartHeaderFooterElementRecord(sal_Int32 const nElement, bool const bDifferentOddEven = false, bool const bDifferentFirst = false) + : XclExpXmlElementRecord(nElement), mbDifferentOddEven(bDifferentOddEven), mbDifferentFirst(bDifferentFirst) {} + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +private: + bool mbDifferentOddEven; + bool mbDifferentFirst; +}; + +} + +void XclExpXmlStartHeaderFooterElementRecord::SaveXml(XclExpXmlStream& rStrm) +{ + // OOXTODO: we currently only emit oddHeader/oddFooter elements, and + // do not support the first/even/odd page distinction. + sax_fastparser::FSHelperPtr& rStream = rStrm.GetCurrentStream(); + rStream->startElement( mnElement, + // OOXTODO: XML_alignWithMargins, + XML_differentFirst, mbDifferentFirst ? "true" : "false", + XML_differentOddEven, mbDifferentOddEven ? "true" : "false" + // OOXTODO: XML_scaleWithDoc + ); +} + +void XclExpPageSettings::Save( XclExpStream& rStrm ) +{ + XclExpBoolRecord( EXC_ID_PRINTHEADERS, maData.mbPrintHeadings ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_PRINTGRIDLINES, maData.mbPrintGrid ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_GRIDSET, true ).Save( rStrm ); + XclExpPageBreaks( EXC_ID_HORPAGEBREAKS, maData.maHorPageBreaks, static_cast< sal_uInt16 >( GetXclMaxPos().Col() ) ).Save( rStrm ); + XclExpPageBreaks( EXC_ID_VERPAGEBREAKS, maData.maVerPageBreaks, static_cast< sal_uInt16 >( GetXclMaxPos().Row() ) ).Save( rStrm ); + XclExpHeaderFooter( EXC_ID_HEADER, maData.maHeader ).Save( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER, maData.maFooter ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_HCENTER, maData.mbHorCenter ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_VCENTER, maData.mbVerCenter ).Save( rStrm ); + XclExpDoubleRecord( EXC_ID_LEFTMARGIN, maData.mfLeftMargin ).Save( rStrm ); + XclExpDoubleRecord( EXC_ID_RIGHTMARGIN, maData.mfRightMargin ).Save( rStrm ); + XclExpDoubleRecord( EXC_ID_TOPMARGIN, maData.mfTopMargin ).Save( rStrm ); + XclExpDoubleRecord( EXC_ID_BOTTOMMARGIN, maData.mfBottomMargin ).Save( rStrm ); + XclExpSetup( maData ).Save( rStrm ); + + if( (GetBiff() == EXC_BIFF8) && maData.mxBrushItem ) + if( const Graphic* pGraphic = maData.mxBrushItem->GetGraphic() ) + XclExpImgData( *pGraphic, EXC_ID8_IMGDATA ).Save( rStrm ); +} + +void XclExpPageSettings::SaveXml( XclExpXmlStream& rStrm ) +{ + XclExpXmlStartSingleElementRecord( XML_printOptions ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_PRINTHEADERS, maData.mbPrintHeadings, XML_headings ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_PRINTGRIDLINES, maData.mbPrintGrid, XML_gridLines ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_GRIDSET, true, XML_gridLinesSet ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_HCENTER, maData.mbHorCenter, XML_horizontalCentered ).SaveXml( rStrm ); + XclExpBoolRecord( EXC_ID_VCENTER, maData.mbVerCenter, XML_verticalCentered ).SaveXml( rStrm ); + XclExpXmlEndSingleElementRecord().SaveXml( rStrm ); // XML_printOptions + + XclExpXmlStartSingleElementRecord( XML_pageMargins ).SaveXml( rStrm ); + XclExpDoubleRecord( EXC_ID_LEFTMARGIN, maData.mfLeftMargin ).SetAttribute( XML_left )->SaveXml( rStrm ); + XclExpDoubleRecord( EXC_ID_RIGHTMARGIN, maData.mfRightMargin ).SetAttribute( XML_right )->SaveXml( rStrm ); + XclExpDoubleRecord( EXC_ID_TOPMARGIN, maData.mfTopMargin ).SetAttribute( XML_top )->SaveXml( rStrm ); + XclExpDoubleRecord( EXC_ID_BOTTOMMARGIN, maData.mfBottomMargin ).SetAttribute( XML_bottom )->SaveXml( rStrm ); + XclExpDoubleRecord( 0, maData.mfHeaderMargin).SetAttribute( XML_header )->SaveXml( rStrm ); + XclExpDoubleRecord( 0, maData.mfFooterMargin).SetAttribute( XML_footer )->SaveXml( rStrm ); + XclExpXmlEndSingleElementRecord().SaveXml( rStrm ); // XML_pageMargins + + XclExpSetup( maData ).SaveXml( rStrm ); + + XclExpXmlStartHeaderFooterElementRecord(XML_headerFooter, maData.mbUseEvenHF, maData.mbUseFirstHF).SaveXml(rStrm); + XclExpHeaderFooter( EXC_ID_HEADER, maData.maHeader ).SaveXml( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER, maData.maFooter ).SaveXml( rStrm ); + if (maData.mbUseEvenHF) + { + XclExpHeaderFooter( EXC_ID_HEADER_EVEN, maData.maHeaderEven ).SaveXml( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER_EVEN, maData.maFooterEven ).SaveXml( rStrm ); + } + if (maData.mbUseFirstHF) + { + XclExpHeaderFooter( EXC_ID_HEADER_FIRST, maData.maHeaderFirst ).SaveXml( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER_FIRST, maData.maFooterFirst ).SaveXml( rStrm ); + } + XclExpXmlEndElementRecord( XML_headerFooter ).SaveXml( rStrm ); + + XclExpPageBreaks( EXC_ID_HORPAGEBREAKS, maData.maHorPageBreaks, + static_cast< sal_uInt16 >( GetXclMaxPos().Col() ) ).SaveXml( rStrm ); + XclExpPageBreaks( EXC_ID_VERPAGEBREAKS, maData.maVerPageBreaks, + static_cast< sal_uInt16 >( GetXclMaxPos().Row() ) ).SaveXml( rStrm ); +} + +XclExpImgData* XclExpPageSettings::getGraphicExport() +{ + if( const Graphic* pGraphic = maData.mxBrushItem->GetGraphic() ) + return new XclExpImgData( *pGraphic, EXC_ID8_IMGDATA ); + + return nullptr; +} + +XclExpChartPageSettings::XclExpChartPageSettings( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpChartPageSettings::Save( XclExpStream& rStrm ) +{ + XclExpHeaderFooter( EXC_ID_HEADER, maData.maHeader ).Save( rStrm ); + XclExpHeaderFooter( EXC_ID_FOOTER, maData.maFooter ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_HCENTER, maData.mbHorCenter ).Save( rStrm ); + XclExpBoolRecord( EXC_ID_VCENTER, maData.mbVerCenter ).Save( rStrm ); + XclExpSetup( maData ).Save( rStrm ); + XclExpUInt16Record( EXC_ID_PRINTSIZE, EXC_PRINTSIZE_FULL ).Save( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xepivot.cxx b/sc/source/filter/excel/xepivot.cxx new file mode 100644 index 000000000..34564e30e --- /dev/null +++ b/sc/source/filter/excel/xepivot.cxx @@ -0,0 +1,1702 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::oox; + +using ::com::sun::star::sheet::DataPilotFieldOrientation; +using ::com::sun::star::sheet::DataPilotFieldOrientation_ROW; +using ::com::sun::star::sheet::DataPilotFieldOrientation_COLUMN; +using ::com::sun::star::sheet::DataPilotFieldOrientation_PAGE; +using ::com::sun::star::sheet::DataPilotFieldOrientation_DATA; +using ::com::sun::star::sheet::DataPilotFieldSortInfo; +using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo; +using ::com::sun::star::sheet::DataPilotFieldLayoutInfo; +using ::com::sun::star::sheet::DataPilotFieldReference; + +// Pivot cache + +namespace { + +// constants to track occurrence of specific data types +const sal_uInt16 EXC_PCITEM_DATA_STRING = 0x0001; /// String, empty, boolean, error. +const sal_uInt16 EXC_PCITEM_DATA_DOUBLE = 0x0002; /// Double with fraction. +const sal_uInt16 EXC_PCITEM_DATA_INTEGER = 0x0004; /// Integer, double without fraction. +const sal_uInt16 EXC_PCITEM_DATA_DATE = 0x0008; /// Date, time, date/time. + +/** Maps a bitfield consisting of EXC_PCITEM_DATA_* flags above to SXFIELD data type bitfield. */ +const sal_uInt16 spnPCItemFlags[] = +{ // STR DBL INT DAT + EXC_SXFIELD_DATA_NONE, + EXC_SXFIELD_DATA_STR, // x + EXC_SXFIELD_DATA_INT, // x + EXC_SXFIELD_DATA_STR_INT, // x x + EXC_SXFIELD_DATA_DBL, // x + EXC_SXFIELD_DATA_STR_DBL, // x x + EXC_SXFIELD_DATA_INT, // x x + EXC_SXFIELD_DATA_STR_INT, // x x x + EXC_SXFIELD_DATA_DATE, // x + EXC_SXFIELD_DATA_DATE_STR, // x x + EXC_SXFIELD_DATA_DATE_NUM, // x x + EXC_SXFIELD_DATA_DATE_STR, // x x x + EXC_SXFIELD_DATA_DATE_NUM, // x x + EXC_SXFIELD_DATA_DATE_STR, // x x x + EXC_SXFIELD_DATA_DATE_NUM, // x x x + EXC_SXFIELD_DATA_DATE_STR // x x x x +}; + +} // namespace + +XclExpPCItem::XclExpPCItem( const OUString& rText ) : + XclExpRecord( (!rText.isEmpty()) ? EXC_ID_SXSTRING : EXC_ID_SXEMPTY, 0 ), + mnTypeFlag( EXC_PCITEM_DATA_STRING ) +{ + if( !rText.isEmpty() ) + SetText( rText ); + else + SetEmpty(); +} + +XclExpPCItem::XclExpPCItem( double fValue, const OUString& rText ) : + XclExpRecord( EXC_ID_SXDOUBLE, 8 ) +{ + SetDouble( fValue, rText ); + mnTypeFlag = (fValue - floor( fValue ) == 0.0) ? + EXC_PCITEM_DATA_INTEGER : EXC_PCITEM_DATA_DOUBLE; +} + +XclExpPCItem::XclExpPCItem( const DateTime& rDateTime, const OUString& rText ) : + XclExpRecord( EXC_ID_SXDATETIME, 8 ) +{ + SetDateTime( rDateTime, rText ); + mnTypeFlag = EXC_PCITEM_DATA_DATE; +} + +XclExpPCItem::XclExpPCItem( sal_Int16 nValue ) : + XclExpRecord( EXC_ID_SXINTEGER, 2 ), + mnTypeFlag( EXC_PCITEM_DATA_INTEGER ) +{ + SetInteger( nValue ); +} + +XclExpPCItem::XclExpPCItem( bool bValue, const OUString& rText ) : + XclExpRecord( EXC_ID_SXBOOLEAN, 2 ), + mnTypeFlag( EXC_PCITEM_DATA_STRING ) +{ + SetBool( bValue, rText ); +} + +bool XclExpPCItem::EqualsText( std::u16string_view rText ) const +{ + return rText.empty() ? IsEmpty() : (GetText() && (*GetText() == rText)); +} + +bool XclExpPCItem::EqualsDouble( double fValue ) const +{ + return GetDouble() && (*GetDouble() == fValue); +} + +bool XclExpPCItem::EqualsDateTime( const DateTime& rDateTime ) const +{ + return GetDateTime() && (*GetDateTime() == rDateTime); +} + +bool XclExpPCItem::EqualsBool( bool bValue ) const +{ + return GetBool() && (*GetBool() == bValue); +} + +void XclExpPCItem::WriteBody( XclExpStream& rStrm ) +{ + if( const OUString* pText = GetText() ) + { + rStrm << XclExpString( *pText ); + } + else if( const double* pfValue = GetDouble() ) + { + rStrm << *pfValue; + } + else if( const sal_Int16* pnValue = GetInteger() ) + { + rStrm << *pnValue; + } + else if( const DateTime* pDateTime = GetDateTime() ) + { + sal_uInt16 nYear = static_cast< sal_uInt16 >( pDateTime->GetYear() ); + sal_uInt16 nMonth = pDateTime->GetMonth(); + sal_uInt8 nDay = static_cast< sal_uInt8 >( pDateTime->GetDay() ); + sal_uInt8 nHour = static_cast< sal_uInt8 >( pDateTime->GetHour() ); + sal_uInt8 nMin = static_cast< sal_uInt8 >( pDateTime->GetMin() ); + sal_uInt8 nSec = static_cast< sal_uInt8 >( pDateTime->GetSec() ); + if( nYear < 1900 ) { nYear = 1900; nMonth = 1; nDay = 0; } + rStrm << nYear << nMonth << nDay << nHour << nMin << nSec; + } + else if( const bool* pbValue = GetBool() ) + { + rStrm << static_cast< sal_uInt16 >( *pbValue ? 1 : 0 ); + } + else + { + // nothing to do for SXEMPTY + OSL_ENSURE( IsEmpty(), "XclExpPCItem::WriteBody - no data found" ); + } +} + +XclExpPCField::XclExpPCField( + const XclExpRoot& rRoot, sal_uInt16 nFieldIdx, + const ScDPObject& rDPObj, const ScRange& rRange ) : + XclExpRecord( EXC_ID_SXFIELD ), + XclPCField( EXC_PCFIELD_STANDARD, nFieldIdx ), + XclExpRoot( rRoot ), + mnTypeFlags( 0 ) +{ + // general settings for the standard field, insert all items from source range + InitStandardField( rRange ); + + // add special settings for inplace numeric grouping + if( const ScDPSaveData* pSaveData = rDPObj.GetSaveData() ) + { + if( const ScDPDimensionSaveData* pSaveDimData = pSaveData->GetExistingDimensionData() ) + { + if( const ScDPSaveNumGroupDimension* pNumGroupDim = pSaveDimData->GetNumGroupDim( GetFieldName() ) ) + { + const ScDPNumGroupInfo& rNumInfo = pNumGroupDim->GetInfo(); + const ScDPNumGroupInfo& rDateInfo = pNumGroupDim->GetDateInfo(); + OSL_ENSURE( !rNumInfo.mbEnable || !rDateInfo.mbEnable, + "XclExpPCField::XclExpPCField - numeric and date grouping enabled" ); + + if( rNumInfo.mbEnable ) + InitNumGroupField( rDPObj, rNumInfo ); + else if( rDateInfo.mbEnable ) + InitDateGroupField( rDPObj, rDateInfo, pNumGroupDim->GetDatePart() ); + } + } + } + + // final settings (flags, item numbers) + Finalize(); +} + +XclExpPCField::XclExpPCField( + const XclExpRoot& rRoot, sal_uInt16 nFieldIdx, + const ScDPObject& rDPObj, const ScDPSaveGroupDimension& rGroupDim, const XclExpPCField& rBaseField ) : + XclExpRecord( EXC_ID_SXFIELD ), + XclPCField( EXC_PCFIELD_STDGROUP, nFieldIdx ), + XclExpRoot( rRoot ), + mnTypeFlags( 0 ) +{ + // add base field info (always using first base field, not predecessor of this field) *** + OSL_ENSURE( rBaseField.GetFieldName() == rGroupDim.GetSourceDimName(), + "XclExpPCField::FillFromGroup - wrong base cache field" ); + maFieldInfo.maName = rGroupDim.GetGroupDimName(); + maFieldInfo.mnGroupBase = rBaseField.GetFieldIndex(); + + // add standard group info or date group info + const ScDPNumGroupInfo& rDateInfo = rGroupDim.GetDateInfo(); + if( rDateInfo.mbEnable && (rGroupDim.GetDatePart() != 0) ) + InitDateGroupField( rDPObj, rDateInfo, rGroupDim.GetDatePart() ); + else + InitStdGroupField( rBaseField, rGroupDim ); + + // final settings (flags, item numbers) + Finalize(); +} + +XclExpPCField::~XclExpPCField() +{ +} + +void XclExpPCField::SetGroupChildField( const XclExpPCField& rChildField ) +{ + OSL_ENSURE( !::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ), + "XclExpPCField::SetGroupChildIndex - field already has a grouping child field" ); + ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ); + maFieldInfo.mnGroupChild = rChildField.GetFieldIndex(); +} + +sal_uInt16 XclExpPCField::GetItemCount() const +{ + return static_cast< sal_uInt16 >( GetVisItemList().GetSize() ); +} + +const XclExpPCItem* XclExpPCField::GetItem( sal_uInt16 nItemIdx ) const +{ + return GetVisItemList().GetRecord( nItemIdx ); +} + +sal_uInt16 XclExpPCField::GetItemIndex( std::u16string_view rItemName ) const +{ + const XclExpPCItemList& rItemList = GetVisItemList(); + for( size_t nPos = 0, nSize = rItemList.GetSize(); nPos < nSize; ++nPos ) + if( rItemList.GetRecord( nPos )->ConvertToText() == rItemName ) + return static_cast< sal_uInt16 >( nPos ); + return EXC_PC_NOITEM; +} + +std::size_t XclExpPCField::GetIndexSize() const +{ + return Has16BitIndexes() ? 2 : 1; +} + +void XclExpPCField::WriteIndex( XclExpStream& rStrm, sal_uInt32 nSrcRow ) const +{ + // only standard fields write item indexes + if( nSrcRow < maIndexVec.size() ) + { + sal_uInt16 nIndex = maIndexVec[ nSrcRow ]; + if( Has16BitIndexes() ) + rStrm << nIndex; + else + rStrm << static_cast< sal_uInt8 >( nIndex ); + } +} + +void XclExpPCField::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( IsSupportedField(), "XclExpPCField::Save - unknown field type" ); + // SXFIELD + XclExpRecord::Save( rStrm ); + // SXFDBTYPE + XclExpUInt16Record( EXC_ID_SXFDBTYPE, EXC_SXFDBTYPE_DEFAULT ).Save( rStrm ); + // list of grouping items + maGroupItemList.Save( rStrm ); + // SXGROUPINFO + WriteSxgroupinfo( rStrm ); + // SXNUMGROUP and additional grouping items (grouping limit settings) + WriteSxnumgroup( rStrm ); + // list of original items + maOrigItemList.Save( rStrm ); +} + +// private -------------------------------------------------------------------- + +const XclExpPCField::XclExpPCItemList& XclExpPCField::GetVisItemList() const +{ + OSL_ENSURE( IsStandardField() == maGroupItemList.IsEmpty(), + "XclExpPCField::GetVisItemList - unexpected additional items in standard field" ); + return IsStandardField() ? maOrigItemList : maGroupItemList; +} + +void XclExpPCField::InitStandardField( const ScRange& rRange ) +{ + OSL_ENSURE( IsStandardField(), "XclExpPCField::InitStandardField - only for standard fields" ); + OSL_ENSURE( rRange.aStart.Col() == rRange.aEnd.Col(), "XclExpPCField::InitStandardField - cell range with multiple columns" ); + + ScDocument& rDoc = GetDoc(); + SvNumberFormatter& rFormatter = GetFormatter(); + + // field name is in top cell of the range + ScAddress aPos( rRange.aStart ); + maFieldInfo.maName = rDoc.GetString(aPos.Col(), aPos.Row(), aPos.Tab()); + // #i76047# maximum field name length in pivot cache is 255 + if (maFieldInfo.maName.getLength() > EXC_PC_MAXSTRLEN) + maFieldInfo.maName = maFieldInfo.maName.copy(0, EXC_PC_MAXSTRLEN); + + // loop over all cells, create pivot cache items + for( aPos.IncRow(); (aPos.Row() <= rRange.aEnd.Row()) && (maOrigItemList.GetSize() < EXC_PC_MAXITEMCOUNT); aPos.IncRow() ) + { + OUString aText = rDoc.GetString(aPos.Col(), aPos.Row(), aPos.Tab()); + if( rDoc.HasValueData( aPos.Col(), aPos.Row(), aPos.Tab() ) ) + { + double fValue = rDoc.GetValue( aPos ); + SvNumFormatType nFmtType = rFormatter.GetType( rDoc.GetNumberFormat( rDoc.GetNonThreadedContext(), aPos ) ); + if( nFmtType == SvNumFormatType::LOGICAL ) + InsertOrigBoolItem( fValue != 0, aText ); + else if( nFmtType & SvNumFormatType::DATETIME ) + InsertOrigDateTimeItem( GetDateTimeFromDouble( ::std::max( fValue, 0.0 ) ), aText ); + else + InsertOrigDoubleItem( fValue, aText ); + } + else + { + InsertOrigTextItem( aText ); + } + } +} + +void XclExpPCField::InitStdGroupField( const XclExpPCField& rBaseField, const ScDPSaveGroupDimension& rGroupDim ) +{ + OSL_ENSURE( IsGroupField(), "XclExpPCField::InitStdGroupField - only for standard grouping fields" ); + + maFieldInfo.mnBaseItems = rBaseField.GetItemCount(); + maGroupOrder.resize( maFieldInfo.mnBaseItems, EXC_PC_NOITEM ); + + // loop over all groups of this field + for( tools::Long nGroupIdx = 0, nGroupCount = rGroupDim.GetGroupCount(); nGroupIdx < nGroupCount; ++nGroupIdx ) + { + const ScDPSaveGroupItem& rGroupItem = rGroupDim.GetGroupByIndex( nGroupIdx ); + // the index of the new item containing the grouping name + sal_uInt16 nGroupItemIdx = EXC_PC_NOITEM; + // loop over all elements of one group + for( size_t nElemIdx = 0, nElemCount = rGroupItem.GetElementCount(); nElemIdx < nElemCount; ++nElemIdx ) + { + if (const OUString* pElemName = rGroupItem.GetElementByIndex(nElemIdx)) + { + // try to find the item that is part of the group in the base field + sal_uInt16 nBaseItemIdx = rBaseField.GetItemIndex( *pElemName ); + if( nBaseItemIdx < maFieldInfo.mnBaseItems ) + { + // add group name item only if there are any valid base items + if( nGroupItemIdx == EXC_PC_NOITEM ) + nGroupItemIdx = InsertGroupItem( new XclExpPCItem( rGroupItem.GetGroupName() ) ); + maGroupOrder[ nBaseItemIdx ] = nGroupItemIdx; + } + } + } + } + + // add items and base item indexes of all ungrouped elements + for( sal_uInt16 nBaseItemIdx = 0; nBaseItemIdx < maFieldInfo.mnBaseItems; ++nBaseItemIdx ) + // items that are not part of a group still have the EXC_PC_NOITEM entry + if( maGroupOrder[ nBaseItemIdx ] == EXC_PC_NOITEM ) + // try to find the base item + if( const XclExpPCItem* pBaseItem = rBaseField.GetItem( nBaseItemIdx ) ) + // create a clone of the base item, insert its index into item order list + maGroupOrder[ nBaseItemIdx ] = InsertGroupItem( new XclExpPCItem( *pBaseItem ) ); +} + +void XclExpPCField::InitNumGroupField( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rNumInfo ) +{ + OSL_ENSURE( IsStandardField(), "XclExpPCField::InitNumGroupField - only for standard fields" ); + OSL_ENSURE( rNumInfo.mbEnable, "XclExpPCField::InitNumGroupField - numeric grouping not enabled" ); + + // new field type, date type, limit settings (min/max/step/auto) + if( rNumInfo.mbDateValues ) + { + // special case: group by days with step count + meFieldType = EXC_PCFIELD_DATEGROUP; + maNumGroupInfo.SetScDateType( css::sheet::DataPilotFieldGroupBy::DAYS ); + SetDateGroupLimit( rNumInfo, true ); + } + else + { + meFieldType = EXC_PCFIELD_NUMGROUP; + maNumGroupInfo.SetNumType(); + SetNumGroupLimit( rNumInfo ); + } + + // generate visible items + InsertNumDateGroupItems( rDPObj, rNumInfo ); +} + +void XclExpPCField::InitDateGroupField( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rDateInfo, sal_Int32 nDatePart ) +{ + OSL_ENSURE( IsStandardField() || IsStdGroupField(), "XclExpPCField::InitDateGroupField - only for standard fields" ); + OSL_ENSURE( rDateInfo.mbEnable, "XclExpPCField::InitDateGroupField - date grouping not enabled" ); + + // new field type + meFieldType = IsStandardField() ? EXC_PCFIELD_DATEGROUP : EXC_PCFIELD_DATECHILD; + + // date type, limit settings (min/max/step/auto) + maNumGroupInfo.SetScDateType( nDatePart ); + SetDateGroupLimit( rDateInfo, false ); + + // generate visible items + InsertNumDateGroupItems( rDPObj, rDateInfo, nDatePart ); +} + +void XclExpPCField::InsertItemArrayIndex( size_t nListPos ) +{ + OSL_ENSURE( IsStandardField(), "XclExpPCField::InsertItemArrayIndex - only for standard fields" ); + maIndexVec.push_back( static_cast< sal_uInt16 >( nListPos ) ); +} + +void XclExpPCField::InsertOrigItem( XclExpPCItem* pNewItem ) +{ + size_t nItemIdx = maOrigItemList.GetSize(); + maOrigItemList.AppendNewRecord( pNewItem ); + InsertItemArrayIndex( nItemIdx ); + mnTypeFlags |= pNewItem->GetTypeFlag(); +} + +void XclExpPCField::InsertOrigTextItem( const OUString& rText ) +{ + size_t nPos = 0; + bool bFound = false; + // #i76047# maximum item text length in pivot cache is 255 + OUString aShortText = rText.copy( 0, ::std::min(rText.getLength(), EXC_PC_MAXSTRLEN ) ); + for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos ) + if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsText( aShortText )) ) + InsertItemArrayIndex( nPos ); + if( !bFound ) + InsertOrigItem( new XclExpPCItem( aShortText ) ); +} + +void XclExpPCField::InsertOrigDoubleItem( double fValue, const OUString& rText ) +{ + size_t nPos = 0; + bool bFound = false; + for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos ) + if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsDouble( fValue )) ) + InsertItemArrayIndex( nPos ); + if( !bFound ) + InsertOrigItem( new XclExpPCItem( fValue, rText ) ); +} + +void XclExpPCField::InsertOrigDateTimeItem( const DateTime& rDateTime, const OUString& rText ) +{ + size_t nPos = 0; + bool bFound = false; + for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos ) + if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsDateTime( rDateTime )) ) + InsertItemArrayIndex( nPos ); + if( !bFound ) + InsertOrigItem( new XclExpPCItem( rDateTime, rText ) ); +} + +void XclExpPCField::InsertOrigBoolItem( bool bValue, const OUString& rText ) +{ + size_t nPos = 0; + bool bFound = false; + for( size_t nSize = maOrigItemList.GetSize(); !bFound && (nPos < nSize); ++nPos ) + if( (bFound = maOrigItemList.GetRecord( nPos )->EqualsBool( bValue )) ) + InsertItemArrayIndex( nPos ); + if( !bFound ) + InsertOrigItem( new XclExpPCItem( bValue, rText ) ); +} + +sal_uInt16 XclExpPCField::InsertGroupItem( XclExpPCItem* pNewItem ) +{ + maGroupItemList.AppendNewRecord( pNewItem ); + return static_cast< sal_uInt16 >( maGroupItemList.GetSize() - 1 ); +} + +void XclExpPCField::InsertNumDateGroupItems( const ScDPObject& rDPObj, const ScDPNumGroupInfo& rNumInfo, sal_Int32 nDatePart ) +{ + OSL_ENSURE( rDPObj.GetSheetDesc(), "XclExpPCField::InsertNumDateGroupItems - cannot generate element list" ); + const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc(); + if(!pSrcDesc) + return; + + // get the string collection with original source elements + const ScDPSaveData* pSaveData = rDPObj.GetSaveData(); + const ScDPDimensionSaveData* pDimData = nullptr; + if (pSaveData) + pDimData = pSaveData->GetExistingDimensionData(); + + const ScDPCache* pCache = pSrcDesc->CreateCache(pDimData); + if (!pCache) + return; + + ScSheetDPData aDPData(&GetDoc(), *pSrcDesc, *pCache); + tools::Long nDim = GetFieldIndex(); + // get the string collection with generated grouping elements + ScDPNumGroupDimension aTmpDim( rNumInfo ); + if( nDatePart != 0 ) + aTmpDim.SetDateDimension(); + const std::vector& aMemberIds = aTmpDim.GetNumEntries( + static_cast(nDim), pCache); + for (SCROW nMemberId : aMemberIds) + { + const ScDPItemData* pData = aDPData.GetMemberById(nDim, nMemberId); + if ( pData ) + { + OUString aStr = pCache->GetFormattedString(nDim, *pData, false); + InsertGroupItem(new XclExpPCItem(aStr)); + } + } +} + +void XclExpPCField::SetNumGroupLimit( const ScDPNumGroupInfo& rNumInfo ) +{ + ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN, rNumInfo.mbAutoStart ); + ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX, rNumInfo.mbAutoEnd ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfStart ) ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfEnd ) ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( rNumInfo.mfStep ) ); +} + +void XclExpPCField::SetDateGroupLimit( const ScDPNumGroupInfo& rDateInfo, bool bUseStep ) +{ + ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN, rDateInfo.mbAutoStart ); + ::set_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX, rDateInfo.mbAutoEnd ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( GetDateTimeFromDouble( rDateInfo.mfStart ) ) ); + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( GetDateTimeFromDouble( rDateInfo.mfEnd ) ) ); + sal_Int16 nStep = bUseStep ? limit_cast< sal_Int16 >( rDateInfo.mfStep, 1, SAL_MAX_INT16 ) : 1; + maNumGroupLimits.AppendNewRecord( new XclExpPCItem( nStep ) ); +} + +void XclExpPCField::Finalize() +{ + // flags + ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASITEMS, !GetVisItemList().IsEmpty() ); + // Excel writes long indexes even for 0x0100 items (indexes from 0x00 to 0xFF) + ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_16BIT, maOrigItemList.GetSize() >= 0x0100 ); + ::set_flag( maFieldInfo.mnFlags, EXC_SXFIELD_NUMGROUP, IsNumGroupField() || IsDateGroupField() ); + /* mnTypeFlags is updated in all Insert***Item() functions. Now the flags + for the current combination of item types is added to the flags. */ + ::set_flag( maFieldInfo.mnFlags, spnPCItemFlags[ mnTypeFlags ] ); + + // item count fields + maFieldInfo.mnVisItems = static_cast< sal_uInt16 >( GetVisItemList().GetSize() ); + maFieldInfo.mnGroupItems = static_cast< sal_uInt16 >( maGroupItemList.GetSize() ); + // maFieldInfo.mnBaseItems set in InitStdGroupField() + maFieldInfo.mnOrigItems = static_cast< sal_uInt16 >( maOrigItemList.GetSize() ); +} + +void XclExpPCField::WriteSxnumgroup( XclExpStream& rStrm ) +{ + if( IsNumGroupField() || IsDateGroupField() ) + { + // SXNUMGROUP record + rStrm.StartRecord( EXC_ID_SXNUMGROUP, 2 ); + rStrm << maNumGroupInfo; + rStrm.EndRecord(); + + // limits (min/max/step) for numeric grouping + OSL_ENSURE( maNumGroupLimits.GetSize() == 3, + "XclExpPCField::WriteSxnumgroup - missing numeric grouping limits" ); + maNumGroupLimits.Save( rStrm ); + } +} + +void XclExpPCField::WriteSxgroupinfo( XclExpStream& rStrm ) +{ + OSL_ENSURE( IsStdGroupField() != maGroupOrder.empty(), + "XclExpPCField::WriteSxgroupinfo - missing grouping info" ); + if( IsStdGroupField() && !maGroupOrder.empty() ) + { + rStrm.StartRecord( EXC_ID_SXGROUPINFO, 2 * maGroupOrder.size() ); + for( const auto& rItem : maGroupOrder ) + rStrm << rItem; + rStrm.EndRecord(); + } +} + +void XclExpPCField::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maFieldInfo; +} + +XclExpPivotCache::XclExpPivotCache( const XclExpRoot& rRoot, const ScDPObject& rDPObj, sal_uInt16 nListIdx ) : + XclExpRoot( rRoot ), + mnListIdx( nListIdx ), + mbValid( false ) +{ + // source from sheet only + const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc(); + if(!pSrcDesc) + return; + + /* maOrigSrcRange: Range received from the DataPilot object. + maExpSrcRange: Range written to the DCONREF record. + maDocSrcRange: Range used to get source data from Calc document. + This range may be shorter than maExpSrcRange to improve export + performance (#i22541#). */ + maOrigSrcRange = maExpSrcRange = maDocSrcRange = pSrcDesc->GetSourceRange(); + maSrcRangeName = pSrcDesc->GetRangeName(); + + // internal sheet data only + SCTAB nScTab = maExpSrcRange.aStart.Tab(); + if( !((nScTab == maExpSrcRange.aEnd.Tab()) && GetTabInfo().IsExportTab( nScTab )) ) + return; + + // ValidateRange() restricts source range to valid Excel limits + if( !GetAddressConverter().ValidateRange( maExpSrcRange, true ) ) + return; + + // #i22541# skip empty cell areas (performance) + SCCOL nDocCol1, nDocCol2; + SCROW nDocRow1, nDocRow2; + GetDoc().GetDataStart( nScTab, nDocCol1, nDocRow1 ); + GetDoc().GetPrintArea( nScTab, nDocCol2, nDocRow2, false ); + SCCOL nSrcCol1 = maExpSrcRange.aStart.Col(); + SCROW nSrcRow1 = maExpSrcRange.aStart.Row(); + SCCOL nSrcCol2 = maExpSrcRange.aEnd.Col(); + SCROW nSrcRow2 = maExpSrcRange.aEnd.Row(); + + // #i22541# do not store index list for too big ranges + if( 2 * (nDocRow2 - nDocRow1) < (nSrcRow2 - nSrcRow1) ) + ::set_flag( maPCInfo.mnFlags, EXC_SXDB_SAVEDATA, false ); + + // adjust row indexes, keep one row of empty area to surely have the empty cache item + if( nSrcRow1 < nDocRow1 ) + nSrcRow1 = nDocRow1 - 1; + if( nSrcRow2 > nDocRow2 ) + nSrcRow2 = nDocRow2 + 1; + + maDocSrcRange.aStart.SetCol( ::std::max( nDocCol1, nSrcCol1 ) ); + maDocSrcRange.aStart.SetRow( nSrcRow1 ); + maDocSrcRange.aEnd.SetCol( ::std::min( nDocCol2, nSrcCol2 ) ); + maDocSrcRange.aEnd.SetRow( nSrcRow2 ); + + GetDoc().GetName( nScTab, maTabName ); + maPCInfo.mnSrcRecs = static_cast< sal_uInt32 >( maExpSrcRange.aEnd.Row() - maExpSrcRange.aStart.Row() ); + maPCInfo.mnStrmId = nListIdx + 1; + maPCInfo.mnSrcType = EXC_SXDB_SRC_SHEET; + + AddFields( rDPObj ); + + mbValid = true; +} + +bool XclExpPivotCache::HasItemIndexList() const +{ + return ::get_flag( maPCInfo.mnFlags, EXC_SXDB_SAVEDATA ); +} + +sal_uInt16 XclExpPivotCache::GetFieldCount() const +{ + return static_cast< sal_uInt16 >( maFieldList.GetSize() ); +} + +const XclExpPCField* XclExpPivotCache::GetField( sal_uInt16 nFieldIdx ) const +{ + return maFieldList.GetRecord( nFieldIdx ); +} + +bool XclExpPivotCache::HasAddFields() const +{ + // pivot cache can be shared, if there are no additional cache fields + return maPCInfo.mnStdFields < maPCInfo.mnTotalFields; +} + +bool XclExpPivotCache::HasEqualDataSource( const ScDPObject& rDPObj ) const +{ + /* For now, only sheet sources are supported, therefore it is enough to + compare the ScSheetSourceDesc. Later, there should be done more complicated + comparisons regarding the source type of rDPObj and this cache. */ + if( const ScSheetSourceDesc* pSrcDesc = rDPObj.GetSheetDesc() ) + return pSrcDesc->GetSourceRange() == maOrigSrcRange; + return false; +} + +void XclExpPivotCache::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( mbValid, "XclExpPivotCache::Save - invalid pivot cache" ); + // SXIDSTM + XclExpUInt16Record( EXC_ID_SXIDSTM, maPCInfo.mnStrmId ).Save( rStrm ); + // SXVS + XclExpUInt16Record( EXC_ID_SXVS, EXC_SXVS_SHEET ).Save( rStrm ); + + if (!maSrcRangeName.isEmpty()) + // DCONNAME + WriteDConName(rStrm); + else + // DCONREF + WriteDconref(rStrm); + + // create the pivot cache storage stream + WriteCacheStream(); +} + +void XclExpPivotCache::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ +} + +void XclExpPivotCache::AddFields( const ScDPObject& rDPObj ) +{ + AddStdFields( rDPObj ); + maPCInfo.mnStdFields = GetFieldCount(); + AddGroupFields( rDPObj ); + maPCInfo.mnTotalFields = GetFieldCount(); +}; + +void XclExpPivotCache::AddStdFields( const ScDPObject& rDPObj ) +{ + // if item index list is not written, used shortened source range (maDocSrcRange) for performance + const ScRange& rRange = HasItemIndexList() ? maExpSrcRange : maDocSrcRange; + // create a standard pivot cache field for each source column + for( SCCOL nScCol = rRange.aStart.Col(), nEndScCol = rRange.aEnd.Col(); nScCol <= nEndScCol; ++nScCol ) + { + ScRange aColRange( rRange ); + aColRange.aStart.SetCol( nScCol ); + aColRange.aEnd.SetCol( nScCol ); + maFieldList.AppendNewRecord( new XclExpPCField( + GetRoot(), GetFieldCount(), rDPObj, aColRange ) ); + } +} + +void XclExpPivotCache::AddGroupFields( const ScDPObject& rDPObj ) +{ + const ScDPSaveData* pSaveData = rDPObj.GetSaveData(); + if(!pSaveData) + return; + const ScDPDimensionSaveData* pSaveDimData = pSaveData->GetExistingDimensionData(); + if( !pSaveDimData ) + return; + + // loop over all existing standard fields to find their group fields + for( sal_uInt16 nFieldIdx = 0; nFieldIdx < maPCInfo.mnStdFields; ++nFieldIdx ) + { + if( XclExpPCField* pCurrStdField = maFieldList.GetRecord( nFieldIdx ) ) + { + const ScDPSaveGroupDimension* pGroupDim = pSaveDimData->GetGroupDimForBase( pCurrStdField->GetFieldName() ); + XclExpPCField* pLastGroupField = pCurrStdField; + while( pGroupDim ) + { + // insert the new grouping field + XclExpPCFieldRef xNewGroupField = new XclExpPCField( + GetRoot(), GetFieldCount(), rDPObj, *pGroupDim, *pCurrStdField ); + maFieldList.AppendRecord( xNewGroupField ); + + // register new grouping field at current grouping field, building a chain + pLastGroupField->SetGroupChildField( *xNewGroupField ); + + // next grouping dimension + pGroupDim = pSaveDimData->GetGroupDimForBase( pGroupDim->GetGroupDimName() ); + pLastGroupField = xNewGroupField.get(); + } + } + } +} + +void XclExpPivotCache::WriteDconref( XclExpStream& rStrm ) const +{ + XclExpString aRef( XclExpUrlHelper::EncodeUrl( GetRoot(), u"", &maTabName ) ); + rStrm.StartRecord( EXC_ID_DCONREF, 7 + aRef.GetSize() ); + rStrm << static_cast< sal_uInt16 >( maExpSrcRange.aStart.Row() ) + << static_cast< sal_uInt16 >( maExpSrcRange.aEnd.Row() ) + << static_cast< sal_uInt8 >( maExpSrcRange.aStart.Col() ) + << static_cast< sal_uInt8 >( maExpSrcRange.aEnd.Col() ) + << aRef + << sal_uInt8( 0 ); + rStrm.EndRecord(); +} + +void XclExpPivotCache::WriteDConName( XclExpStream& rStrm ) const +{ + XclExpString aName(maSrcRangeName); + rStrm.StartRecord(EXC_ID_DCONNAME, aName.GetSize() + 2); + rStrm << aName << sal_uInt16(0); + rStrm.EndRecord(); +} + +void XclExpPivotCache::WriteCacheStream() +{ + tools::SvRef xSvStrg = OpenStorage( EXC_STORAGE_PTCACHE ); + tools::SvRef xSvStrm = OpenStream( xSvStrg, ScfTools::GetHexStr( maPCInfo.mnStrmId ) ); + if( !xSvStrm.is() ) + return; + + XclExpStream aStrm( *xSvStrm, GetRoot() ); + // SXDB + WriteSxdb( aStrm ); + // SXDBEX + WriteSxdbex( aStrm ); + // field list (SXFIELD and items) + maFieldList.Save( aStrm ); + // index table (list of SXINDEXLIST) + WriteSxindexlistList( aStrm ); + // EOF + XclExpEmptyRecord( EXC_ID_EOF ).Save( aStrm ); +} + +void XclExpPivotCache::WriteSxdb( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXDB, 21 ); + rStrm << maPCInfo; + rStrm.EndRecord(); +} + +void XclExpPivotCache::WriteSxdbex( XclExpStream& rStrm ) +{ + rStrm.StartRecord( EXC_ID_SXDBEX, 12 ); + rStrm << EXC_SXDBEX_CREATION_DATE + << sal_uInt32( 0 ); // number of SXFORMULA records + rStrm.EndRecord(); +} + +void XclExpPivotCache::WriteSxindexlistList( XclExpStream& rStrm ) const +{ + if( !HasItemIndexList() ) + return; + + std::size_t nRecSize = 0; + size_t nPos, nSize = maFieldList.GetSize(); + for( nPos = 0; nPos < nSize; ++nPos ) + nRecSize += maFieldList.GetRecord( nPos )->GetIndexSize(); + + for( sal_uInt32 nSrcRow = 0; nSrcRow < maPCInfo.mnSrcRecs; ++nSrcRow ) + { + rStrm.StartRecord( EXC_ID_SXINDEXLIST, nRecSize ); + for( nPos = 0; nPos < nSize; ++nPos ) + maFieldList.GetRecord( nPos )->WriteIndex( rStrm, nSrcRow ); + rStrm.EndRecord(); + } +} + +// Pivot table + +namespace { + +/** Returns a display string for a data field containing the field name and aggregation function. */ +OUString lclGetDataFieldCaption( std::u16string_view rFieldName, ScGeneralFunction eFunc ) +{ + OUString aCaption; + + TranslateId pResIdx; + switch( eFunc ) + { + case ScGeneralFunction::SUM: pResIdx = STR_FUN_TEXT_SUM; break; + case ScGeneralFunction::COUNT: pResIdx = STR_FUN_TEXT_COUNT; break; + case ScGeneralFunction::AVERAGE: pResIdx = STR_FUN_TEXT_AVG; break; + case ScGeneralFunction::MAX: pResIdx = STR_FUN_TEXT_MAX; break; + case ScGeneralFunction::MIN: pResIdx = STR_FUN_TEXT_MIN; break; + case ScGeneralFunction::PRODUCT: pResIdx = STR_FUN_TEXT_PRODUCT; break; + case ScGeneralFunction::COUNTNUMS: pResIdx = STR_FUN_TEXT_COUNT; break; + case ScGeneralFunction::STDEV: pResIdx = STR_FUN_TEXT_STDDEV; break; + case ScGeneralFunction::STDEVP: pResIdx = STR_FUN_TEXT_STDDEV; break; + case ScGeneralFunction::VAR: pResIdx = STR_FUN_TEXT_VAR; break; + case ScGeneralFunction::VARP: pResIdx = STR_FUN_TEXT_VAR; break; + default:; + } + if (pResIdx) + aCaption = ScResId(pResIdx) + " - "; + aCaption += rFieldName; + return aCaption; +} + +} // namespace + +XclExpPTItem::XclExpPTItem( const XclExpPCField& rCacheField, sal_uInt16 nCacheIdx ) : + XclExpRecord( EXC_ID_SXVI, 8 ), + mpCacheItem( rCacheField.GetItem( nCacheIdx ) ) +{ + maItemInfo.mnType = EXC_SXVI_TYPE_DATA; + maItemInfo.mnCacheIdx = nCacheIdx; + maItemInfo.maVisName.mbUseCache = mpCacheItem != nullptr; +} + +XclExpPTItem::XclExpPTItem( sal_uInt16 nItemType, sal_uInt16 nCacheIdx ) : + XclExpRecord( EXC_ID_SXVI, 8 ), + mpCacheItem( nullptr ) +{ + maItemInfo.mnType = nItemType; + maItemInfo.mnCacheIdx = nCacheIdx; + maItemInfo.maVisName.mbUseCache = true; +} + +OUString XclExpPTItem::GetItemName() const +{ + return mpCacheItem ? mpCacheItem->ConvertToText() : OUString(); +} + +void XclExpPTItem::SetPropertiesFromMember( const ScDPSaveMember& rSaveMem ) +{ + // #i115659# GetIsVisible() is not valid if HasIsVisible() returns false, default is 'visible' then + ::set_flag( maItemInfo.mnFlags, EXC_SXVI_HIDDEN, rSaveMem.HasIsVisible() && !rSaveMem.GetIsVisible() ); + // #i115659# GetShowDetails() is not valid if HasShowDetails() returns false, default is 'show detail' then + ::set_flag( maItemInfo.mnFlags, EXC_SXVI_HIDEDETAIL, rSaveMem.HasShowDetails() && !rSaveMem.GetShowDetails() ); + + // visible name + const std::optional & pVisName = rSaveMem.GetLayoutName(); + if (pVisName && *pVisName != GetItemName()) + maItemInfo.SetVisName(*pVisName); +} + +void XclExpPTItem::WriteBody( XclExpStream& rStrm ) +{ + rStrm << maItemInfo; +} + +XclExpPTField::XclExpPTField( const XclExpPivotTable& rPTable, sal_uInt16 nCacheIdx ) : + mrPTable( rPTable ), + mpCacheField( rPTable.GetCacheField( nCacheIdx ) ) +{ + maFieldInfo.mnCacheIdx = nCacheIdx; + + // create field items + if( mpCacheField ) + for( sal_uInt16 nItemIdx = 0, nItemCount = mpCacheField->GetItemCount(); nItemIdx < nItemCount; ++nItemIdx ) + maItemList.AppendNewRecord( new XclExpPTItem( *mpCacheField, nItemIdx ) ); + maFieldInfo.mnItemCount = static_cast< sal_uInt16 >( maItemList.GetSize() ); +} + +// data access ---------------------------------------------------------------- + +OUString XclExpPTField::GetFieldName() const +{ + return mpCacheField ? mpCacheField->GetFieldName() : OUString(); +} + +sal_uInt16 XclExpPTField::GetLastDataInfoIndex() const +{ + OSL_ENSURE( !maDataInfoVec.empty(), "XclExpPTField::GetLastDataInfoIndex - no data info found" ); + // will return 0xFFFF for empty vector -> ok + return static_cast< sal_uInt16 >( maDataInfoVec.size() - 1 ); +} + +sal_uInt16 XclExpPTField::GetItemIndex( std::u16string_view rName, sal_uInt16 nDefaultIdx ) const +{ + for( size_t nPos = 0, nSize = maItemList.GetSize(); nPos < nSize; ++nPos ) + if( maItemList.GetRecord( nPos )->GetItemName() == rName ) + return static_cast< sal_uInt16 >( nPos ); + return nDefaultIdx; +} + +// fill data -------------------------------------------------------------- + +/** + * Calc's subtotal names are escaped with backslashes ('\'), while Excel's + * are not escaped at all. + */ +static OUString lcl_convertCalcSubtotalName(const OUString& rName) +{ + OUStringBuffer aBuf; + const sal_Unicode* p = rName.getStr(); + sal_Int32 n = rName.getLength(); + bool bEscaped = false; + for (sal_Int32 i = 0; i < n; ++i) + { + const sal_Unicode c = p[i]; + if (!bEscaped && c == '\\') + { + bEscaped = true; + continue; + } + + aBuf.append(c); + bEscaped = false; + } + return aBuf.makeStringAndClear(); +} + +void XclExpPTField::SetPropertiesFromDim( const ScDPSaveDimension& rSaveDim ) +{ + // orientation + DataPilotFieldOrientation eOrient = rSaveDim.GetOrientation(); + OSL_ENSURE( eOrient != DataPilotFieldOrientation_DATA, "XclExpPTField::SetPropertiesFromDim - called for data field" ); + maFieldInfo.AddApiOrient( eOrient ); + + // show empty items (#i115659# GetShowEmpty() is not valid if HasShowEmpty() returns false, default is false then) + ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SHOWALL, rSaveDim.HasShowEmpty() && rSaveDim.GetShowEmpty() ); + + // visible name + const std::optional & pLayoutName = rSaveDim.GetLayoutName(); + if (pLayoutName && *pLayoutName != GetFieldName()) + maFieldInfo.SetVisName(*pLayoutName); + + const std::optional & pSubtotalName = rSaveDim.GetSubtotalName(); + if (pSubtotalName) + { + OUString aSubName = lcl_convertCalcSubtotalName(*pSubtotalName); + maFieldExtInfo.mpFieldTotalName = aSubName; + } + + // subtotals + XclPTSubtotalVec aSubtotals; + aSubtotals.reserve( static_cast< size_t >( rSaveDim.GetSubTotalsCount() ) ); + for( tools::Long nSubtIdx = 0, nSubtCount = rSaveDim.GetSubTotalsCount(); nSubtIdx < nSubtCount; ++nSubtIdx ) + aSubtotals.push_back( rSaveDim.GetSubTotalFunc( nSubtIdx ) ); + maFieldInfo.SetSubtotals( aSubtotals ); + + // sorting + if( const DataPilotFieldSortInfo* pSortInfo = rSaveDim.GetSortInfo() ) + { + maFieldExtInfo.SetApiSortMode( pSortInfo->Mode ); + if( pSortInfo->Mode == css::sheet::DataPilotFieldSortMode::DATA ) + maFieldExtInfo.mnSortField = mrPTable.GetDataFieldIndex( pSortInfo->Field, EXC_SXVDEX_SORT_OWN ); + ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SORT_ASC, pSortInfo->IsAscending ); + } + + // auto show + if( const DataPilotFieldAutoShowInfo* pShowInfo = rSaveDim.GetAutoShowInfo() ) + { + ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_AUTOSHOW, pShowInfo->IsEnabled ); + maFieldExtInfo.SetApiAutoShowMode( pShowInfo->ShowItemsMode ); + maFieldExtInfo.SetApiAutoShowCount( pShowInfo->ItemCount ); + maFieldExtInfo.mnShowField = mrPTable.GetDataFieldIndex( pShowInfo->DataField, EXC_SXVDEX_SHOW_NONE ); + } + + // layout + if( const DataPilotFieldLayoutInfo* pLayoutInfo = rSaveDim.GetLayoutInfo() ) + { + maFieldExtInfo.SetApiLayoutMode( pLayoutInfo->LayoutMode ); + ::set_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_LAYOUT_BLANK, pLayoutInfo->AddEmptyLines ); + } + + // special page field properties + if( eOrient == DataPilotFieldOrientation_PAGE ) + { + maPageInfo.mnField = GetFieldIndex(); + maPageInfo.mnSelItem = EXC_SXPI_ALLITEMS; + } + + // item properties + const ScDPSaveDimension::MemberList &rMembers = rSaveDim.GetMembers(); + for (const auto& pMember : rMembers) + if( XclExpPTItem* pItem = GetItemAcc( pMember->GetName() ) ) + pItem->SetPropertiesFromMember( *pMember ); +} + +void XclExpPTField::SetDataPropertiesFromDim( const ScDPSaveDimension& rSaveDim ) +{ + maDataInfoVec.emplace_back( ); + XclPTDataFieldInfo& rDataInfo = maDataInfoVec.back(); + rDataInfo.mnField = GetFieldIndex(); + + // orientation + maFieldInfo.AddApiOrient( DataPilotFieldOrientation_DATA ); + + // aggregation function + ScGeneralFunction eFunc = rSaveDim.GetFunction(); + rDataInfo.SetApiAggFunc( eFunc ); + + // visible name + const std::optional & pVisName = rSaveDim.GetLayoutName(); + if (pVisName) + rDataInfo.SetVisName(*pVisName); + else + rDataInfo.SetVisName( lclGetDataFieldCaption( GetFieldName(), eFunc ) ); + + // result field reference + if( const DataPilotFieldReference* pFieldRef = rSaveDim.GetReferenceValue() ) + { + rDataInfo.SetApiRefType( pFieldRef->ReferenceType ); + rDataInfo.SetApiRefItemType( pFieldRef->ReferenceItemType ); + if( const XclExpPTField* pRefField = mrPTable.GetField( pFieldRef->ReferenceField ) ) + { + rDataInfo.mnRefField = pRefField->GetFieldIndex(); + if( pFieldRef->ReferenceItemType == css::sheet::DataPilotFieldReferenceItemType::NAMED ) + rDataInfo.mnRefItem = pRefField->GetItemIndex( pFieldRef->ReferenceItemName, 0 ); + } + } +} + +void XclExpPTField::AppendSubtotalItems() +{ + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_DEFAULT ) AppendSubtotalItem( EXC_SXVI_TYPE_DEFAULT ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_SUM ) AppendSubtotalItem( EXC_SXVI_TYPE_SUM ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_COUNT ) AppendSubtotalItem( EXC_SXVI_TYPE_COUNT ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_AVERAGE ) AppendSubtotalItem( EXC_SXVI_TYPE_AVERAGE ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_MAX ) AppendSubtotalItem( EXC_SXVI_TYPE_MAX ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_MIN ) AppendSubtotalItem( EXC_SXVI_TYPE_MIN ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_PROD ) AppendSubtotalItem( EXC_SXVI_TYPE_PROD ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_COUNTNUM ) AppendSubtotalItem( EXC_SXVI_TYPE_COUNTNUM ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_STDDEV ) AppendSubtotalItem( EXC_SXVI_TYPE_STDDEV ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_STDDEVP ) AppendSubtotalItem( EXC_SXVI_TYPE_STDDEVP ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_VAR ) AppendSubtotalItem( EXC_SXVI_TYPE_VAR ); + if( maFieldInfo.mnSubtotals & EXC_SXVD_SUBT_VARP ) AppendSubtotalItem( EXC_SXVI_TYPE_VARP ); +} + +// records -------------------------------------------------------------------- + +void XclExpPTField::WriteSxpiEntry( XclExpStream& rStrm ) const +{ + rStrm << maPageInfo; +} + +void XclExpPTField::WriteSxdi( XclExpStream& rStrm, sal_uInt16 nDataInfoIdx ) const +{ + OSL_ENSURE( nDataInfoIdx < maDataInfoVec.size(), "XclExpPTField::WriteSxdi - data field not found" ); + if( nDataInfoIdx < maDataInfoVec.size() ) + { + rStrm.StartRecord( EXC_ID_SXDI, 12 ); + rStrm << maDataInfoVec[ nDataInfoIdx ]; + rStrm.EndRecord(); + } +} + +void XclExpPTField::Save( XclExpStream& rStrm ) +{ + // SXVD + WriteSxvd( rStrm ); + // list of SXVI records + maItemList.Save( rStrm ); + // SXVDEX + WriteSxvdex( rStrm ); +} + +// private -------------------------------------------------------------------- + +XclExpPTItem* XclExpPTField::GetItemAcc( std::u16string_view rName ) +{ + XclExpPTItem* pItem = nullptr; + for( size_t nPos = 0, nSize = maItemList.GetSize(); !pItem && (nPos < nSize); ++nPos ) + if( maItemList.GetRecord( nPos )->GetItemName() == rName ) + pItem = maItemList.GetRecord( nPos ); + return pItem; +} + +void XclExpPTField::AppendSubtotalItem( sal_uInt16 nItemType ) +{ + maItemList.AppendNewRecord( new XclExpPTItem( nItemType, EXC_SXVI_DEFAULT_CACHE ) ); + ++maFieldInfo.mnItemCount; +} + +void XclExpPTField::WriteSxvd( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXVD, 10 ); + rStrm << maFieldInfo; + rStrm.EndRecord(); +} + +void XclExpPTField::WriteSxvdex( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXVDEX, 20 ); + rStrm << maFieldExtInfo; + rStrm.EndRecord(); +} + +XclExpPivotTable::XclExpPivotTable( const XclExpRoot& rRoot, const ScDPObject& rDPObj, const XclExpPivotCache& rPCache ) : + XclExpRoot( rRoot ), + mrPCache( rPCache ), + maDataOrientField( *this, EXC_SXIVD_DATA ), + mnOutScTab( 0 ), + mbValid( false ), + mbFilterBtn( false ) +{ + const ScRange& rOutScRange = rDPObj.GetOutRange(); + if( !GetAddressConverter().ConvertRange( maPTInfo.maOutXclRange, rOutScRange, true ) ) + return; + + // DataPilot properties ----------------------------------------------- + + // pivot table properties from DP object + mnOutScTab = rOutScRange.aStart.Tab(); + maPTInfo.maTableName = rDPObj.GetName(); + maPTInfo.mnCacheIdx = mrPCache.GetCacheIndex(); + + maPTViewEx9Info.Init( rDPObj ); + + const ScDPSaveData* pSaveData = rDPObj.GetSaveData(); + if( !pSaveData ) + return; + + // additional properties from ScDPSaveData + SetPropertiesFromDP( *pSaveData ); + + // loop over all dimensions --------------------------------------- + + /* 1) Default-construct all pivot table fields for all pivot cache fields. */ + for( sal_uInt16 nFieldIdx = 0, nFieldCount = mrPCache.GetFieldCount(); nFieldIdx < nFieldCount; ++nFieldIdx ) + maFieldList.AppendNewRecord( new XclExpPTField( *this, nFieldIdx ) ); + + const ScDPSaveData::DimsType& rDimList = pSaveData->GetDimensions(); + + /* 2) First process all data dimensions, they are needed for extended + settings of row/column/page fields (sorting/auto show). */ + for (auto const& iter : rDimList) + { + if (iter->GetOrientation() == DataPilotFieldOrientation_DATA) + SetDataFieldPropertiesFromDim(*iter); + } + + /* 3) Row/column/page/hidden fields. */ + for (auto const& iter : rDimList) + { + if (iter->GetOrientation() != DataPilotFieldOrientation_DATA) + SetFieldPropertiesFromDim(*iter); + } + + // Finalize ------------------------------------------------------- + + Finalize(); + mbValid = true; +} + +const XclExpPCField* XclExpPivotTable::GetCacheField( sal_uInt16 nCacheIdx ) const +{ + return mrPCache.GetField( nCacheIdx ); +} + +const XclExpPTField* XclExpPivotTable::GetField( sal_uInt16 nFieldIdx ) const +{ + return (nFieldIdx == EXC_SXIVD_DATA) ? &maDataOrientField : maFieldList.GetRecord( nFieldIdx ); +} + +const XclExpPTField* XclExpPivotTable::GetField( std::u16string_view rName ) const +{ + return const_cast< XclExpPivotTable* >( this )->GetFieldAcc( rName ); +} + +sal_uInt16 XclExpPivotTable::GetDataFieldIndex( const OUString& rName, sal_uInt16 nDefaultIdx ) const +{ + auto aIt = std::find_if(maDataFields.begin(), maDataFields.end(), + [this, &rName](const XclPTDataFieldPos& rDataField) { + const XclExpPTField* pField = GetField( rDataField.first ); + return pField && pField->GetFieldName() == rName; + }); + if (aIt != maDataFields.end()) + return static_cast< sal_uInt16 >( std::distance(maDataFields.begin(), aIt) ); + return nDefaultIdx; +} + +void XclExpPivotTable::Save( XclExpStream& rStrm ) +{ + if( !mbValid ) + return; + + // SXVIEW + WriteSxview( rStrm ); + // pivot table fields (SXVD, SXVDEX, and item records) + maFieldList.Save( rStrm ); + // SXIVD records for row and column fields + WriteSxivd( rStrm, maRowFields ); + WriteSxivd( rStrm, maColFields ); + // SXPI + WriteSxpi( rStrm ); + // list of SXDI records containing data field info + WriteSxdiList( rStrm ); + // SXLI records + WriteSxli( rStrm, maPTInfo.mnDataRows, maPTInfo.mnRowFields ); + WriteSxli( rStrm, maPTInfo.mnDataCols, maPTInfo.mnColFields ); + // SXEX + WriteSxex( rStrm ); + // QSISXTAG + WriteQsiSxTag( rStrm ); + // SXVIEWEX9 + WriteSxViewEx9( rStrm ); +} + +XclExpPTField* XclExpPivotTable::GetFieldAcc( std::u16string_view rName ) +{ + XclExpPTField* pField = nullptr; + for( size_t nPos = 0, nSize = maFieldList.GetSize(); !pField && (nPos < nSize); ++nPos ) + if( maFieldList.GetRecord( nPos )->GetFieldName() == rName ) + pField = maFieldList.GetRecord( nPos ); + return pField; +} + +XclExpPTField* XclExpPivotTable::GetFieldAcc( const ScDPSaveDimension& rSaveDim ) +{ + // data field orientation field? + if( rSaveDim.IsDataLayout() ) + return &maDataOrientField; + + // a real dimension + OUString aFieldName = ScDPUtil::getSourceDimensionName(rSaveDim.GetName()); + return aFieldName.isEmpty() ? nullptr : GetFieldAcc(aFieldName); +} + +// fill data -------------------------------------------------------------- + +void XclExpPivotTable::SetPropertiesFromDP( const ScDPSaveData& rSaveData ) +{ + ::set_flag( maPTInfo.mnFlags, EXC_SXVIEW_ROWGRAND, rSaveData.GetRowGrand() ); + ::set_flag( maPTInfo.mnFlags, EXC_SXVIEW_COLGRAND, rSaveData.GetColumnGrand() ); + ::set_flag( maPTExtInfo.mnFlags, EXC_SXEX_DRILLDOWN, rSaveData.GetDrillDown() ); + mbFilterBtn = rSaveData.GetFilterButton(); + const ScDPSaveDimension* pDim = rSaveData.GetExistingDataLayoutDimension(); + + if (pDim && pDim->GetLayoutName()) + maPTInfo.maDataName = *pDim->GetLayoutName(); + else + maPTInfo.maDataName = ScResId(STR_PIVOT_DATA); +} + +void XclExpPivotTable::SetFieldPropertiesFromDim( const ScDPSaveDimension& rSaveDim ) +{ + XclExpPTField* pField = GetFieldAcc( rSaveDim ); + if(!pField) + return; + + // field properties + pField->SetPropertiesFromDim( rSaveDim ); + + // update the corresponding field position list + DataPilotFieldOrientation eOrient = rSaveDim.GetOrientation(); + sal_uInt16 nFieldIdx = pField->GetFieldIndex(); + bool bDataLayout = nFieldIdx == EXC_SXIVD_DATA; + bool bMultiData = maDataFields.size() > 1; + + if( bDataLayout && !bMultiData ) + return; + + switch( eOrient ) + { + case DataPilotFieldOrientation_ROW: + maRowFields.push_back( nFieldIdx ); + if( bDataLayout ) + maPTInfo.mnDataAxis = EXC_SXVD_AXIS_ROW; + break; + case DataPilotFieldOrientation_COLUMN: + maColFields.push_back( nFieldIdx ); + if( bDataLayout ) + maPTInfo.mnDataAxis = EXC_SXVD_AXIS_COL; + break; + case DataPilotFieldOrientation_PAGE: + maPageFields.push_back( nFieldIdx ); + OSL_ENSURE( !bDataLayout, "XclExpPivotTable::SetFieldPropertiesFromDim - wrong orientation for data fields" ); + break; + case DataPilotFieldOrientation_DATA: + OSL_FAIL( "XclExpPivotTable::SetFieldPropertiesFromDim - called for data field" ); + break; + default:; + } +} + +void XclExpPivotTable::SetDataFieldPropertiesFromDim( const ScDPSaveDimension& rSaveDim ) +{ + if( XclExpPTField* pField = GetFieldAcc( rSaveDim ) ) + { + // field properties + pField->SetDataPropertiesFromDim( rSaveDim ); + // update the data field position list + maDataFields.emplace_back( pField->GetFieldIndex(), pField->GetLastDataInfoIndex() ); + } +} + +void XclExpPivotTable::Finalize() +{ + // field numbers + maPTInfo.mnFields = static_cast< sal_uInt16 >( maFieldList.GetSize() ); + maPTInfo.mnRowFields = static_cast< sal_uInt16 >( maRowFields.size() ); + maPTInfo.mnColFields = static_cast< sal_uInt16 >( maColFields.size() ); + maPTInfo.mnPageFields = static_cast< sal_uInt16 >( maPageFields.size() ); + maPTInfo.mnDataFields = static_cast< sal_uInt16 >( maDataFields.size() ); + + maPTExtInfo.mnPagePerRow = maPTInfo.mnPageFields; + maPTExtInfo.mnPagePerCol = (maPTInfo.mnPageFields > 0) ? 1 : 0; + + // subtotal items + for( size_t nPos = 0, nSize = maFieldList.GetSize(); nPos < nSize; ++nPos ) + maFieldList.GetRecord( nPos )->AppendSubtotalItems(); + + // find data field orientation field + maPTInfo.mnDataPos = EXC_SXVIEW_DATALAST; + const ScfUInt16Vec* pFieldVec = nullptr; + switch( maPTInfo.mnDataAxis ) + { + case EXC_SXVD_AXIS_ROW: pFieldVec = &maRowFields; break; + case EXC_SXVD_AXIS_COL: pFieldVec = &maColFields; break; + } + + if( pFieldVec && !pFieldVec->empty() && (pFieldVec->back() != EXC_SXIVD_DATA) ) + { + ScfUInt16Vec::const_iterator aIt = ::std::find( pFieldVec->begin(), pFieldVec->end(), EXC_SXIVD_DATA ); + if( aIt != pFieldVec->end() ) + maPTInfo.mnDataPos = static_cast< sal_uInt16 >( std::distance(pFieldVec->begin(), aIt) ); + } + + // single data field is always row oriented + if( maPTInfo.mnDataAxis == EXC_SXVD_AXIS_NONE ) + maPTInfo.mnDataAxis = EXC_SXVD_AXIS_ROW; + + // update output range (initialized in ctor) + sal_uInt16& rnXclCol1 = maPTInfo.maOutXclRange.maFirst.mnCol; + sal_uInt32& rnXclRow1 = maPTInfo.maOutXclRange.maFirst.mnRow; + sal_uInt16& rnXclCol2 = maPTInfo.maOutXclRange.maLast.mnCol; + sal_uInt32& rnXclRow2 = maPTInfo.maOutXclRange.maLast.mnRow; + // exclude page fields from output range + rnXclRow1 = rnXclRow1 + maPTInfo.mnPageFields; + // exclude filter button from output range + if( mbFilterBtn ) + ++rnXclRow1; + // exclude empty row between (filter button and/or page fields) and table + if( mbFilterBtn || maPTInfo.mnPageFields ) + ++rnXclRow1; + + // data area + sal_uInt16& rnDataXclCol = maPTInfo.maDataXclPos.mnCol; + sal_uInt32& rnDataXclRow = maPTInfo.maDataXclPos.mnRow; + rnDataXclCol = rnXclCol1 + maPTInfo.mnRowFields; + rnDataXclRow = rnXclRow1 + maPTInfo.mnColFields + 1; + if( maDataFields.empty() ) + ++rnDataXclRow; + + bool bExtraHeaderRow = (0 == maPTViewEx9Info.mnGridLayout && maPTInfo.mnColFields == 0); + if (bExtraHeaderRow) + // Insert an extra row only when there is no column field. + ++rnDataXclRow; + + rnXclCol2 = ::std::max( rnXclCol2, rnDataXclCol ); + rnXclRow2 = ::std::max( rnXclRow2, rnDataXclRow ); + maPTInfo.mnDataCols = rnXclCol2 - rnDataXclCol + 1; + maPTInfo.mnDataRows = rnXclRow2 - rnDataXclRow + 1; + + // first heading + maPTInfo.mnFirstHeadRow = rnXclRow1 + 1; + if (bExtraHeaderRow) + maPTInfo.mnFirstHeadRow += 1; +} + +// records ---------------------------------------------------------------- + +void XclExpPivotTable::WriteSxview( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXVIEW, 46 + maPTInfo.maTableName.getLength() + maPTInfo.maDataName.getLength() ); + rStrm << maPTInfo; + rStrm.EndRecord(); +} + +void XclExpPivotTable::WriteSxivd( XclExpStream& rStrm, const ScfUInt16Vec& rFields ) +{ + if( !rFields.empty() ) + { + rStrm.StartRecord( EXC_ID_SXIVD, rFields.size() * 2 ); + for( const auto& rField : rFields ) + rStrm << rField; + rStrm.EndRecord(); + } +} + +void XclExpPivotTable::WriteSxpi( XclExpStream& rStrm ) const +{ + if( !maPageFields.empty() ) + { + rStrm.StartRecord( EXC_ID_SXPI, maPageFields.size() * 6 ); + rStrm.SetSliceSize( 6 ); + for( const auto& rPageField : maPageFields ) + { + XclExpPTFieldRef xField = maFieldList.GetRecord( rPageField ); + if( xField ) + xField->WriteSxpiEntry( rStrm ); + } + rStrm.EndRecord(); + } +} + +void XclExpPivotTable::WriteSxdiList( XclExpStream& rStrm ) const +{ + for( const auto& [rFieldIdx, rDataInfoIdx] : maDataFields ) + { + XclExpPTFieldRef xField = maFieldList.GetRecord( rFieldIdx ); + if( xField ) + xField->WriteSxdi( rStrm, rDataInfoIdx ); + } +} + +void XclExpPivotTable::WriteSxli( XclExpStream& rStrm, sal_uInt16 nLineCount, sal_uInt16 nIndexCount ) +{ + if( nLineCount <= 0 ) + return; + + std::size_t nLineSize = 8 + 2 * nIndexCount; + rStrm.StartRecord( EXC_ID_SXLI, nLineSize * nLineCount ); + + /* Excel expects the records to be filled completely, do not + set a segment size... */ +// rStrm.SetSliceSize( nLineSize ); + + for( sal_uInt16 nLine = 0; nLine < nLineCount; ++nLine ) + { + // Excel XP needs a partly initialized SXLI record + rStrm << sal_uInt16( 0 ) // number of equal index entries + << EXC_SXVI_TYPE_DATA + << nIndexCount + << EXC_SXLI_DEFAULTFLAGS; + rStrm.WriteZeroBytes( 2 * nIndexCount ); + } + rStrm.EndRecord(); +} + +void XclExpPivotTable::WriteSxex( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( EXC_ID_SXEX, 24 ); + rStrm << maPTExtInfo; + rStrm.EndRecord(); +} + +void XclExpPivotTable::WriteQsiSxTag( XclExpStream& rStrm ) const +{ + rStrm.StartRecord( 0x0802, 32 ); + + sal_uInt16 const nRecordType = 0x0802; + sal_uInt16 const nDummyFlags = 0x0000; + sal_uInt16 const nTableType = 1; // 0 = query table : 1 = pivot table + + rStrm << nRecordType << nDummyFlags << nTableType; + + // General flags + sal_uInt16 const nFlags = 0x0001; +#if 0 + // for doc purpose + sal_uInt16 nFlags = 0x0000; + bool bEnableRefresh = true; + bool bPCacheInvalid = false; + bool bOlapPTReport = false; + + if (bEnableRefresh) nFlags |= 0x0001; + if (bPCacheInvalid) nFlags |= 0x0002; + if (bOlapPTReport) nFlags |= 0x0004; +#endif + rStrm << nFlags; + + // Feature-specific options. The value differs depending on the table + // type, but we assume the table type is always pivot table. + sal_uInt32 const nOptions = 0x00000000; +#if 0 + // documentation for which bit is for what + bool bNoStencil = false; + bool bHideTotal = false; + bool bEmptyRows = false; + bool bEmptyCols = false; + if (bNoStencil) nOptions |= 0x00000001; + if (bHideTotal) nOptions |= 0x00000002; + if (bEmptyRows) nOptions |= 0x00000008; + if (bEmptyCols) nOptions |= 0x00000010; +#endif + rStrm << nOptions; + + sal_uInt8 eXclVer = 0; // Excel2000 + sal_uInt8 const nOffsetBytes = 16; + rStrm << eXclVer // version table last refreshed + << eXclVer // minimum version to refresh + << nOffsetBytes + << eXclVer; // first version created + + rStrm << XclExpString(maPTInfo.maTableName); + rStrm << static_cast(0x0001); // no idea what this is for. + + rStrm.EndRecord(); +} + +void XclExpPivotTable::WriteSxViewEx9( XclExpStream& rStrm ) const +{ + // Until we sync the autoformat ids only export if using grid header layout + // That could only have been set via xls import so far. + if ( 0 == maPTViewEx9Info.mnGridLayout ) + { + rStrm.StartRecord( EXC_ID_SXVIEWEX9, 17 ); + rStrm << maPTViewEx9Info; + rStrm.EndRecord(); + } +} + +namespace { + +const SCTAB EXC_PTMGR_PIVOTCACHES = SCTAB_MAX; + +/** Record wrapper class to write the pivot caches or pivot tables. */ +class XclExpPivotRecWrapper : public XclExpRecordBase +{ +public: + explicit XclExpPivotRecWrapper( XclExpPivotTableManager& rPTMgr, SCTAB nScTab ); + virtual void Save( XclExpStream& rStrm ) override; +private: + XclExpPivotTableManager& mrPTMgr; + SCTAB mnScTab; +}; + +XclExpPivotRecWrapper::XclExpPivotRecWrapper( XclExpPivotTableManager& rPTMgr, SCTAB nScTab ) : + mrPTMgr( rPTMgr ), + mnScTab( nScTab ) +{ +} + +void XclExpPivotRecWrapper::Save( XclExpStream& rStrm ) +{ + if( mnScTab == EXC_PTMGR_PIVOTCACHES ) + mrPTMgr.WritePivotCaches( rStrm ); + else + mrPTMgr.WritePivotTables( rStrm, mnScTab ); +} + +} // namespace + +XclExpPivotTableManager::XclExpPivotTableManager( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpPivotTableManager::CreatePivotTables() +{ + if( ScDPCollection* pDPColl = GetDoc().GetDPCollection() ) + for( size_t nDPObj = 0, nCount = pDPColl->GetCount(); nDPObj < nCount; ++nDPObj ) + { + ScDPObject& rDPObj = (*pDPColl)[ nDPObj ]; + if( const XclExpPivotCache* pPCache = CreatePivotCache( rDPObj ) ) + maPTableList.AppendNewRecord( new XclExpPivotTable( GetRoot(), rDPObj, *pPCache ) ); + } +} + +XclExpRecordRef XclExpPivotTableManager::CreatePivotCachesRecord() +{ + return new XclExpPivotRecWrapper( *this, EXC_PTMGR_PIVOTCACHES ); +} + +XclExpRecordRef XclExpPivotTableManager::CreatePivotTablesRecord( SCTAB nScTab ) +{ + return new XclExpPivotRecWrapper( *this, nScTab ); +} + +void XclExpPivotTableManager::WritePivotCaches( XclExpStream& rStrm ) +{ + maPCacheList.Save( rStrm ); +} + +void XclExpPivotTableManager::WritePivotTables( XclExpStream& rStrm, SCTAB nScTab ) +{ + for( size_t nPos = 0, nSize = maPTableList.GetSize(); nPos < nSize; ++nPos ) + { + XclExpPivotTableRef xPTable = maPTableList.GetRecord( nPos ); + if( xPTable->GetScTab() == nScTab ) + xPTable->Save( rStrm ); + } +} + +const XclExpPivotCache* XclExpPivotTableManager::CreatePivotCache( const ScDPObject& rDPObj ) +{ + // try to find a pivot cache with the same data source + /* #i25110# In Excel, the pivot cache contains additional fields + (i.e. grouping info, calculated fields). If the passed DataPilot object + or the found cache contains this data, do not share the cache with + multiple pivot tables. */ + if( const ScDPSaveData* pSaveData = rDPObj.GetSaveData() ) + { + const ScDPDimensionSaveData* pDimSaveData = pSaveData->GetExistingDimensionData(); + // no dimension save data at all or save data does not contain grouping info + if( !pDimSaveData || !pDimSaveData->HasGroupDimensions() ) + { + // check all existing pivot caches + for( size_t nPos = 0, nSize = maPCacheList.GetSize(); nPos < nSize; ++nPos ) + { + XclExpPivotCache* pPCache = maPCacheList.GetRecord( nPos ); + // pivot cache does not have grouping info and source data is equal + if( !pPCache->HasAddFields() && pPCache->HasEqualDataSource( rDPObj ) ) + return pPCache; + } + } + } + + // create a new pivot cache + sal_uInt16 nNewCacheIdx = static_cast< sal_uInt16 >( maPCacheList.GetSize() ); + XclExpPivotCacheRef xNewPCache = new XclExpPivotCache( GetRoot(), rDPObj, nNewCacheIdx ); + if( xNewPCache->IsValid() ) + { + maPCacheList.AppendRecord( xNewPCache.get() ); + return xNewPCache.get(); + } + + return nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xepivotxml.cxx b/sc/source/filter/excel/xepivotxml.cxx new file mode 100644 index 000000000..ecc39caae --- /dev/null +++ b/sc/source/filter/excel/xepivotxml.cxx @@ -0,0 +1,1187 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace oox; +using namespace com::sun::star; + +namespace { + +void savePivotCacheRecordsXml( XclExpXmlStream& rStrm, const ScDPCache& rCache ) +{ + SCROW nCount = rCache.GetDataSize(); + size_t nFieldCount = rCache.GetFieldCount(); + + sax_fastparser::FSHelperPtr& pRecStrm = rStrm.GetCurrentStream(); + pRecStrm->startElement(XML_pivotCacheRecords, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + XML_count, OString::number(static_cast(nCount))); + + for (SCROW i = 0; i < nCount; ++i) + { + pRecStrm->startElement(XML_r); + for (size_t nField = 0; nField < nFieldCount; ++nField) + { + const ScDPCache::IndexArrayType* pArray = rCache.GetFieldIndexArray(nField); + assert(pArray); + assert(o3tl::make_unsigned(i) < pArray->size()); + + // We are using XML_x reference (like: ), instead of values here (eg: ). + // That's why in SavePivotCacheXml method, we need to list all items. + pRecStrm->singleElement(XML_x, XML_v, OString::number((*pArray)[i])); + } + pRecStrm->endElement(XML_r); + } + + pRecStrm->endElement(XML_pivotCacheRecords); +} + +const char* toOOXMLAxisType( sheet::DataPilotFieldOrientation eOrient ) +{ + switch (eOrient) + { + case sheet::DataPilotFieldOrientation_COLUMN: + return "axisCol"; + case sheet::DataPilotFieldOrientation_ROW: + return "axisRow"; + case sheet::DataPilotFieldOrientation_PAGE: + return "axisPage"; + case sheet::DataPilotFieldOrientation_DATA: + return "axisValues"; + case sheet::DataPilotFieldOrientation_HIDDEN: + default: + ; + } + + return ""; +} + +const char* toOOXMLSubtotalType(ScGeneralFunction eFunc) +{ + switch (eFunc) + { + case ScGeneralFunction::SUM: + return "sum"; + case ScGeneralFunction::COUNT: + return "count"; + case ScGeneralFunction::AVERAGE: + return "average"; + case ScGeneralFunction::MAX: + return "max"; + case ScGeneralFunction::MIN: + return "min"; + case ScGeneralFunction::PRODUCT: + return "product"; + case ScGeneralFunction::COUNTNUMS: + return "countNums"; + case ScGeneralFunction::STDEV: + return "stdDev"; + case ScGeneralFunction::STDEVP: + return "stdDevp"; + case ScGeneralFunction::VAR: + return "var"; + case ScGeneralFunction::VARP: + return "varp"; + default: + ; + } + return nullptr; +} + +} + +XclExpXmlPivotCaches::XclExpXmlPivotCaches( const XclExpRoot& rRoot ) : + XclExpRoot(rRoot) {} + +void XclExpXmlPivotCaches::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& pWorkbookStrm = rStrm.GetCurrentStream(); + pWorkbookStrm->startElement(XML_pivotCaches); + + for (size_t i = 0, n = maCaches.size(); i < n; ++i) + { + const Entry& rEntry = maCaches[i]; + + sal_Int32 nCacheId = i + 1; + OUString aRelId; + sax_fastparser::FSHelperPtr pPCStrm = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/pivotCache/", "pivotCacheDefinition", nCacheId), + XclXmlUtils::GetStreamName(nullptr, "pivotCache/pivotCacheDefinition", nCacheId), + rStrm.GetCurrentStream()->getOutputStream(), + CREATE_XL_CONTENT_TYPE("pivotCacheDefinition"), + CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"), + &aRelId); + + pWorkbookStrm->singleElement(XML_pivotCache, + XML_cacheId, OString::number(nCacheId), + FSNS(XML_r, XML_id), aRelId.toUtf8()); + + rStrm.PushStream(pPCStrm); + SavePivotCacheXml(rStrm, rEntry, nCacheId); + rStrm.PopStream(); + } + + pWorkbookStrm->endElement(XML_pivotCaches); +} + +void XclExpXmlPivotCaches::SetCaches( std::vector&& rCaches ) +{ + maCaches = std::move(rCaches); +} + +bool XclExpXmlPivotCaches::HasCaches() const +{ + return !maCaches.empty(); +} + +const XclExpXmlPivotCaches::Entry* XclExpXmlPivotCaches::GetCache( sal_Int32 nCacheId ) const +{ + if (nCacheId <= 0) + // cache ID is 1-based. + return nullptr; + + size_t nPos = nCacheId - 1; + if (nPos >= maCaches.size()) + return nullptr; + + return &maCaches[nPos]; +} + +namespace { +/** + * Create combined date and time string according the requirements of Excel. + * A single point in time can be represented by concatenating a complete date expression, + * the letter T as a delimiter, and a valid time expression. For example, "2007-04-05T14:30". + * + * fSerialDateTime - a number representing the number of days since 1900-Jan-0 (integer portion of the number), + * plus a fractional portion of a 24 hour day (fractional portion of the number). + */ +OUString GetExcelFormattedDate( double fSerialDateTime, const SvNumberFormatter& rFormatter ) +{ + // tdf#125055: properly round the value to seconds when truncating nanoseconds below + constexpr double fHalfSecond = 1 / 86400.0 * 0.5; + css::util::DateTime aUDateTime + = (DateTime(rFormatter.GetNullDate()) + fSerialDateTime + fHalfSecond).GetUNODateTime(); + // We need to reset nanoseconds, to avoid string like: "1982-02-18T16:04:47.999999849" + aUDateTime.NanoSeconds = 0; + OUStringBuffer sBuf; + ::sax::Converter::convertDateTime(sBuf, aUDateTime, nullptr, true); + return sBuf.makeStringAndClear(); +} + +// Excel seems to expect different order of group item values; we need to rearrange elements +// to output "date2" last. +// Since ScDPItemData::DateFirst is -1, ScDPItemData::DateLast is 10000, and other date group +// items would fit between those in order (like 0 = Jan, 1 = Feb, etc.), we can simply sort +// the items by value. +std::vector SortGroupItems(const ScDPCache& rCache, tools::Long nDim) +{ + struct ItemData + { + sal_Int32 nVal; + const ScDPItemData* pData; + }; + std::vector aDataToSort; + ScfInt32Vec aGIIds; + rCache.GetGroupDimMemberIds(nDim, aGIIds); + for (sal_Int32 id : aGIIds) + { + const ScDPItemData* pGIData = rCache.GetItemDataById(nDim, id); + if (pGIData->GetType() == ScDPItemData::GroupValue) + { + auto aGroupVal = pGIData->GetGroupValue(); + aDataToSort.push_back({ aGroupVal.mnValue, pGIData }); + } + } + std::sort(aDataToSort.begin(), aDataToSort.end(), + [](const ItemData& a, const ItemData& b) { return a.nVal < b.nVal; }); + + std::vector aSortedResult; + for (const auto& el : aDataToSort) + { + aSortedResult.push_back(rCache.GetFormattedString(nDim, *el.pData, false)); + } + return aSortedResult; +} +} // namespace + +void XclExpXmlPivotCaches::SavePivotCacheXml( XclExpXmlStream& rStrm, const Entry& rEntry, sal_Int32 nCounter ) +{ + assert(rEntry.mpCache); + const ScDPCache& rCache = *rEntry.mpCache; + + sax_fastparser::FSHelperPtr& pDefStrm = rStrm.GetCurrentStream(); + + OUString aRelId; + sax_fastparser::FSHelperPtr pRecStrm = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/pivotCache/", "pivotCacheRecords", nCounter), + XclXmlUtils::GetStreamName(nullptr, "pivotCacheRecords", nCounter), + pDefStrm->getOutputStream(), + CREATE_XL_CONTENT_TYPE("pivotCacheRecords"), + CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheRecords"), + &aRelId); + + rStrm.PushStream(pRecStrm); + savePivotCacheRecordsXml(rStrm, rCache); + rStrm.PopStream(); + + pDefStrm->startElement(XML_pivotCacheDefinition, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + FSNS(XML_xmlns, XML_r), rStrm.getNamespaceURL(OOX_NS(officeRel)).toUtf8(), + FSNS(XML_r, XML_id), aRelId.toUtf8(), + XML_recordCount, OString::number(rEntry.mpCache->GetDataSize()), + XML_createdVersion, "3"); // MS Excel 2007, tdf#112936: setting version number makes MSO to handle the pivot table differently + + pDefStrm->startElement(XML_cacheSource, XML_type, "worksheet"); + + OUString aSheetName; + GetDoc().GetName(rEntry.maSrcRange.aStart.Tab(), aSheetName); + pDefStrm->singleElement(XML_worksheetSource, + XML_ref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), rEntry.maSrcRange), + XML_sheet, aSheetName.toUtf8()); + + pDefStrm->endElement(XML_cacheSource); + + size_t nCount = rCache.GetFieldCount(); + const size_t nGroupFieldCount = rCache.GetGroupFieldCount(); + pDefStrm->startElement(XML_cacheFields, + XML_count, OString::number(static_cast(nCount + nGroupFieldCount))); + + auto WriteFieldGroup = [this, &rCache, pDefStrm](size_t i, size_t base) { + const sal_Int32 nDatePart = rCache.GetGroupType(i); + if (!nDatePart) + return; + OString sGroupBy; + switch (nDatePart) + { + case sheet::DataPilotFieldGroupBy::SECONDS: + sGroupBy = "seconds"; + break; + case sheet::DataPilotFieldGroupBy::MINUTES: + sGroupBy = "minutes"; + break; + case sheet::DataPilotFieldGroupBy::HOURS: + sGroupBy = "hours"; + break; + case sheet::DataPilotFieldGroupBy::DAYS: + sGroupBy = "days"; + break; + case sheet::DataPilotFieldGroupBy::MONTHS: + sGroupBy = "months"; + break; + case sheet::DataPilotFieldGroupBy::QUARTERS: + sGroupBy = "quarters"; + break; + case sheet::DataPilotFieldGroupBy::YEARS: + sGroupBy = "years"; + break; + } + + // fieldGroup element + pDefStrm->startElement(XML_fieldGroup, XML_base, OString::number(base)); + + SvNumberFormatter& rFormatter = GetFormatter(); + + // rangePr element + const ScDPNumGroupInfo* pGI = rCache.GetNumGroupInfo(i); + auto pGroupAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + pGroupAttList->add(XML_groupBy, sGroupBy); + // Possible TODO: find out when to write autoStart attribute for years grouping + pGroupAttList->add(XML_startDate, GetExcelFormattedDate(pGI->mfStart, rFormatter).toUtf8()); + pGroupAttList->add(XML_endDate, GetExcelFormattedDate(pGI->mfEnd, rFormatter).toUtf8()); + if (pGI->mfStep) + pGroupAttList->add(XML_groupInterval, OString::number(pGI->mfStep)); + pDefStrm->singleElement(XML_rangePr, pGroupAttList); + + // groupItems element + auto aElemVec = SortGroupItems(rCache, i); + pDefStrm->startElement(XML_groupItems, XML_count, OString::number(aElemVec.size())); + for (const auto& sElem : aElemVec) + { + pDefStrm->singleElement(XML_s, XML_v, sElem.toUtf8()); + } + pDefStrm->endElement(XML_groupItems); + pDefStrm->endElement(XML_fieldGroup); + }; + + for (size_t i = 0; i < nCount; ++i) + { + OUString aName = rCache.GetDimensionName(i); + + pDefStrm->startElement(XML_cacheField, + XML_name, aName.toUtf8(), + XML_numFmtId, OString::number(0)); + + const ScDPCache::ScDPItemDataVec& rFieldItems = rCache.GetDimMemberValues(i); + + std::set aDPTypes; + double fMin = std::numeric_limits::infinity(), fMax = -std::numeric_limits::infinity(); + bool isValueInteger = true; + bool isContainsDate = rCache.IsDateDimension(i); + bool isLongText = false; + for (const auto& rFieldItem : rFieldItems) + { + ScDPItemData::Type eType = rFieldItem.GetType(); + // tdf#123939 : error and string are same for cache; if both are present, keep only one + if (eType == ScDPItemData::Error) + eType = ScDPItemData::String; + aDPTypes.insert(eType); + if (eType == ScDPItemData::Value) + { + double fVal = rFieldItem.GetValue(); + fMin = std::min(fMin, fVal); + fMax = std::max(fMax, fVal); + + // Check if all values are integers + if (isValueInteger && (modf(fVal, &o3tl::temporary(double())) != 0.0)) + { + isValueInteger = false; + } + } + else if (eType == ScDPItemData::String && !isLongText) + { + isLongText = rFieldItem.GetString().getLength() > 255; + } + } + + auto pAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + // TODO In same cases, disable listing of items, as it is done in MS Excel. + // Exporting savePivotCacheRecordsXml method needs to be updated accordingly + //bool bListItems = true; + + std::set aDPTypesWithoutBlank = aDPTypes; + aDPTypesWithoutBlank.erase(ScDPItemData::Empty); + + const bool isContainsString = aDPTypesWithoutBlank.count(ScDPItemData::String) > 0; + const bool isContainsBlank = aDPTypes.count(ScDPItemData::Empty) > 0; + const bool isContainsNumber + = !isContainsDate && aDPTypesWithoutBlank.count(ScDPItemData::Value) > 0; + bool isContainsNonDate = !(isContainsDate && aDPTypesWithoutBlank.size() <= 1); + + // XML_containsSemiMixedTypes possible values: + // 1 - (Default) at least one text value, or can also contain a mix of other data types and blank values, + // or blank values only + // 0 - the field does not have a mix of text and other values + if (!(isContainsString || (aDPTypes.size() > 1) || (isContainsBlank && aDPTypesWithoutBlank.empty()))) + pAttList->add(XML_containsSemiMixedTypes, ToPsz10(false)); + + if (!isContainsNonDate) + pAttList->add(XML_containsNonDate, ToPsz10(false)); + + if (isContainsDate) + pAttList->add(XML_containsDate, ToPsz10(true)); + + // default for containsString field is true, so we are writing only when is false + if (!isContainsString) + pAttList->add(XML_containsString, ToPsz10(false)); + + if (isContainsBlank) + pAttList->add(XML_containsBlank, ToPsz10(true)); + + // XML_containsMixedType possible values: + // 1 - field contains more than one data type + // 0 - (Default) only one data type. The field can still contain blank values (that's why we are using aDPTypesWithoutBlank) + if (aDPTypesWithoutBlank.size() > 1) + pAttList->add(XML_containsMixedTypes, ToPsz10(true)); + + // If field contain mixed types (Date and Numbers), MS Excel is saving only "minDate" and "maxDate" and not "minValue" and "maxValue" + // Example how Excel is saving mixed Date and Numbers: + // + // Example how Excel is saving Dates only: + // + if (isContainsNumber) + pAttList->add(XML_containsNumber, ToPsz10(true)); + + if (isValueInteger && isContainsNumber) + pAttList->add(XML_containsInteger, ToPsz10(true)); + + + // Number type fields could be mixed with blank types, and it shouldn't be treated as listed items. + // Example: + // + // + // + if (isContainsNumber) + { + pAttList->add(XML_minValue, OString::number(fMin)); + pAttList->add(XML_maxValue, OString::number(fMax)); + } + + if (isContainsDate) + { + pAttList->add(XML_minDate, GetExcelFormattedDate(fMin, GetFormatter()).toUtf8()); + pAttList->add(XML_maxDate, GetExcelFormattedDate(fMax, GetFormatter()).toUtf8()); + } + + //if (bListItems) // see TODO above + { + pAttList->add(XML_count, OString::number(static_cast(rFieldItems.size()))); + } + + if (isLongText) + { + pAttList->add(XML_longText, ToPsz10(true)); + } + + pDefStrm->startElement(XML_sharedItems, pAttList); + + //if (bListItems) // see TODO above + { + for (const ScDPItemData& rItem : rFieldItems) + { + switch (rItem.GetType()) + { + case ScDPItemData::String: + pDefStrm->singleElement(XML_s, XML_v, rItem.GetString().toUtf8()); + break; + case ScDPItemData::Value: + if (isContainsDate) + { + pDefStrm->singleElement(XML_d, + XML_v, GetExcelFormattedDate(rItem.GetValue(), GetFormatter()).toUtf8()); + } + else + pDefStrm->singleElement(XML_n, + XML_v, OString::number(rItem.GetValue())); + break; + case ScDPItemData::Empty: + pDefStrm->singleElement(XML_m); + break; + case ScDPItemData::Error: + pDefStrm->singleElement(XML_e, + XML_v, rItem.GetString().toUtf8()); + break; + case ScDPItemData::GroupValue: // Should not happen here! + case ScDPItemData::RangeStart: + // TODO : What do we do with these types? + pDefStrm->singleElement(XML_m); + break; + default: + ; + } + } + } + + pDefStrm->endElement(XML_sharedItems); + + WriteFieldGroup(i, i); + + pDefStrm->endElement(XML_cacheField); + } + + ScDPObject* pDPObject + = rCache.GetAllReferences().empty() ? nullptr : *rCache.GetAllReferences().begin(); + + for (size_t i = nCount; pDPObject && i < nCount + nGroupFieldCount; ++i) + { + const OUString aName = pDPObject->GetDimName(i, o3tl::temporary(bool())); + // tdf#126748: DPObject might not reference all group fields, when there are several + // DPObjects referencing this cache. Trying to get a dimension data for a field not used + // in a given DPObject will give nullptr, and dereferencing it then will crash. To avoid + // the crash, until there's a correct method to find the names of group fields in cache, + // just skip the fields, creating bad cache data, which is of course a temporary hack. + // TODO: reimplement the whole block to get the names from another source, not from first + // cache reference. + if (aName.isEmpty()) + break; + + ScDPSaveData* pSaveData = pDPObject->GetSaveData(); + assert(pSaveData); + + const ScDPSaveGroupDimension* pDim = pSaveData->GetDimensionData()->GetNamedGroupDim(aName); + assert(pDim); + + const SCCOL nBase = rCache.GetDimensionIndex(pDim->GetSourceDimName()); + assert(nBase >= 0); + + pDefStrm->startElement(XML_cacheField, XML_name, aName.toUtf8(), XML_numFmtId, + OString::number(0), XML_databaseField, ToPsz10(false)); + WriteFieldGroup(i, nBase); + pDefStrm->endElement(XML_cacheField); + } + + pDefStrm->endElement(XML_cacheFields); + + pDefStrm->endElement(XML_pivotCacheDefinition); +} + +XclExpXmlPivotTableManager::XclExpXmlPivotTableManager( const XclExpRoot& rRoot ) : + XclExpRoot(rRoot), maCaches(rRoot) {} + +void XclExpXmlPivotTableManager::Initialize() +{ + ScDocument& rDoc = GetDoc(); + if (!rDoc.HasPivotTable()) + // No pivot table to export. + return; + + ScDPCollection* pDPColl = rDoc.GetDPCollection(); + if (!pDPColl) + return; + + // Update caches from DPObject + for (size_t i = 0; i < pDPColl->GetCount(); ++i) + { + ScDPObject& rDPObj = (*pDPColl)[i]; + rDPObj.SyncAllDimensionMembers(); + (void)rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::TABLE); + } + + // Go through the caches first. + + std::vector aCaches; + const ScDPCollection::SheetCaches& rSheetCaches = pDPColl->GetSheetCaches(); + const std::vector& rRanges = rSheetCaches.getAllRanges(); + for (const auto & rRange : rRanges) + { + const ScDPCache* pCache = rSheetCaches.getExistingCache(rRange); + if (!pCache) + continue; + + // Get all pivot objects that reference this cache, and set up an + // object to cache ID mapping. + const ScDPCache::ScDPObjectSet& rRefs = pCache->GetAllReferences(); + for (const auto& rRef : rRefs) + maCacheIdMap.emplace(rRef, aCaches.size()+1); + + XclExpXmlPivotCaches::Entry aEntry; + aEntry.mpCache = pCache; + aEntry.maSrcRange = rRange; + aCaches.push_back(aEntry); // Cache ID equals position + 1. + } + + // TODO : Handle name and database caches as well. + + for (size_t i = 0, n = pDPColl->GetCount(); i < n; ++i) + { + const ScDPObject& rDPObj = (*pDPColl)[i]; + + // Get the cache ID for this pivot table. + CacheIdMapType::iterator itCache = maCacheIdMap.find(&rDPObj); + if (itCache == maCacheIdMap.end()) + // No cache ID found. Something is wrong here... + continue; + + sal_Int32 nCacheId = itCache->second; + SCTAB nTab = rDPObj.GetOutRange().aStart.Tab(); + + TablesType::iterator it = m_Tables.find(nTab); + if (it == m_Tables.end()) + { + // Insert a new instance for this sheet index. + std::pair r = + m_Tables.insert(std::make_pair(nTab, std::make_unique(GetRoot(), maCaches))); + it = r.first; + } + + XclExpXmlPivotTables *const p = it->second.get(); + p->AppendTable(&rDPObj, nCacheId, i+1); + } + + maCaches.SetCaches(std::move(aCaches)); +} + +XclExpXmlPivotCaches& XclExpXmlPivotTableManager::GetCaches() +{ + return maCaches; +} + +XclExpXmlPivotTables* XclExpXmlPivotTableManager::GetTablesBySheet( SCTAB nTab ) +{ + TablesType::iterator const it = m_Tables.find(nTab); + return it == m_Tables.end() ? nullptr : it->second.get(); +} + +XclExpXmlPivotTables::Entry::Entry( const ScDPObject* pTable, sal_Int32 nCacheId, sal_Int32 nPivotId ) : + mpTable(pTable), mnCacheId(nCacheId), mnPivotId(nPivotId) {} + +XclExpXmlPivotTables::XclExpXmlPivotTables( const XclExpRoot& rRoot, const XclExpXmlPivotCaches& rCaches ) : + XclExpRoot(rRoot), mrCaches(rCaches) {} + +void XclExpXmlPivotTables::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& pWSStrm = rStrm.GetCurrentStream(); // worksheet stream + + for (const auto& rTable : maTables) + { + const ScDPObject& rObj = *rTable.mpTable; + sal_Int32 nCacheId = rTable.mnCacheId; + sal_Int32 nPivotId = rTable.mnPivotId; + + sax_fastparser::FSHelperPtr pPivotStrm = rStrm.CreateOutputStream( + XclXmlUtils::GetStreamName("xl/pivotTables/", "pivotTable", nPivotId), + XclXmlUtils::GetStreamName(nullptr, "../pivotTables/pivotTable", nPivotId), + pWSStrm->getOutputStream(), + CREATE_XL_CONTENT_TYPE("pivotTable"), + CREATE_OFFICEDOC_RELATION_TYPE("pivotTable")); + + rStrm.PushStream(pPivotStrm); + SavePivotTableXml(rStrm, rObj, nCacheId); + rStrm.PopStream(); + } +} + +namespace { + +struct DataField +{ + tools::Long mnPos; // field index in pivot cache. + const ScDPSaveDimension* mpDim; + + DataField( tools::Long nPos, const ScDPSaveDimension* pDim ) : mnPos(nPos), mpDim(pDim) {} +}; + +/** Returns an OOXML subtotal function name string. See ECMA-376-1:2016 18.18.43 */ +OString GetSubtotalFuncName(ScGeneralFunction eFunc) +{ + switch (eFunc) + { + case ScGeneralFunction::SUM: return "sum"; + case ScGeneralFunction::COUNT: return "count"; + case ScGeneralFunction::AVERAGE: return "avg"; + case ScGeneralFunction::MAX: return "max"; + case ScGeneralFunction::MIN: return "min"; + case ScGeneralFunction::PRODUCT: return "product"; + case ScGeneralFunction::COUNTNUMS: return "countA"; + case ScGeneralFunction::STDEV: return "stdDev"; + case ScGeneralFunction::STDEVP: return "stdDevP"; + case ScGeneralFunction::VAR: return "var"; + case ScGeneralFunction::VARP: return "varP"; + default:; + } + return "default"; +} + +sal_Int32 GetSubtotalAttrToken(ScGeneralFunction eFunc) +{ + switch (eFunc) + { + case ScGeneralFunction::SUM: return XML_sumSubtotal; + case ScGeneralFunction::COUNT: return XML_countSubtotal; + case ScGeneralFunction::AVERAGE: return XML_avgSubtotal; + case ScGeneralFunction::MAX: return XML_maxSubtotal; + case ScGeneralFunction::MIN: return XML_minSubtotal; + case ScGeneralFunction::PRODUCT: return XML_productSubtotal; + case ScGeneralFunction::COUNTNUMS: return XML_countASubtotal; + case ScGeneralFunction::STDEV: return XML_stdDevSubtotal; + case ScGeneralFunction::STDEVP: return XML_stdDevPSubtotal; + case ScGeneralFunction::VAR: return XML_varSubtotal; + case ScGeneralFunction::VARP: return XML_varPSubtotal; + default:; + } + return XML_defaultSubtotal; +} + +// An item is expected to contain sequences of css::xml::FastAttribute and css::xml::Attribute +void WriteGrabBagItemToStream(XclExpXmlStream& rStrm, sal_Int32 tokenId, const css::uno::Any& rItem) +{ + css::uno::Sequence aSeqs; + if(!(rItem >>= aSeqs)) + return; + + auto& pStrm = rStrm.GetCurrentStream(); + pStrm->write("<")->writeId(tokenId); + + css::uno::Sequence aFastSeq; + css::uno::Sequence aUnkSeq; + for (const auto& a : std::as_const(aSeqs)) + { + if (a >>= aFastSeq) + { + for (const auto& rAttr : std::as_const(aFastSeq)) + rStrm.WriteAttributes(rAttr.Token, rAttr.Value); + } + else if (a >>= aUnkSeq) + { + for (const auto& rAttr : std::as_const(aUnkSeq)) + pStrm->write(" ") + ->write(rAttr.Name) + ->write("=\"") + ->writeEscaped(rAttr.Value) + ->write("\""); + } + } + + pStrm->write("/>"); +} +} + +void XclExpXmlPivotTables::SavePivotTableXml( XclExpXmlStream& rStrm, const ScDPObject& rDPObj, sal_Int32 nCacheId ) +{ + typedef std::unordered_map NameToIdMapType; + + const XclExpXmlPivotCaches::Entry* pCacheEntry = mrCaches.GetCache(nCacheId); + if (!pCacheEntry) + // Something is horribly wrong. Check your logic. + return; + + const ScDPCache& rCache = *pCacheEntry->mpCache; + + const ScDPSaveData& rSaveData = *rDPObj.GetSaveData(); + + size_t nFieldCount = rCache.GetFieldCount() + rCache.GetGroupFieldCount(); + std::vector aCachedDims; + NameToIdMapType aNameToIdMap; + + aCachedDims.reserve(nFieldCount); + for (size_t i = 0; i < nFieldCount; ++i) + { + OUString aName = const_cast(rDPObj).GetDimName(i, o3tl::temporary(bool())); + aNameToIdMap.emplace(aName, aCachedDims.size()); + const ScDPSaveDimension* pDim = rSaveData.GetExistingDimensionByName(aName); + aCachedDims.push_back(pDim); + } + + std::vector aRowFields; + std::vector aColFields; + std::vector aPageFields; + std::vector aDataFields; + + tools::Long nDataDimCount = rSaveData.GetDataDimensionCount(); + // Use dimensions in the save data to get their correct ordering. + // Dimension order here is significant as they specify the order of + // appearance in each axis. + const ScDPSaveData::DimsType& rDims = rSaveData.GetDimensions(); + bool bTabularMode = false; + for (const auto & i : rDims) + { + const ScDPSaveDimension& rDim = *i; + + tools::Long nPos = -1; // position in cache + if (rDim.IsDataLayout()) + nPos = -2; // Excel uses an index of -2 to indicate a data layout field. + else + { + OUString aSrcName = ScDPUtil::getSourceDimensionName(rDim.GetName()); + NameToIdMapType::iterator it = aNameToIdMap.find(aSrcName); + if (it != aNameToIdMap.end()) + nPos = it->second; + + if (nPos == -1) + continue; + + if (!aCachedDims[nPos]) + continue; + } + + sheet::DataPilotFieldOrientation eOrient = rDim.GetOrientation(); + + switch (eOrient) + { + case sheet::DataPilotFieldOrientation_COLUMN: + if (nPos == -2 && nDataDimCount <= 1) + break; + aColFields.push_back(nPos); + break; + case sheet::DataPilotFieldOrientation_ROW: + aRowFields.push_back(nPos); + break; + case sheet::DataPilotFieldOrientation_PAGE: + aPageFields.push_back(nPos); + break; + case sheet::DataPilotFieldOrientation_DATA: + aDataFields.emplace_back(nPos, &rDim); + break; + case sheet::DataPilotFieldOrientation_HIDDEN: + default: + ; + } + if(rDim.GetLayoutInfo()) + bTabularMode |= (rDim.GetLayoutInfo()->LayoutMode == sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT); + } + + sax_fastparser::FSHelperPtr& pPivotStrm = rStrm.GetCurrentStream(); + pPivotStrm->startElement(XML_pivotTableDefinition, + XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls)).toUtf8(), + XML_name, rDPObj.GetName().toUtf8(), + XML_cacheId, OString::number(nCacheId), + XML_applyNumberFormats, ToPsz10(false), + XML_applyBorderFormats, ToPsz10(false), + XML_applyFontFormats, ToPsz10(false), + XML_applyPatternFormats, ToPsz10(false), + XML_applyAlignmentFormats, ToPsz10(false), + XML_applyWidthHeightFormats, ToPsz10(false), + XML_dataCaption, "Values", + XML_useAutoFormatting, ToPsz10(false), + XML_itemPrintTitles, ToPsz10(true), + XML_indent, ToPsz10(false), + XML_outline, ToPsz10(!bTabularMode), + XML_outlineData, ToPsz10(!bTabularMode), + XML_compact, ToPsz10(false), + XML_compactData, ToPsz10(false)); + + // NB: Excel's range does not include page field area (if any). + ScRange aOutRange = rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::TABLE); + + sal_Int32 nFirstHeaderRow = rDPObj.GetHeaderLayout() ? 2 : 1; + sal_Int32 nFirstDataRow = 2; + sal_Int32 nFirstDataCol = 1; + ScRange aResRange = rDPObj.GetOutputRangeByType(sheet::DataPilotOutputRangeType::RESULT); + + if (!aOutRange.IsValid()) + aOutRange = rDPObj.GetOutRange(); + + if (aOutRange.IsValid() && aResRange.IsValid()) + { + nFirstDataRow = aResRange.aStart.Row() - aOutRange.aStart.Row(); + nFirstDataCol = aResRange.aStart.Col() - aOutRange.aStart.Col(); + } + + pPivotStrm->write("<")->writeId(XML_location); + rStrm.WriteAttributes(XML_ref, + XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), aOutRange), + XML_firstHeaderRow, OUString::number(nFirstHeaderRow), + XML_firstDataRow, OUString::number(nFirstDataRow), + XML_firstDataCol, OUString::number(nFirstDataCol)); + + if (!aPageFields.empty()) + { + rStrm.WriteAttributes(XML_rowPageCount, OUString::number(static_cast(aPageFields.size()))); + rStrm.WriteAttributes(XML_colPageCount, OUString::number(1)); + } + + pPivotStrm->write("/>"); + + // - It must contain all fields in the pivot cache even if + // only some of them are used in the pivot table. The order must be as + // they appear in the cache. + + pPivotStrm->startElement(XML_pivotFields, + XML_count, OString::number(static_cast(aCachedDims.size()))); + + for (size_t i = 0; i < nFieldCount; ++i) + { + const ScDPSaveDimension* pDim = aCachedDims[i]; + if (!pDim) + { + pPivotStrm->singleElement(XML_pivotField, + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false)); + continue; + } + + bool bDimInTabularMode = false; + if(pDim->GetLayoutInfo()) + bDimInTabularMode = (pDim->GetLayoutInfo()->LayoutMode == sheet::DataPilotFieldLayoutMode::TABULAR_LAYOUT); + + sheet::DataPilotFieldOrientation eOrient = pDim->GetOrientation(); + + if (eOrient == sheet::DataPilotFieldOrientation_HIDDEN) + { + if(bDimInTabularMode) + { + pPivotStrm->singleElement(XML_pivotField, + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false), + XML_outline, ToPsz10(false)); + } + else + { + pPivotStrm->singleElement(XML_pivotField, + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false)); + } + continue; + } + + if (eOrient == sheet::DataPilotFieldOrientation_DATA) + { + if(bDimInTabularMode) + { + pPivotStrm->singleElement(XML_pivotField, + XML_dataField, ToPsz10(true), + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false), + XML_outline, ToPsz10(false)); + } + else + { + pPivotStrm->singleElement(XML_pivotField, + XML_dataField, ToPsz10(true), + XML_compact, ToPsz10(false), + XML_showAll, ToPsz10(false)); + } + continue; + } + + // Dump field items. + std::vector aMembers; + { + // We need to get the members in actual order, getting which requires non-const reference here + auto& dpo = const_cast(rDPObj); + dpo.GetMembers(i, dpo.GetUsedHierarchy(i), aMembers); + } + + std::vector aCacheFieldItems; + if (i < rCache.GetFieldCount() && !rCache.GetGroupType(i)) + { + for (const auto& it : rCache.GetDimMemberValues(i)) + { + OUString sFormattedName; + if (it.HasStringData() || it.IsEmpty()) + sFormattedName = it.GetString(); + else + sFormattedName = const_cast(rDPObj).GetFormattedString( + pDim->GetName(), it.GetValue()); + aCacheFieldItems.push_back(sFormattedName); + } + } + else + { + aCacheFieldItems = SortGroupItems(rCache, i); + } + // The pair contains the member index in cache and if it is hidden + std::vector< std::pair > aMemberSequence; + std::set aUsedCachePositions; + for (const auto & rMember : aMembers) + { + auto it = std::find(aCacheFieldItems.begin(), aCacheFieldItems.end(), rMember.maName); + if (it != aCacheFieldItems.end()) + { + size_t nCachePos = static_cast(std::distance(aCacheFieldItems.begin(), it)); + auto aInserted = aUsedCachePositions.insert(nCachePos); + if (aInserted.second) + aMemberSequence.emplace_back(std::make_pair(nCachePos, !rMember.mbVisible)); + } + } + // Now add all remaining cache items as hidden + for (size_t nItem = 0; nItem < aCacheFieldItems.size(); ++nItem) + { + if (aUsedCachePositions.find(nItem) == aUsedCachePositions.end()) + aMemberSequence.emplace_back(nItem, true); + } + + // tdf#125086: check if this field *also* appears in Data region + bool bAppearsInData = false; + { + OUString aSrcName = ScDPUtil::getSourceDimensionName(pDim->GetName()); + const auto it = std::find_if( + aDataFields.begin(), aDataFields.end(), [&aSrcName](const DataField& rDataField) { + OUString aThisName + = ScDPUtil::getSourceDimensionName(rDataField.mpDim->GetName()); + return aThisName == aSrcName; + }); + if (it != aDataFields.end()) + bAppearsInData = true; + } + + auto pAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + pAttList->add(XML_axis, toOOXMLAxisType(eOrient)); + if (bAppearsInData) + pAttList->add(XML_dataField, ToPsz10(true)); + pAttList->add(XML_compact, ToPsz10(false)); + pAttList->add(XML_showAll, ToPsz10(false)); + + tools::Long nSubTotalCount = pDim->GetSubTotalsCount(); + std::vector aSubtotalSequence; + bool bHasDefaultSubtotal = false; + for (tools::Long nSubTotal = 0; nSubTotal < nSubTotalCount; ++nSubTotal) + { + ScGeneralFunction eFunc = pDim->GetSubTotalFunc(nSubTotal); + aSubtotalSequence.push_back(GetSubtotalFuncName(eFunc)); + sal_Int32 nAttToken = GetSubtotalAttrToken(eFunc); + if (nAttToken == XML_defaultSubtotal) + bHasDefaultSubtotal = true; + else if (!pAttList->hasAttribute(nAttToken)) + pAttList->add(nAttToken, ToPsz10(true)); + } + // XML_defaultSubtotal is true by default; only write it if it's false + if (!bHasDefaultSubtotal) + pAttList->add(XML_defaultSubtotal, ToPsz10(false)); + + if(bDimInTabularMode) + pAttList->add( XML_outline, ToPsz10(false)); + pPivotStrm->startElement(XML_pivotField, pAttList); + + pPivotStrm->startElement(XML_items, + XML_count, OString::number(static_cast(aMemberSequence.size() + aSubtotalSequence.size()))); + + for (const auto & nMember : aMemberSequence) + { + auto pItemAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + if (nMember.second) + pItemAttList->add(XML_h, ToPsz10(true)); + pItemAttList->add(XML_x, OString::number(static_cast(nMember.first))); + pPivotStrm->singleElement(XML_item, pItemAttList); + } + + for (const OString& sSubtotal : aSubtotalSequence) + { + pPivotStrm->singleElement(XML_item, XML_t, sSubtotal); + } + + pPivotStrm->endElement(XML_items); + pPivotStrm->endElement(XML_pivotField); + } + + pPivotStrm->endElement(XML_pivotFields); + + // + + if (!aRowFields.empty()) + { + pPivotStrm->startElement(XML_rowFields, + XML_count, OString::number(static_cast(aRowFields.size()))); + + for (const auto& rRowField : aRowFields) + { + pPivotStrm->singleElement(XML_field, XML_x, OString::number(rRowField)); + } + + pPivotStrm->endElement(XML_rowFields); + } + + // + + // + + if (!aColFields.empty()) + { + pPivotStrm->startElement(XML_colFields, + XML_count, OString::number(static_cast(aColFields.size()))); + + for (const auto& rColField : aColFields) + { + pPivotStrm->singleElement(XML_field, XML_x, OString::number(rColField)); + } + + pPivotStrm->endElement(XML_colFields); + } + + // + + // + + if (!aPageFields.empty()) + { + pPivotStrm->startElement(XML_pageFields, + XML_count, OString::number(static_cast(aPageFields.size()))); + + for (const auto& rPageField : aPageFields) + { + pPivotStrm->singleElement(XML_pageField, + XML_fld, OString::number(rPageField), + XML_hier, OString::number(-1)); // TODO : handle this correctly. + } + + pPivotStrm->endElement(XML_pageFields); + } + + // + + if (!aDataFields.empty()) + { + css::uno::Reference xDimsByName; + if (auto xDimSupplier = const_cast(rDPObj).GetSource()) + xDimsByName = xDimSupplier->getDimensions(); + + pPivotStrm->startElement(XML_dataFields, + XML_count, OString::number(static_cast(aDataFields.size()))); + + for (const auto& rDataField : aDataFields) + { + tools::Long nDimIdx = rDataField.mnPos; + assert(nDimIdx == -2 || aCachedDims[nDimIdx]); // the loop above should have screened for NULL's, skip check for -2 "data field" + const ScDPSaveDimension& rDim = *rDataField.mpDim; + std::optional pName = rDim.GetLayoutName(); + // tdf#124651: despite being optional in CT_DataField according to ECMA-376 Part 1, + // Excel (at least 2016) seems to insist on the presence of "name" attribute in + // dataField element. + // tdf#124881: try to create a meaningful name; don't use empty string. + if (!pName) + pName = ScDPUtil::getDisplayedMeasureName( + rDim.GetName(), ScDPUtil::toSubTotalFunc(rDim.GetFunction())); + auto pItemAttList = sax_fastparser::FastSerializerHelper::createAttrList(); + pItemAttList->add(XML_name, pName->toUtf8()); + pItemAttList->add(XML_fld, OString::number(nDimIdx)); + const char* pSubtotal = toOOXMLSubtotalType(rDim.GetFunction()); + if (pSubtotal) + pItemAttList->add(XML_subtotal, pSubtotal); + if (xDimsByName) + { + try + { + css::uno::Reference xDimProps( + xDimsByName->getByName(rDim.GetName()), uno::UNO_QUERY_THROW); + css::uno::Any aVal = xDimProps->getPropertyValue(SC_UNONAME_NUMFMT); + sal_uInt32 nScNumFmt = aVal.get(); + sal_uInt16 nXclNumFmt = GetRoot().GetNumFmtBuffer().Insert(nScNumFmt); + pItemAttList->add(XML_numFmtId, OString::number(nXclNumFmt)); + } + catch (uno::Exception&) + { + SAL_WARN("sc.filter", + "Couldn't get number format for data field " << rDim.GetName()); + // Just skip exporting number format + } + } + pPivotStrm->singleElement(XML_dataField, pItemAttList); + } + + pPivotStrm->endElement(XML_dataFields); + } + + // Now add style info (use grab bag, or just a set which is default on Excel 2007 through 2016) + if (const auto [bHas, aVal] = rDPObj.GetInteropGrabBagValue("pivotTableStyleInfo"); bHas) + WriteGrabBagItemToStream(rStrm, XML_pivotTableStyleInfo, aVal); + else + pPivotStrm->singleElement(XML_pivotTableStyleInfo, XML_name, "PivotStyleLight16", + XML_showRowHeaders, "1", XML_showColHeaders, "1", + XML_showRowStripes, "0", XML_showColStripes, "0", + XML_showLastColumn, "1"); + + OUString aBuf = "../pivotCache/pivotCacheDefinition" + + OUString::number(nCacheId) + + ".xml"; + + rStrm.addRelation( + pPivotStrm->getOutputStream(), + CREATE_OFFICEDOC_RELATION_TYPE("pivotCacheDefinition"), + aBuf); + + pPivotStrm->endElement(XML_pivotTableDefinition); +} + +void XclExpXmlPivotTables::AppendTable( const ScDPObject* pTable, sal_Int32 nCacheId, sal_Int32 nPivotId ) +{ + maTables.emplace_back(pTable, nCacheId, nPivotId); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xerecord.cxx b/sc/source/filter/excel/xerecord.cxx new file mode 100644 index 000000000..8778d75b5 --- /dev/null +++ b/sc/source/filter/excel/xerecord.cxx @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include +#include +#include + +using namespace ::oox; + +// Base classes to export Excel records ======================================= + +XclExpRecordBase::~XclExpRecordBase() +{ +} + +void XclExpRecordBase::Save( XclExpStream& /*rStrm*/ ) +{ +} + +void XclExpRecordBase::SaveXml( XclExpXmlStream& /*rStrm*/ ) +{ +} + +XclExpDelegatingRecord::XclExpDelegatingRecord( XclExpRecordBase* pRecord ) : + mpRecord( pRecord ) +{ +} + +XclExpDelegatingRecord::~XclExpDelegatingRecord() +{ + // Do Nothing; we use Delegating Record for other objects we "know" will + // survive... +} + +void XclExpDelegatingRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mpRecord ) + mpRecord->SaveXml( rStrm ); +} + +XclExpXmlElementRecord::XclExpXmlElementRecord(sal_Int32 const nElement) + : mnElement( nElement ) +{ +} + +XclExpXmlElementRecord::~XclExpXmlElementRecord() +{ +} + +XclExpXmlStartElementRecord::XclExpXmlStartElementRecord(sal_Int32 const nElement) + : XclExpXmlElementRecord(nElement) +{ +} + +XclExpXmlStartElementRecord::~XclExpXmlStartElementRecord() +{ +} + +void XclExpXmlStartElementRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStream = rStrm.GetCurrentStream(); + // TODO: no generic way to add attributes here, but it appears to + // not be needed yet + rStream->startElement(mnElement); +} + +XclExpXmlEndElementRecord::XclExpXmlEndElementRecord( sal_Int32 nElement ) + : XclExpXmlElementRecord( nElement ) +{ +} + +XclExpXmlEndElementRecord::~XclExpXmlEndElementRecord() +{ +} + +void XclExpXmlEndElementRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->endElement( mnElement ); +} + +XclExpXmlStartSingleElementRecord::XclExpXmlStartSingleElementRecord( + sal_Int32 const nElement) + : XclExpXmlElementRecord( nElement ) +{ +} + +XclExpXmlStartSingleElementRecord::~XclExpXmlStartSingleElementRecord() +{ +} + +void XclExpXmlStartSingleElementRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStream = rStrm.GetCurrentStream(); + rStream->write( "<" )->writeId( mnElement ); +} + +XclExpXmlEndSingleElementRecord::XclExpXmlEndSingleElementRecord() +{ +} + +XclExpXmlEndSingleElementRecord::~XclExpXmlEndSingleElementRecord() +{ +} + +void XclExpXmlEndSingleElementRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->write( "/>" ); +} + +XclExpRecord::XclExpRecord( sal_uInt16 nRecId, std::size_t nRecSize ) : + mnRecSize( nRecSize ), + mnRecId( nRecId ) +{ +} + +XclExpRecord::~XclExpRecord() +{ +} + +void XclExpRecord::SetRecHeader( sal_uInt16 nRecId, std::size_t nRecSize ) +{ + SetRecId( nRecId ); + SetRecSize( nRecSize ); +} + +void XclExpRecord::WriteBody( XclExpStream& /*rStrm*/ ) +{ +} + +void XclExpRecord::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE( mnRecId != EXC_ID_UNKNOWN, "XclExpRecord::Save - record ID uninitialized" ); + rStrm.StartRecord( mnRecId, mnRecSize ); + WriteBody( rStrm ); + rStrm.EndRecord(); +} + +template<> +void XclExpValueRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mnAttribute == -1 ) + return; + rStrm.WriteAttributes(mnAttribute, OUString::number(maValue)); +} + +void XclExpBoolRecord::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast< sal_uInt16 >( mbValue ? 1 : 0 ); +} + +void XclExpBoolRecord::SaveXml( XclExpXmlStream& rStrm ) +{ + if( mnAttribute == -1 ) + return; + + rStrm.WriteAttributes( + // HACK: HIDEOBJ (excdoc.cxx) should be its own object to handle XML_showObjects + mnAttribute, mnAttribute == XML_showObjects ? "all" : ToPsz( mbValue )); +} + +XclExpDummyRecord::XclExpDummyRecord( sal_uInt16 nRecId, const void* pRecData, std::size_t nRecSize ) : + XclExpRecord( nRecId ) +{ + SetData( pRecData, nRecSize ); +} + +void XclExpDummyRecord::SetData( const void* pRecData, std::size_t nRecSize ) +{ + mpData = pRecData; + SetRecSize( pRecData ? nRecSize : 0 ); +} + +void XclExpDummyRecord::WriteBody( XclExpStream& rStrm ) +{ + rStrm.Write( mpData, GetRecSize() ); +} + +// Future records ============================================================= + +XclExpFutureRecord::XclExpFutureRecord( XclFutureRecType eRecType, sal_uInt16 nRecId, std::size_t nRecSize ) : + XclExpRecord( nRecId, nRecSize ), + meRecType( eRecType ) +{ +} + +void XclExpFutureRecord::Save( XclExpStream& rStrm ) +{ + rStrm.StartRecord( GetRecId(), GetRecSize() + ((meRecType == EXC_FUTUREREC_UNUSEDREF) ? 12 : 4) ); + rStrm << GetRecId() << sal_uInt16( 0 ); + if( meRecType == EXC_FUTUREREC_UNUSEDREF ) + rStrm.WriteZeroBytes( 8 ); + WriteBody( rStrm ); + rStrm.EndRecord(); +} + +XclExpSubStream::XclExpSubStream( sal_uInt16 nSubStrmType ) : + mnSubStrmType( nSubStrmType ) +{ +} + +void XclExpSubStream::Save( XclExpStream& rStrm ) +{ + // BOF record + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF2: + rStrm.StartRecord( EXC_ID2_BOF, 4 ); + rStrm << sal_uInt16( 7 ) << mnSubStrmType; + rStrm.EndRecord(); + break; + case EXC_BIFF3: + rStrm.StartRecord( EXC_ID3_BOF, 6 ); + rStrm << sal_uInt16( 0 ) << mnSubStrmType << sal_uInt16( 2104 ); + rStrm.EndRecord(); + break; + case EXC_BIFF4: + rStrm.StartRecord( EXC_ID4_BOF, 6 ); + rStrm << sal_uInt16( 0 ) << mnSubStrmType << sal_uInt16( 1705 ); + rStrm.EndRecord(); + break; + case EXC_BIFF5: + rStrm.StartRecord( EXC_ID5_BOF, 8 ); + rStrm << EXC_BOF_BIFF5 << mnSubStrmType << sal_uInt16( 4915 ) << sal_uInt16( 1994 ); + rStrm.EndRecord(); + break; + case EXC_BIFF8: + rStrm.StartRecord( EXC_ID5_BOF, 16 ); + rStrm << EXC_BOF_BIFF8 << mnSubStrmType << sal_uInt16( 3612 ) << sal_uInt16( 1996 ); + rStrm << sal_uInt32( 1 ) << sal_uInt32( 6 ); + rStrm.EndRecord(); + break; + default: + DBG_ERROR_BIFF(); + } + + // substream records + XclExpRecordList<>::Save( rStrm ); + + // EOF record + rStrm.StartRecord( EXC_ID_EOF, 0 ); + rStrm.EndRecord(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeroot.cxx b/sc/source/filter/excel/xeroot.cxx new file mode 100644 index 000000000..3bb35d301 --- /dev/null +++ b/sc/source/filter/excel/xeroot.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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +using namespace ::com::sun::star; + +// Global data ================================================================ + +XclExpRootData::XclExpRootData( XclBiff eBiff, SfxMedium& rMedium, + const tools::SvRef& xRootStrg, ScDocument& rDoc, rtl_TextEncoding eTextEnc ) : + XclRootData( eBiff, rMedium, xRootStrg, rDoc, eTextEnc, true ) +{ + mbRelUrl = mrMedium.IsRemote() + ? officecfg::Office::Common::Save::URL::Internet::get() + : officecfg::Office::Common::Save::URL::FileSystem::get(); + maStringBuf.setLength(0); +} + +XclExpRootData::~XclExpRootData() +{ +} + +XclExpRoot::XclExpRoot( XclExpRootData& rExpRootData ) : + XclRoot( rExpRootData ), + mrExpData( rExpRootData ) +{ +} + +XclExpTabInfo& XclExpRoot::GetTabInfo() const +{ + OSL_ENSURE( mrExpData.mxTabInfo, "XclExpRoot::GetTabInfo - missing object (wrong BIFF?)" ); + return *mrExpData.mxTabInfo; +} + +XclExpAddressConverter& XclExpRoot::GetAddressConverter() const +{ + OSL_ENSURE( mrExpData.mxAddrConv, "XclExpRoot::GetAddressConverter - missing object (wrong BIFF?)" ); + return *mrExpData.mxAddrConv; +} + +XclExpFormulaCompiler& XclExpRoot::GetFormulaCompiler() const +{ + OSL_ENSURE( mrExpData.mxFmlaComp, "XclExpRoot::GetFormulaCompiler - missing object (wrong BIFF?)" ); + return *mrExpData.mxFmlaComp; +} + +XclExpProgressBar& XclExpRoot::GetProgressBar() const +{ + OSL_ENSURE( mrExpData.mxProgress, "XclExpRoot::GetProgressBar - missing object (wrong BIFF?)" ); + return *mrExpData.mxProgress; +} + +XclExpSst& XclExpRoot::GetSst() const +{ + OSL_ENSURE( mrExpData.mxSst, "XclExpRoot::GetSst - missing object (wrong BIFF?)" ); + return *mrExpData.mxSst; +} + +XclExpPalette& XclExpRoot::GetPalette() const +{ + OSL_ENSURE( mrExpData.mxPalette, "XclExpRoot::GetPalette - missing object (wrong BIFF?)" ); + return *mrExpData.mxPalette; +} + +XclExpFontBuffer& XclExpRoot::GetFontBuffer() const +{ + OSL_ENSURE( mrExpData.mxFontBfr, "XclExpRoot::GetFontBuffer - missing object (wrong BIFF?)" ); + return *mrExpData.mxFontBfr; +} + +XclExpNumFmtBuffer& XclExpRoot::GetNumFmtBuffer() const +{ + OSL_ENSURE( mrExpData.mxNumFmtBfr, "XclExpRoot::GetNumFmtBuffer - missing object (wrong BIFF?)" ); + return *mrExpData.mxNumFmtBfr; +} + +XclExpXFBuffer& XclExpRoot::GetXFBuffer() const +{ + OSL_ENSURE( mrExpData.mxXFBfr, "XclExpRoot::GetXFBuffer - missing object (wrong BIFF?)" ); + return *mrExpData.mxXFBfr; +} + +XclExpLinkManager& XclExpRoot::GetGlobalLinkManager() const +{ + OSL_ENSURE( mrExpData.mxGlobLinkMgr, "XclExpRoot::GetGlobalLinkManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxGlobLinkMgr; +} + +XclExpLinkManager& XclExpRoot::GetLocalLinkManager() const +{ + OSL_ENSURE( GetLocalLinkMgrRef(), "XclExpRoot::GetLocalLinkManager - missing object (wrong BIFF?)" ); + return *GetLocalLinkMgrRef(); +} + +XclExpNameManager& XclExpRoot::GetNameManager() const +{ + OSL_ENSURE( mrExpData.mxNameMgr, "XclExpRoot::GetNameManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxNameMgr; +} + +XclExpObjectManager& XclExpRoot::GetObjectManager() const +{ + OSL_ENSURE( mrExpData.mxObjMgr, "XclExpRoot::GetObjectManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxObjMgr; +} + +XclExpFilterManager& XclExpRoot::GetFilterManager() const +{ + OSL_ENSURE( mrExpData.mxFilterMgr, "XclExpRoot::GetFilterManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxFilterMgr; +} + +XclExpDxfs& XclExpRoot::GetDxfs() const +{ + OSL_ENSURE( mrExpData.mxDxfs, "XclExpRoot::GetDxfs - missing object ( wrong BIFF?)" ); + return *mrExpData.mxDxfs; +} + +XclExpPivotTableManager& XclExpRoot::GetPivotTableManager() const +{ + OSL_ENSURE( mrExpData.mxPTableMgr, "XclExpRoot::GetPivotTableManager - missing object (wrong BIFF?)" ); + return *mrExpData.mxPTableMgr; +} + +XclExpXmlPivotTableManager& XclExpRoot::GetXmlPivotTableManager() +{ + assert(mrExpData.mxXmlPTableMgr); + return *mrExpData.mxXmlPTableMgr; +} + +XclExpTablesManager& XclExpRoot::GetTablesManager() +{ + assert(mrExpData.mxTablesMgr); + return *mrExpData.mxTablesMgr; +} + +void XclExpRoot::InitializeConvert() +{ + mrExpData.mxTabInfo = std::make_shared( GetRoot() ); + mrExpData.mxAddrConv = std::make_shared( GetRoot() ); + mrExpData.mxFmlaComp = std::make_shared( GetRoot() ); + mrExpData.mxProgress = std::make_shared( GetRoot() ); + + GetProgressBar().Initialize(); +} + +void XclExpRoot::InitializeGlobals() +{ + SetCurrScTab( SCTAB_GLOBAL ); + + if( GetBiff() >= EXC_BIFF5 ) + { + mrExpData.mxPalette = new XclExpPalette( GetRoot() ); + mrExpData.mxFontBfr = new XclExpFontBuffer( GetRoot() ); + mrExpData.mxNumFmtBfr = new XclExpNumFmtBuffer( GetRoot() ); + mrExpData.mxXFBfr = new XclExpXFBuffer( GetRoot() ); + mrExpData.mxGlobLinkMgr = new XclExpLinkManager( GetRoot() ); + mrExpData.mxNameMgr = new XclExpNameManager( GetRoot() ); + } + + if( GetBiff() == EXC_BIFF8 ) + { + mrExpData.mxSst = new XclExpSst(); + mrExpData.mxObjMgr = std::make_shared( GetRoot() ); + mrExpData.mxFilterMgr = std::make_shared( GetRoot() ); + mrExpData.mxPTableMgr = std::make_shared( GetRoot() ); + // BIFF8: only one link manager for all sheets + mrExpData.mxLocLinkMgr = mrExpData.mxGlobLinkMgr; + mrExpData.mxDxfs = new XclExpDxfs( GetRoot() ); + } + + if( GetOutput() == EXC_OUTPUT_XML_2007 ) + { + mrExpData.mxXmlPTableMgr = std::make_shared(GetRoot()); + mrExpData.mxTablesMgr = std::make_shared(GetRoot()); + + do + { + ScDocument& rDoc = GetDoc(); + // Pass the model factory to OpCodeProvider, not the process + // service factory, otherwise a FormulaOpCodeMapperObj would be + // instantiated instead of a ScFormulaOpCodeMapperObj and the + // ScCompiler virtuals not be called! Which would be the case with + // the current (2013-01-24) rDoc.GetServiceManager() + const SfxObjectShell* pShell = rDoc.GetDocumentShell(); + if (!pShell) + { + SAL_WARN( "sc", "XclExpRoot::InitializeGlobals - no object shell"); + break; + } + uno::Reference< lang::XComponent > xComponent = pShell->GetModel(); + if (!xComponent.is()) + { + SAL_WARN( "sc", "XclExpRoot::InitializeGlobals - no component"); + break; + } + uno::Reference< lang::XMultiServiceFactory > xModelFactory( xComponent, uno::UNO_QUERY); + oox::xls::OpCodeProvider aOpCodeProvider(xModelFactory, false); + // Compiler mocks about non-matching ctor or conversion from + // Sequence<...> to Sequence if directly created or passed, + // conversion through Any works around. + uno::Any aAny( aOpCodeProvider.getOoxParserMap()); + uno::Sequence< const sheet::FormulaOpCodeMapEntry > aOpCodeMapping; + if (!(aAny >>= aOpCodeMapping)) + { + SAL_WARN( "sc", "XclExpRoot::InitializeGlobals - no OpCodeMap"); + break; + } + ScCompiler aCompiler( rDoc, ScAddress(), rDoc.GetGrammar()); + mrExpData.mxOpCodeMap = formula::FormulaCompiler::CreateOpCodeMap( aOpCodeMapping, true); + } while(false); + } + + GetXFBuffer().Initialize(); + GetNameManager().Initialize(); +} + +void XclExpRoot::InitializeTable( SCTAB nScTab ) +{ + SetCurrScTab( nScTab ); + if( GetBiff() == EXC_BIFF5 ) + { + // local link manager per sheet + mrExpData.mxLocLinkMgr = new XclExpLinkManager( GetRoot() ); + } +} + +void XclExpRoot::InitializeSave() +{ + GetPalette().Finalize(); + GetXFBuffer().Finalize(); +} + +XclExpRecordRef XclExpRoot::CreateRecord( sal_uInt16 nRecId ) const +{ + XclExpRecordRef xRec; + switch( nRecId ) + { + case EXC_ID_PALETTE: xRec = mrExpData.mxPalette; break; + case EXC_ID_FONTLIST: xRec = mrExpData.mxFontBfr; break; + case EXC_ID_FORMATLIST: xRec = mrExpData.mxNumFmtBfr; break; + case EXC_ID_XFLIST: xRec = mrExpData.mxXFBfr; break; + case EXC_ID_SST: xRec = mrExpData.mxSst; break; + case EXC_ID_EXTERNSHEET: xRec = GetLocalLinkMgrRef(); break; + case EXC_ID_NAME: xRec = mrExpData.mxNameMgr; break; + case EXC_ID_DXFS: xRec = mrExpData.mxDxfs; break; + } + OSL_ENSURE( xRec, "XclExpRoot::CreateRecord - unknown record ID or missing object" ); + return xRec; +} + +bool XclExpRoot::IsDocumentEncrypted() const +{ + // We need to encrypt the content when the document structure is protected. + const ScDocProtection* pDocProt = GetDoc().GetDocProtection(); + if (pDocProt && pDocProt->isProtected() && pDocProt->isOptionEnabled(ScDocProtection::STRUCTURE)) + return true; + + // Whether password is entered directly into the save dialog. + return GetEncryptionData().hasElements(); +} + +uno::Sequence< beans::NamedValue > XclExpRoot::GenerateEncryptionData( const OUString& aPass ) +{ + uno::Sequence< beans::NamedValue > aEncryptionData; + + if ( !aPass.isEmpty() && aPass.getLength() < 16 ) + { + rtlRandomPool aRandomPool = rtl_random_createPool (); + sal_uInt8 pnDocId[16]; + rtl_random_getBytes( aRandomPool, pnDocId, 16 ); + + rtl_random_destroyPool( aRandomPool ); + + sal_uInt16 pnPasswd[16] = {}; + for( sal_Int32 nChar = 0; nChar < aPass.getLength(); ++nChar ) + pnPasswd[nChar] = aPass[nChar]; + + ::msfilter::MSCodec_Std97 aCodec; + aCodec.InitKey( pnPasswd, pnDocId ); + aEncryptionData = aCodec.GetEncryptionData(); + } + + return aEncryptionData; +} + +uno::Sequence< beans::NamedValue > XclExpRoot::GetEncryptionData() const +{ + uno::Sequence< beans::NamedValue > aEncryptionData; + const SfxUnoAnyItem* pEncryptionDataItem = SfxItemSet::GetItem(GetMedium().GetItemSet(), SID_ENCRYPTIONDATA, false); + if ( pEncryptionDataItem ) + pEncryptionDataItem->GetValue() >>= aEncryptionData; + else + { + // try to get the encryption data from the password + const SfxStringItem* pPasswordItem = SfxItemSet::GetItem(GetMedium().GetItemSet(), SID_PASSWORD, false); + if ( pPasswordItem && !pPasswordItem->GetValue().isEmpty() ) + aEncryptionData = GenerateEncryptionData( pPasswordItem->GetValue() ); + } + + return aEncryptionData; +} + +uno::Sequence< beans::NamedValue > XclExpRoot::GenerateDefaultEncryptionData() +{ + return GenerateEncryptionData( GetDefaultPassword() ); +} + +XclExpRootData::XclExpLinkMgrRef const & XclExpRoot::GetLocalLinkMgrRef() const +{ + return IsInGlobals() ? mrExpData.mxGlobLinkMgr : mrExpData.mxLocLinkMgr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xestream.cxx b/sc/source/filter/excel/xestream.cxx new file mode 100644 index 000000000..f0486cc37 --- /dev/null +++ b/sc/source/filter/excel/xestream.cxx @@ -0,0 +1,1259 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define DEBUG_XL_ENCRYPTION 0 + +using ::com::sun::star::uno::XInterface; +using ::std::vector; + +using namespace com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::sheet; +using namespace ::com::sun::star::uno; +using namespace ::formula; +using namespace ::oox; + +XclExpStream::XclExpStream( SvStream& rOutStrm, const XclExpRoot& rRoot, sal_uInt16 nMaxRecSize ) : + mrStrm( rOutStrm ), + mrRoot( rRoot ), + mbUseEncrypter( false ), + mnMaxRecSize( nMaxRecSize ), + mnCurrMaxSize( 0 ), + mnMaxSliceSize( 0 ), + mnHeaderSize( 0 ), + mnCurrSize( 0 ), + mnSliceSize( 0 ), + mnPredictSize( 0 ), + mnLastSizePos( 0 ), + mbInRec( false ) +{ + if( mnMaxRecSize == 0 ) + mnMaxRecSize = (mrRoot.GetBiff() <= EXC_BIFF5) ? EXC_MAXRECSIZE_BIFF5 : EXC_MAXRECSIZE_BIFF8; + mnMaxContSize = mnMaxRecSize; +} + +XclExpStream::~XclExpStream() +{ + mrStrm.FlushBuffer(); +} + +void XclExpStream::StartRecord( sal_uInt16 nRecId, std::size_t nRecSize ) +{ + OSL_ENSURE( !mbInRec, "XclExpStream::StartRecord - another record still open" ); + DisableEncryption(); + mnMaxContSize = mnCurrMaxSize = mnMaxRecSize; + mnPredictSize = nRecSize; + mbInRec = true; + InitRecord( nRecId ); + SetSliceSize( 0 ); + EnableEncryption(); +} + +void XclExpStream::EndRecord() +{ + OSL_ENSURE( mbInRec, "XclExpStream::EndRecord - no record open" ); + DisableEncryption(); + UpdateRecSize(); + mrStrm.Seek( STREAM_SEEK_TO_END ); + mbInRec = false; +} + +void XclExpStream::SetSliceSize( sal_uInt16 nSize ) +{ + mnMaxSliceSize = nSize; + mnSliceSize = 0; +} + +XclExpStream& XclExpStream::operator<<( sal_Int8 nValue ) +{ + PrepareWrite( 1 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteSChar( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_uInt8 nValue ) +{ + PrepareWrite( 1 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteUChar( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_Int16 nValue ) +{ + PrepareWrite( 2 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteInt16( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_uInt16 nValue ) +{ + PrepareWrite( 2 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteUInt16( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_Int32 nValue ) +{ + PrepareWrite( 4 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteInt32( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( sal_uInt32 nValue ) +{ + PrepareWrite( 4 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, nValue); + else + mrStrm.WriteUInt32( nValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( float fValue ) +{ + PrepareWrite( 4 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, fValue); + else + mrStrm.WriteFloat( fValue ); + return *this; +} + +XclExpStream& XclExpStream::operator<<( double fValue ) +{ + PrepareWrite( 8 ); + if (mbUseEncrypter && HasValidEncrypter()) + mxEncrypter->Encrypt(mrStrm, fValue); + else + mrStrm.WriteDouble( fValue ); + return *this; +} + +std::size_t XclExpStream::Write( const void* pData, std::size_t nBytes ) +{ + std::size_t nRet = 0; + if( pData && (nBytes > 0) ) + { + if( mbInRec ) + { + const sal_uInt8* pBuffer = static_cast< const sal_uInt8* >( pData ); + std::size_t nBytesLeft = nBytes; + bool bValid = true; + + while( bValid && (nBytesLeft > 0) ) + { + std::size_t nWriteLen = ::std::min< std::size_t >( PrepareWrite(), nBytesLeft ); + std::size_t nWriteRet = nWriteLen; + if (mbUseEncrypter && HasValidEncrypter()) + { + OSL_ENSURE(nWriteLen > 0, "XclExpStream::Write: write length is 0!"); + vector aBytes(nWriteLen); + memcpy(aBytes.data(), pBuffer, nWriteLen); + mxEncrypter->EncryptBytes(mrStrm, aBytes); + // TODO: How do I check if all the bytes have been successfully written ? + } + else + { + nWriteRet = mrStrm.WriteBytes(pBuffer, nWriteLen); + bValid = (nWriteLen == nWriteRet); + OSL_ENSURE( bValid, "XclExpStream::Write - stream write error" ); + } + pBuffer += nWriteRet; + nRet += nWriteRet; + nBytesLeft -= nWriteRet; + UpdateSizeVars( nWriteRet ); + } + } + else + nRet = mrStrm.WriteBytes(pData, nBytes); + } + return nRet; +} + +void XclExpStream::WriteZeroBytes( std::size_t nBytes ) +{ + if( mbInRec ) + { + std::size_t nBytesLeft = nBytes; + while( nBytesLeft > 0 ) + { + std::size_t nWriteLen = ::std::min< std::size_t >( PrepareWrite(), nBytesLeft ); + WriteRawZeroBytes( nWriteLen ); + nBytesLeft -= nWriteLen; + UpdateSizeVars( nWriteLen ); + } + } + else + WriteRawZeroBytes( nBytes ); +} + +void XclExpStream::WriteZeroBytesToRecord( std::size_t nBytes ) +{ + if (!mbInRec) + // not in record. + return; + + for (std::size_t i = 0; i < nBytes; ++i) + *this << sal_uInt8(0)/*nZero*/; +} + +void XclExpStream::CopyFromStream(SvStream& rInStrm, sal_uInt64 const nBytes) +{ + sal_uInt64 const nRemaining(rInStrm.remainingSize()); + sal_uInt64 nBytesLeft = ::std::min(nBytes, nRemaining); + if( nBytesLeft <= 0 ) + return; + + const std::size_t nMaxBuffer = 4096; + std::unique_ptr pBuffer( + new sal_uInt8[ ::std::min(nBytesLeft, nMaxBuffer) ]); + bool bValid = true; + + while( bValid && (nBytesLeft > 0) ) + { + std::size_t nWriteLen = ::std::min(nBytesLeft, nMaxBuffer); + rInStrm.ReadBytes(pBuffer.get(), nWriteLen); + std::size_t nWriteRet = Write( pBuffer.get(), nWriteLen ); + bValid = (nWriteLen == nWriteRet); + nBytesLeft -= nWriteRet; + } +} + +void XclExpStream::WriteUnicodeBuffer( const ScfUInt16Vec& rBuffer, sal_uInt8 nFlags ) +{ + SetSliceSize( 0 ); + nFlags &= EXC_STRF_16BIT; // repeat only 16bit flag + sal_uInt16 nCharLen = nFlags ? 2 : 1; + + for( const auto& rItem : rBuffer ) + { + if( mbInRec && (mnCurrSize + nCharLen > mnCurrMaxSize) ) + { + StartContinue(); + operator<<( nFlags ); + } + if( nCharLen == 2 ) + operator<<( rItem ); + else + operator<<( static_cast< sal_uInt8 >( rItem ) ); + } +} + +// Xcl has an obscure sense of whether starting a new record or not, +// and crashes if it encounters the string header at the very end of a record. +// Thus we add 1 to give some room, seems like they do it that way but with another count (10?) +void XclExpStream::WriteByteString( const OString& rString ) +{ + SetSliceSize( 0 ); + std::size_t nLen = ::std::min< std::size_t >( rString.getLength(), 0x00FF ); + nLen = ::std::min< std::size_t >( nLen, 0xFF ); + + sal_uInt16 nLeft = PrepareWrite(); + if( mbInRec && (nLeft <= 1) ) + StartContinue(); + + operator<<( static_cast< sal_uInt8 >( nLen ) ); + Write( rString.getStr(), nLen ); +} + +void XclExpStream::WriteCharBuffer( const ScfUInt8Vec& rBuffer ) +{ + SetSliceSize( 0 ); + Write( rBuffer.data(), rBuffer.size() ); +} + +void XclExpStream::SetEncrypter( XclExpEncrypterRef const & xEncrypter ) +{ + mxEncrypter = xEncrypter; +} + +bool XclExpStream::HasValidEncrypter() const +{ + return mxEncrypter && mxEncrypter->IsValid(); +} + +void XclExpStream::EnableEncryption( bool bEnable ) +{ + mbUseEncrypter = bEnable && HasValidEncrypter(); +} + +void XclExpStream::DisableEncryption() +{ + EnableEncryption(false); +} + +void XclExpStream::SetSvStreamPos(sal_uInt64 const nPos) +{ + OSL_ENSURE( !mbInRec, "XclExpStream::SetSvStreamPos - not allowed inside of a record" ); + mbInRec ? 0 : mrStrm.Seek( nPos ); +} + +// private -------------------------------------------------------------------- + +void XclExpStream::InitRecord( sal_uInt16 nRecId ) +{ + mrStrm.Seek( STREAM_SEEK_TO_END ); + mrStrm.WriteUInt16( nRecId ); + + mnLastSizePos = mrStrm.Tell(); + mnHeaderSize = static_cast< sal_uInt16 >( ::std::min< std::size_t >( mnPredictSize, mnCurrMaxSize ) ); + mrStrm.WriteUInt16( mnHeaderSize ); + mnCurrSize = mnSliceSize = 0; +} + +void XclExpStream::UpdateRecSize() +{ + if( mnCurrSize != mnHeaderSize ) + { + mrStrm.Seek( mnLastSizePos ); + mrStrm.WriteUInt16( mnCurrSize ); + } +} + +void XclExpStream::UpdateSizeVars( std::size_t nSize ) +{ + OSL_ENSURE( mnCurrSize + nSize <= mnCurrMaxSize, "XclExpStream::UpdateSizeVars - record overwritten" ); + mnCurrSize = mnCurrSize + static_cast< sal_uInt16 >( nSize ); + + if( mnMaxSliceSize > 0 ) + { + OSL_ENSURE( mnSliceSize + nSize <= mnMaxSliceSize, "XclExpStream::UpdateSizeVars - slice overwritten" ); + mnSliceSize = mnSliceSize + static_cast< sal_uInt16 >( nSize ); + if( mnSliceSize >= mnMaxSliceSize ) + mnSliceSize = 0; + } +} + +void XclExpStream::StartContinue() +{ + UpdateRecSize(); + mnCurrMaxSize = mnMaxContSize; + mnPredictSize -= mnCurrSize; + InitRecord( EXC_ID_CONT ); +} + +void XclExpStream::PrepareWrite( sal_uInt16 nSize ) +{ + if( mbInRec ) + { + if( (mnCurrSize + nSize > mnCurrMaxSize) || + ((mnMaxSliceSize > 0) && (mnSliceSize == 0) && (mnCurrSize + mnMaxSliceSize > mnCurrMaxSize)) ) + StartContinue(); + UpdateSizeVars( nSize ); + } +} + +sal_uInt16 XclExpStream::PrepareWrite() +{ + sal_uInt16 nRet = 0; + if( mbInRec ) + { + if( (mnCurrSize >= mnCurrMaxSize) || + ((mnMaxSliceSize > 0) && (mnSliceSize == 0) && (mnCurrSize + mnMaxSliceSize > mnCurrMaxSize)) ) + StartContinue(); + UpdateSizeVars( 0 ); + + nRet = (mnMaxSliceSize > 0) ? (mnMaxSliceSize - mnSliceSize) : (mnCurrMaxSize - mnCurrSize); + } + return nRet; +} + +void XclExpStream::WriteRawZeroBytes( std::size_t nBytes ) +{ + const sal_uInt32 nData = 0; + std::size_t nBytesLeft = nBytes; + while( nBytesLeft >= sizeof( nData ) ) + { + mrStrm.WriteUInt32( nData ); + nBytesLeft -= sizeof( nData ); + } + if( nBytesLeft ) + mrStrm.WriteBytes(&nData, nBytesLeft); +} + +XclExpBiff8Encrypter::XclExpBiff8Encrypter( const XclExpRoot& rRoot ) : + mnOldPos(STREAM_SEEK_TO_END), + mbValid(false) +{ + Sequence< NamedValue > aEncryptionData = rRoot.GetEncryptionData(); + if( !aEncryptionData.hasElements() ) + // Empty password. Get the default biff8 password. + aEncryptionData = XclExpRoot::GenerateDefaultEncryptionData(); + Init( aEncryptionData ); +} + +XclExpBiff8Encrypter::~XclExpBiff8Encrypter() +{ +} + +void XclExpBiff8Encrypter::GetSaltDigest( sal_uInt8 pnSaltDigest[16] ) const +{ + if ( sizeof( mpnSaltDigest ) == 16 ) + memcpy( pnSaltDigest, mpnSaltDigest, 16 ); +} + +void XclExpBiff8Encrypter::GetSalt( sal_uInt8 pnSalt[16] ) const +{ + if ( sizeof( mpnSalt ) == 16 ) + memcpy( pnSalt, mpnSalt, 16 ); +} + +void XclExpBiff8Encrypter::GetDocId( sal_uInt8 pnDocId[16] ) const +{ + if ( sizeof( mpnDocId ) == 16 ) + memcpy( pnDocId, mpnDocId, 16 ); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt8 nData ) +{ + vector aByte { nData }; + EncryptBytes(rStrm, aByte); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt16 nData ) +{ + ::std::vector pnBytes + { + o3tl::narrowing(nData & 0xFF), + o3tl::narrowing((nData >> 8) & 0xFF) + }; + EncryptBytes(rStrm, pnBytes); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_uInt32 nData ) +{ + ::std::vector pnBytes + { + o3tl::narrowing(nData & 0xFF), + o3tl::narrowing((nData >> 8) & 0xFF), + o3tl::narrowing((nData >> 16) & 0xFF), + o3tl::narrowing((nData >> 24) & 0xFF) + }; + EncryptBytes(rStrm, pnBytes); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, float fValue ) +{ + ::std::vector pnBytes(4); + memcpy(pnBytes.data(), &fValue, 4); + EncryptBytes(rStrm, pnBytes); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, double fValue ) +{ + ::std::vector pnBytes(8); + memcpy(pnBytes.data(), &fValue, 8); + EncryptBytes(rStrm, pnBytes); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int8 nData ) +{ + Encrypt(rStrm, static_cast(nData)); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int16 nData ) +{ + Encrypt(rStrm, static_cast(nData)); +} + +void XclExpBiff8Encrypter::Encrypt( SvStream& rStrm, sal_Int32 nData ) +{ + Encrypt(rStrm, static_cast(nData)); +} + +void XclExpBiff8Encrypter::Init( const Sequence< NamedValue >& rEncryptionData ) +{ + mbValid = false; + + if( !maCodec.InitCodec( rEncryptionData ) ) + return; + + maCodec.GetDocId( mpnDocId ); + + // generate the salt here + rtlRandomPool aRandomPool = rtl_random_createPool (); + rtl_random_getBytes( aRandomPool, mpnSalt, 16 ); + rtl_random_destroyPool( aRandomPool ); + + memset( mpnSaltDigest, 0, sizeof( mpnSaltDigest ) ); + + // generate salt hash. + ::msfilter::MSCodec_Std97 aCodec; + aCodec.InitCodec( rEncryptionData ); + aCodec.CreateSaltDigest( mpnSalt, mpnSaltDigest ); + + // verify to make sure it's in good shape. + mbValid = maCodec.VerifyKey( mpnSalt, mpnSaltDigest ); +} + +sal_uInt32 XclExpBiff8Encrypter::GetBlockPos( std::size_t nStrmPos ) +{ + return static_cast< sal_uInt32 >( nStrmPos / EXC_ENCR_BLOCKSIZE ); +} + +sal_uInt16 XclExpBiff8Encrypter::GetOffsetInBlock( std::size_t nStrmPos ) +{ + return static_cast< sal_uInt16 >( nStrmPos % EXC_ENCR_BLOCKSIZE ); +} + +void XclExpBiff8Encrypter::EncryptBytes( SvStream& rStrm, vector& aBytes ) +{ + sal_uInt64 nStrmPos = rStrm.Tell(); + sal_uInt16 nBlockOffset = GetOffsetInBlock(nStrmPos); + sal_uInt32 nBlockPos = GetBlockPos(nStrmPos); + +#if DEBUG_XL_ENCRYPTION + fprintf(stdout, "XclExpBiff8Encrypter::EncryptBytes: stream pos = %ld offset in block = %d block pos = %ld\n", + nStrmPos, nBlockOffset, nBlockPos); +#endif + + sal_uInt16 nSize = static_cast< sal_uInt16 >( aBytes.size() ); + if (nSize == 0) + return; + +#if DEBUG_XL_ENCRYPTION + fprintf(stdout, "RAW: "); + for (sal_uInt16 i = 0; i < nSize; ++i) + fprintf(stdout, "%2.2X ", aBytes[i]); + fprintf(stdout, "\n"); +#endif + + if (mnOldPos != nStrmPos) + { + sal_uInt16 nOldOffset = GetOffsetInBlock(mnOldPos); + sal_uInt32 nOldBlockPos = GetBlockPos(mnOldPos); + + if ( (nBlockPos != nOldBlockPos) || (nBlockOffset < nOldOffset) ) + { + maCodec.InitCipher(nBlockPos); + nOldOffset = 0; + } + + if (nBlockOffset > nOldOffset) + maCodec.Skip(nBlockOffset - nOldOffset); + } + + sal_uInt16 nBytesLeft = nSize; + sal_uInt16 nPos = 0; + while (nBytesLeft > 0) + { + sal_uInt16 nBlockLeft = EXC_ENCR_BLOCKSIZE - nBlockOffset; + sal_uInt16 nEncBytes = ::std::min(nBlockLeft, nBytesLeft); + + bool bRet = maCodec.Encode(&aBytes[nPos], nEncBytes, &aBytes[nPos], nEncBytes); + OSL_ENSURE(bRet, "XclExpBiff8Encrypter::EncryptBytes: encryption failed!!"); + + std::size_t nRet = rStrm.WriteBytes(&aBytes[nPos], nEncBytes); + OSL_ENSURE(nRet == nEncBytes, "XclExpBiff8Encrypter::EncryptBytes: fail to write to stream!!"); + + nStrmPos = rStrm.Tell(); + nBlockOffset = GetOffsetInBlock(nStrmPos); + nBlockPos = GetBlockPos(nStrmPos); + if (nBlockOffset == 0) + maCodec.InitCipher(nBlockPos); + + nBytesLeft -= nEncBytes; + nPos += nEncBytes; + } + mnOldPos = nStrmPos; +} + +static const char* lcl_GetErrorString( FormulaError nScErrCode ) +{ + sal_uInt8 nXclErrCode = XclTools::GetXclErrorCode( nScErrCode ); + switch( nXclErrCode ) + { + case EXC_ERR_NULL: return "#NULL!"; + case EXC_ERR_DIV0: return "#DIV/0!"; + case EXC_ERR_VALUE: return "#VALUE!"; + case EXC_ERR_REF: return "#REF!"; + case EXC_ERR_NAME: return "#NAME?"; + case EXC_ERR_NUM: return "#NUM!"; + case EXC_ERR_NA: + default: return "#N/A"; + } +} + +void XclXmlUtils::GetFormulaTypeAndValue( ScFormulaCell& rCell, const char*& rsType, OUString& rsValue ) +{ + sc::FormulaResultValue aResValue = rCell.GetResult(); + + switch (aResValue.meType) + { + case sc::FormulaResultValue::Error: + rsType = "e"; + rsValue = ToOUString(lcl_GetErrorString(aResValue.mnError)); + break; + case sc::FormulaResultValue::Value: + rsType = rCell.GetFormatType() == SvNumFormatType::LOGICAL + && (aResValue.mfValue == 0.0 || aResValue.mfValue == 1.0) + ? "b" + : "n"; + rsValue = OUString::number(aResValue.mfValue); + break; + case sc::FormulaResultValue::String: + rsType = "str"; + rsValue = rCell.GetString().getString(); + break; + case sc::FormulaResultValue::Invalid: + default: + // TODO : double-check this to see if this is correct. + rsType = "inlineStr"; + rsValue = rCell.GetString().getString(); + } +} + +OUString XclXmlUtils::GetStreamName( const char* sStreamDir, const char* sStream, sal_Int32 nId ) +{ + OUStringBuffer sBuf; + if( sStreamDir ) + sBuf.appendAscii( sStreamDir ); + sBuf.appendAscii( sStream ); + if( nId ) + sBuf.append( nId ); + if( strstr(sStream, "vml") ) + sBuf.append( ".vml" ); + else + sBuf.append( ".xml" ); + return sBuf.makeStringAndClear(); +} + +OString XclXmlUtils::ToOString( const Color& rColor ) +{ + char buf[9]; + sprintf( buf, "%.2X%.2X%.2X%.2X", rColor.GetAlpha(), rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() ); + buf[8] = '\0'; + return OString(buf); +} + +OStringBuffer& XclXmlUtils::ToOString( OStringBuffer& s, const ScAddress& rAddress ) +{ + rAddress.Format(s, ScRefFlags::VALID, nullptr, ScAddress::Details( FormulaGrammar::CONV_XL_A1)); + return s; +} + +OString XclXmlUtils::ToOString( const ScfUInt16Vec& rBuffer ) +{ + if(rBuffer.empty()) + return OString(); + + const sal_uInt16* pBuffer = rBuffer.data(); + return OString( + reinterpret_cast(pBuffer), rBuffer.size(), + RTL_TEXTENCODING_UTF8); +} + +OString XclXmlUtils::ToOString( const ScDocument& rDoc, const ScRange& rRange, bool bFullAddressNotation ) +{ + OUString sRange(rRange.Format( rDoc, ScRefFlags::VALID, + ScAddress::Details( FormulaGrammar::CONV_XL_A1 ), + bFullAddressNotation ) ); + return sRange.toUtf8(); +} + +OString XclXmlUtils::ToOString( const ScDocument& rDoc, const ScRangeList& rRangeList ) +{ + OUString s; + rRangeList.Format(s, ScRefFlags::VALID, rDoc, FormulaGrammar::CONV_XL_OOX, ' '); + return s.toUtf8(); +} + +static ScAddress lcl_ToAddress( const XclAddress& rAddress ) +{ + return ScAddress( rAddress.mnCol, rAddress.mnRow, 0 ); +} + +OStringBuffer& XclXmlUtils::ToOString( OStringBuffer& s, const XclAddress& rAddress ) +{ + return ToOString( s, lcl_ToAddress( rAddress )); +} + +OString XclXmlUtils::ToOString( const XclExpString& s ) +{ + OSL_ENSURE( !s.IsRich(), "XclXmlUtils::ToOString(XclExpString): rich text string found!" ); + return ToOString( s.GetUnicodeBuffer() ); +} + +static ScRange lcl_ToRange( const XclRange& rRange ) +{ + ScRange aRange; + + aRange.aStart = lcl_ToAddress( rRange.maFirst ); + aRange.aEnd = lcl_ToAddress( rRange.maLast ); + + return aRange; +} + +OString XclXmlUtils::ToOString( const ScDocument& rDoc, const XclRangeList& rRanges ) +{ + ScRangeList aRanges; + for( const auto& rRange : rRanges ) + { + aRanges.push_back( lcl_ToRange( rRange ) ); + } + return ToOString( rDoc, aRanges ); +} + +OUString XclXmlUtils::ToOUString( const char* s ) +{ + return OUString( s, static_cast(strlen( s )), RTL_TEXTENCODING_ASCII_US ); +} + +OUString XclXmlUtils::ToOUString( const ScfUInt16Vec& rBuf, sal_Int32 nStart, sal_Int32 nLength ) +{ + if( nLength == -1 || ( nLength > (static_cast(rBuf.size()) - nStart) ) ) + nLength = (rBuf.size() - nStart); + + return nLength > 0 + ? OUString( + reinterpret_cast(&rBuf[nStart]), nLength) + : OUString(); +} + +OUString XclXmlUtils::ToOUString( + sc::CompileFormulaContext& rCtx, const ScAddress& rAddress, const ScTokenArray* pTokenArray, + FormulaError nErrCode ) +{ + ScCompiler aCompiler( rCtx, rAddress, const_cast(*pTokenArray)); + + /* TODO: isn't this the same as passed in rCtx and thus superfluous? */ + aCompiler.SetGrammar(FormulaGrammar::GRAM_OOXML); + + sal_Int32 nLen = pTokenArray->GetLen(); + OUStringBuffer aBuffer( nLen ? (nLen * 5) : 8 ); + if (nLen) + aCompiler.CreateStringFromTokenArray( aBuffer ); + else + { + if (nErrCode != FormulaError::NONE) + aCompiler.AppendErrorConstant( aBuffer, nErrCode); + else + { + // No code SHOULD be an "error cell", assert caller thought of that + // and it really is. + assert(!"No code and no error."); + } + } + + return aBuffer.makeStringAndClear(); +} + +OUString XclXmlUtils::ToOUString( const XclExpString& s ) +{ + OSL_ENSURE( !s.IsRich(), "XclXmlUtils::ToOString(XclExpString): rich text string found!" ); + return ToOUString( s.GetUnicodeBuffer() ); +} + +static void lcl_WriteValue( const sax_fastparser::FSHelperPtr& rStream, sal_Int32 nElement, const char* pValue ) +{ + if( !pValue ) + return; + rStream->singleElement(nElement, XML_val, pValue); +} + +static const char* lcl_GetUnderlineStyle( FontLineStyle eUnderline, bool& bHaveUnderline ) +{ + bHaveUnderline = true; + switch( eUnderline ) + { + // OOXTODO: doubleAccounting, singleAccounting + // OOXTODO: what should be done with the other FontLineStyle values? + case LINESTYLE_SINGLE: return "single"; + case LINESTYLE_DOUBLE: return "double"; + case LINESTYLE_NONE: + default: bHaveUnderline = false; return "none"; + } +} + +static const char* lcl_ToVerticalAlignmentRun( SvxEscapement eEscapement, bool& bHaveAlignment ) +{ + bHaveAlignment = true; + switch( eEscapement ) + { + case SvxEscapement::Superscript: return "superscript"; + case SvxEscapement::Subscript: return "subscript"; + case SvxEscapement::Off: + default: bHaveAlignment = false; return "baseline"; + } +} + +sax_fastparser::FSHelperPtr XclXmlUtils::WriteFontData( sax_fastparser::FSHelperPtr pStream, const XclFontData& rFontData, sal_Int32 nFontId ) +{ + bool bHaveUnderline, bHaveVertAlign; + const char* pUnderline = lcl_GetUnderlineStyle( rFontData.GetScUnderline(), bHaveUnderline ); + const char* pVertAlign = lcl_ToVerticalAlignmentRun( rFontData.GetScEscapement(), bHaveVertAlign ); + + lcl_WriteValue( pStream, XML_b, rFontData.mnWeight > 400 ? ToPsz( true ) : nullptr ); + lcl_WriteValue( pStream, XML_i, rFontData.mbItalic ? ToPsz( true ) : nullptr ); + lcl_WriteValue( pStream, XML_strike, rFontData.mbStrikeout ? ToPsz( true ) : nullptr ); + // OOXTODO: lcl_WriteValue( rStream, XML_condense, ); // mac compatibility setting + // OOXTODO: lcl_WriteValue( rStream, XML_extend, ); // compatibility setting + lcl_WriteValue( pStream, XML_outline, rFontData.mbOutline ? ToPsz( true ) : nullptr ); + lcl_WriteValue( pStream, XML_shadow, rFontData.mbShadow ? ToPsz( true ) : nullptr ); + lcl_WriteValue( pStream, XML_u, bHaveUnderline ? pUnderline : nullptr ); + lcl_WriteValue( pStream, XML_vertAlign, bHaveVertAlign ? pVertAlign : nullptr ); + lcl_WriteValue( pStream, XML_sz, OString::number( rFontData.mnHeight / 20.0 ).getStr() ); // Twips->Pt + if( rFontData.maColor != Color( ColorAlpha, 0, 0xFF, 0xFF, 0xFF ) ) + pStream->singleElement( XML_color, + // OOXTODO: XML_auto, bool + // OOXTODO: XML_indexed, uint + XML_rgb, XclXmlUtils::ToOString(rFontData.maColor) + // OOXTODO: XML_theme, index into + // OOXTODO: XML_tint, double + ); + lcl_WriteValue( pStream, nFontId, rFontData.maName.toUtf8().getStr() ); + lcl_WriteValue( pStream, XML_family, OString::number( rFontData.mnFamily ).getStr() ); + lcl_WriteValue( pStream, XML_charset, rFontData.mnCharSet != 0 ? OString::number( rFontData.mnCharSet ).getStr() : nullptr ); + + return pStream; +} + +XclExpXmlStream::XclExpXmlStream( const uno::Reference< XComponentContext >& rCC, bool bExportVBA, bool bExportTemplate ) + : XmlFilterBase( rCC ), + mpRoot( nullptr ), + mbExportVBA(bExportVBA), + mbExportTemplate(bExportTemplate) +{ +} + +XclExpXmlStream::~XclExpXmlStream() +{ + assert(maStreams.empty() && "Forgotten PopStream()?"); +} + +sax_fastparser::FSHelperPtr& XclExpXmlStream::GetCurrentStream() +{ + OSL_ENSURE( !maStreams.empty(), "XclExpXmlStream::GetCurrentStream - no current stream" ); + return maStreams.top(); +} + +void XclExpXmlStream::PushStream( sax_fastparser::FSHelperPtr const & aStream ) +{ + maStreams.push( aStream ); +} + +void XclExpXmlStream::PopStream() +{ + OSL_ENSURE( !maStreams.empty(), "XclExpXmlStream::PopStream - stack is empty!" ); + maStreams.pop(); +} + +sax_fastparser::FSHelperPtr XclExpXmlStream::GetStreamForPath( const OUString& sPath ) +{ + if( maOpenedStreamMap.find( sPath ) == maOpenedStreamMap.end() ) + return sax_fastparser::FSHelperPtr(); + return maOpenedStreamMap[ sPath ].second; +} + +void XclExpXmlStream::WriteAttribute(sal_Int32 nAttr, std::u16string_view sVal) +{ + GetCurrentStream()->write(" ")->writeId(nAttr)->write("=\"")->writeEscaped(sVal)->write("\""); +} + +sax_fastparser::FSHelperPtr XclExpXmlStream::CreateOutputStream ( + const OUString& sFullStream, + std::u16string_view sRelativeStream, + const uno::Reference< XOutputStream >& xParentRelation, + const char* sContentType, + std::u16string_view sRelationshipType, + OUString* pRelationshipId ) +{ + OUString sRelationshipId; + if (xParentRelation.is()) + sRelationshipId = addRelation( xParentRelation, OUString(sRelationshipType), sRelativeStream ); + else + sRelationshipId = addRelation( OUString(sRelationshipType), sRelativeStream ); + + if( pRelationshipId ) + *pRelationshipId = sRelationshipId; + + sax_fastparser::FSHelperPtr p = openFragmentStreamWithSerializer( sFullStream, OUString::createFromAscii( sContentType ) ); + + maOpenedStreamMap[ sFullStream ] = std::make_pair( sRelationshipId, p ); + + return p; +} + +bool XclExpXmlStream::importDocument() noexcept +{ + return false; +} + +oox::vml::Drawing* XclExpXmlStream::getVmlDrawing() +{ + return nullptr; +} + +const oox::drawingml::Theme* XclExpXmlStream::getCurrentTheme() const +{ + return nullptr; +} + +oox::drawingml::table::TableStyleListPtr XclExpXmlStream::getTableStyles() +{ + return oox::drawingml::table::TableStyleListPtr(); +} + +oox::drawingml::chart::ChartConverter* XclExpXmlStream::getChartConverter() +{ + // DO NOT CALL + return nullptr; +} + +ScDocShell* XclExpXmlStream::getDocShell() +{ + uno::Reference< XInterface > xModel( getModel(), UNO_QUERY ); + + ScModelObj *pObj = dynamic_cast < ScModelObj* >( xModel.get() ); + + if ( pObj ) + return static_cast < ScDocShell* >( pObj->GetEmbeddedObject() ); + + return nullptr; +} + +bool XclExpXmlStream::exportDocument() +{ + ScDocShell* pShell = getDocShell(); + ScDocument& rDoc = pShell->GetDocument(); + ScRefreshTimerProtector aProt(rDoc.GetRefreshTimerControlAddress()); + + const bool bValidateTabNames = officecfg::Office::Calc::Filter::Export::MS_Excel::TruncateLongSheetNames::get(); + std::vector aOriginalTabNames; + if (bValidateTabNames) + { + validateTabNames(aOriginalTabNames); + } + + uno::Reference xStatusIndicator = getStatusIndicator(); + + if (xStatusIndicator.is()) + xStatusIndicator->start(ScResId(STR_SAVE_DOC), 100); + + // NOTE: Don't use SotStorage or SvStream any more, and never call + // SfxMedium::GetOutStream() anywhere in the xlsx export filter code! + // Instead, write via XOutputStream instance. + tools::SvRef rStorage = static_cast(nullptr); + drawingml::DrawingML::ResetMlCounters(); + drawingml::DrawingML::PushExportGraphics(); + + XclExpRootData aData( + EXC_BIFF8, *pShell->GetMedium (), rStorage, rDoc, + msfilter::util::getBestTextEncodingFromLocale( + Application::GetSettings().GetLanguageTag().getLocale())); + aData.meOutput = EXC_OUTPUT_XML_2007; + aData.maXclMaxPos.Set( EXC_MAXCOL_XML_2007, EXC_MAXROW_XML_2007, EXC_MAXTAB_XML_2007 ); + aData.maMaxPos.SetCol( ::std::min( aData.maScMaxPos.Col(), aData.maXclMaxPos.Col() ) ); + aData.maMaxPos.SetRow( ::std::min( aData.maScMaxPos.Row(), aData.maXclMaxPos.Row() ) ); + aData.maMaxPos.SetTab( ::std::min( aData.maScMaxPos.Tab(), aData.maXclMaxPos.Tab() ) ); + aData.mpCompileFormulaCxt = std::make_shared(rDoc); + // set target path to get correct relative links to target document, not source + INetURLObject aPath(getFileUrl()); + aData.maBasePath = OUString("file:///" + aPath.GetPath() + "\\").replace('\\', '/') + // fix for Linux + .replaceFirst("file:////", "file:///"); + + XclExpRoot aRoot( aData ); + + mpRoot = &aRoot; + aRoot.GetOldRoot().pER = &aRoot; + aRoot.GetOldRoot().eDateiTyp = Biff8; + // Get the viewsettings before processing + if (ScViewData* pViewData = ScDocShell::GetViewData()) + pViewData->WriteExtOptions( mpRoot->GetExtDocOptions() ); + + OUString const workbook = "xl/workbook.xml"; + const char* pWorkbookContentType = nullptr; + if (mbExportVBA) + { + if (mbExportTemplate) + { + pWorkbookContentType = "application/vnd.ms-excel.template.macroEnabled.main+xml"; + } + else + { + pWorkbookContentType = "application/vnd.ms-excel.sheet.macroEnabled.main+xml"; + } + } + else + { + if (mbExportTemplate) + { + pWorkbookContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"; + } + else + { + pWorkbookContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; + } + } + + PushStream( CreateOutputStream( workbook, workbook, + uno::Reference (), + pWorkbookContentType, + oox::getRelationship(Relationship::OFFICEDOCUMENT) ) ); + + if (mbExportVBA) + { + VbaExport aExport(getModel()); + if (aExport.containsVBAProject()) + { + SvMemoryStream aVbaStream(4096, 4096); + tools::SvRef pVBAStorage(new SotStorage(aVbaStream)); + aExport.exportVBA( pVBAStorage.get() ); + aVbaStream.Seek(0); + css::uno::Reference xVBAStream( + new utl::OInputStreamWrapper(aVbaStream)); + css::uno::Reference xVBAOutput = + openFragmentStream("xl/vbaProject.bin", "application/vnd.ms-office.vbaProject"); + comphelper::OStorageHelper::CopyInputToOutput(xVBAStream, xVBAOutput); + + addRelation(GetCurrentStream()->getOutputStream(), oox::getRelationship(Relationship::VBAPROJECT), u"vbaProject.bin"); + } + } + + // destruct at the end of the block + { + ExcDocument aDocRoot( aRoot ); + if (xStatusIndicator.is()) + xStatusIndicator->setValue(10); + aDocRoot.ReadDoc(); + if (xStatusIndicator.is()) + xStatusIndicator->setValue(40); + aDocRoot.WriteXml( *this ); + rDoc.GetExternalRefManager()->disableSkipUnusedFileIds(); + } + + PopStream(); + // Free all FSHelperPtr, to flush data before committing storage + maOpenedStreamMap.clear(); + + commitStorage(); + + if (bValidateTabNames) + { + restoreTabNames(aOriginalTabNames); + } + + if (xStatusIndicator.is()) + xStatusIndicator->end(); + mpRoot = nullptr; + + drawingml::DrawingML::PopExportGraphics(); + + return true; +} + +::oox::ole::VbaProject* XclExpXmlStream::implCreateVbaProject() const +{ + return new ::oox::xls::ExcelVbaProject( getComponentContext(), uno::Reference< XSpreadsheetDocument >( getModel(), UNO_QUERY ) ); +} + +OUString XclExpXmlStream::getImplementationName() +{ + return "TODO"; +} + +void XclExpXmlStream::validateTabNames(std::vector& aOriginalTabNames) +{ + const int MAX_TAB_NAME_LENGTH = 31; + + ScDocShell* pShell = getDocShell(); + ScDocument& rDoc = pShell->GetDocument(); + + // get original names + aOriginalTabNames.resize(rDoc.GetTableCount()); + for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++) + { + rDoc.GetName(nTab, aOriginalTabNames[nTab]); + } + + // new tab names + std::vector aNewTabNames; + aNewTabNames.reserve(rDoc.GetTableCount()); + + // check and rename + for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++) + { + const OUString& rOriginalName = aOriginalTabNames[nTab]; + if (rOriginalName.getLength() > MAX_TAB_NAME_LENGTH) + { + OUString aNewName; + + // let's try just truncate "" + if (aNewName.isEmpty()) + { + aNewName = rOriginalName.copy(0, MAX_TAB_NAME_LENGTH); + if (aNewTabNames.end() != std::find(aNewTabNames.begin(), aNewTabNames.end(), aNewName) || + aOriginalTabNames.end() != std::find(aOriginalTabNames.begin(), aOriginalTabNames.end(), aNewName)) + { + // was found => let's use another tab name + aNewName.clear(); + } + } + + // let's try "-XXX" template + for (int digits=1; digits<10 && aNewName.isEmpty(); digits++) + { + const int rangeStart = pow(10, digits - 1); + const int rangeEnd = pow(10, digits); + + for (int i=rangeStart; i let's use another tab name + aNewName.clear(); + } + } + } + + if (!aNewName.isEmpty()) + { + // new name was created => rename + renameTab(nTab, aNewName); + aNewTabNames.push_back(aNewName); + } + else + { + // default: do not rename + aNewTabNames.push_back(rOriginalName); + } + } + else + { + // default: do not rename + aNewTabNames.push_back(rOriginalName); + } + } +} + +void XclExpXmlStream::restoreTabNames(const std::vector& aOriginalTabNames) +{ + ScDocShell* pShell = getDocShell(); + ScDocument& rDoc = pShell->GetDocument(); + + for (SCTAB nTab=0; nTab < rDoc.GetTableCount(); nTab++) + { + const OUString& rOriginalName = aOriginalTabNames[nTab]; + + OUString rModifiedName; + rDoc.GetName(nTab, rModifiedName); + + if (rOriginalName != rModifiedName) + { + renameTab(nTab, rOriginalName); + } + } +} + +void XclExpXmlStream::renameTab(SCTAB aTab, OUString aNewName) +{ + ScDocShell* pShell = getDocShell(); + ScDocument& rDoc = pShell->GetDocument(); + + bool bAutoCalcShellDisabled = rDoc.IsAutoCalcShellDisabled(); + bool bIdleEnabled = rDoc.IsIdleEnabled(); + + rDoc.SetAutoCalcShellDisabled( true ); + rDoc.EnableIdle(false); + + if (rDoc.RenameTab(aTab, aNewName)) + { + SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScTablesChanged)); + } + + rDoc.SetAutoCalcShellDisabled( bAutoCalcShellDisabled ); + rDoc.EnableIdle(bIdleEnabled); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xestring.cxx b/sc/source/filter/excel/xestring.cxx new file mode 100644 index 000000000..295f37709 --- /dev/null +++ b/sc/source/filter/excel/xestring.cxx @@ -0,0 +1,565 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::oox; + +namespace { + +// compare vectors + +/** Compares two vectors. + @return A negative value, if rLeftrRight; + or 0, if rLeft==rRight. */ +template< typename Type > +int lclCompareVectors( const ::std::vector< Type >& rLeft, const ::std::vector< Type >& rRight ) +{ + int nResult = 0; + + // 1st: compare all elements of the vectors + auto [aItL, aItR] = std::mismatch(rLeft.begin(), rLeft.end(), rRight.begin(), rRight.end()); + if ((aItL != rLeft.end()) && (aItR != rRight.end())) + nResult = static_cast< int >( *aItL ) - static_cast< int >( *aItR ); + else + // 2nd: compare the vector sizes. Shorter vector is less + nResult = static_cast< int >( rLeft.size() ) - static_cast< int >( rRight.size() ); + + return nResult; +} + +// hashing helpers + +/** Base class for value hashers. + @descr These function objects are used to hash any value to a sal_uInt32 value. */ +template< typename Type > +struct XclHasher {}; + +template< typename Type > +struct XclDirectHasher : public XclHasher< Type > +{ + sal_uInt32 operator()( Type nVal ) const { return nVal; } +}; + +struct XclFormatRunHasher : public XclHasher< const XclFormatRun& > +{ + sal_uInt32 operator()( const XclFormatRun& rRun ) const + { return (rRun.mnChar << 8) ^ rRun.mnFontIdx; } +}; + +/** Calculates a hash value from a vector. + @descr Uses the passed hasher function object to calculate hash values from + all vector elements. */ +template< typename Type, typename ValueHasher > +sal_uInt16 lclHashVector( const ::std::vector< Type >& rVec, const ValueHasher& rHasher ) +{ + sal_uInt32 nHash = rVec.size(); + for( const auto& rItem : rVec ) + nHash = (nHash * 31) + rHasher( rItem ); + return static_cast< sal_uInt16 >( nHash ^ (nHash >> 16) ); +} + +/** Calculates a hash value from a vector. Uses XclDirectHasher to hash the vector elements. */ +template< typename Type > +sal_uInt16 lclHashVector( const ::std::vector< Type >& rVec ) +{ + return lclHashVector( rVec, XclDirectHasher< Type >() ); +} + +} // namespace + +// constructors --------------------------------------------------------------- + +XclExpString::XclExpString( XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Init( 0, nFlags, nMaxLen, true ); +} + +XclExpString::XclExpString( const OUString& rString, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Assign( rString, nFlags, nMaxLen ); +} + +// assign --------------------------------------------------------------------- + +void XclExpString::Assign( const OUString& rString, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Build( rString.getStr(), rString.getLength(), nFlags, nMaxLen ); +} + +void XclExpString::Assign( sal_Unicode cChar ) +{ + Build( &cChar, 1, XclStrFlags::NONE, EXC_STR_MAXLEN ); +} + +void XclExpString::AssignByte( + std::u16string_view rString, rtl_TextEncoding eTextEnc, XclStrFlags nFlags, + sal_uInt16 nMaxLen ) +{ + // length may differ from length of rString + OString aByteStr(OUStringToOString(rString, eTextEnc)); + Build(aByteStr.getStr(), aByteStr.getLength(), nFlags, nMaxLen); +} + +// append --------------------------------------------------------------------- + +void XclExpString::Append( std::u16string_view rString ) +{ + BuildAppend( rString ); +} + +void XclExpString::AppendByte( std::u16string_view rString, rtl_TextEncoding eTextEnc ) +{ + if (!rString.empty()) + { + // length may differ from length of rString + OString aByteStr(OUStringToOString(rString, eTextEnc)); + BuildAppend(aByteStr); + } +} + +void XclExpString::AppendByte( sal_Unicode cChar, rtl_TextEncoding eTextEnc ) +{ + if( !cChar ) + { + char cByteChar = 0; + BuildAppend( std::string_view(&cByteChar, 1) ); + } + else + { + OString aByteStr( &cChar, 1, eTextEnc ); // length may be >1 + BuildAppend( aByteStr ); + } +} + +// formatting runs ------------------------------------------------------------ + +void XclExpString::AppendFormat( sal_uInt16 nChar, sal_uInt16 nFontIdx, bool bDropDuplicate ) +{ + OSL_ENSURE( maFormats.empty() || (maFormats.back().mnChar < nChar), "XclExpString::AppendFormat - invalid char index" ); + size_t nMaxSize = static_cast< size_t >( mbIsBiff8 ? EXC_STR_MAXLEN : EXC_STR_MAXLEN_8BIT ); + if( maFormats.empty() || ((maFormats.size() < nMaxSize) && (!bDropDuplicate || (maFormats.back().mnFontIdx != nFontIdx))) ) + maFormats.emplace_back( nChar, nFontIdx ); +} + +void XclExpString::AppendTrailingFormat( sal_uInt16 nFontIdx ) +{ + AppendFormat( mnLen, nFontIdx, false ); +} + +void XclExpString::LimitFormatCount( sal_uInt16 nMaxCount ) +{ + if( maFormats.size() > nMaxCount ) + maFormats.erase( maFormats.begin() + nMaxCount, maFormats.end() ); +} + +sal_uInt16 XclExpString::GetLeadingFont() +{ + sal_uInt16 nFontIdx = EXC_FONT_NOTFOUND; + if( !maFormats.empty() && (maFormats.front().mnChar == 0) ) + { + nFontIdx = maFormats.front().mnFontIdx; + } + return nFontIdx; +} + +sal_uInt16 XclExpString::RemoveLeadingFont() +{ + sal_uInt16 nFontIdx = GetLeadingFont(); + if( nFontIdx != EXC_FONT_NOTFOUND ) + { + maFormats.erase( maFormats.begin() ); + } + return nFontIdx; +} + +bool XclExpString::IsEqual( const XclExpString& rCmp ) const +{ + return + (mnLen == rCmp.mnLen) && + (mbIsBiff8 == rCmp.mbIsBiff8) && + (mbIsUnicode == rCmp.mbIsUnicode) && + (mbWrapped == rCmp.mbWrapped) && + ( + ( mbIsBiff8 && (maUniBuffer == rCmp.maUniBuffer)) || + (!mbIsBiff8 && (maCharBuffer == rCmp.maCharBuffer)) + ) && + (maFormats == rCmp.maFormats); +} + +bool XclExpString::IsLessThan( const XclExpString& rCmp ) const +{ + int nResult = mbIsBiff8 ? + lclCompareVectors( maUniBuffer, rCmp.maUniBuffer ) : + lclCompareVectors( maCharBuffer, rCmp.maCharBuffer ); + return (nResult != 0) ? (nResult < 0) : (maFormats < rCmp.maFormats); +} + +// get data ------------------------------------------------------------------- + +sal_uInt16 XclExpString::GetFormatsCount() const +{ + return static_cast< sal_uInt16 >( maFormats.size() ); +} + +sal_uInt8 XclExpString::GetFlagField() const +{ + return (mbIsUnicode ? EXC_STRF_16BIT : 0) | (IsWriteFormats() ? EXC_STRF_RICH : 0); +} + +sal_uInt16 XclExpString::GetHeaderSize() const +{ + return + (mb8BitLen ? 1 : 2) + // length field + (IsWriteFlags() ? 1 : 0) + // flag field + (IsWriteFormats() ? 2 : 0); // richtext formatting count +} + +std::size_t XclExpString::GetBufferSize() const +{ + return static_cast(mnLen) * (mbIsUnicode ? 2 : 1); +} + +std::size_t XclExpString::GetSize() const +{ + return + GetHeaderSize() + // header + GetBufferSize() + // character buffer + (IsWriteFormats() ? (4 * GetFormatsCount()) : 0); // richtext formatting +} + +sal_uInt16 XclExpString::GetChar( sal_uInt16 nCharIdx ) const +{ + OSL_ENSURE( nCharIdx < Len(), "XclExpString::GetChar - invalid character index" ); + return static_cast< sal_uInt16 >( mbIsBiff8 ? maUniBuffer[ nCharIdx ] : maCharBuffer[ nCharIdx ] ); +} + +sal_uInt16 XclExpString::GetHash() const +{ + return + (mbIsBiff8 ? lclHashVector( maUniBuffer ) : lclHashVector( maCharBuffer )) ^ + lclHashVector( maFormats, XclFormatRunHasher() ); +} + +// streaming ------------------------------------------------------------------ + +void XclExpString::WriteLenField( XclExpStream& rStrm ) const +{ + if( mb8BitLen ) + rStrm << static_cast< sal_uInt8 >( mnLen ); + else + rStrm << mnLen; +} + +void XclExpString::WriteFlagField( XclExpStream& rStrm ) const +{ + if( mbIsBiff8 ) + { + PrepareWrite( rStrm, 1 ); + rStrm << GetFlagField(); + rStrm.SetSliceSize( 0 ); + } +} + +void XclExpString::WriteHeader( XclExpStream& rStrm ) const +{ + OSL_ENSURE( !mb8BitLen || (mnLen < 256), "XclExpString::WriteHeader - string too long" ); + PrepareWrite( rStrm, GetHeaderSize() ); + // length + WriteLenField( rStrm ); + // flag field + if( IsWriteFlags() ) + rStrm << GetFlagField(); + // format run count + if( IsWriteFormats() ) + rStrm << GetFormatsCount(); + rStrm.SetSliceSize( 0 ); +} + +void XclExpString::WriteBuffer( XclExpStream& rStrm ) const +{ + if( mbIsBiff8 ) + rStrm.WriteUnicodeBuffer( maUniBuffer, GetFlagField() ); + else + rStrm.WriteCharBuffer( maCharBuffer ); +} + +void XclExpString::WriteFormats( XclExpStream& rStrm, bool bWriteSize ) const +{ + if( !IsRich() ) + return; + + if( mbIsBiff8 ) + { + if( bWriteSize ) + rStrm << GetFormatsCount(); + rStrm.SetSliceSize( 4 ); + for( const auto& rFormat : maFormats ) + rStrm << rFormat.mnChar << rFormat.mnFontIdx; + } + else + { + if( bWriteSize ) + rStrm << static_cast< sal_uInt8 >( GetFormatsCount() ); + rStrm.SetSliceSize( 2 ); + for( const auto& rFormat : maFormats ) + rStrm << static_cast< sal_uInt8 >( rFormat.mnChar ) << static_cast< sal_uInt8 >( rFormat.mnFontIdx ); + } + rStrm.SetSliceSize( 0 ); +} + +void XclExpString::Write( XclExpStream& rStrm ) const +{ + if (!mbSkipHeader) + WriteHeader( rStrm ); + WriteBuffer( rStrm ); + if( IsWriteFormats() ) // only in BIFF8 included in string + WriteFormats( rStrm ); +} + +void XclExpString::WriteHeaderToMem( sal_uInt8* pnMem ) const +{ + assert(pnMem); + OSL_ENSURE( !mb8BitLen || (mnLen < 256), "XclExpString::WriteHeaderToMem - string too long" ); + OSL_ENSURE( !IsWriteFormats(), "XclExpString::WriteHeaderToMem - formatted strings not supported" ); + // length + if( mb8BitLen ) + { + *pnMem = static_cast< sal_uInt8 >( mnLen ); + ++pnMem; + } + else + { + ShortToSVBT16( mnLen, pnMem ); + pnMem += 2; + } + // flag field + if( IsWriteFlags() ) + *pnMem = GetFlagField(); +} + +void XclExpString::WriteBufferToMem( sal_uInt8* pnMem ) const +{ + assert(pnMem); + if( IsEmpty() ) + return; + + if( mbIsBiff8 ) + { + for( const sal_uInt16 nChar : maUniBuffer ) + { + *pnMem = static_cast< sal_uInt8 >( nChar ); + ++pnMem; + if( mbIsUnicode ) + { + *pnMem = static_cast< sal_uInt8 >( nChar >> 8 ); + ++pnMem; + } + } + } + else + memcpy( pnMem, maCharBuffer.data(), mnLen ); +} + +void XclExpString::WriteToMem( sal_uInt8* pnMem ) const +{ + WriteHeaderToMem( pnMem ); + WriteBufferToMem( pnMem + GetHeaderSize() ); +} + +static sal_uInt16 lcl_WriteRun( XclExpXmlStream& rStrm, const ScfUInt16Vec& rBuffer, sal_uInt16 nStart, sal_Int32 nLength, const XclExpFont* pFont ) +{ + if( nLength == 0 ) + return nStart; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + + rWorksheet->startElement(XML_r); + if( pFont ) + { + const XclFontData& rFontData = pFont->GetFontData(); + rWorksheet->startElement(XML_rPr); + XclXmlUtils::WriteFontData( rWorksheet, rFontData, XML_rFont ); + rWorksheet->endElement( XML_rPr ); + } + rWorksheet->startElement(XML_t, FSNS(XML_xml, XML_space), "preserve"); + rWorksheet->writeEscaped( XclXmlUtils::ToOUString( rBuffer, nStart, nLength ) ); + rWorksheet->endElement( XML_t ); + rWorksheet->endElement( XML_r ); + return nStart + nLength; +} + +void XclExpString::WriteXml( XclExpXmlStream& rStrm ) const +{ + sax_fastparser::FSHelperPtr rWorksheet = rStrm.GetCurrentStream(); + + if( !IsWriteFormats() ) + { + rWorksheet->startElement(XML_t, FSNS(XML_xml, XML_space), "preserve"); + rWorksheet->writeEscaped( XclXmlUtils::ToOUString( *this ) ); + rWorksheet->endElement( XML_t ); + } + else + { + XclExpFontBuffer& rFonts = rStrm.GetRoot().GetFontBuffer(); + + sal_uInt16 nStart = 0; + const XclExpFont* pFont = nullptr; + for ( const auto& rFormat : maFormats ) + { + nStart = lcl_WriteRun( rStrm, GetUnicodeBuffer(), + nStart, rFormat.mnChar-nStart, pFont ); + pFont = rFonts.GetFont( rFormat.mnFontIdx ); + } + lcl_WriteRun( rStrm, GetUnicodeBuffer(), + nStart, GetUnicodeBuffer().size() - nStart, pFont ); + } +} + +bool XclExpString::IsWriteFlags() const +{ + return mbIsBiff8 && (!IsEmpty() || !mbSmartFlags); +} + +bool XclExpString::IsWriteFormats() const +{ + return mbIsBiff8 && !mbSkipFormats && IsRich(); +} + +void XclExpString::SetStrLen( sal_Int32 nNewLen ) +{ + sal_uInt16 nAllowedLen = (mb8BitLen && (mnMaxLen > 255)) ? 255 : mnMaxLen; + mnLen = limit_cast< sal_uInt16 >( nNewLen, 0, nAllowedLen ); +} + +void XclExpString::CharsToBuffer( const sal_Unicode* pcSource, sal_Int32 nBegin, sal_Int32 nLen ) +{ + OSL_ENSURE( maUniBuffer.size() >= o3tl::make_unsigned( nBegin + nLen ), + "XclExpString::CharsToBuffer - char buffer invalid" ); + ScfUInt16Vec::iterator aBeg = maUniBuffer.begin() + nBegin; + ScfUInt16Vec::iterator aEnd = aBeg + nLen; + const sal_Unicode* pcSrcChar = pcSource; + for( ScfUInt16Vec::iterator aIt = aBeg; aIt != aEnd; ++aIt, ++pcSrcChar ) + { + *aIt = static_cast< sal_uInt16 >( *pcSrcChar ); + if( *aIt & 0xFF00 ) + mbIsUnicode = true; + } + if( !mbWrapped ) + mbWrapped = ::std::find( aBeg, aEnd, EXC_LF ) != aEnd; +} + +void XclExpString::CharsToBuffer( const char* pcSource, sal_Int32 nBegin, sal_Int32 nLen ) +{ + OSL_ENSURE( maCharBuffer.size() >= o3tl::make_unsigned( nBegin + nLen ), + "XclExpString::CharsToBuffer - char buffer invalid" ); + ScfUInt8Vec::iterator aBeg = maCharBuffer.begin() + nBegin; + ScfUInt8Vec::iterator aEnd = aBeg + nLen; + const char* pcSrcChar = pcSource; + for( ScfUInt8Vec::iterator aIt = aBeg; aIt != aEnd; ++aIt, ++pcSrcChar ) + *aIt = static_cast< sal_uInt8 >( *pcSrcChar ); + mbIsUnicode = false; + if( !mbWrapped ) + mbWrapped = ::std::find( aBeg, aEnd, EXC_LF_C ) != aEnd; +} + +void XclExpString::Init( sal_Int32 nCurrLen, XclStrFlags nFlags, sal_uInt16 nMaxLen, bool bBiff8 ) +{ + mbIsBiff8 = bBiff8; + mbIsUnicode = bBiff8 && ( nFlags & XclStrFlags::ForceUnicode ); + mb8BitLen = bool( nFlags & XclStrFlags::EightBitLength ); + mbSmartFlags = bBiff8 && ( nFlags & XclStrFlags::SmartFlags ); + mbSkipFormats = bool( nFlags & XclStrFlags::SeparateFormats ); + mbWrapped = false; + mbSkipHeader = bool( nFlags & XclStrFlags::NoHeader ); + mnMaxLen = nMaxLen; + SetStrLen( nCurrLen ); + + maFormats.clear(); + if( mbIsBiff8 ) + { + maCharBuffer.clear(); + maUniBuffer.resize( mnLen ); + } + else + { + maUniBuffer.clear(); + maCharBuffer.resize( mnLen ); + } +} + +void XclExpString::Build( const sal_Unicode* pcSource, sal_Int32 nCurrLen, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Init( nCurrLen, nFlags, nMaxLen, true ); + CharsToBuffer( pcSource, 0, mnLen ); +} + +void XclExpString::Build( const char* pcSource, sal_Int32 nCurrLen, XclStrFlags nFlags, sal_uInt16 nMaxLen ) +{ + Init( nCurrLen, nFlags, nMaxLen, false ); + CharsToBuffer( pcSource, 0, mnLen ); +} + +void XclExpString::InitAppend( sal_Int32 nAddLen ) +{ + SetStrLen( static_cast< sal_Int32 >( mnLen ) + nAddLen ); + if( mbIsBiff8 ) + maUniBuffer.resize( mnLen ); + else + maCharBuffer.resize( mnLen ); +} + +void XclExpString::BuildAppend( std::u16string_view rSource ) +{ + OSL_ENSURE( mbIsBiff8, "XclExpString::BuildAppend - must not be called at byte strings" ); + if( mbIsBiff8 ) + { + sal_uInt16 nOldLen = mnLen; + InitAppend( rSource.size() ); + CharsToBuffer( rSource.data(), nOldLen, mnLen - nOldLen ); + } +} + +void XclExpString::BuildAppend( std::string_view rSource ) +{ + OSL_ENSURE( !mbIsBiff8, "XclExpString::BuildAppend - must not be called at unicode strings" ); + if( !mbIsBiff8 ) + { + sal_uInt16 nOldLen = mnLen; + InitAppend( rSource.size() ); + CharsToBuffer( rSource.data(), nOldLen, mnLen - nOldLen ); + } +} + +void XclExpString::PrepareWrite( XclExpStream& rStrm, sal_uInt16 nBytes ) const +{ + rStrm.SetSliceSize( nBytes + (mbIsUnicode ? 2 : 1) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xestyle.cxx b/sc/source/filter/excel/xestyle.cxx new file mode 100644 index 000000000..1e9c426a3 --- /dev/null +++ b/sc/source/filter/excel/xestyle.cxx @@ -0,0 +1,3306 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace ::com::sun::star; +using namespace oox; + +// PALETTE record - color information ========================================= + +namespace { + +sal_uInt32 lclGetWeighting( XclExpColorType eType ) +{ + switch( eType ) + { + case EXC_COLOR_CHARTLINE: return 1; + case EXC_COLOR_CELLBORDER: + case EXC_COLOR_CHARTAREA: return 2; + case EXC_COLOR_CELLTEXT: + case EXC_COLOR_CHARTTEXT: + case EXC_COLOR_CTRLTEXT: return 10; + case EXC_COLOR_TABBG: + case EXC_COLOR_CELLAREA: return 20; + case EXC_COLOR_GRID: return 50; + default: OSL_FAIL( "lclGetWeighting - unknown color type" ); + } + return 1; +} + +sal_Int32 lclGetColorDistance( const Color& rColor1, const Color& rColor2 ) +{ + sal_Int32 nDist = rColor1.GetRed() - rColor2.GetRed(); + nDist *= nDist * 77; + sal_Int32 nDummy = rColor1.GetGreen() - rColor2.GetGreen(); + nDist += nDummy * nDummy * 151; + nDummy = rColor1.GetBlue() - rColor2.GetBlue(); + nDist += nDummy * nDummy * 28; + return nDist; +} + +sal_uInt8 lclGetMergedColorComp( sal_uInt8 nComp1, sal_uInt32 nWeight1, sal_uInt8 nComp2, sal_uInt32 nWeight2 ) +{ + sal_uInt8 nComp1Dist = ::std::min< sal_uInt8 >( nComp1, 0xFF - nComp1 ); + sal_uInt8 nComp2Dist = ::std::min< sal_uInt8 >( nComp2, 0xFF - nComp2 ); + if( nComp1Dist != nComp2Dist ) + { + /* #i36945# One of the passed RGB components is nearer at the limits (0x00 or 0xFF). + Increase its weighting to prevent fading of the colors during reduction. */ + const sal_uInt8& rnCompNearer = (nComp1Dist < nComp2Dist) ? nComp1 : nComp2; + sal_uInt32& rnWeight = (nComp1Dist < nComp2Dist) ? nWeight1 : nWeight2; + rnWeight *= ((rnCompNearer - 0x80L) * (rnCompNearer - 0x7FL) / 0x1000L + 1); + } + sal_uInt32 nWSum = nWeight1 + nWeight2; + return static_cast< sal_uInt8 >( (nComp1 * nWeight1 + nComp2 * nWeight2 + nWSum / 2) / nWSum ); +} + +void lclSetMixedColor( Color& rDest, const Color& rSrc1, const Color& rSrc2 ) +{ + rDest.SetRed( static_cast< sal_uInt8 >( (static_cast< sal_uInt16 >( rSrc1.GetRed() ) + rSrc2.GetRed()) / 2 ) ); + rDest.SetGreen( static_cast< sal_uInt8 >( (static_cast< sal_uInt16 >( rSrc1.GetGreen() ) + rSrc2.GetGreen()) / 2 ) ); + rDest.SetBlue( static_cast< sal_uInt8 >( (static_cast< sal_uInt16 >( rSrc1.GetBlue() ) + rSrc2.GetBlue()) / 2 ) ); +} + +} // namespace + +// additional classes for color reduction ------------------------------------- + +namespace { + +/** Represents an entry in a color list. + + The color stores a weighting value, which increases the more the color is + used in the document. Heavy-weighted colors will change less than others on + color reduction. + */ +class XclListColor +{ +private: + Color maColor; /// The color value of this palette entry. + sal_uInt32 mnColorId; /// Unique color ID for color reduction. + sal_uInt32 mnWeight; /// Weighting for color reduction. + bool mbBaseColor; /// true = Handle as base color, (don't remove/merge). + +public: + explicit XclListColor( const Color& rColor, sal_uInt32 nColorId ); + + /** Returns the RGB color value of the color. */ + const Color& GetColor() const { return maColor; } + /** Returns the unique ID of the color. */ + sal_uInt32 GetColorId() const { return mnColorId; } + /** Returns the current weighting of the color. */ + sal_uInt32 GetWeighting() const { return mnWeight; } + /** Returns true, if this color is a base color, i.e. it will not be removed or merged. */ + bool IsBaseColor() const { return mbBaseColor; } + + /** Adds the passed weighting to this color. */ + void AddWeighting( sal_uInt32 nWeight ) { mnWeight += nWeight; } + /** Merges this color with rColor, regarding weighting settings. */ + void Merge( const XclListColor& rColor ); +}; + +XclListColor::XclListColor( const Color& rColor, sal_uInt32 nColorId ) : + maColor( rColor ), + mnColorId( nColorId ), + mnWeight( 0 ) +{ + mbBaseColor = + ((rColor.GetRed() == 0x00) || (rColor.GetRed() == 0xFF)) && + ((rColor.GetGreen() == 0x00) || (rColor.GetGreen() == 0xFF)) && + ((rColor.GetBlue() == 0x00) || (rColor.GetBlue() == 0xFF)); +} + +void XclListColor::Merge( const XclListColor& rColor ) +{ + sal_uInt32 nWeight2 = rColor.GetWeighting(); + // do not change RGB value of base colors + if( !mbBaseColor ) + { + maColor.SetRed( lclGetMergedColorComp( maColor.GetRed(), mnWeight, rColor.maColor.GetRed(), nWeight2 ) ); + maColor.SetGreen( lclGetMergedColorComp( maColor.GetGreen(), mnWeight, rColor.maColor.GetGreen(), nWeight2 ) ); + maColor.SetBlue( lclGetMergedColorComp( maColor.GetBlue(), mnWeight, rColor.maColor.GetBlue(), nWeight2 ) ); + } + AddWeighting( nWeight2 ); +} + +/** Data for each inserted original color, represented by a color ID. */ +struct XclColorIdData +{ + Color maColor; /// The original inserted color. + sal_uInt32 mnIndex; /// Maps current color ID to color list or export color vector. + /** Sets the contents of this struct. */ + void Set( const Color& rColor, sal_uInt32 nIndex ) { maColor = rColor; mnIndex = nIndex; } +}; + +/** A color that will be written to the Excel file. */ +struct XclPaletteColor +{ + Color maColor; /// Resulting color to export. + bool mbUsed; /// true = Entry is used in the document. + + explicit XclPaletteColor( const Color& rColor ) : maColor( rColor ), mbUsed( false ) {} + void SetColor( const Color& rColor ) { maColor = rColor; mbUsed = true; } +}; + +/** Maps a color list index to a palette index. + @descr Used to remap the color ID data vector from list indexes to palette indexes. */ +struct XclRemap +{ + sal_uInt32 mnPalIndex; /// Index to palette. + bool mbProcessed; /// true = List color already processed. + + explicit XclRemap() : mnPalIndex( 0 ), mbProcessed( false ) {} + void SetIndex( sal_uInt32 nPalIndex ) + { mnPalIndex = nPalIndex; mbProcessed = true; } +}; + +/** Stores the nearest palette color index of a list color. */ +struct XclNearest +{ + sal_uInt32 mnPalIndex; /// Index to nearest palette color. + sal_Int32 mnDist; /// Distance to palette color. + + explicit XclNearest() : mnPalIndex( 0 ), mnDist( 0 ) {} +}; + +} // namespace + +class XclExpPaletteImpl +{ +public: + explicit XclExpPaletteImpl( const XclDefaultPalette& rDefPal ); + + /** Inserts the color into the list and updates weighting. + @param nAutoDefault The Excel palette index for automatic color. + @return A unique ID for this color. */ + sal_uInt32 InsertColor( const Color& rColor, XclExpColorType eType, sal_uInt16 nAutoDefault = 0 ); + /** Returns the color ID representing a fixed Excel palette index (i.e. for auto colors). */ + static sal_uInt32 GetColorIdFromIndex( sal_uInt16 nIndex ); + + /** Reduces the color list to the maximum count of the current BIFF version. */ + void Finalize(); + + /** Returns the Excel palette index of the color with passed color ID. */ + sal_uInt16 GetColorIndex( sal_uInt32 nColorId ) const; + + /** Returns a foreground and background color for the two passed color IDs. + @descr If rnXclPattern contains a solid pattern, this function tries to find + the two best fitting colors and a mix pattern (25%, 50% or 75%) for nForeColorId. + This will result in a better approximation to the passed foreground color. */ + void GetMixedColors( + sal_uInt16& rnXclForeIx, sal_uInt16& rnXclBackIx, sal_uInt8& rnXclPattern, + sal_uInt32 nForeColorId, sal_uInt32 nBackColorId ) const; + + /** Returns the RGB color for a (non-zero-based) Excel palette entry. + @return The color from current or default palette or COL_AUTO, if nothing else found. */ + Color GetColor( sal_uInt16 nXclIndex ) const; + + /** Returns true, if all colors of the palette are equal to default palette colors. */ + bool IsDefaultPalette() const; + /** Writes the color list (contents of the palette record) to the passed stream. */ + void WriteBody( XclExpStream& rStrm ); + void SaveXml( XclExpXmlStream& rStrm ); + +private: + /** Returns the Excel index of a 0-based color index. */ + static sal_uInt16 GetXclIndex( sal_uInt32 nIndex ) + { return static_cast< sal_uInt16 >( nIndex + EXC_COLOR_USEROFFSET ); } + + /** Returns the original inserted color represented by the color ID nColorId. */ + const Color& GetOriginalColor( sal_uInt32 nColorId ) const; + + /** Searches for rColor, returns the ordered insertion index for rColor in rnIndex. */ + XclListColor* SearchListEntry( const Color& rColor, sal_uInt32& rnIndex ); + /** Creates and inserts a new color list entry at the specified list position. */ + XclListColor* CreateListEntry( const Color& rColor, sal_uInt32 nIndex ); + + /** Raw and fast reduction of the palette. */ + void RawReducePalette( sal_uInt32 nPass ); + /** Reduction of one color using advanced color merging based on color weighting. */ + void ReduceLeastUsedColor(); + + /** Finds the least used color and returns its current list index. */ + sal_uInt32 GetLeastUsedListColor() const; + /** Returns the list index of the color nearest to rColor. + @param nIgnore List index of a color which will be ignored. + @return The list index of the found color. */ + sal_uInt32 GetNearestListColor( const Color& rColor, sal_uInt32 nIgnore ) const; + /** Returns the list index of the color nearest to the color with list index nIndex. */ + sal_uInt32 GetNearestListColor( sal_uInt32 nIndex ) const; + + /** Returns in rnIndex the palette index of the color nearest to rColor. + Searches for default colors only (colors never replaced). + @return The distance from passed color to found color. */ + sal_Int32 GetNearestPaletteColor( + sal_uInt32& rnIndex, + const Color& rColor ) const; + /** Returns in rnFirst and rnSecond the palette indexes of the two colors nearest to rColor. + @return The minimum distance from passed color to found colors. */ + sal_Int32 GetNearPaletteColors( + sal_uInt32& rnFirst, sal_uInt32& rnSecond, + const Color& rColor ) const; + +private: + typedef std::vector< std::unique_ptr > XclListColorList; + typedef std::shared_ptr< XclListColorList > XclListColorListRef; + + const XclDefaultPalette& mrDefPal; /// The default palette for the current BIFF version. + XclListColorListRef mxColorList; /// Working color list. + std::vector< XclColorIdData > + maColorIdDataVec; /// Data of all CIDs. + std::vector< XclPaletteColor > + maPalette; /// Contains resulting colors to export. + sal_uInt32 mnLastIdx; /// Last insertion index for search opt. +}; + +const sal_uInt32 EXC_PAL_INDEXBASE = 0xFFFF0000; +const sal_uInt32 EXC_PAL_MAXRAWSIZE = 1024; + +XclExpPaletteImpl::XclExpPaletteImpl( const XclDefaultPalette& rDefPal ) : + mrDefPal( rDefPal ), + mxColorList( std::make_shared() ), + mnLastIdx( 0 ) +{ + // initialize maPalette with default colors + sal_uInt16 nCount = static_cast< sal_uInt16 >( mrDefPal.GetColorCount() ); + maPalette.reserve( nCount ); + for( sal_uInt16 nIdx = 0; nIdx < nCount; ++nIdx ) + maPalette.emplace_back( mrDefPal.GetDefColor( GetXclIndex( nIdx ) ) ); + + InsertColor( COL_BLACK, EXC_COLOR_CELLTEXT ); +} + +sal_uInt32 XclExpPaletteImpl::InsertColor( const Color& rColor, XclExpColorType eType, sal_uInt16 nAutoDefault ) +{ + if( rColor == COL_AUTO ) + return GetColorIdFromIndex( nAutoDefault ); + + sal_uInt32 nFoundIdx = 0; + XclListColor* pEntry = SearchListEntry( rColor, nFoundIdx ); + if( !pEntry || (pEntry->GetColor() != rColor) ) + pEntry = CreateListEntry( rColor, nFoundIdx ); + pEntry->AddWeighting( lclGetWeighting( eType ) ); + + return pEntry->GetColorId(); +} + +sal_uInt32 XclExpPaletteImpl::GetColorIdFromIndex( sal_uInt16 nIndex ) +{ + return EXC_PAL_INDEXBASE | nIndex; +} + +void XclExpPaletteImpl::Finalize() +{ +// --- build initial color ID data vector (maColorIdDataVec) --- + + sal_uInt32 nCount = mxColorList->size(); + maColorIdDataVec.resize( nCount ); + for( sal_uInt32 nIdx = 0; nIdx < nCount; ++nIdx ) + { + const XclListColor& listColor = *mxColorList->at( nIdx ); + maColorIdDataVec[ listColor.GetColorId() ].Set( listColor.GetColor(), nIdx ); + } + +// --- loop as long as current color count does not fit into palette of current BIFF --- + + // phase 1: raw reduction (performance reasons, #i36945#) + sal_uInt32 nPass = 0; + while( mxColorList->size() > EXC_PAL_MAXRAWSIZE ) + RawReducePalette( nPass++ ); + + // phase 2: precise reduction using advanced color merging based on color weighting + while( mxColorList->size() > mrDefPal.GetColorCount() ) + ReduceLeastUsedColor(); + +// --- use default palette and replace colors with nearest used colors --- + + nCount = mxColorList->size(); + std::vector< XclRemap > aRemapVec( nCount ); + std::vector< XclNearest > aNearestVec( nCount ); + + // in each run: search the best fitting color and replace a default color with it + for( sal_uInt32 nRun = 0; nRun < nCount; ++nRun ) + { + sal_uInt32 nIndex; + // find nearest unused default color for each unprocessed list color + for( nIndex = 0; nIndex < nCount; ++nIndex ) + aNearestVec[ nIndex ].mnDist = aRemapVec[ nIndex ].mbProcessed ? SAL_MAX_INT32 : + GetNearestPaletteColor( aNearestVec[ nIndex ].mnPalIndex, mxColorList->at( nIndex )->GetColor() ); + // find the list color which is nearest to a default color + sal_uInt32 nFound = 0; + for( nIndex = 1; nIndex < nCount; ++nIndex ) + if( aNearestVec[ nIndex ].mnDist < aNearestVec[ nFound ].mnDist ) + nFound = nIndex; + // replace default color with list color + sal_uInt32 nNearest = aNearestVec[ nFound ].mnPalIndex; + OSL_ENSURE( nNearest < maPalette.size(), "XclExpPaletteImpl::Finalize - algorithm error" ); + maPalette[ nNearest ].SetColor( mxColorList->at( nFound )->GetColor() ); + aRemapVec[ nFound ].SetIndex( nNearest ); + } + + // remap color ID data map (maColorIdDataVec) from list indexes to palette indexes + for( auto& rColorIdData : maColorIdDataVec ) + rColorIdData.mnIndex = aRemapVec[ rColorIdData.mnIndex ].mnPalIndex; +} + +sal_uInt16 XclExpPaletteImpl::GetColorIndex( sal_uInt32 nColorId ) const +{ + sal_uInt16 nRet = 0; + if( nColorId >= EXC_PAL_INDEXBASE ) + nRet = static_cast< sal_uInt16 >( nColorId & ~EXC_PAL_INDEXBASE ); + else if( nColorId < maColorIdDataVec.size() ) + nRet = GetXclIndex( maColorIdDataVec[ nColorId ].mnIndex ); + return nRet; +} + +void XclExpPaletteImpl::GetMixedColors( + sal_uInt16& rnXclForeIx, sal_uInt16& rnXclBackIx, sal_uInt8& rnXclPattern, + sal_uInt32 nForeColorId, sal_uInt32 nBackColorId ) const +{ + rnXclForeIx = GetColorIndex( nForeColorId ); + rnXclBackIx = GetColorIndex( nBackColorId ); + if( (rnXclPattern != EXC_PATT_SOLID) || (nForeColorId >= maColorIdDataVec.size()) ) + return; + + // now we have solid pattern, and a defined foreground (background doesn't care for solid pattern) + + sal_uInt32 nIndex1, nIndex2; + Color aForeColor( GetOriginalColor( nForeColorId ) ); + sal_Int32 nFirstDist = GetNearPaletteColors( nIndex1, nIndex2, aForeColor ); + if( (nIndex1 >= maPalette.size()) || (nIndex2 >= maPalette.size()) ) + return; + + Color aColorArr[ 5 ]; + aColorArr[ 0 ] = maPalette[ nIndex1 ].maColor; + aColorArr[ 4 ] = maPalette[ nIndex2 ].maColor; + lclSetMixedColor( aColorArr[ 2 ], aColorArr[ 0 ], aColorArr[ 4 ] ); + lclSetMixedColor( aColorArr[ 1 ], aColorArr[ 0 ], aColorArr[ 2 ] ); + lclSetMixedColor( aColorArr[ 3 ], aColorArr[ 2 ], aColorArr[ 4 ] ); + + sal_Int32 nMinDist = nFirstDist; + sal_uInt32 nMinIndex = 0; + for( sal_uInt32 nCnt = 1; nCnt < 4; ++nCnt ) + { + sal_Int32 nDist = lclGetColorDistance( aForeColor, aColorArr[ nCnt ] ); + if( nDist < nMinDist ) + { + nMinDist = nDist; + nMinIndex = nCnt; + } + } + rnXclForeIx = GetXclIndex( nIndex1 ); + rnXclBackIx = GetXclIndex( nIndex2 ); + if( nMinDist < nFirstDist ) + { + switch( nMinIndex ) + { + case 1: rnXclPattern = EXC_PATT_75_PERC; break; + case 2: rnXclPattern = EXC_PATT_50_PERC; break; + case 3: rnXclPattern = EXC_PATT_25_PERC; break; + } + } +} + +Color XclExpPaletteImpl::GetColor( sal_uInt16 nXclIndex ) const +{ + if( nXclIndex >= EXC_COLOR_USEROFFSET ) + { + sal_uInt32 nIdx = nXclIndex - EXC_COLOR_USEROFFSET; + if( nIdx < maPalette.size() ) + return maPalette[ nIdx ].maColor; + } + return mrDefPal.GetDefColor( nXclIndex ); +} + +bool XclExpPaletteImpl::IsDefaultPalette() const +{ + bool bDefault = true; + for( sal_uInt32 nIdx = 0, nSize = static_cast< sal_uInt32 >( maPalette.size() ); bDefault && (nIdx < nSize); ++nIdx ) + bDefault = maPalette[ nIdx ].maColor == mrDefPal.GetDefColor( GetXclIndex( nIdx ) ); + return bDefault; +} + +void XclExpPaletteImpl::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast< sal_uInt16 >( maPalette.size() ); + for( const auto& rColor : maPalette ) + rStrm << rColor.maColor; +} + +void XclExpPaletteImpl::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maPalette.empty() ) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_colors); + rStyleSheet->startElement(XML_indexedColors); + for( const auto& rColor : maPalette ) + rStyleSheet->singleElement(XML_rgbColor, XML_rgb, XclXmlUtils::ToOString(rColor.maColor)); + rStyleSheet->endElement( XML_indexedColors ); + rStyleSheet->endElement( XML_colors ); +} + +const Color& XclExpPaletteImpl::GetOriginalColor( sal_uInt32 nColorId ) const +{ + if( nColorId < maColorIdDataVec.size() ) + return maColorIdDataVec[ nColorId ].maColor; + return maPalette[ 0 ].maColor; +} + +XclListColor* XclExpPaletteImpl::SearchListEntry( const Color& rColor, sal_uInt32& rnIndex ) +{ + rnIndex = 0; + + if (mxColorList->empty()) + return nullptr; + + XclListColor* pEntry = nullptr; + + // search optimization for equal-colored objects occurring repeatedly + if (mnLastIdx < mxColorList->size()) + { + pEntry = (*mxColorList)[mnLastIdx].get(); + if( pEntry->GetColor() == rColor ) + { + rnIndex = mnLastIdx; + return pEntry; + } + } + + // binary search for color + sal_uInt32 nBegIdx = 0; + sal_uInt32 nEndIdx = mxColorList->size(); + bool bFound = false; + while( !bFound && (nBegIdx < nEndIdx) ) + { + rnIndex = (nBegIdx + nEndIdx) / 2; + pEntry = (*mxColorList)[rnIndex].get(); + bFound = pEntry->GetColor() == rColor; + if( !bFound ) + { + if( pEntry->GetColor() < rColor ) + nBegIdx = rnIndex + 1; + else + nEndIdx = rnIndex; + } + } + + // not found - use end of range as new insertion position + if( !bFound ) + rnIndex = nEndIdx; + + mnLastIdx = rnIndex; + return pEntry; +} + +XclListColor* XclExpPaletteImpl::CreateListEntry( const Color& rColor, sal_uInt32 nIndex ) +{ + XclListColor* pEntry = new XclListColor( rColor, mxColorList->size() ); + mxColorList->insert(mxColorList->begin() + nIndex, std::unique_ptr(pEntry)); + return pEntry; +} + +void XclExpPaletteImpl::RawReducePalette( sal_uInt32 nPass ) +{ + /* Fast palette reduction - in each call of this function one RGB component + of each color is reduced to a lower number of distinct values. + Pass 0: Blue is reduced to 128 distinct values. + Pass 1: Red is reduced to 128 distinct values. + Pass 2: Green is reduced to 128 distinct values. + Pass 3: Blue is reduced to 64 distinct values. + Pass 4: Red is reduced to 64 distinct values. + Pass 5: Green is reduced to 64 distinct values. + And so on... + */ + + XclListColorListRef xOldList = mxColorList; + mxColorList = std::make_shared(); + + // maps old list indexes to new list indexes, used to update maColorIdDataVec + ScfUInt32Vec aListIndexMap; + aListIndexMap.reserve( xOldList->size() ); + + // preparations + sal_uInt8 nR, nG, nB; + sal_uInt8& rnComp = ((nPass % 3 == 0) ? nB : ((nPass % 3 == 1) ? nR : nG)); + nPass /= 3; + OSL_ENSURE( nPass < 7, "XclExpPaletteImpl::RawReducePalette - reduction not terminated" ); + + static const sal_uInt8 spnFactor2[] = { 0x81, 0x82, 0x84, 0x88, 0x92, 0xAA, 0xFF }; + sal_uInt8 nFactor1 = static_cast< sal_uInt8 >( 0x02 << nPass ); + sal_uInt8 nFactor2 = spnFactor2[ nPass ]; + sal_uInt8 nFactor3 = static_cast< sal_uInt8 >( 0x40 >> nPass ); + + // process each color in the old color list + for(const std::unique_ptr & pOldColor : *xOldList) + { + // get the old list entry + const XclListColor* pOldEntry = pOldColor.get(); + nR = pOldEntry->GetColor().GetRed(); + nG = pOldEntry->GetColor().GetGreen(); + nB = pOldEntry->GetColor().GetBlue(); + + /* Calculate the new RGB component (rnComp points to one of nR, nG, nB). + Using integer arithmetic with its rounding errors, the results of + this calculation are always exactly in the range 0x00 to 0xFF + (simply cutting the lower bits would darken the colors slightly). */ + sal_uInt32 nNewComp = rnComp; + nNewComp /= nFactor1; + nNewComp *= nFactor2; + nNewComp /= nFactor3; + rnComp = static_cast< sal_uInt8 >( nNewComp ); + Color aNewColor( nR, nG, nB ); + + // find or insert the new color + sal_uInt32 nFoundIdx = 0; + XclListColor* pNewEntry = SearchListEntry( aNewColor, nFoundIdx ); + if( !pNewEntry || (pNewEntry->GetColor() != aNewColor) ) + pNewEntry = CreateListEntry( aNewColor, nFoundIdx ); + pNewEntry->AddWeighting( pOldEntry->GetWeighting() ); + aListIndexMap.push_back( nFoundIdx ); + } + + // update color ID data map (maps color IDs to color list indexes), replace old by new list indexes + for( auto& rColorIdData : maColorIdDataVec ) + rColorIdData.mnIndex = aListIndexMap[ rColorIdData.mnIndex ]; +} + +void XclExpPaletteImpl::ReduceLeastUsedColor() +{ + // find a list color to remove + sal_uInt32 nRemove = GetLeastUsedListColor(); + // find its nearest neighbor + sal_uInt32 nKeep = GetNearestListColor( nRemove ); + + // merge both colors to one color, remove one color from list + XclListColor* pKeepEntry = mxColorList->at(nKeep).get(); + XclListColor* pRemoveEntry = mxColorList->at(nRemove).get(); + if( !(pKeepEntry && pRemoveEntry) ) + return; + + // merge both colors (if pKeepEntry is a base color, it will not change) + pKeepEntry->Merge( *pRemoveEntry ); + // remove the less used color, adjust nKeep index if kept color follows removed color + XclListColorList::iterator itr = mxColorList->begin(); + ::std::advance(itr, nRemove); + mxColorList->erase(itr); + if( nKeep > nRemove ) --nKeep; + + // recalculate color ID data map (maps color IDs to color list indexes) + for( auto& rColorIdData : maColorIdDataVec ) + { + if( rColorIdData.mnIndex > nRemove ) + --rColorIdData.mnIndex; + else if( rColorIdData.mnIndex == nRemove ) + rColorIdData.mnIndex = nKeep; + } +} + +sal_uInt32 XclExpPaletteImpl::GetLeastUsedListColor() const +{ + sal_uInt32 nFound = 0; + sal_uInt32 nMinW = SAL_MAX_UINT32; + + for( sal_uInt32 nIdx = 0, nCount = mxColorList->size(); nIdx < nCount; ++nIdx ) + { + XclListColor& rEntry = *mxColorList->at( nIdx ); + // ignore the base colors + if( !rEntry.IsBaseColor() && (rEntry.GetWeighting() < nMinW) ) + { + nFound = nIdx; + nMinW = rEntry.GetWeighting(); + } + } + return nFound; +} + +sal_uInt32 XclExpPaletteImpl::GetNearestListColor( const Color& rColor, sal_uInt32 nIgnore ) const +{ + sal_uInt32 nFound = 0; + sal_Int32 nMinD = SAL_MAX_INT32; + + for( sal_uInt32 nIdx = 0, nCount = mxColorList->size(); nIdx < nCount; ++nIdx ) + { + if( nIdx != nIgnore ) + { + if( XclListColor* pEntry = mxColorList->at(nIdx).get() ) + { + sal_Int32 nDist = lclGetColorDistance( rColor, pEntry->GetColor() ); + if( nDist < nMinD ) + { + nFound = nIdx; + nMinD = nDist; + } + } + } + } + return nFound; +} + +sal_uInt32 XclExpPaletteImpl::GetNearestListColor( sal_uInt32 nIndex ) const +{ + if (nIndex >= mxColorList->size()) + return 0; + XclListColor* pEntry = mxColorList->at(nIndex).get(); + return GetNearestListColor( pEntry->GetColor(), nIndex ); +} + +sal_Int32 XclExpPaletteImpl::GetNearestPaletteColor( + sal_uInt32& rnIndex, const Color& rColor ) const +{ + rnIndex = 0; + sal_Int32 nDist = SAL_MAX_INT32; + + sal_uInt32 nPaletteIndex = 0; + for( const auto& rPaletteColor : maPalette ) + { + if( !rPaletteColor.mbUsed ) + { + sal_Int32 nCurrDist = lclGetColorDistance( rColor, rPaletteColor.maColor ); + if( nCurrDist < nDist ) + { + rnIndex = nPaletteIndex; + nDist = nCurrDist; + } + } + ++nPaletteIndex; + } + return nDist; +} + +sal_Int32 XclExpPaletteImpl::GetNearPaletteColors( + sal_uInt32& rnFirst, sal_uInt32& rnSecond, const Color& rColor ) const +{ + rnFirst = rnSecond = 0; + sal_Int32 nDist1 = SAL_MAX_INT32; + sal_Int32 nDist2 = SAL_MAX_INT32; + + sal_uInt32 nPaletteIndex = 0; + for( const auto& rPaletteColor : maPalette ) + { + sal_Int32 nCurrDist = lclGetColorDistance( rColor, rPaletteColor.maColor ); + if( nCurrDist < nDist1 ) + { + rnSecond = rnFirst; + nDist2 = nDist1; + rnFirst = nPaletteIndex; + nDist1 = nCurrDist; + } + else if( nCurrDist < nDist2 ) + { + rnSecond = nPaletteIndex; + nDist2 = nCurrDist; + } + ++nPaletteIndex; + } + return nDist1; +} + +XclExpPalette::XclExpPalette( const XclExpRoot& rRoot ) : + XclDefaultPalette( rRoot ), + XclExpRecord( EXC_ID_PALETTE ) +{ + mxImpl = std::make_shared( *this ); + SetRecSize( GetColorCount() * 4 + 2 ); +} + +XclExpPalette::~XclExpPalette() +{ +} + +sal_uInt32 XclExpPalette::InsertColor( const Color& rColor, XclExpColorType eType, sal_uInt16 nAutoDefault ) +{ + return mxImpl->InsertColor( rColor, eType, nAutoDefault ); +} + +sal_uInt32 XclExpPalette::GetColorIdFromIndex( sal_uInt16 nIndex ) +{ + return XclExpPaletteImpl::GetColorIdFromIndex( nIndex ); +} + +void XclExpPalette::Finalize() +{ + mxImpl->Finalize(); +} + +sal_uInt16 XclExpPalette::GetColorIndex( sal_uInt32 nColorId ) const +{ + return mxImpl->GetColorIndex( nColorId ); +} + +void XclExpPalette::GetMixedColors( + sal_uInt16& rnXclForeIx, sal_uInt16& rnXclBackIx, sal_uInt8& rnXclPattern, + sal_uInt32 nForeColorId, sal_uInt32 nBackColorId ) const +{ + return mxImpl->GetMixedColors( rnXclForeIx, rnXclBackIx, rnXclPattern, nForeColorId, nBackColorId ); +} + +Color XclExpPalette::GetColor( sal_uInt16 nXclIndex ) const +{ + return mxImpl->GetColor( nXclIndex ); +} + +void XclExpPalette::Save( XclExpStream& rStrm ) +{ + if( !mxImpl->IsDefaultPalette() ) + XclExpRecord::Save( rStrm ); +} + +void XclExpPalette::SaveXml( XclExpXmlStream& rStrm ) +{ + if( !mxImpl->IsDefaultPalette() ) + mxImpl->SaveXml( rStrm ); +} + +void XclExpPalette::WriteBody( XclExpStream& rStrm ) +{ + mxImpl->WriteBody( rStrm ); +} + +// FONT record - font information ============================================= + +namespace { + +typedef ::std::pair< sal_uInt16, sal_Int16 > WhichAndScript; + +sal_Int16 lclCheckFontItems( const SfxItemSet& rItemSet, + const WhichAndScript& rWAS1, const WhichAndScript& rWAS2, const WhichAndScript& rWAS3 ) +{ + if( ScfTools::CheckItem( rItemSet, rWAS1.first, false ) ) return rWAS1.second; + if( ScfTools::CheckItem( rItemSet, rWAS2.first, false ) ) return rWAS2.second; + if( ScfTools::CheckItem( rItemSet, rWAS3.first, false ) ) return rWAS3.second; + return 0; +}; + +} // namespace + +sal_Int16 XclExpFontHelper::GetFirstUsedScript( const XclExpRoot& rRoot, const SfxItemSet& rItemSet ) +{ + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + + /* #i17050# #i107170# We need to determine which font items are set in the + item set, and which script type we should prefer according to the + current language settings. */ + + static const WhichAndScript WAS_LATIN( ATTR_FONT, css::i18n::ScriptType::LATIN ); + static const WhichAndScript WAS_ASIAN( ATTR_CJK_FONT, css::i18n::ScriptType::ASIAN ); + static const WhichAndScript WAS_CMPLX( ATTR_CTL_FONT, css::i18n::ScriptType::COMPLEX ); + + /* do not let a font from a parent style override an explicit + cell font. */ + + sal_Int16 nDefScript = rRoot.GetDefApiScript(); + sal_Int16 nScript = 0; + const SfxItemSet* pCurrSet = &rItemSet; + + while( (nScript == 0) && pCurrSet ) + { + switch( nDefScript ) + { + case ApiScriptType::LATIN: + nScript = lclCheckFontItems( *pCurrSet, WAS_LATIN, WAS_CMPLX, WAS_ASIAN ); + break; + case ApiScriptType::ASIAN: + nScript = lclCheckFontItems( *pCurrSet, WAS_ASIAN, WAS_CMPLX, WAS_LATIN ); + break; + case ApiScriptType::COMPLEX: + nScript = lclCheckFontItems( *pCurrSet, WAS_CMPLX, WAS_ASIAN, WAS_LATIN ); + break; + default: + OSL_FAIL( "XclExpFontHelper::GetFirstUsedScript - unknown script type" ); + nScript = ApiScriptType::LATIN; + }; + pCurrSet = pCurrSet->GetParent(); + } + + if (nScript == 0) + nScript = nDefScript; + + if (nScript == 0) + { + OSL_FAIL( "XclExpFontHelper::GetFirstUsedScript - unknown script type" ); + nScript = ApiScriptType::LATIN; + } + + return nScript; +} + +vcl::Font XclExpFontHelper::GetFontFromItemSet( const XclExpRoot& rRoot, const SfxItemSet& rItemSet, sal_Int16 nScript ) +{ + // if WEAK is passed, guess script type from existing items in the item set + if( nScript == css::i18n::ScriptType::WEAK ) + nScript = GetFirstUsedScript( rRoot, rItemSet ); + + // convert to core script type constants + SvtScriptType nScScript = SvtLanguageOptions::FromI18NToSvtScriptType(nScript); + + // fill the font object + vcl::Font aFont; + ScPatternAttr::GetFont( aFont, rItemSet, SC_AUTOCOL_RAW, nullptr, nullptr, nullptr, nScScript ); + return aFont; +} + +ScDxfFont XclExpFontHelper::GetDxfFontFromItemSet(const XclExpRoot& rRoot, const SfxItemSet& rItemSet) +{ + sal_Int16 nScript = GetFirstUsedScript(rRoot, rItemSet); + + // convert to core script type constants + SvtScriptType nScScript = SvtLanguageOptions::FromI18NToSvtScriptType(nScript); + return ScPatternAttr::GetDxfFont(rItemSet, nScScript); +} + +bool XclExpFontHelper::CheckItems( const XclExpRoot& rRoot, const SfxItemSet& rItemSet, sal_Int16 nScript, bool bDeep ) +{ + static const sal_uInt16 pnCommonIds[] = { + ATTR_FONT_UNDERLINE, ATTR_FONT_CROSSEDOUT, ATTR_FONT_CONTOUR, + ATTR_FONT_SHADOWED, ATTR_FONT_COLOR, ATTR_FONT_LANGUAGE, 0 }; + static const sal_uInt16 pnLatinIds[] = { + ATTR_FONT, ATTR_FONT_HEIGHT, ATTR_FONT_WEIGHT, ATTR_FONT_POSTURE, 0 }; + static const sal_uInt16 pnAsianIds[] = { + ATTR_CJK_FONT, ATTR_CJK_FONT_HEIGHT, ATTR_CJK_FONT_WEIGHT, ATTR_CJK_FONT_POSTURE, 0 }; + static const sal_uInt16 pnComplexIds[] = { + ATTR_CTL_FONT, ATTR_CTL_FONT_HEIGHT, ATTR_CTL_FONT_WEIGHT, ATTR_CTL_FONT_POSTURE, 0 }; + + bool bUsed = ScfTools::CheckItems( rItemSet, pnCommonIds, bDeep ); + if( !bUsed ) + { + namespace ApiScriptType = css::i18n::ScriptType; + // if WEAK is passed, guess script type from existing items in the item set + if( nScript == ApiScriptType::WEAK ) + nScript = GetFirstUsedScript( rRoot, rItemSet ); + // check the correct items + switch( nScript ) + { + case ApiScriptType::LATIN: bUsed = ScfTools::CheckItems( rItemSet, pnLatinIds, bDeep ); break; + case ApiScriptType::ASIAN: bUsed = ScfTools::CheckItems( rItemSet, pnAsianIds, bDeep ); break; + case ApiScriptType::COMPLEX: bUsed = ScfTools::CheckItems( rItemSet, pnComplexIds, bDeep ); break; + default: OSL_FAIL( "XclExpFontHelper::CheckItems - unknown script type" ); + } + } + return bUsed; +} + +namespace { + +sal_uInt32 lclCalcHash( const XclFontData& rFontData ) +{ + sal_uInt32 nHash = rFontData.maName.getLength(); + nHash += sal_uInt32(rFontData.maColor) * 2; + nHash += rFontData.mnWeight * 3; + nHash += rFontData.mnCharSet * 5; + nHash += rFontData.mnFamily * 7; + nHash += rFontData.mnHeight * 11; + nHash += rFontData.mnUnderline * 13; + nHash += rFontData.mnEscapem * 17; + if( rFontData.mbItalic ) nHash += 19; + if( rFontData.mbStrikeout ) nHash += 23; + if( rFontData.mbOutline ) nHash += 29; + if( rFontData.mbShadow ) nHash += 31; + return nHash; +} + +} // namespace + +XclExpFont::XclExpFont( const XclExpRoot& rRoot, + const XclFontData& rFontData, XclExpColorType eColorType ) : + XclExpRecord( EXC_ID2_FONT, 14 ), + XclExpRoot( rRoot ), + maData( rFontData ) +{ + // insert font color into palette + mnColorId = rRoot.GetPalette().InsertColor( rFontData.maColor, eColorType, EXC_COLOR_FONTAUTO ); + // hash value for faster comparison + mnHash = lclCalcHash( maData ); + // record size + sal_Int32 nStrLen = maData.maName.getLength(); + SetRecSize( ((GetBiff() == EXC_BIFF8) ? (nStrLen * 2 + 1) : nStrLen) + 15 ); +} + +bool XclExpFont::Equals( const XclFontData& rFontData, sal_uInt32 nHash ) const +{ + return (mnHash == nHash) && (maData == rFontData); +} + +void XclExpFont::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_font); + XclXmlUtils::WriteFontData( rStyleSheet, maData, XML_name ); + // OOXTODO: XML_scheme; //scheme/@val values: "major", "minor", "none" + rStyleSheet->endElement( XML_font ); +} + +// private -------------------------------------------------------------------- + +void XclExpFont::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt16 nAttr = EXC_FONTATTR_NONE; + ::set_flag( nAttr, EXC_FONTATTR_ITALIC, maData.mbItalic ); + if( maData.mnUnderline > 0 ) + ::set_flag( nAttr, EXC_FONTATTR_UNDERLINE, true ); + ::set_flag( nAttr, EXC_FONTATTR_STRIKEOUT, maData.mbStrikeout ); + ::set_flag( nAttr, EXC_FONTATTR_OUTLINE, maData.mbOutline ); + ::set_flag( nAttr, EXC_FONTATTR_SHADOW, maData.mbShadow ); + + OSL_ENSURE( maData.maName.getLength() < 256, "XclExpFont::WriteBody - font name too long" ); + XclExpString aFontName; + if( GetBiff() <= EXC_BIFF5 ) + aFontName.AssignByte( maData.maName, GetTextEncoding(), XclStrFlags::EightBitLength ); + else + aFontName.Assign( maData.maName, XclStrFlags::ForceUnicode | XclStrFlags::EightBitLength ); + + rStrm << maData.mnHeight + << nAttr + << GetPalette().GetColorIndex( mnColorId ) + << maData.mnWeight + << maData.mnEscapem + << maData.mnUnderline + << maData.mnFamily + << maData.mnCharSet + << sal_uInt8( 0 ) + << aFontName; +} + +XclExpDxfFont::XclExpDxfFont(const XclExpRoot& rRoot, + const SfxItemSet& rItemSet): + XclExpRoot(rRoot) +{ + maDxfData = XclExpFontHelper::GetDxfFontFromItemSet(rRoot, rItemSet); +} + +namespace { + +const char* getUnderlineOOXValue(FontLineStyle eUnderline) +{ + switch (eUnderline) + { + case LINESTYLE_NONE: + case LINESTYLE_DONTKNOW: + return "none"; + case LINESTYLE_DOUBLE: + case LINESTYLE_DOUBLEWAVE: + return "double"; + default: + return "single"; + } +} + +const char* getFontFamilyOOXValue(FontFamily eValue) +{ + switch (eValue) + { + case FAMILY_DONTKNOW: + return "0"; + case FAMILY_SWISS: + case FAMILY_SYSTEM: + return "2"; + case FAMILY_ROMAN: + return "1"; + case FAMILY_SCRIPT: + return "4"; + case FAMILY_MODERN: + return "3"; + case FAMILY_DECORATIVE: + return "5"; + default: + return "0"; + } +} + +} + +void XclExpDxfFont::SaveXml(XclExpXmlStream& rStrm) +{ + if (maDxfData.isEmpty()) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_font); + + if (maDxfData.pFontAttr) + { + OUString aFontName = (*maDxfData.pFontAttr)->GetFamilyName(); + + aFontName = XclTools::GetXclFontName(aFontName); + if (!aFontName.isEmpty()) + { + rStyleSheet->singleElement(XML_name, XML_val, aFontName); + } + + rtl_TextEncoding eTextEnc = (*maDxfData.pFontAttr)->GetCharSet(); + sal_uInt8 nExcelCharSet = rtl_getBestWindowsCharsetFromTextEncoding(eTextEnc); + if (nExcelCharSet) + { + rStyleSheet->singleElement(XML_charset, XML_val, OString::number(nExcelCharSet)); + } + + FontFamily eFamily = (*maDxfData.pFontAttr)->GetFamily(); + const char* pVal = getFontFamilyOOXValue(eFamily); + if (pVal) + { + rStyleSheet->singleElement(XML_family, XML_val, pVal); + } + } + + if (maDxfData.eWeight) + { + rStyleSheet->singleElement(XML_b, + XML_val, ToPsz10(*maDxfData.eWeight != WEIGHT_NORMAL)); + } + + if (maDxfData.eItalic) + { + bool bItalic = (*maDxfData.eItalic == ITALIC_OBLIQUE) || (*maDxfData.eItalic == ITALIC_NORMAL); + rStyleSheet->singleElement(XML_i, XML_val, ToPsz10(bItalic)); + } + + if (maDxfData.eStrike) + { + bool bStrikeout = + (*maDxfData.eStrike == STRIKEOUT_SINGLE) || (*maDxfData.eStrike == STRIKEOUT_DOUBLE) || + (*maDxfData.eStrike == STRIKEOUT_BOLD) || (*maDxfData.eStrike == STRIKEOUT_SLASH) || + (*maDxfData.eStrike == STRIKEOUT_X); + + rStyleSheet->singleElement(XML_strike, XML_val, ToPsz10(bStrikeout)); + } + + if (maDxfData.bOutline) + { + rStyleSheet->singleElement(XML_outline, XML_val, ToPsz10(*maDxfData.bOutline)); + } + + if (maDxfData.bShadow) + { + rStyleSheet->singleElement(XML_shadow, XML_val, ToPsz10(*maDxfData.bShadow)); + } + + if (maDxfData.aColor) + { + rStyleSheet->singleElement(XML_color, + XML_rgb, XclXmlUtils::ToOString(*maDxfData.aColor)); + } + + if (maDxfData.nFontHeight) + { + rStyleSheet->singleElement(XML_sz, + XML_val, OString::number(*maDxfData.nFontHeight/20)); + } + + if (maDxfData.eUnder) + { + const char* pVal = getUnderlineOOXValue(*maDxfData.eUnder); + rStyleSheet->singleElement(XML_u, XML_val, pVal); + } + + rStyleSheet->endElement(XML_font); +} + +XclExpBlindFont::XclExpBlindFont( const XclExpRoot& rRoot ) : + XclExpFont( rRoot, XclFontData(), EXC_COLOR_CELLTEXT ) +{ +} + +bool XclExpBlindFont::Equals( const XclFontData& /*rFontData*/, sal_uInt32 /*nHash*/ ) const +{ + return false; +} + +void XclExpBlindFont::Save( XclExpStream& /*rStrm*/ ) +{ + // do nothing +} + +XclExpFontBuffer::XclExpFontBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mnXclMaxSize( 0 ) +{ + switch( GetBiff() ) + { + case EXC_BIFF4: mnXclMaxSize = EXC_FONT_MAXCOUNT4; break; + case EXC_BIFF5: mnXclMaxSize = EXC_FONT_MAXCOUNT5; break; + case EXC_BIFF8: mnXclMaxSize = EXC_FONT_MAXCOUNT8; break; + default: DBG_ERROR_BIFF(); + } + InitDefaultFonts(); +} + +const XclExpFont* XclExpFontBuffer::GetFont( sal_uInt16 nXclFont ) const +{ + return maFontList.GetRecord( nXclFont ); +} + +const XclFontData& XclExpFontBuffer::GetAppFontData() const +{ + return maFontList.GetRecord( EXC_FONT_APP )->GetFontData(); // exists always +} + +sal_uInt16 XclExpFontBuffer::Insert( + const XclFontData& rFontData, XclExpColorType eColorType, bool bAppFont ) +{ + if( bAppFont ) + { + XclExpFontRef xFont = new XclExpFont( GetRoot(), rFontData, eColorType ); + maFontList.ReplaceRecord( xFont, EXC_FONT_APP ); + // set width of '0' character for column width export + SetCharWidth( xFont->GetFontData() ); + return EXC_FONT_APP; + } + + size_t nPos = Find( rFontData ); + if( nPos == EXC_FONTLIST_NOTFOUND ) + { + // not found in buffer - create new font + size_t nSize = maFontList.GetSize(); + if( nSize < mnXclMaxSize ) + { + // possible to insert + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), rFontData, eColorType ) ); + nPos = nSize; // old size is last position now + } + else + { + // buffer is full - ignore new font, use default font + nPos = EXC_FONT_APP; + } + } + return static_cast< sal_uInt16 >( nPos ); +} + +sal_uInt16 XclExpFontBuffer::Insert( + const SvxFont& rFont, XclExpColorType eColorType ) +{ + return Insert( XclFontData( rFont ), eColorType ); +} + +sal_uInt16 XclExpFontBuffer::Insert( const SfxItemSet& rItemSet, + sal_Int16 nScript, XclExpColorType eColorType, bool bAppFont ) +{ + // #i17050# script type now provided by caller + vcl::Font aFont = XclExpFontHelper::GetFontFromItemSet( GetRoot(), rItemSet, nScript ); + return Insert( XclFontData( aFont ), eColorType, bAppFont ); +} + +void XclExpFontBuffer::Save( XclExpStream& rStrm ) +{ + maFontList.Save( rStrm ); +} + +void XclExpFontBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maFontList.IsEmpty() ) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_fonts, XML_count, OString::number(maFontList.GetSize())); + + maFontList.SaveXml( rStrm ); + + rStyleSheet->endElement( XML_fonts ); +} + +// private -------------------------------------------------------------------- + +void XclExpFontBuffer::InitDefaultFonts() +{ + XclFontData aFontData; + aFontData.maName = "Arial"; + aFontData.SetScFamily( FAMILY_DONTKNOW ); + aFontData.SetFontEncoding( ScfTools::GetSystemTextEncoding() ); + aFontData.SetScHeight( 200 ); // 200 twips = 10 pt + aFontData.SetScWeight( WEIGHT_NORMAL ); + + switch( GetBiff() ) + { + case EXC_BIFF5: + { + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + aFontData.SetScWeight( WEIGHT_BOLD ); + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + aFontData.SetScWeight( WEIGHT_NORMAL ); + aFontData.SetScPosture( ITALIC_NORMAL ); + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + aFontData.SetScWeight( WEIGHT_BOLD ); + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + // the blind font with index 4 + maFontList.AppendNewRecord( new XclExpBlindFont( GetRoot() ) ); + // already add the first user defined font (Excel does it too) + aFontData.SetScWeight( WEIGHT_NORMAL ); + aFontData.SetScPosture( ITALIC_NONE ); + maFontList.AppendNewRecord( new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ) ); + } + break; + case EXC_BIFF8: + { + XclExpFontRef xFont = new XclExpFont( GetRoot(), aFontData, EXC_COLOR_CELLTEXT ); + maFontList.AppendRecord( xFont ); + maFontList.AppendRecord( xFont ); + maFontList.AppendRecord( xFont ); + maFontList.AppendRecord( xFont ); + if( GetOutput() == EXC_OUTPUT_BINARY ) + // the blind font with index 4 + maFontList.AppendNewRecord( new XclExpBlindFont( GetRoot() ) ); + } + break; + default: + DBG_ERROR_BIFF(); + } +} + +size_t XclExpFontBuffer::Find( const XclFontData& rFontData ) +{ + sal_uInt32 nHash = lclCalcHash( rFontData ); + for( size_t nPos = 0, nSize = maFontList.GetSize(); nPos < nSize; ++nPos ) + if( maFontList.GetRecord( nPos )->Equals( rFontData, nHash ) ) + return nPos; + return EXC_FONTLIST_NOTFOUND; +} + +// FORMAT record - number formats ============================================= + +namespace { + +/** Predicate for search algorithm. */ +struct XclExpNumFmtPred +{ + sal_uInt32 mnScNumFmt; + explicit XclExpNumFmtPred( sal_uInt32 nScNumFmt ) : mnScNumFmt( nScNumFmt ) {} + bool operator()( const XclExpNumFmt& rFormat ) const + { return rFormat.mnScNumFmt == mnScNumFmt; } +}; + +} + +void XclExpNumFmt::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->singleElement( XML_numFmt, + XML_numFmtId, OString::number(mnXclNumFmt), + XML_formatCode, maNumFmtString ); +} + +XclExpNumFmtBuffer::XclExpNumFmtBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + mxFormatter( new SvNumberFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US ) ), + mpKeywordTable( new NfKeywordTable ), + mnStdFmt( GetFormatter().GetStandardIndex( ScGlobal::eLnge ) ) +{ + switch( GetBiff() ) + { + case EXC_BIFF5: mnXclOffset = EXC_FORMAT_OFFSET5; break; + case EXC_BIFF8: mnXclOffset = EXC_FORMAT_OFFSET8; break; + default: mnXclOffset = 0; DBG_ERROR_BIFF(); + } + + mxFormatter->FillKeywordTableForExcel( *mpKeywordTable ); +} + +XclExpNumFmtBuffer::~XclExpNumFmtBuffer() +{ +} + +sal_uInt16 XclExpNumFmtBuffer::Insert( sal_uInt32 nScNumFmt ) +{ + XclExpNumFmtVec::const_iterator aIt = + ::std::find_if( maFormatMap.begin(), maFormatMap.end(), XclExpNumFmtPred( nScNumFmt ) ); + if( aIt != maFormatMap.end() ) + return aIt->mnXclNumFmt; + + size_t nSize = maFormatMap.size(); + if( nSize < o3tl::make_unsigned( 0xFFFF - mnXclOffset ) ) + { + sal_uInt16 nXclNumFmt = static_cast< sal_uInt16 >( nSize + mnXclOffset ); + maFormatMap.emplace_back( nScNumFmt, nXclNumFmt, GetFormatCode( nScNumFmt ) ); + return nXclNumFmt; + } + + return 0; +} + +void XclExpNumFmtBuffer::Save( XclExpStream& rStrm ) +{ + for( const auto& rEntry : maFormatMap ) + WriteFormatRecord( rStrm, rEntry ); +} + +void XclExpNumFmtBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maFormatMap.empty() ) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_numFmts, XML_count, OString::number(maFormatMap.size())); + for( auto& rEntry : maFormatMap ) + { + rEntry.SaveXml( rStrm ); + } + rStyleSheet->endElement( XML_numFmts ); +} + +void XclExpNumFmtBuffer::WriteFormatRecord( XclExpStream& rStrm, sal_uInt16 nXclNumFmt, const OUString& rFormatStr ) +{ + XclExpString aExpStr; + if( GetBiff() <= EXC_BIFF5 ) + aExpStr.AssignByte( rFormatStr, GetTextEncoding(), XclStrFlags::EightBitLength ); + else + aExpStr.Assign( rFormatStr ); + + rStrm.StartRecord( EXC_ID4_FORMAT, 2 + aExpStr.GetSize() ); + rStrm << nXclNumFmt << aExpStr; + rStrm.EndRecord(); +} + +void XclExpNumFmtBuffer::WriteFormatRecord( XclExpStream& rStrm, const XclExpNumFmt& rFormat ) +{ + WriteFormatRecord( rStrm, rFormat.mnXclNumFmt, GetFormatCode( rFormat.mnScNumFmt ) ); +} + +namespace { + +OUString GetNumberFormatCode(const XclRoot& rRoot, const sal_uInt32 nScNumFmt, SvNumberFormatter* pFormatter, const NfKeywordTable* pKeywordTable) +{ + return rRoot.GetFormatter().GetFormatStringForExcel( nScNumFmt, *pKeywordTable, *pFormatter); +} + +} + +OUString XclExpNumFmtBuffer::GetFormatCode( sal_uInt32 nScNumFmt ) +{ + return GetNumberFormatCode( *this, nScNumFmt, mxFormatter.get(), mpKeywordTable.get() ); +} + +// XF, STYLE record - Cell formatting ========================================= + +bool XclExpCellProt::FillFromItemSet( const SfxItemSet& rItemSet, bool bStyle ) +{ + const ScProtectionAttr& rProtItem = rItemSet.Get( ATTR_PROTECTION ); + mbLocked = rProtItem.GetProtection(); + mbHidden = rProtItem.GetHideFormula() || rProtItem.GetHideCell(); + return ScfTools::CheckItem( rItemSet, ATTR_PROTECTION, bStyle ); +} + +void XclExpCellProt::FillToXF3( sal_uInt16& rnProt ) const +{ + ::set_flag( rnProt, EXC_XF_LOCKED, mbLocked ); + ::set_flag( rnProt, EXC_XF_HIDDEN, mbHidden ); +} + +void XclExpCellProt::SaveXml( XclExpXmlStream& rStrm ) const +{ + rStrm.GetCurrentStream()->singleElement( XML_protection, + XML_locked, ToPsz( mbLocked ), + XML_hidden, ToPsz( mbHidden ) ); +} + +bool XclExpCellAlign::FillFromItemSet(const XclRoot& rRoot, const SfxItemSet& rItemSet, + bool bForceLineBreak, XclBiff eBiff, bool bStyle) +{ + bool bUsed = false; + SvxCellHorJustify eHorAlign = rItemSet.Get( ATTR_HOR_JUSTIFY ).GetValue(); + SvxCellVerJustify eVerAlign = rItemSet.Get( ATTR_VER_JUSTIFY ).GetValue(); + + switch( eBiff ) + { + case EXC_BIFF8: // attributes new in BIFF8 + { + // text indent + tools::Long nTmpIndent = rItemSet.Get( ATTR_INDENT ).GetValue(); // already in twips + tools::Long nSpaceWidth = rRoot.GetSpaceWidth(); + sal_Int32 nIndent = static_cast(nTmpIndent) / (3.0 * nSpaceWidth) + 0.5; + mnIndent = limit_cast< sal_uInt8 >( nIndent, 0, 15 ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_INDENT, bStyle ); + + // shrink to fit + mbShrink = rItemSet.Get( ATTR_SHRINKTOFIT ).GetValue(); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_SHRINKTOFIT, bStyle ); + + // CTL text direction + SetScFrameDir( rItemSet.Get( ATTR_WRITINGDIR ).GetValue() ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_WRITINGDIR, bStyle ); + + [[fallthrough]]; + } + + case EXC_BIFF5: // attributes new in BIFF5 + case EXC_BIFF4: // attributes new in BIFF4 + { + // vertical alignment + SetScVerAlign( eVerAlign ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_VER_JUSTIFY, bStyle ); + + // stacked/rotation + bool bStacked = rItemSet.Get( ATTR_STACKED ).GetValue(); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_STACKED, bStyle ); + if( bStacked ) + { + mnRotation = EXC_ROT_STACKED; + } + else + { + // rotation + Degree100 nScRot = rItemSet.Get( ATTR_ROTATE_VALUE ).GetValue(); + mnRotation = XclTools::GetXclRotation( nScRot ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_ROTATE_VALUE, bStyle ); + } + mnOrient = XclTools::GetXclOrientFromRot( mnRotation ); + + [[fallthrough]]; + } + + case EXC_BIFF3: // attributes new in BIFF3 + { + // text wrap + mbLineBreak = bForceLineBreak || rItemSet.Get( ATTR_LINEBREAK ).GetValue(); + bUsed |= bForceLineBreak || ScfTools::CheckItem( rItemSet, ATTR_LINEBREAK, bStyle ); + + [[fallthrough]]; + } + + case EXC_BIFF2: // attributes new in BIFF2 + { + // horizontal alignment + SetScHorAlign( eHorAlign ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_HOR_JUSTIFY, bStyle ); + } + + break; + default: DBG_ERROR_BIFF(); + } + + if (eBiff == EXC_BIFF8) + { + // Adjust for distributed alignments. + if (eHorAlign == SvxCellHorJustify::Block) + { + SvxCellJustifyMethod eHorJustMethod = + rItemSet.GetItem(ATTR_HOR_JUSTIFY_METHOD)->GetValue(); + if (eHorJustMethod == SvxCellJustifyMethod::Distribute) + mnHorAlign = EXC_XF_HOR_DISTRIB; + } + + if (eVerAlign == SvxCellVerJustify::Block) + { + SvxCellJustifyMethod eVerJustMethod = + rItemSet.GetItem(ATTR_VER_JUSTIFY_METHOD)->GetValue(); + if (eVerJustMethod == SvxCellJustifyMethod::Distribute) + mnVerAlign = EXC_XF_VER_DISTRIB; + } + } + + return bUsed; +} + +void XclExpCellAlign::FillToXF5( sal_uInt16& rnAlign ) const +{ + ::insert_value( rnAlign, mnHorAlign, 0, 3 ); + ::set_flag( rnAlign, EXC_XF_LINEBREAK, mbLineBreak ); + ::insert_value( rnAlign, mnVerAlign, 4, 3 ); + ::insert_value( rnAlign, mnOrient, 8, 2 ); +} + +void XclExpCellAlign::FillToXF8( sal_uInt16& rnAlign, sal_uInt16& rnMiscAttrib ) const +{ + ::insert_value( rnAlign, mnHorAlign, 0, 3 ); + ::set_flag( rnAlign, EXC_XF_LINEBREAK, mbLineBreak ); + ::insert_value( rnAlign, mnVerAlign, 4, 3 ); + ::insert_value( rnAlign, mnRotation, 8, 8 ); + ::insert_value( rnMiscAttrib, mnIndent, 0, 4 ); + ::set_flag( rnMiscAttrib, EXC_XF8_SHRINK, mbShrink ); + ::insert_value( rnMiscAttrib, mnTextDir, 6, 2 ); +} + +static const char* ToHorizontalAlignment( sal_uInt8 nHorAlign ) +{ + switch( nHorAlign ) + { + case EXC_XF_HOR_GENERAL: return "general"; + case EXC_XF_HOR_LEFT: return "left"; + case EXC_XF_HOR_CENTER: return "center"; + case EXC_XF_HOR_RIGHT: return "right"; + case EXC_XF_HOR_FILL: return "fill"; + case EXC_XF_HOR_JUSTIFY: return "justify"; + case EXC_XF_HOR_CENTER_AS: return "centerContinuous"; + case EXC_XF_HOR_DISTRIB: return "distributed"; + } + return "*unknown*"; +} + +static const char* ToVerticalAlignment( sal_uInt8 nVerAlign ) +{ + switch( nVerAlign ) + { + case EXC_XF_VER_TOP: return "top"; + case EXC_XF_VER_CENTER: return "center"; + case EXC_XF_VER_BOTTOM: return "bottom"; + case EXC_XF_VER_JUSTIFY: return "justify"; + case EXC_XF_VER_DISTRIB: return "distributed"; + } + return "*unknown*"; +} + +void XclExpCellAlign::SaveXml( XclExpXmlStream& rStrm ) const +{ + rStrm.GetCurrentStream()->singleElement( XML_alignment, + XML_horizontal, ToHorizontalAlignment( mnHorAlign ), + XML_vertical, ToVerticalAlignment( mnVerAlign ), + XML_textRotation, OString::number(mnRotation), + XML_wrapText, ToPsz( mbLineBreak ), + XML_indent, OString::number(mnIndent), + // OOXTODO: XML_relativeIndent, mnIndent? + // OOXTODO: XML_justifyLastLine, + XML_shrinkToFit, ToPsz( mbShrink ), + XML_readingOrder, sax_fastparser::UseIf(OString::number(mnTextDir), mnTextDir != EXC_XF_TEXTDIR_CONTEXT) ); +} + +namespace { + +void lclGetBorderLine( + sal_uInt8& rnXclLine, sal_uInt32& rnColorId, + const ::editeng::SvxBorderLine* pLine, XclExpPalette& rPalette, XclBiff eBiff ) +{ + // Document: sc/qa/unit/data/README.cellborders + + enum CalcLineIndex{Idx_None, Idx_Solid, Idx_Dotted, Idx_Dashed, Idx_FineDashed, Idx_DashDot, Idx_DashDotDot, Idx_DoubleThin, Idx_Last}; + enum ExcelWidthIndex{Width_Hair, Width_Thin, Width_Medium, Width_Thick, Width_Last}; + static sal_uInt8 Map_LineLO_toMS[Idx_Last][Width_Last] = + { + // 0,05 - 0,74 0,75 - 1,49 1,50 - 2,49 2,50 - 9,00 Width Range [pt] + // EXC_BORDER_HAIR EXC_BORDER_THIN EXC_BORDER_MEDIUM EXC_BORDER_THICK MS Width + {EXC_LINE_NONE , EXC_LINE_NONE , EXC_LINE_NONE , EXC_LINE_NONE }, // 0 BorderLineStyle::NONE + {EXC_LINE_HAIR , EXC_LINE_THIN , EXC_LINE_MEDIUM , EXC_LINE_THICK }, // 1 BorderLineStyle::SOLID + {EXC_LINE_DOTTED , EXC_LINE_DOTTED , EXC_LINE_MEDIUM_SLANT_DASHDOT, EXC_LINE_MEDIUM_SLANT_DASHDOT}, // 2 BorderLineStyle::DOTTED + {EXC_LINE_DOTTED , EXC_LINE_DASHED , EXC_LINE_MEDIUM_DASHED , EXC_LINE_MEDIUM_DASHED }, // 3 BorderLineStyle::DASHED + {EXC_LINE_DASHED , EXC_LINE_DASHED , EXC_LINE_MEDIUM_SLANT_DASHDOT, EXC_LINE_MEDIUM_SLANT_DASHDOT}, // 4 BorderLineStyle::FINE_DASHED + {EXC_LINE_DASHED , EXC_LINE_THIN_DASHDOT , EXC_LINE_MEDIUM_DASHDOT , EXC_LINE_MEDIUM_DASHDOT }, // 5 BorderLineStyle::DASH_DOT + {EXC_LINE_DASHED , EXC_LINE_THIN_DASHDOTDOT , EXC_LINE_MEDIUM_DASHDOTDOT , EXC_LINE_MEDIUM_DASHDOTDOT }, // 6 BorderLineStyle::DASH_DOT_DOT + {EXC_LINE_DOUBLE , EXC_LINE_DOUBLE , EXC_LINE_DOUBLE , EXC_LINE_DOUBLE } // 7 BorderLineStyle::DOUBLE_THIN + }; // Line Name + + rnXclLine = EXC_LINE_NONE; + if( pLine ) + { + sal_uInt16 nOuterWidth = pLine->GetOutWidth(); + ExcelWidthIndex nOuterWidthIndx; + CalcLineIndex nStyleIndex; + + switch (pLine->GetBorderLineStyle()) + { + case SvxBorderLineStyle::NONE: + nStyleIndex = Idx_None; + break; + case SvxBorderLineStyle::SOLID: + nStyleIndex = Idx_Solid; + break; + case SvxBorderLineStyle::DOTTED: + nStyleIndex = Idx_Dotted; + break; + case SvxBorderLineStyle::DASHED: + nStyleIndex = Idx_Dashed; + break; + case SvxBorderLineStyle::FINE_DASHED: + nStyleIndex = Idx_FineDashed; + break; + case SvxBorderLineStyle::DASH_DOT: + nStyleIndex = Idx_DashDot; + break; + case SvxBorderLineStyle::DASH_DOT_DOT: + nStyleIndex = Idx_DashDotDot; + break; + case SvxBorderLineStyle::DOUBLE_THIN: + // the "nOuterWidth" is not right for this line type + // but at the moment width it not important for that + // the right function is nOuterWidth = (sal_uInt16) pLine->GetWidth(); + nStyleIndex = Idx_DoubleThin; + break; + default: + nStyleIndex = Idx_Solid; + } + + if( nOuterWidth >= EXC_BORDER_THICK ) + nOuterWidthIndx = Width_Thick; + else if( nOuterWidth >= EXC_BORDER_MEDIUM ) + nOuterWidthIndx = Width_Medium; + else if( nOuterWidth >= EXC_BORDER_THIN ) + nOuterWidthIndx = Width_Thin; + else if ( nOuterWidth >= EXC_BORDER_HAIR ) + nOuterWidthIndx = Width_Hair; + else + nOuterWidthIndx = Width_Thin; + + rnXclLine = Map_LineLO_toMS[nStyleIndex][nOuterWidthIndx]; + } + + if( (eBiff == EXC_BIFF2) && (rnXclLine != EXC_LINE_NONE) ) + rnXclLine = EXC_LINE_THIN; + + rnColorId = (pLine && (rnXclLine != EXC_LINE_NONE)) ? + rPalette.InsertColor( pLine->GetColor(), EXC_COLOR_CELLBORDER ) : + XclExpPalette::GetColorIdFromIndex( 0 ); +} + +} // namespace + +XclExpCellBorder::XclExpCellBorder() : + mnLeftColorId( XclExpPalette::GetColorIdFromIndex( mnLeftColor ) ), + mnRightColorId( XclExpPalette::GetColorIdFromIndex( mnRightColor ) ), + mnTopColorId( XclExpPalette::GetColorIdFromIndex( mnTopColor ) ), + mnBottomColorId( XclExpPalette::GetColorIdFromIndex( mnBottomColor ) ), + mnDiagColorId( XclExpPalette::GetColorIdFromIndex( mnDiagColor ) ) +{ +} + +bool XclExpCellBorder::FillFromItemSet( + const SfxItemSet& rItemSet, XclExpPalette& rPalette, XclBiff eBiff, bool bStyle ) +{ + bool bUsed = false; + + switch( eBiff ) + { + case EXC_BIFF8: // attributes new in BIFF8 + { + const SvxLineItem& rTLBRItem = rItemSet.Get( ATTR_BORDER_TLBR ); + sal_uInt8 nTLBRLine; + sal_uInt32 nTLBRColorId; + lclGetBorderLine( nTLBRLine, nTLBRColorId, rTLBRItem.GetLine(), rPalette, eBiff ); + mbDiagTLtoBR = (nTLBRLine != EXC_LINE_NONE); + + const SvxLineItem& rBLTRItem = rItemSet.Get( ATTR_BORDER_BLTR ); + sal_uInt8 nBLTRLine; + sal_uInt32 nBLTRColorId; + lclGetBorderLine( nBLTRLine, nBLTRColorId, rBLTRItem.GetLine(), rPalette, eBiff ); + mbDiagBLtoTR = (nBLTRLine != EXC_LINE_NONE); + + if( ::ScHasPriority( rTLBRItem.GetLine(), rBLTRItem.GetLine() ) ) + { + mnDiagLine = nTLBRLine; + mnDiagColorId = nTLBRColorId; + } + else + { + mnDiagLine = nBLTRLine; + mnDiagColorId = nBLTRColorId; + } + + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_BORDER_TLBR, bStyle ) || + ScfTools::CheckItem( rItemSet, ATTR_BORDER_BLTR, bStyle ); + + [[fallthrough]]; + } + + case EXC_BIFF5: + case EXC_BIFF4: + case EXC_BIFF3: + case EXC_BIFF2: + { + const SvxBoxItem& rBoxItem = rItemSet.Get( ATTR_BORDER ); + lclGetBorderLine( mnLeftLine, mnLeftColorId, rBoxItem.GetLeft(), rPalette, eBiff ); + lclGetBorderLine( mnRightLine, mnRightColorId, rBoxItem.GetRight(), rPalette, eBiff ); + lclGetBorderLine( mnTopLine, mnTopColorId, rBoxItem.GetTop(), rPalette, eBiff ); + lclGetBorderLine( mnBottomLine, mnBottomColorId, rBoxItem.GetBottom(), rPalette, eBiff ); + bUsed |= ScfTools::CheckItem( rItemSet, ATTR_BORDER, bStyle ); + } + + break; + default: DBG_ERROR_BIFF(); + } + + return bUsed; +} + +void XclExpCellBorder::SetFinalColors( const XclExpPalette& rPalette ) +{ + mnLeftColor = rPalette.GetColorIndex( mnLeftColorId ); + mnRightColor = rPalette.GetColorIndex( mnRightColorId ); + mnTopColor = rPalette.GetColorIndex( mnTopColorId ); + mnBottomColor = rPalette.GetColorIndex( mnBottomColorId ); + mnDiagColor = rPalette.GetColorIndex( mnDiagColorId ); +} + +void XclExpCellBorder::FillToXF5( sal_uInt32& rnBorder, sal_uInt32& rnArea ) const +{ + ::insert_value( rnBorder, mnTopLine, 0, 3 ); + ::insert_value( rnBorder, mnLeftLine, 3, 3 ); + ::insert_value( rnArea, mnBottomLine, 22, 3 ); + ::insert_value( rnBorder, mnRightLine, 6, 3 ); + ::insert_value( rnBorder, mnTopColor, 9, 7 ); + ::insert_value( rnBorder, mnLeftColor, 16, 7 ); + ::insert_value( rnArea, mnBottomColor, 25, 7 ); + ::insert_value( rnBorder, mnRightColor, 23, 7 ); +} + +void XclExpCellBorder::FillToXF8( sal_uInt32& rnBorder1, sal_uInt32& rnBorder2 ) const +{ + ::insert_value( rnBorder1, mnLeftLine, 0, 4 ); + ::insert_value( rnBorder1, mnRightLine, 4, 4 ); + ::insert_value( rnBorder1, mnTopLine, 8, 4 ); + ::insert_value( rnBorder1, mnBottomLine, 12, 4 ); + ::insert_value( rnBorder1, mnLeftColor, 16, 7 ); + ::insert_value( rnBorder1, mnRightColor, 23, 7 ); + ::insert_value( rnBorder2, mnTopColor, 0, 7 ); + ::insert_value( rnBorder2, mnBottomColor, 7, 7 ); + ::insert_value( rnBorder2, mnDiagColor, 14, 7 ); + ::insert_value( rnBorder2, mnDiagLine, 21, 4 ); + ::set_flag( rnBorder1, EXC_XF_DIAGONAL_TL_TO_BR, mbDiagTLtoBR ); + ::set_flag( rnBorder1, EXC_XF_DIAGONAL_BL_TO_TR, mbDiagBLtoTR ); +} + +void XclExpCellBorder::FillToCF8( sal_uInt16& rnLine, sal_uInt32& rnColor ) const +{ + ::insert_value( rnLine, mnLeftLine, 0, 4 ); + ::insert_value( rnLine, mnRightLine, 4, 4 ); + ::insert_value( rnLine, mnTopLine, 8, 4 ); + ::insert_value( rnLine, mnBottomLine, 12, 4 ); + ::insert_value( rnColor, mnLeftColor, 0, 7 ); + ::insert_value( rnColor, mnRightColor, 7, 7 ); + ::insert_value( rnColor, mnTopColor, 16, 7 ); + ::insert_value( rnColor, mnBottomColor, 23, 7 ); +} + +static const char* ToLineStyle( sal_uInt8 nLineStyle ) +{ + switch( nLineStyle ) + { + case EXC_LINE_NONE: return "none"; + case EXC_LINE_THIN: return "thin"; + case EXC_LINE_MEDIUM: return "medium"; + case EXC_LINE_THICK: return "thick"; + case EXC_LINE_DOUBLE: return "double"; + case EXC_LINE_HAIR: return "hair"; + case EXC_LINE_DOTTED: return "dotted"; + case EXC_LINE_DASHED: return "dashed"; + case EXC_LINE_MEDIUM_DASHED: return "mediumDashed"; + case EXC_LINE_THIN_DASHDOT: return "dashDot"; + case EXC_LINE_THIN_DASHDOTDOT: return "dashDotDot"; + case EXC_LINE_MEDIUM_DASHDOT: return "mediumDashDot"; + case EXC_LINE_MEDIUM_DASHDOTDOT: return "mediumDashDotDot"; + case EXC_LINE_MEDIUM_SLANT_DASHDOT: return "slantDashDot"; + } + return "*unknown*"; +} + +static void lcl_WriteBorder( XclExpXmlStream& rStrm, sal_Int32 nElement, sal_uInt8 nLineStyle, const Color& rColor ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + if( nLineStyle == EXC_LINE_NONE ) + rStyleSheet->singleElement(nElement); + else if( rColor == Color( 0, 0, 0 ) ) + rStyleSheet->singleElement(nElement, XML_style, ToLineStyle(nLineStyle)); + else + { + rStyleSheet->startElement(nElement, XML_style, ToLineStyle(nLineStyle)); + rStyleSheet->singleElement(XML_color, XML_rgb, XclXmlUtils::ToOString(rColor)); + rStyleSheet->endElement( nElement ); + } +} + +void XclExpCellBorder::SaveXml( XclExpXmlStream& rStrm ) const +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + + XclExpPalette& rPalette = rStrm.GetRoot().GetPalette(); + + rStyleSheet->startElement( XML_border, + XML_diagonalUp, ToPsz( mbDiagBLtoTR ), + XML_diagonalDown, ToPsz( mbDiagTLtoBR ) + // OOXTODO: XML_outline + ); + lcl_WriteBorder( rStrm, XML_left, mnLeftLine, rPalette.GetColor( mnLeftColor ) ); + lcl_WriteBorder( rStrm, XML_right, mnRightLine, rPalette.GetColor( mnRightColor ) ); + lcl_WriteBorder( rStrm, XML_top, mnTopLine, rPalette.GetColor( mnTopColor ) ); + lcl_WriteBorder( rStrm, XML_bottom, mnBottomLine, rPalette.GetColor( mnBottomColor ) ); + lcl_WriteBorder( rStrm, XML_diagonal, mnDiagLine, rPalette.GetColor( mnDiagColor ) ); + // OOXTODO: XML_vertical, XML_horizontal + rStyleSheet->endElement( XML_border ); +} + +XclExpCellArea::XclExpCellArea() : + mnForeColorId( XclExpPalette::GetColorIdFromIndex( mnForeColor ) ), + mnBackColorId( XclExpPalette::GetColorIdFromIndex( mnBackColor ) ), + maForeColor(0), + maBackColor(0) +{ +} + +XclExpCellArea::XclExpCellArea(Color aForeColor, Color aBackColor) + : XclCellArea(EXC_PATT_SOLID) + , mnForeColorId(0) + , mnBackColorId(0) + , maForeColor(aForeColor) + , maBackColor(aBackColor) +{ +} + +bool XclExpCellArea::FillFromItemSet( const SfxItemSet& rItemSet, XclExpPalette& rPalette, bool bStyle ) +{ + const SvxBrushItem& rBrushItem = rItemSet.Get( ATTR_BACKGROUND ); + if( rBrushItem.GetColor().IsTransparent() ) + { + mnPattern = EXC_PATT_NONE; + mnForeColorId = XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWTEXT ); + mnBackColorId = XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWBACK ); + } + else + { + mnPattern = EXC_PATT_SOLID; + mnForeColorId = rPalette.InsertColor( rBrushItem.GetColor(), EXC_COLOR_CELLAREA ); + mnBackColorId = XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWTEXT ); + } + return ScfTools::CheckItem( rItemSet, ATTR_BACKGROUND, bStyle ); +} + +void XclExpCellArea::SetFinalColors( const XclExpPalette& rPalette ) +{ + rPalette.GetMixedColors( mnForeColor, mnBackColor, mnPattern, mnForeColorId, mnBackColorId ); +} + +void XclExpCellArea::FillToXF5( sal_uInt32& rnArea ) const +{ + ::insert_value( rnArea, mnPattern, 16, 6 ); + ::insert_value( rnArea, mnForeColor, 0, 7 ); + ::insert_value( rnArea, mnBackColor, 7, 7 ); +} + +void XclExpCellArea::FillToXF8( sal_uInt32& rnBorder2, sal_uInt16& rnArea ) const +{ + ::insert_value( rnBorder2, mnPattern, 26, 6 ); + ::insert_value( rnArea, mnForeColor, 0, 7 ); + ::insert_value( rnArea, mnBackColor, 7, 7 ); +} + +void XclExpCellArea::FillToCF8( sal_uInt16& rnPattern, sal_uInt16& rnColor ) const +{ + XclCellArea aTmp( *this ); + if( !aTmp.IsTransparent() && (aTmp.mnBackColor == EXC_COLOR_WINDOWTEXT) ) + aTmp.mnBackColor = 0; + if( aTmp.mnPattern == EXC_PATT_SOLID ) + ::std::swap( aTmp.mnForeColor, aTmp.mnBackColor ); + ::insert_value( rnColor, aTmp.mnForeColor, 0, 7 ); + ::insert_value( rnColor, aTmp.mnBackColor, 7, 7 ); + ::insert_value( rnPattern, aTmp.mnPattern, 10, 6 ); +} + +static const char* ToPatternType( sal_uInt8 nPattern ) +{ + switch( nPattern ) + { + case EXC_PATT_NONE: return "none"; + case EXC_PATT_SOLID: return "solid"; + case EXC_PATT_50_PERC: return "mediumGray"; + case EXC_PATT_75_PERC: return "darkGray"; + case EXC_PATT_25_PERC: return "lightGray"; + case EXC_PATT_12_5_PERC: return "gray125"; + case EXC_PATT_6_25_PERC: return "gray0625"; + } + return "*unknown*"; +} + +void XclExpCellArea::SaveXml( XclExpXmlStream& rStrm ) const +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_fill); + + // OOXTODO: XML_gradientFill + + XclExpPalette& rPalette = rStrm.GetRoot().GetPalette(); + + if (mnPattern == EXC_PATT_NONE + || (mnForeColor == 0 && mnBackColor == 0 && maForeColor == 0 && maBackColor == 0)) + { + rStyleSheet->singleElement(XML_patternFill, XML_patternType, ToPatternType(mnPattern)); + } + else + { + rStyleSheet->startElement(XML_patternFill, XML_patternType, ToPatternType(mnPattern)); + if (maForeColor != 0 || maBackColor != 0) + { + if (maForeColor != 0) + { + rStyleSheet->singleElement(XML_fgColor, XML_rgb, + XclXmlUtils::ToOString(maForeColor)); + } + + if (maBackColor != 0) + { + rStyleSheet->singleElement(XML_bgColor, XML_rgb, + XclXmlUtils::ToOString(maBackColor)); + } + } + else + { + if (mnForeColor != 0) + { + rStyleSheet->singleElement(XML_fgColor, XML_rgb, + XclXmlUtils::ToOString(rPalette.GetColor(mnForeColor))); + } + if (mnBackColor != 0) + { + rStyleSheet->singleElement(XML_bgColor, XML_rgb, + XclXmlUtils::ToOString(rPalette.GetColor(mnBackColor))); + } + } + + rStyleSheet->endElement( XML_patternFill ); + } + + rStyleSheet->endElement( XML_fill ); +} + +bool XclExpColor::FillFromItemSet( const SfxItemSet& rItemSet ) +{ + if( !ScfTools::CheckItem( rItemSet, ATTR_BACKGROUND, true ) ) + return false; + + const SvxBrushItem& rBrushItem = rItemSet.Get( ATTR_BACKGROUND ); + maColor = rBrushItem.GetColor(); + + return true; +} + +void XclExpColor::SaveXml( XclExpXmlStream& rStrm ) const +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_fill); + rStyleSheet->startElement(XML_patternFill); + rStyleSheet->singleElement(XML_bgColor, XML_rgb, XclXmlUtils::ToOString(maColor)); + + rStyleSheet->endElement( XML_patternFill ); + rStyleSheet->endElement( XML_fill ); +} + +XclExpXFId::XclExpXFId() : + mnXFId( XclExpXFBuffer::GetDefCellXFId() ), + mnXFIndex( EXC_XF_DEFAULTCELL ) +{ +} + +void XclExpXFId::ConvertXFIndex( const XclExpRoot& rRoot ) +{ + mnXFIndex = rRoot.GetXFBuffer().GetXFIndex( mnXFId ); +} + +XclExpXF::XclExpXF( + const XclExpRoot& rRoot, const ScPatternAttr& rPattern, sal_Int16 nScript, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak ) : + XclXFBase( true ), + XclExpRoot( rRoot ) +{ + mnParentXFId = GetXFBuffer().InsertStyle( rPattern.GetStyleSheet() ); + Init( rPattern.GetItemSet(), nScript, nForceScNumFmt, nForceXclFont, bForceLineBreak, false ); +} + +XclExpXF::XclExpXF( const XclExpRoot& rRoot, const SfxStyleSheetBase& rStyleSheet ) : + XclXFBase( false ), + XclExpRoot( rRoot ), + mnParentXFId( XclExpXFBuffer::GetXFIdFromIndex( EXC_XF_STYLEPARENT ) ) +{ + bool bDefStyle = (rStyleSheet.GetName() == ScResId( STR_STYLENAME_STANDARD )); + sal_Int16 nScript = bDefStyle ? GetDefApiScript() : css::i18n::ScriptType::WEAK; + Init( const_cast< SfxStyleSheetBase& >( rStyleSheet ).GetItemSet(), nScript, + NUMBERFORMAT_ENTRY_NOT_FOUND, EXC_FONT_NOTFOUND, false, bDefStyle ); +} + +XclExpXF::XclExpXF( const XclExpRoot& rRoot, bool bCellXF ) : + XclXFBase( bCellXF ), + XclExpRoot( rRoot ), + mnParentXFId( XclExpXFBuffer::GetXFIdFromIndex( EXC_XF_STYLEPARENT ) ) +{ + InitDefault(); +} + +bool XclExpXF::Equals( const ScPatternAttr& rPattern, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak ) const +{ + return IsCellXF() && (mpItemSet == &rPattern.GetItemSet()) && + (!bForceLineBreak || maAlignment.mbLineBreak) && + ((nForceScNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND) || (mnScNumFmt == nForceScNumFmt)) && + ((nForceXclFont == EXC_FONT_NOTFOUND) || (mnXclFont == nForceXclFont)); +} + +bool XclExpXF::Equals( const SfxStyleSheetBase& rStyleSheet ) const +{ + return IsStyleXF() && (mpItemSet == &const_cast< SfxStyleSheetBase& >( rStyleSheet ).GetItemSet()); +} + +void XclExpXF::SetFinalColors() +{ + maBorder.SetFinalColors( GetPalette() ); + maArea.SetFinalColors( GetPalette() ); +} + +bool XclExpXF::Equals( const XclExpXF& rCmpXF ) const +{ + return XclXFBase::Equals( rCmpXF ) && + (maProtection == rCmpXF.maProtection) && (maAlignment == rCmpXF.maAlignment) && + (maBorder == rCmpXF.maBorder) && (maArea == rCmpXF.maArea) && + (mnXclFont == rCmpXF.mnXclFont) && (mnXclNumFmt == rCmpXF.mnXclNumFmt) && + (mnParentXFId == rCmpXF.mnParentXFId); +} + +void XclExpXF::InitDefault() +{ + SetRecHeader( EXC_ID5_XF, (GetBiff() == EXC_BIFF8) ? 20 : 16 ); + mpItemSet = nullptr; + mnScNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND; + mnXclFont = mnXclNumFmt = 0; + SetXmlIds(0, 0); +} + +void XclExpXF::Init( const SfxItemSet& rItemSet, sal_Int16 nScript, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak, bool bDefStyle ) +{ + InitDefault(); + mpItemSet = &rItemSet; + + // cell protection + mbProtUsed = maProtection.FillFromItemSet( rItemSet, IsStyleXF() ); + + // font + if( nForceXclFont == EXC_FONT_NOTFOUND ) + { + mnXclFont = GetFontBuffer().Insert( rItemSet, nScript, EXC_COLOR_CELLTEXT, bDefStyle ); + mbFontUsed = XclExpFontHelper::CheckItems( GetRoot(), rItemSet, nScript, IsStyleXF() ); + } + else + { + mnXclFont = nForceXclFont; + mbFontUsed = true; + } + + // number format + if (nForceScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND) + mnXclNumFmt = nForceScNumFmt; + else + { + // Built-in formats of dedicated languages may be attributed using the + // system language (or even other?) format with a language attribute, + // obtain the "real" format key. + mnScNumFmt = rItemSet.Get( ATTR_VALUE_FORMAT ).GetValue(); + LanguageType nLang = rItemSet.Get( ATTR_LANGUAGE_FORMAT).GetLanguage(); + if (mnScNumFmt >= SV_COUNTRY_LANGUAGE_OFFSET || nLang != LANGUAGE_SYSTEM) + mnScNumFmt = GetFormatter().GetFormatForLanguageIfBuiltIn( mnScNumFmt, nLang); + } + mnXclNumFmt = GetNumFmtBuffer().Insert( mnScNumFmt ); + mbFmtUsed = ScfTools::CheckItem( rItemSet, ATTR_VALUE_FORMAT, IsStyleXF() ); + + // alignment + mbAlignUsed = maAlignment.FillFromItemSet(*this, rItemSet, bForceLineBreak, GetBiff(), IsStyleXF()); + + // cell border + mbBorderUsed = maBorder.FillFromItemSet( rItemSet, GetPalette(), GetBiff(), IsStyleXF() ); + + // background area + mbAreaUsed = maArea.FillFromItemSet( rItemSet, GetPalette(), IsStyleXF() ); + + // set all b***Used flags to true in "Default"/"Normal" style + if( bDefStyle ) + SetAllUsedFlags( true ); +} + +sal_uInt8 XclExpXF::GetUsedFlags() const +{ + sal_uInt8 nUsedFlags = 0; + /* In cell XFs a set bit means a used attribute, in style XFs a cleared bit. + "mbCellXF == mb***Used" evaluates to correct value in cell and style XFs. */ + ::set_flag( nUsedFlags, EXC_XF_DIFF_PROT, mbCellXF == mbProtUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_FONT, mbCellXF == mbFontUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_VALFMT, mbCellXF == mbFmtUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_ALIGN, mbCellXF == mbAlignUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_BORDER, mbCellXF == mbBorderUsed ); + ::set_flag( nUsedFlags, EXC_XF_DIFF_AREA, mbCellXF == mbAreaUsed ); + return nUsedFlags; +} + +void XclExpXF::WriteBody5( XclExpStream& rStrm ) +{ + sal_uInt16 nTypeProt = 0, nAlign = 0; + sal_uInt32 nArea = 0, nBorder = 0; + + ::set_flag( nTypeProt, EXC_XF_STYLE, IsStyleXF() ); + ::insert_value( nTypeProt, mnParent, 4, 12 ); + ::insert_value( nAlign, GetUsedFlags(), 10, 6 ); + + maProtection.FillToXF3( nTypeProt ); + maAlignment.FillToXF5( nAlign ); + maBorder.FillToXF5( nBorder, nArea ); + maArea.FillToXF5( nArea ); + + rStrm << mnXclFont << mnXclNumFmt << nTypeProt << nAlign << nArea << nBorder; +} + +void XclExpXF::WriteBody8( XclExpStream& rStrm ) +{ + sal_uInt16 nTypeProt = 0, nAlign = 0, nMiscAttrib = 0, nArea = 0; + sal_uInt32 nBorder1 = 0, nBorder2 = 0; + + ::set_flag( nTypeProt, EXC_XF_STYLE, IsStyleXF() ); + ::insert_value( nTypeProt, mnParent, 4, 12 ); + ::insert_value( nMiscAttrib, GetUsedFlags(), 10, 6 ); + + maProtection.FillToXF3( nTypeProt ); + maAlignment.FillToXF8( nAlign, nMiscAttrib ); + maBorder.FillToXF8( nBorder1, nBorder2 ); + maArea.FillToXF8( nBorder2, nArea ); + + rStrm << mnXclFont << mnXclNumFmt << nTypeProt << nAlign << nMiscAttrib << nBorder1 << nBorder2 << nArea; +} + +void XclExpXF::WriteBody( XclExpStream& rStrm ) +{ + XclExpXFId aParentId( mnParentXFId ); + aParentId.ConvertXFIndex( GetRoot() ); + mnParent = aParentId.mnXFIndex; + switch( GetBiff() ) + { + case EXC_BIFF5: WriteBody5( rStrm ); break; + case EXC_BIFF8: WriteBody8( rStrm ); break; + default: DBG_ERROR_BIFF(); + } +} + +void XclExpXF::SetXmlIds( sal_uInt32 nBorderId, sal_uInt32 nFillId ) +{ + mnBorderId = nBorderId; + mnFillId = nFillId; +} + +void XclExpXF::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + + sal_Int32 nXfId = 0; + const XclExpXF* pStyleXF = nullptr; + if( IsCellXF() ) + { + sal_uInt16 nXFIndex = rStrm.GetRoot().GetXFBuffer().GetXFIndex( mnParentXFId ); + nXfId = rStrm.GetRoot().GetXFBuffer().GetXmlStyleIndex( nXFIndex ); + pStyleXF = rStrm.GetRoot().GetXFBuffer().GetXFById( mnParentXFId ); + } + + rStyleSheet->startElement( XML_xf, + XML_numFmtId, OString::number(mnXclNumFmt), + XML_fontId, OString::number(mnXclFont), + XML_fillId, OString::number(mnFillId), + XML_borderId, OString::number(mnBorderId), + XML_xfId, sax_fastparser::UseIf(OString::number(nXfId), !IsStyleXF()), + // OOXTODO: XML_quotePrefix, + // OOXTODO: XML_pivotButton, + // OOXTODO: XML_applyNumberFormat, ; + XML_applyFont, ToPsz( mbFontUsed ), + // OOXTODO: XML_applyFill, + XML_applyBorder, ToPsz( mbBorderUsed ), + XML_applyAlignment, ToPsz( mbAlignUsed ), + XML_applyProtection, ToPsz( mbProtUsed ) ); + if( mbAlignUsed ) + maAlignment.SaveXml( rStrm ); + else if ( pStyleXF ) + pStyleXF->GetAlignmentData().SaveXml( rStrm ); + if( mbProtUsed ) + maProtection.SaveXml( rStrm ); + else if ( pStyleXF ) + pStyleXF->GetProtectionData().SaveXml( rStrm ); + + // OOXTODO: XML_extLst + rStyleSheet->endElement( XML_xf ); +} + +XclExpDefaultXF::XclExpDefaultXF( const XclExpRoot& rRoot, bool bCellXF ) : + XclExpXF( rRoot, bCellXF ) +{ +} + +void XclExpDefaultXF::SetFont( sal_uInt16 nXclFont ) +{ + mnXclFont = nXclFont; + mbFontUsed = true; +} + +void XclExpDefaultXF::SetNumFmt( sal_uInt16 nXclNumFmt ) +{ + mnXclNumFmt = nXclNumFmt; + mbFmtUsed = true; +} + +XclExpStyle::XclExpStyle( sal_uInt32 nXFId, const OUString& rStyleName ) : + XclExpRecord( EXC_ID_STYLE, 4 ), + maName( rStyleName ), + maXFId( nXFId ), + mnStyleId( EXC_STYLE_USERDEF ), + mnLevel( EXC_STYLE_NOLEVEL ) +{ + OSL_ENSURE( !maName.isEmpty(), "XclExpStyle::XclExpStyle - empty style name" ); +#if OSL_DEBUG_LEVEL > 0 + sal_uInt8 nStyleId, nLevel; // do not use members for debug tests + OSL_ENSURE( !XclTools::GetBuiltInStyleId( nStyleId, nLevel, maName ), + "XclExpStyle::XclExpStyle - this is a built-in style" ); +#endif +} + +XclExpStyle::XclExpStyle( sal_uInt32 nXFId, sal_uInt8 nStyleId, sal_uInt8 nLevel ) : + XclExpRecord( EXC_ID_STYLE, 4 ), + maXFId( nXFId ), + mnStyleId( nStyleId ), + mnLevel( nLevel ) +{ +} + +void XclExpStyle::WriteBody( XclExpStream& rStrm ) +{ + maXFId.ConvertXFIndex( rStrm.GetRoot() ); + ::set_flag( maXFId.mnXFIndex, EXC_STYLE_BUILTIN, IsBuiltIn() ); + rStrm << maXFId.mnXFIndex; + + if( IsBuiltIn() ) + { + rStrm << mnStyleId << mnLevel; + } + else + { + XclExpString aNameEx; + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + aNameEx.Assign( maName ); + else + aNameEx.AssignByte( maName, rStrm.GetRoot().GetTextEncoding(), XclStrFlags::EightBitLength ); + rStrm << aNameEx; + } +} + +static const char* lcl_StyleNameFromId( sal_Int32 nStyleId ) +{ + switch( nStyleId ) + { + case 0: return "Normal"; + case 3: return "Comma"; + case 4: return "Currency"; + case 5: return "Percent"; + case 6: return "Comma [0]"; + case 7: return "Currency [0]"; + } + return "*unknown*"; +} + +void XclExpStyle::SaveXml( XclExpXmlStream& rStrm ) +{ + constexpr sal_Int32 CELL_STYLE_MAX_BUILTIN_ID = 54; + OString sName; + OString sBuiltinId; + const char* pBuiltinId = nullptr; + if( IsBuiltIn() ) + { + sName = OString( lcl_StyleNameFromId( mnStyleId ) ); + sBuiltinId = OString::number( std::min( static_cast( CELL_STYLE_MAX_BUILTIN_ID - 1 ), static_cast ( mnStyleId ) ) ); + pBuiltinId = sBuiltinId.getStr(); + } + else + sName = maName.toUtf8(); + + // get the index in sortedlist associated with the mnXId + sal_Int32 nXFId = rStrm.GetRoot().GetXFBuffer().GetXFIndex( maXFId.mnXFId ); + // get the style index associated with index into sortedlist + nXFId = rStrm.GetRoot().GetXFBuffer().GetXmlStyleIndex( nXFId ); + rStrm.GetCurrentStream()->singleElement( XML_cellStyle, + XML_name, sName, + XML_xfId, OString::number(nXFId), +// builtinId of 54 or above is invalid according to OpenXML SDK validator. + XML_builtinId, pBuiltinId + // OOXTODO: XML_iLevel, + // OOXTODO: XML_hidden, + // XML_customBuiltin, ToPsz( ! IsBuiltIn() ) + ); + // OOXTODO: XML_extLst +} + +namespace { + +const sal_uInt32 EXC_XFLIST_INDEXBASE = 0xFFFE0000; +/** Maximum count of XF records to store in the XF list (performance). */ +const sal_uInt32 EXC_XFLIST_HARDLIMIT = 256 * 1024; + +bool lclIsBuiltInStyle( const OUString& rStyleName ) +{ + return + XclTools::IsBuiltInStyleName( rStyleName ) || + XclTools::IsCondFormatStyleName( rStyleName ); +} + +} // namespace + +XclExpXFBuffer::XclExpBuiltInInfo::XclExpBuiltInInfo() : + mnStyleId( EXC_STYLE_USERDEF ), + mnLevel( EXC_STYLE_NOLEVEL ), + mbPredefined( true ), + mbHasStyleRec( false ) +{ +} + +namespace { + +/** Predicate for search algorithm. */ +struct XclExpBorderPred +{ + const XclExpCellBorder& + mrBorder; + explicit XclExpBorderPred( const XclExpCellBorder& rBorder ) : mrBorder( rBorder ) {} + bool operator()( const XclExpCellBorder& rBorder ) const; +}; + +} + +bool XclExpBorderPred::operator()( const XclExpCellBorder& rBorder ) const +{ + return + mrBorder.mnLeftColor == rBorder.mnLeftColor && + mrBorder.mnRightColor == rBorder.mnRightColor && + mrBorder.mnTopColor == rBorder.mnTopColor && + mrBorder.mnBottomColor == rBorder.mnBottomColor && + mrBorder.mnDiagColor == rBorder.mnDiagColor && + mrBorder.mnLeftLine == rBorder.mnLeftLine && + mrBorder.mnRightLine == rBorder.mnRightLine && + mrBorder.mnTopLine == rBorder.mnTopLine && + mrBorder.mnBottomLine == rBorder.mnBottomLine && + mrBorder.mnDiagLine == rBorder.mnDiagLine && + mrBorder.mbDiagTLtoBR == rBorder.mbDiagTLtoBR && + mrBorder.mbDiagBLtoTR == rBorder.mbDiagBLtoTR && + mrBorder.mnLeftColorId == rBorder.mnLeftColorId && + mrBorder.mnRightColorId == rBorder.mnRightColorId && + mrBorder.mnTopColorId == rBorder.mnTopColorId && + mrBorder.mnBottomColorId == rBorder.mnBottomColorId && + mrBorder.mnDiagColorId == rBorder.mnDiagColorId; +} + +namespace { + +struct XclExpFillPred +{ + const XclExpCellArea& + mrFill; + explicit XclExpFillPred( const XclExpCellArea& rFill ) : mrFill( rFill ) {} + bool operator()( const XclExpCellArea& rFill ) const; +}; + +} + +bool XclExpFillPred::operator()( const XclExpCellArea& rFill ) const +{ + return + mrFill.mnForeColor == rFill.mnForeColor && + mrFill.mnBackColor == rFill.mnBackColor && + mrFill.mnPattern == rFill.mnPattern && + mrFill.mnForeColorId == rFill.mnForeColorId && + mrFill.mnBackColorId == rFill.mnBackColorId; +} + +XclExpXFBuffer::XclExpXFBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +void XclExpXFBuffer::Initialize() +{ + InsertDefaultRecords(); + InsertUserStyles(); +} + +sal_uInt32 XclExpXFBuffer::Insert( const ScPatternAttr* pPattern, sal_Int16 nScript ) +{ + return InsertCellXF( pPattern, nScript, NUMBERFORMAT_ENTRY_NOT_FOUND, EXC_FONT_NOTFOUND, false ); +} + +sal_uInt32 XclExpXFBuffer::InsertWithFont( const ScPatternAttr* pPattern, sal_Int16 nScript, + sal_uInt16 nForceXclFont, bool bForceLineBreak ) +{ + return InsertCellXF( pPattern, nScript, NUMBERFORMAT_ENTRY_NOT_FOUND, nForceXclFont, bForceLineBreak ); +} + +sal_uInt32 XclExpXFBuffer::InsertWithNumFmt( const ScPatternAttr* pPattern, sal_Int16 nScript, sal_uInt32 nForceScNumFmt, bool bForceLineBreak ) +{ + return InsertCellXF( pPattern, nScript, nForceScNumFmt, EXC_FONT_NOTFOUND, bForceLineBreak ); +} + +sal_uInt32 XclExpXFBuffer::InsertStyle( const SfxStyleSheetBase* pStyleSheet ) +{ + return pStyleSheet ? InsertStyleXF( *pStyleSheet ) : GetXFIdFromIndex( EXC_XF_DEFAULTSTYLE ); +} + +sal_uInt32 XclExpXFBuffer::GetXFIdFromIndex( sal_uInt16 nXFIndex ) +{ + return EXC_XFLIST_INDEXBASE | nXFIndex; +} + +sal_uInt32 XclExpXFBuffer::GetDefCellXFId() +{ + return GetXFIdFromIndex( EXC_XF_DEFAULTCELL ); +} + +const XclExpXF* XclExpXFBuffer::GetXFById( sal_uInt32 nXFId ) const +{ + return maXFList.GetRecord( nXFId ); +} + +void XclExpXFBuffer::Finalize() +{ + for( size_t nPos = 0, nSize = maXFList.GetSize(); nPos < nSize; ++nPos ) + maXFList.GetRecord( nPos )->SetFinalColors(); + + sal_uInt32 nTotalCount = static_cast< sal_uInt32 >( maXFList.GetSize() ); + sal_uInt32 nId; + maXFIndexVec.resize( nTotalCount, EXC_XF_DEFAULTCELL ); + maStyleIndexes.resize( nTotalCount, EXC_XF_DEFAULTCELL ); + maCellIndexes.resize( nTotalCount, EXC_XF_DEFAULTCELL ); + + XclExpBuiltInMap::const_iterator aBuiltInEnd = maBuiltInMap.end(); + /* nMaxBuiltInXFId used to decide faster whether an XF record is + user-defined. If the current XF ID is greater than this value, + maBuiltInMap doesn't need to be searched. */ + sal_uInt32 nMaxBuiltInXFId = maBuiltInMap.empty() ? 0 : maBuiltInMap.rbegin()->first; + + // *** map all built-in XF records (cell and style) *** ------------------- + + // do not change XF order -> std::map<> iterates elements in ascending order + for( const auto& rEntry : maBuiltInMap ) + AppendXFIndex( rEntry.first ); + + // *** insert all user-defined style XF records, without reduce *** ------- + + sal_uInt32 nStyleXFCount = 0; // counts up to EXC_XF_MAXSTYLECOUNT limit + + for( nId = 0; nId < nTotalCount; ++nId ) + { + XclExpXFRef xXF = maXFList.GetRecord( nId ); + if( xXF->IsStyleXF() && ((nId > nMaxBuiltInXFId) || (maBuiltInMap.find( nId ) == aBuiltInEnd)) ) + { + if( nStyleXFCount < EXC_XF_MAXSTYLECOUNT ) + { + // maximum count of styles not reached + AppendXFIndex( nId ); + ++nStyleXFCount; + } + else + { + /* Maximum count of styles reached - do not append more + pointers to XFs; use default style XF instead; do not break + the loop to initialize all maXFIndexVec elements. */ + maXFIndexVec[ nId ] = EXC_XF_DEFAULTSTYLE; + } + } + } + + // *** insert all cell XF records *** ------------------------------------- + + // start position to search for equal inserted XF records + size_t nSearchStart = maSortedXFList.GetSize(); + + // break the loop if XF limit reached - maXFIndexVec is already initialized with default index + XclExpXFRef xDefCellXF = maXFList.GetRecord( EXC_XF_DEFAULTCELL ); + for( nId = 0; (nId < nTotalCount) && (maSortedXFList.GetSize() < EXC_XF_MAXCOUNT); ++nId ) + { + XclExpXFRef xXF = maXFList.GetRecord( nId ); + if( xXF->IsCellXF() && ((nId > nMaxBuiltInXFId) || (maBuiltInMap.find( nId ) == aBuiltInEnd)) ) + { + // try to find an XF record equal to *xXF, which is already inserted + sal_uInt16 nFoundIndex = EXC_XF_NOTFOUND; + + // first try if it is equal to the default cell XF + if( xDefCellXF->Equals( *xXF ) ) + { + nFoundIndex = EXC_XF_DEFAULTCELL; + } + else for( size_t nSearchPos = nSearchStart, nSearchEnd = maSortedXFList.GetSize(); + (nSearchPos < nSearchEnd) && (nFoundIndex == EXC_XF_NOTFOUND); ++nSearchPos ) + { + if( maSortedXFList.GetRecord( nSearchPos )->Equals( *xXF ) ) + nFoundIndex = static_cast< sal_uInt16 >( nSearchPos ); + } + + if( nFoundIndex != EXC_XF_NOTFOUND ) + // equal XF already in the list, use its resulting XF index + maXFIndexVec[ nId ] = nFoundIndex; + else + AppendXFIndex( nId ); + } + } + + sal_uInt16 nXmlStyleIndex = 0; + sal_uInt16 nXmlCellIndex = 0; + + size_t nXFCount = maSortedXFList.GetSize(); + for( size_t i = 0; i < nXFCount; ++i ) + { + XclExpXFList::RecordRefType xXF = maSortedXFList.GetRecord( i ); + if( xXF->IsStyleXF() ) + maStyleIndexes[ i ] = nXmlStyleIndex++; + else + maCellIndexes[ i ] = nXmlCellIndex++; + } +} + +sal_uInt16 XclExpXFBuffer::GetXFIndex( sal_uInt32 nXFId ) const +{ + sal_uInt16 nXFIndex = EXC_XF_DEFAULTSTYLE; + if( nXFId >= EXC_XFLIST_INDEXBASE ) + nXFIndex = static_cast< sal_uInt16 >( nXFId & ~EXC_XFLIST_INDEXBASE ); + else if( nXFId < maXFIndexVec.size() ) + nXFIndex = maXFIndexVec[ nXFId ]; + return nXFIndex; +} + +sal_Int32 XclExpXFBuffer::GetXmlStyleIndex( sal_uInt32 nXFIndex ) const +{ + OSL_ENSURE( nXFIndex < maStyleIndexes.size(), "XclExpXFBuffer::GetXmlStyleIndex - invalid index!" ); + if( nXFIndex >= maStyleIndexes.size() ) + return 0; // should be caught/debugged via above assert; return "valid" index. + return maStyleIndexes[ nXFIndex ]; +} + +sal_Int32 XclExpXFBuffer::GetXmlCellIndex( sal_uInt32 nXFIndex ) const +{ + OSL_ENSURE( nXFIndex < maCellIndexes.size(), "XclExpXFBuffer::GetXmlStyleIndex - invalid index!" ); + if( nXFIndex >= maCellIndexes.size() ) + return 0; // should be caught/debugged via above assert; return "valid" index. + return maCellIndexes[ nXFIndex ]; +} + +void XclExpXFBuffer::Save( XclExpStream& rStrm ) +{ + // save all XF records contained in the maSortedXFList vector (sorted by XF index) + maSortedXFList.Save( rStrm ); + // save all STYLE records + maStyleList.Save( rStrm ); +} + +static void lcl_GetCellCounts( const XclExpRecordList< XclExpXF >& rXFList, sal_Int32& rCells, sal_Int32& rStyles ) +{ + rCells = 0; + rStyles = 0; + size_t nXFCount = rXFList.GetSize(); + for( size_t i = 0; i < nXFCount; ++i ) + { + XclExpRecordList< XclExpXF >::RecordRefType xXF = rXFList.GetRecord( i ); + if( xXF->IsCellXF() ) + ++rCells; + else if( xXF->IsStyleXF() ) + ++rStyles; + } +} + +void XclExpXFBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + + rStyleSheet->startElement(XML_fills, XML_count, OString::number(maFills.size())); + for( const auto& rFill : maFills ) + { + rFill.SaveXml( rStrm ); + } + rStyleSheet->endElement( XML_fills ); + + rStyleSheet->startElement(XML_borders, XML_count, OString::number(maBorders.size())); + for( const auto& rBorder : maBorders ) + { + rBorder.SaveXml( rStrm ); + } + rStyleSheet->endElement( XML_borders ); + + // save all XF records contained in the maSortedXFList vector (sorted by XF index) + sal_Int32 nCells, nStyles; + lcl_GetCellCounts( maSortedXFList, nCells, nStyles ); + + if( nStyles > 0 ) + { + rStyleSheet->startElement(XML_cellStyleXfs, XML_count, OString::number(nStyles)); + size_t nXFCount = maSortedXFList.GetSize(); + for( size_t i = 0; i < nXFCount; ++i ) + { + XclExpXFList::RecordRefType xXF = maSortedXFList.GetRecord( i ); + if( ! xXF->IsStyleXF() ) + continue; + SaveXFXml( rStrm, *xXF ); + } + rStyleSheet->endElement( XML_cellStyleXfs ); + } + + if( nCells > 0 ) + { + rStyleSheet->startElement(XML_cellXfs, XML_count, OString::number(nCells)); + size_t nXFCount = maSortedXFList.GetSize(); + for( size_t i = 0; i < nXFCount; ++i ) + { + XclExpXFList::RecordRefType xXF = maSortedXFList.GetRecord( i ); + if( ! xXF->IsCellXF() ) + continue; + SaveXFXml( rStrm, *xXF ); + } + rStyleSheet->endElement( XML_cellXfs ); + } + + // save all STYLE records + rStyleSheet->startElement(XML_cellStyles, XML_count, OString::number(maStyleList.GetSize())); + maStyleList.SaveXml( rStrm ); + rStyleSheet->endElement( XML_cellStyles ); +} + +void XclExpXFBuffer::SaveXFXml( XclExpXmlStream& rStrm, XclExpXF& rXF ) +{ + XclExpBorderList::iterator aBorderPos = + std::find_if( maBorders.begin(), maBorders.end(), XclExpBorderPred( rXF.GetBorderData() ) ); + OSL_ENSURE( aBorderPos != maBorders.end(), "XclExpXFBuffer::SaveXml - Invalid @borderId!" ); + XclExpFillList::iterator aFillPos = + std::find_if( maFills.begin(), maFills.end(), XclExpFillPred( rXF.GetAreaData() ) ); + OSL_ENSURE( aFillPos != maFills.end(), "XclExpXFBuffer::SaveXml - Invalid @fillId!" ); + + sal_Int32 nBorderId = 0, nFillId = 0; + if( aBorderPos != maBorders.end() ) + nBorderId = std::distance( maBorders.begin(), aBorderPos ); + if( aFillPos != maFills.end() ) + nFillId = std::distance( maFills.begin(), aFillPos ); + + rXF.SetXmlIds( nBorderId, nFillId ); + rXF.SaveXml( rStrm ); +} + +sal_uInt32 XclExpXFBuffer::FindXF( const ScPatternAttr& rPattern, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak ) const +{ + if (nForceScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND && nForceXclFont == EXC_FONT_NOTFOUND) + { + FindKey key1 { /*mbCellXF*/true, &rPattern.GetItemSet(), nForceScNumFmt, 0 }; + FindKey key2 { /*mbCellXF*/true, &rPattern.GetItemSet(), nForceScNumFmt, EXC_FONT_NOTFOUND }; + auto it1 = maXFFindMap.lower_bound(key1); + if (it1 != maXFFindMap.end()) + { + auto it2 = maXFFindMap.upper_bound(key2); + for (auto it = it1; it != it2; ++it) + for (auto const & nPos : it->second) + if( maXFList.GetRecord( nPos )->Equals( rPattern, nForceScNumFmt, nForceXclFont, bForceLineBreak ) ) + return nPos; + } + } + else if (nForceScNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND || nForceXclFont == EXC_FONT_NOTFOUND) + { + FindKey key1 { /*mbCellXF*/true, &rPattern.GetItemSet(), 0, 0 }; + FindKey key2 { /*mbCellXF*/true, &rPattern.GetItemSet(), NUMBERFORMAT_ENTRY_NOT_FOUND, EXC_FONT_NOTFOUND }; + auto it1 = maXFFindMap.lower_bound(key1); + if (it1 != maXFFindMap.end()) + { + auto it2 = maXFFindMap.upper_bound(key2); + for (auto it = it1; it != it2; ++it) + for (auto const & nPos : it->second) + if( maXFList.GetRecord( nPos )->Equals( rPattern, nForceScNumFmt, nForceXclFont, bForceLineBreak ) ) + return nPos; + } + } + else + { + FindKey key { /*mbCellXF*/true, &rPattern.GetItemSet(), nForceScNumFmt, nForceXclFont }; + auto it = maXFFindMap.find(key); + if (it == maXFFindMap.end()) + return EXC_XFID_NOTFOUND; + for (auto const & nPos : it->second) + if( maXFList.GetRecord( nPos )->Equals( rPattern, nForceScNumFmt, nForceXclFont, bForceLineBreak ) ) + return nPos; + } + return EXC_XFID_NOTFOUND; +} + +sal_uInt32 XclExpXFBuffer::FindXF( const SfxStyleSheetBase& rStyleSheet ) const +{ + const SfxItemSet* pItemSet = &const_cast< SfxStyleSheetBase& >( rStyleSheet ).GetItemSet(); + FindKey key1 { /*mbCellXF*/false, pItemSet, 0, 0 }; + FindKey key2 { /*mbCellXF*/false, pItemSet, NUMBERFORMAT_ENTRY_NOT_FOUND, EXC_FONT_NOTFOUND }; + auto it1 = maXFFindMap.lower_bound(key1); + auto it2 = maXFFindMap.upper_bound(key2); + for (auto it = it1; it != it2; ++it) + for (auto const & nPos : it->second) + if( maXFList.GetRecord( nPos )->Equals( rStyleSheet ) ) + return nPos; + return EXC_XFID_NOTFOUND; +} + +sal_uInt32 XclExpXFBuffer::FindBuiltInXF( sal_uInt8 nStyleId, sal_uInt8 nLevel ) const +{ + auto aIt = std::find_if(maBuiltInMap.begin(), maBuiltInMap.end(), + [&nStyleId, nLevel](const XclExpBuiltInMap::value_type& rEntry) { + return (rEntry.second.mnStyleId == nStyleId) && (rEntry.second.mnLevel == nLevel); + }); + if (aIt != maBuiltInMap.end()) + return aIt->first; + return EXC_XFID_NOTFOUND; +} + +XclExpXFBuffer::FindKey XclExpXFBuffer::ToFindKey(XclExpXF const & rRec) +{ + return { rRec.IsCellXF(), rRec.GetItemSet(), rRec.GetScNumFmt(), rRec.GetXclFont() }; +} + +sal_uInt32 XclExpXFBuffer::InsertCellXF( const ScPatternAttr* pPattern, sal_Int16 nScript, + sal_uInt32 nForceScNumFmt, sal_uInt16 nForceXclFont, bool bForceLineBreak ) +{ + const ScPatternAttr* pDefPattern = GetDoc().GetDefPattern(); + if( !pPattern ) + pPattern = pDefPattern; + + // special handling for default cell formatting + if( (pPattern == pDefPattern) && !bForceLineBreak && + (nForceScNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND) && + (nForceXclFont == EXC_FONT_NOTFOUND) ) + { + // Is it the first try to insert the default cell format? + bool& rbPredefined = maBuiltInMap[ EXC_XF_DEFAULTCELL ].mbPredefined; + if( rbPredefined ) + { + // remove old entry in find-map + auto & rPositions = maXFFindMap[ToFindKey(*maXFList.GetRecord(EXC_XF_DEFAULTCELL))]; + auto it = std::find(rPositions.begin(), rPositions.end(), EXC_XF_DEFAULTCELL); + rPositions.erase(it); + // replace default cell pattern + XclExpXFRef xNewXF = new XclExpXF( GetRoot(), *pPattern, nScript ); + maXFList.ReplaceRecord( xNewXF, EXC_XF_DEFAULTCELL ); + // and add new entry in find-map + maXFFindMap[ToFindKey(*xNewXF)].push_back(EXC_XF_DEFAULTCELL); + rbPredefined = false; + } + return GetDefCellXFId(); + } + + sal_uInt32 nXFId = FindXF( *pPattern, nForceScNumFmt, nForceXclFont, bForceLineBreak ); + if( nXFId == EXC_XFID_NOTFOUND ) + { + // not found - insert new cell XF + if( maXFList.GetSize() < EXC_XFLIST_HARDLIMIT ) + { + auto pNewExp = new XclExpXF( + GetRoot(), *pPattern, nScript, nForceScNumFmt, nForceXclFont, bForceLineBreak ); + maXFList.AppendNewRecord( pNewExp ); + // do not set nXFId before the AppendNewRecord() call - it may insert 2 XFs (style+cell) + nXFId = static_cast< sal_uInt32 >( maXFList.GetSize() - 1 ); + maXFFindMap[ToFindKey(*pNewExp)].push_back(nXFId); + } + else + { + // list full - fall back to default cell XF + nXFId = GetDefCellXFId(); + } + } + return nXFId; +} + +sal_uInt32 XclExpXFBuffer::InsertStyleXF( const SfxStyleSheetBase& rStyleSheet ) +{ + // *** try, if it is a built-in style - create new XF or replace existing predefined XF *** + + sal_uInt8 nStyleId, nLevel; + if( XclTools::GetBuiltInStyleId( nStyleId, nLevel, rStyleSheet.GetName() ) ) + { + // try to find the built-in XF record (if already created in InsertDefaultRecords()) + sal_uInt32 nXFId = FindBuiltInXF( nStyleId, nLevel ); + if( nXFId == EXC_XFID_NOTFOUND ) + { + // built-in style XF not yet created - do it now + XclExpXFRef xXF = new XclExpXF( GetRoot(), rStyleSheet ); + nXFId = AppendBuiltInXFWithStyle( xXF, nStyleId, nLevel ); + // this new XF record is not predefined + maBuiltInMap[ nXFId ].mbPredefined = false; + } + else + { + OSL_ENSURE( maXFList.HasRecord( nXFId ), "XclExpXFBuffer::InsertStyleXF - built-in XF not found" ); + // XF record still predefined? -> Replace with real XF + bool& rbPredefined = maBuiltInMap[ nXFId ].mbPredefined; + if( rbPredefined ) + { + // remove old entry in find-map + auto & rPositions = maXFFindMap[ToFindKey(*maXFList.GetRecord(nXFId))]; + auto it = std::find(rPositions.begin(), rPositions.end(), nXFId); + rPositions.erase(it); + // replace predefined built-in style (ReplaceRecord() deletes old record) + XclExpXFRef pNewExp = new XclExpXF( GetRoot(), rStyleSheet ); + maXFList.ReplaceRecord( pNewExp, nXFId ); + // and add new entry in find-map + maXFFindMap[ToFindKey(*pNewExp)].push_back(nXFId); + rbPredefined = false; + } + } + + // STYLE already inserted? (may be not, i.e. for RowLevel/ColLevel or Hyperlink styles) + bool& rbHasStyleRec = maBuiltInMap[ nXFId ].mbHasStyleRec; + if( !rbHasStyleRec ) + { + maStyleList.AppendNewRecord( new XclExpStyle( nXFId, nStyleId, nLevel ) ); + rbHasStyleRec = true; + } + + return nXFId; + } + + // *** try to find the XF record of a user-defined style *** + + sal_uInt32 nXFId = FindXF( rStyleSheet ); + if( nXFId == EXC_XFID_NOTFOUND ) + { + // not found - insert new style XF and STYLE + nXFId = static_cast< sal_uInt32 >( maXFList.GetSize() ); + if( nXFId < EXC_XFLIST_HARDLIMIT ) + { + auto pNewExp = new XclExpXF( GetRoot(), rStyleSheet ); + maXFList.AppendNewRecord( pNewExp ); + // create the STYLE record + if( !rStyleSheet.GetName().isEmpty() ) + maStyleList.AppendNewRecord( new XclExpStyle( nXFId, rStyleSheet.GetName() ) ); + maXFFindMap[ToFindKey(*pNewExp)].push_back(nXFId); + } + else + // list full - fall back to default style XF + nXFId = GetXFIdFromIndex( EXC_XF_DEFAULTSTYLE ); + } + return nXFId; +} + +void XclExpXFBuffer::InsertUserStyles() +{ + SfxStyleSheetIterator aStyleIter( GetDoc().GetStyleSheetPool(), SfxStyleFamily::Para ); + for( SfxStyleSheetBase* pStyleSheet = aStyleIter.First(); pStyleSheet; pStyleSheet = aStyleIter.Next() ) + if( pStyleSheet->IsUserDefined() && !lclIsBuiltInStyle( pStyleSheet->GetName() ) ) + InsertStyleXF( *pStyleSheet ); +} + +sal_uInt32 XclExpXFBuffer::AppendBuiltInXF( XclExpXFRef const & xXF, sal_uInt8 nStyleId, sal_uInt8 nLevel ) +{ + sal_uInt32 nXFId = static_cast< sal_uInt32 >( maXFList.GetSize() ); + maXFList.AppendRecord( xXF ); + maXFFindMap[ToFindKey(*xXF)].push_back(nXFId); + XclExpBuiltInInfo& rInfo = maBuiltInMap[ nXFId ]; + rInfo.mnStyleId = nStyleId; + rInfo.mnLevel = nLevel; + rInfo.mbPredefined = true; + return nXFId; +} + +sal_uInt32 XclExpXFBuffer::AppendBuiltInXFWithStyle( XclExpXFRef const & xXF, sal_uInt8 nStyleId, sal_uInt8 nLevel ) +{ + sal_uInt32 nXFId = AppendBuiltInXF( xXF, nStyleId, nLevel ); + maStyleList.AppendNewRecord( new XclExpStyle( nXFId, nStyleId, nLevel ) ); + maBuiltInMap[ nXFId ].mbHasStyleRec = true; // mark existing STYLE record + return nXFId; +} + +static XclExpCellArea lcl_GetPatternFill_None() +{ + XclExpCellArea aFill; + aFill.mnPattern = EXC_PATT_NONE; + return aFill; +} + +static XclExpCellArea lcl_GetPatternFill_Gray125() +{ + XclExpCellArea aFill; + aFill.mnPattern = EXC_PATT_12_5_PERC; + aFill.mnForeColor = 0; + aFill.mnBackColor = 0; + return aFill; +} + +void XclExpXFBuffer::InsertDefaultRecords() +{ + maFills.push_back( lcl_GetPatternFill_None() ); + maFills.push_back( lcl_GetPatternFill_Gray125() ); + + // index 0: default style + if( SfxStyleSheetBase* pDefStyleSheet = GetStyleSheetPool().Find( ScResId( STR_STYLENAME_STANDARD ), SfxStyleFamily::Para ) ) + { + XclExpXFRef xDefStyle = new XclExpXF( GetRoot(), *pDefStyleSheet ); + sal_uInt32 nXFId = AppendBuiltInXFWithStyle( xDefStyle, EXC_STYLE_NORMAL ); + // mark this XF as not predefined, prevents overwriting + maBuiltInMap[ nXFId ].mbPredefined = false; + } + else + { + OSL_FAIL( "XclExpXFBuffer::InsertDefaultRecords - default style not found" ); + XclExpXFRef xDefStyle = new XclExpDefaultXF( GetRoot(), false ); + xDefStyle->SetAllUsedFlags( true ); + AppendBuiltInXFWithStyle( xDefStyle, EXC_STYLE_NORMAL ); + } + + // index 1-14: RowLevel and ColLevel styles (without STYLE records) + XclExpDefaultXF aLevelStyle( GetRoot(), false ); + // RowLevel_1, ColLevel_1 + aLevelStyle.SetFont( 1 ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_ROWLEVEL, 0 ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_COLLEVEL, 0 ); + // RowLevel_2, ColLevel_2 + aLevelStyle.SetFont( 2 ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_ROWLEVEL, 1 ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_COLLEVEL, 1 ); + // RowLevel_3, ColLevel_3 ... RowLevel_7, ColLevel_7 + aLevelStyle.SetFont( 0 ); + for( sal_uInt8 nLevel = 2; nLevel < EXC_STYLE_LEVELCOUNT; ++nLevel ) + { + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_ROWLEVEL, nLevel ); + AppendBuiltInXF( new XclExpDefaultXF( aLevelStyle ), EXC_STYLE_COLLEVEL, nLevel ); + } + + // index 15: default hard cell format, placeholder to be able to add more built-in styles + maXFList.AppendNewRecord( new XclExpDefaultXF( GetRoot(), true ) ); + maXFFindMap[ToFindKey(*maXFList.GetRecord(maXFList.GetSize()-1))].push_back(maXFList.GetSize()-1); + maBuiltInMap[ EXC_XF_DEFAULTCELL ].mbPredefined = true; + + // index 16-20: other built-in styles + XclExpDefaultXF aFormatStyle( GetRoot(), false ); + aFormatStyle.SetFont( 1 ); + aFormatStyle.SetNumFmt( 43 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_COMMA ); + aFormatStyle.SetNumFmt( 41 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_COMMA_0 ); + aFormatStyle.SetNumFmt( 44 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_CURRENCY ); + aFormatStyle.SetNumFmt( 42 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_CURRENCY_0 ); + aFormatStyle.SetNumFmt( 9 ); + AppendBuiltInXFWithStyle( new XclExpDefaultXF( aFormatStyle ), EXC_STYLE_PERCENT ); + + // other built-in style XF records (i.e. Hyperlink styles) are created on demand + + /* Insert the real default hard cell format -> 0 is document default pattern. + Do it here (and not already above) to really have all built-in styles. */ + Insert( nullptr, GetDefApiScript() ); +} + +void XclExpXFBuffer::AppendXFIndex( sal_uInt32 nXFId ) +{ + OSL_ENSURE( nXFId < maXFIndexVec.size(), "XclExpXFBuffer::AppendXFIndex - XF ID out of range" ); + maXFIndexVec[ nXFId ] = static_cast< sal_uInt16 >( maSortedXFList.GetSize() ); + XclExpXFRef xXF = maXFList.GetRecord( nXFId ); + AddBorderAndFill( *xXF ); + maSortedXFList.AppendRecord( xXF ); + OSL_ENSURE( maXFList.HasRecord( nXFId ), "XclExpXFBuffer::AppendXFIndex - XF not found" ); +} + +void XclExpXFBuffer::AddBorderAndFill( const XclExpXF& rXF ) +{ + if( std::none_of( maBorders.begin(), maBorders.end(), XclExpBorderPred( rXF.GetBorderData() ) ) ) + { + maBorders.push_back( rXF.GetBorderData() ); + } + + if( std::none_of( maFills.begin(), maFills.end(), XclExpFillPred( rXF.GetAreaData() ) ) ) + { + maFills.push_back( rXF.GetAreaData() ); + } +} + +XclExpDxfs::XclExpDxfs( const XclExpRoot& rRoot ) + : XclExpRoot( rRoot ), + mpKeywordTable( new NfKeywordTable ) +{ + // Special number formatter for conversion. + SvNumberFormatterPtr xFormatter(new SvNumberFormatter( comphelper::getProcessComponentContext(), LANGUAGE_ENGLISH_US )); + xFormatter->FillKeywordTableForExcel( *mpKeywordTable ); + + SCTAB nTables = rRoot.GetDoc().GetTableCount(); + sal_Int32 nDxfId = 0; + for(SCTAB nTab = 0; nTab < nTables; ++nTab) + { + // Color filters + std::vector pDBData = rRoot.GetDoc().GetDBCollection()->GetAllDBsFromTab(nTab); + for (auto& pData : pDBData) + { + ScRange aRange; + pData->GetArea(aRange); + for (auto nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); nCol++) + { + ScFilterEntries aFilterEntries; + rRoot.GetDoc().GetFilterEntriesArea(nCol, aRange.aStart.Row(), + aRange.aEnd.Row(), nTab, true, aFilterEntries); + + // Excel has all filter values stored as foreground colors + // Does not matter it is text color or cell background color + for (auto& rColor : aFilterEntries.getBackgroundColors()) + { + if (!maColorToDxfId.emplace(rColor, nDxfId).second) + continue; + + std::unique_ptr pExpCellArea(new XclExpCellArea(rColor, 0)); + maDxf.push_back(std::make_unique(rRoot, std::move(pExpCellArea))); + nDxfId++; + } + for (auto& rColor : aFilterEntries.getTextColors()) + { + if (!maColorToDxfId.emplace(rColor, nDxfId).second) + continue; + + std::unique_ptr pExpCellArea(new XclExpCellArea(rColor, 0)); + maDxf.push_back(std::make_unique(rRoot, std::move(pExpCellArea))); + nDxfId++; + } + } + } + + // Conditional formatting + ScConditionalFormatList* pList = rRoot.GetDoc().GetCondFormList(nTab); + if (pList) + { + for (const auto& rxItem : *pList) + { + size_t nEntryCount = rxItem->size(); + for (size_t nFormatEntry = 0; nFormatEntry < nEntryCount; ++nFormatEntry) + { + const ScFormatEntry* pFormatEntry = rxItem->GetEntry(nFormatEntry); + if (!pFormatEntry + || (pFormatEntry->GetType() != ScFormatEntry::Type::Condition + && pFormatEntry->GetType() != ScFormatEntry::Type::Date + && pFormatEntry->GetType() != ScFormatEntry::Type::ExtCondition)) + continue; + + OUString aStyleName; + if (pFormatEntry->GetType() == ScFormatEntry::Type::Condition + || pFormatEntry->GetType() == ScFormatEntry::Type::ExtCondition) + { + const ScCondFormatEntry* pEntry = static_cast(pFormatEntry); + aStyleName= pEntry->GetStyle(); + } + else + { + const ScCondDateFormatEntry* pEntry = static_cast(pFormatEntry); + aStyleName = pEntry->GetStyleName(); + } + + if (maStyleNameToDxfId.emplace(aStyleName, nDxfId).second) + { + SfxStyleSheetBase* pStyle = rRoot.GetDoc().GetStyleSheetPool()->Find(aStyleName, SfxStyleFamily::Para); + if(!pStyle) + continue; + + SfxItemSet& rSet = pStyle->GetItemSet(); + + std::unique_ptr pBorder(new XclExpCellBorder); + if (!pBorder->FillFromItemSet( rSet, GetPalette(), GetBiff()) ) + { + pBorder.reset(); + } + + std::unique_ptr pAlign(new XclExpCellAlign); + if (!pAlign->FillFromItemSet(rRoot, rSet, false, GetBiff())) + { + pAlign.reset(); + } + + std::unique_ptr pCellProt(new XclExpCellProt); + if (!pCellProt->FillFromItemSet( rSet )) + { + pCellProt.reset(); + } + + std::unique_ptr pColor(new XclExpColor); + if(!pColor->FillFromItemSet( rSet )) + { + pColor.reset(); + } + + std::unique_ptr pFont(new XclExpDxfFont(rRoot, rSet)); + + std::unique_ptr pNumFormat; + if( const SfxUInt32Item *pPoolItem = rSet.GetItemIfSet( ATTR_VALUE_FORMAT ) ) + { + sal_uInt32 nScNumFmt = pPoolItem->GetValue(); + sal_Int32 nXclNumFmt = GetRoot().GetNumFmtBuffer().Insert(nScNumFmt); + pNumFormat.reset(new XclExpNumFmt( nScNumFmt, nXclNumFmt, GetNumberFormatCode( *this, nScNumFmt, xFormatter.get(), mpKeywordTable.get() ))); + } + + maDxf.push_back(std::make_unique( rRoot, std::move(pAlign), std::move(pBorder), + std::move(pFont), std::move(pNumFormat), std::move(pCellProt), std::move(pColor) )); + ++nDxfId; + } + + } + } + } + } +} + +sal_Int32 XclExpDxfs::GetDxfId( const OUString& rStyleName ) const +{ + std::map::const_iterator itr = maStyleNameToDxfId.find(rStyleName); + if(itr!= maStyleNameToDxfId.end()) + return itr->second; + return -1; +} + +sal_Int32 XclExpDxfs::GetDxfByColor(Color aColor) const +{ + std::map::const_iterator itr = maColorToDxfId.find(aColor); + if (itr != maColorToDxfId.end()) + return itr->second; + return -1; +} + +void XclExpDxfs::AddColor(Color aColor) +{ + maColorToDxfId.emplace(aColor, maDxf.size()); + + std::unique_ptr pExpCellArea(new XclExpCellArea(aColor, 0)); + maDxf.push_back(std::make_unique(GetRoot(), std::move(pExpCellArea))); +} + +void XclExpDxfs::SaveXml( XclExpXmlStream& rStrm ) +{ + if(maDxf.empty()) + return; + + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_dxfs, XML_count, OString::number(maDxf.size())); + + for ( auto& rxDxf : maDxf ) + { + rxDxf->SaveXml( rStrm ); + } + + rStyleSheet->endElement( XML_dxfs ); +} + +XclExpDxf::XclExpDxf( const XclExpRoot& rRoot, std::unique_ptr pAlign, std::unique_ptr pBorder, + std::unique_ptr pFont, std::unique_ptr pNumberFmt, std::unique_ptr pProt, + std::unique_ptr pColor) + : XclExpRoot( rRoot ), + mpAlign(std::move(pAlign)), + mpBorder(std::move(pBorder)), + mpFont(std::move(pFont)), + mpNumberFmt(std::move(pNumberFmt)), + mpProt(std::move(pProt)), + mpColor(std::move(pColor)) +{ +} + +XclExpDxf::XclExpDxf(const XclExpRoot& rRoot, std::unique_ptr pCellArea) + : XclExpRoot(rRoot) + , mpCellArea(std::move(pCellArea)) +{ +} + +XclExpDxf::~XclExpDxf() +{ +} + +void XclExpDxf::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElement(XML_dxf); + + if (mpFont) + mpFont->SaveXml(rStrm); + if (mpNumberFmt) + mpNumberFmt->SaveXml(rStrm); + if (mpColor) + mpColor->SaveXml(rStrm); + if (mpAlign) + mpAlign->SaveXml(rStrm); + if (mpBorder) + mpBorder->SaveXml(rStrm); + if (mpProt) + mpProt->SaveXml(rStrm); + if (mpCellArea) + mpCellArea->SaveXml(rStrm); + rStyleSheet->endElement( XML_dxf ); +} + +void XclExpDxf::SaveXmlExt( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rStyleSheet = rStrm.GetCurrentStream(); + rStyleSheet->startElementNS( XML_x14, XML_dxf ); + + if (mpFont) + mpFont->SaveXml(rStrm); + if (mpNumberFmt) + mpNumberFmt->SaveXml(rStrm); + if (mpColor) + mpColor->SaveXml(rStrm); + if (mpAlign) + mpAlign->SaveXml(rStrm); + if (mpBorder) + mpBorder->SaveXml(rStrm); + if (mpProt) + mpProt->SaveXml(rStrm); + rStyleSheet->endElementNS( XML_x14, XML_dxf ); +} + + +XclExpXmlStyleSheet::XclExpXmlStyleSheet( const XclExpRoot& rRoot ) + : XclExpRoot( rRoot ) +{ +} + +void XclExpXmlStyleSheet::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr aStyleSheet = rStrm.CreateOutputStream( + "xl/styles.xml", + u"styles.xml", + rStrm.GetCurrentStream()->getOutputStream(), + "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", + oox::getRelationship(Relationship::STYLES)); + rStrm.PushStream( aStyleSheet ); + + aStyleSheet->startElement(XML_styleSheet, XML_xmlns, rStrm.getNamespaceURL(OOX_NS(xls))); + + CreateRecord( EXC_ID_FORMATLIST )->SaveXml( rStrm ); + CreateRecord( EXC_ID_FONTLIST )->SaveXml( rStrm ); + CreateRecord( EXC_ID_XFLIST )->SaveXml( rStrm ); + CreateRecord( EXC_ID_DXFS )->SaveXml( rStrm ); + CreateRecord( EXC_ID_PALETTE )->SaveXml( rStrm ); + + aStyleSheet->endElement( XML_styleSheet ); + + rStrm.PopStream(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xetable.cxx b/sc/source/filter/excel/xetable.cxx new file mode 100644 index 000000000..728ea2339 --- /dev/null +++ b/sc/source/filter/excel/xetable.cxx @@ -0,0 +1,2841 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::oox; + +namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + +// Helper records for cell records + +XclExpStringRec::XclExpStringRec( const XclExpRoot& rRoot, const OUString& rResult ) : + XclExpRecord( EXC_ID3_STRING ), + mxResult( XclExpStringHelper::CreateString( rRoot, rResult ) ) +{ + OSL_ENSURE( (rRoot.GetBiff() <= EXC_BIFF5) || (mxResult->Len() > 0), + "XclExpStringRec::XclExpStringRec - empty result not allowed in BIFF8+" ); + SetRecSize( mxResult->GetSize() ); +} + +void XclExpStringRec::WriteBody( XclExpStream& rStrm ) +{ + rStrm << *mxResult; +} + +// Additional records for special formula ranges ============================== + +XclExpRangeFmlaBase::XclExpRangeFmlaBase( + sal_uInt16 nRecId, sal_uInt32 nRecSize, const ScAddress& rScPos ) : + XclExpRecord( nRecId, nRecSize ), + maXclRange( ScAddress::UNINITIALIZED ), + maBaseXclPos( ScAddress::UNINITIALIZED ) +{ + maBaseXclPos.Set( static_cast< sal_uInt16 >( rScPos.Col() ), static_cast< sal_uInt16 >( rScPos.Row() ) ); + maXclRange.maFirst = maXclRange.maLast = maBaseXclPos; +} + +XclExpRangeFmlaBase::XclExpRangeFmlaBase( + sal_uInt16 nRecId, sal_uInt32 nRecSize, const ScRange& rScRange ) : + XclExpRecord( nRecId, nRecSize ), + maXclRange( ScAddress::UNINITIALIZED ), + maBaseXclPos( ScAddress::UNINITIALIZED ) +{ + maXclRange.Set( + static_cast< sal_uInt16 >( rScRange.aStart.Col() ), + static_cast< sal_uInt16 >( rScRange.aStart.Row() ), + static_cast< sal_uInt16 >( rScRange.aEnd.Col() ), + static_cast< sal_uInt16 >( rScRange.aEnd.Row() ) ); + maBaseXclPos = maXclRange.maFirst; +} + +bool XclExpRangeFmlaBase::IsBasePos( sal_uInt16 nXclCol, sal_uInt32 nXclRow ) const +{ + return (maBaseXclPos.mnCol == nXclCol) && (maBaseXclPos.mnRow == nXclRow); +} + +void XclExpRangeFmlaBase::Extend( const ScAddress& rScPos ) +{ + sal_uInt16 nXclCol = static_cast< sal_uInt16 >( rScPos.Col() ); + sal_uInt32 nXclRow = static_cast< sal_uInt32 >( rScPos.Row() ); + maXclRange.maFirst.mnCol = ::std::min( maXclRange.maFirst.mnCol, nXclCol ); + maXclRange.maFirst.mnRow = ::std::min( maXclRange.maFirst.mnRow, nXclRow ); + maXclRange.maLast.mnCol = ::std::max( maXclRange.maLast.mnCol, nXclCol ); + maXclRange.maLast.mnRow = ::std::max( maXclRange.maLast.mnRow, nXclRow ); +} + +void XclExpRangeFmlaBase::WriteRangeAddress( XclExpStream& rStrm ) const +{ + maXclRange.Write( rStrm, false ); +} + +// Array formulas ============================================================= + +XclExpArray::XclExpArray( const XclTokenArrayRef& xTokArr, const ScRange& rScRange ) : + XclExpRangeFmlaBase( EXC_ID3_ARRAY, 14 + xTokArr->GetSize(), rScRange ), + mxTokArr( xTokArr ) +{ +} + +XclTokenArrayRef XclExpArray::CreateCellTokenArray( const XclExpRoot& rRoot ) const +{ + return rRoot.GetFormulaCompiler().CreateSpecialRefFormula( EXC_TOKID_EXP, maBaseXclPos ); +} + +bool XclExpArray::IsVolatile() const +{ + return mxTokArr->IsVolatile(); +} + +void XclExpArray::WriteBody( XclExpStream& rStrm ) +{ + WriteRangeAddress( rStrm ); + sal_uInt16 nFlags = EXC_ARRAY_DEFAULTFLAGS; + ::set_flag( nFlags, EXC_ARRAY_RECALC_ALWAYS, IsVolatile() ); + rStrm << nFlags << sal_uInt32( 0 ) << *mxTokArr; +} + +XclExpArrayBuffer::XclExpArrayBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +XclExpArrayRef XclExpArrayBuffer::CreateArray( const ScTokenArray& rScTokArr, const ScRange& rScRange ) +{ + const ScAddress& rScPos = rScRange.aStart; + XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_MATRIX, rScTokArr, &rScPos ); + + OSL_ENSURE( maRecMap.find( rScPos ) == maRecMap.end(), "XclExpArrayBuffer::CreateArray - array exists already" ); + XclExpArrayRef& rxRec = maRecMap[ rScPos ]; + rxRec = new XclExpArray( xTokArr, rScRange ); + return rxRec; +} + +XclExpArrayRef XclExpArrayBuffer::FindArray( const ScTokenArray& rScTokArr, const ScAddress& rBasePos ) const +{ + XclExpArrayRef xRec; + // try to extract a matrix reference token + if (rScTokArr.GetLen() != 1) + // Must consist of a single reference token. + return xRec; + + const formula::FormulaToken* pToken = rScTokArr.GetArray()[0]; + if (!pToken || pToken->GetOpCode() != ocMatRef) + // not a matrix reference token. + return xRec; + + const ScSingleRefData& rRef = *pToken->GetSingleRef(); + ScAddress aAbsPos = rRef.toAbs(GetRoot().GetDoc(), rBasePos); + XclExpArrayMap::const_iterator it = maRecMap.find(aAbsPos); + + if (it != maRecMap.end()) + xRec = it->second; + return xRec; +} + +// Shared formulas ============================================================ + +XclExpShrfmla::XclExpShrfmla( const XclTokenArrayRef& xTokArr, const ScAddress& rScPos ) : + XclExpRangeFmlaBase( EXC_ID_SHRFMLA, 10 + xTokArr->GetSize(), rScPos ), + mxTokArr( xTokArr ), + mnUsedCount( 1 ) +{ +} + +void XclExpShrfmla::ExtendRange( const ScAddress& rScPos ) +{ + Extend( rScPos ); + ++mnUsedCount; +} + +XclTokenArrayRef XclExpShrfmla::CreateCellTokenArray( const XclExpRoot& rRoot ) const +{ + return rRoot.GetFormulaCompiler().CreateSpecialRefFormula( EXC_TOKID_EXP, maBaseXclPos ); +} + +bool XclExpShrfmla::IsVolatile() const +{ + return mxTokArr->IsVolatile(); +} + +void XclExpShrfmla::WriteBody( XclExpStream& rStrm ) +{ + WriteRangeAddress( rStrm ); + rStrm << sal_uInt8( 0 ) << mnUsedCount << *mxTokArr; +} + +XclExpShrfmlaBuffer::XclExpShrfmlaBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +bool XclExpShrfmlaBuffer::IsValidTokenArray( const ScTokenArray& rArray ) const +{ + using namespace formula; + + FormulaToken** pTokens = rArray.GetArray(); + sal_uInt16 nLen = rArray.GetLen(); + for (sal_uInt16 i = 0; i < nLen; ++i) + { + const FormulaToken* p = pTokens[i]; + switch (p->GetType()) + { + case svSingleRef: + { + const ScSingleRefData& rRefData = *p->GetSingleRef(); + if (!GetFormulaCompiler().IsRef2D(rRefData)) + // Excel's shared formula cannot include 3D reference. + return false; + } + break; + case svDoubleRef: + { + const ScComplexRefData& rRefData = *p->GetDoubleRef(); + if (!GetFormulaCompiler().IsRef2D(rRefData)) + // Excel's shared formula cannot include 3D reference. + return false; + } + break; + case svExternalSingleRef: + case svExternalDoubleRef: + case svExternalName: + // External references aren't allowed. + return false; + default: + ; + } + } + return true; +} + +XclExpShrfmlaRef XclExpShrfmlaBuffer::CreateOrExtendShrfmla( + const ScFormulaCell& rScCell, const ScAddress& rScPos ) +{ + XclExpShrfmlaRef xRec; + const ScTokenArray* pShrdScTokArr = rScCell.GetSharedCode(); + if (!pShrdScTokArr) + // This formula cell is not shared formula cell. + return xRec; + + // Check to see if this shared formula contains any tokens that Excel's shared formula cannot handle. + if (maBadTokens.count(pShrdScTokArr) > 0) + // Already on the black list. Skip it. + return xRec; + + if (!IsValidTokenArray(*pShrdScTokArr)) + { + // We can't export this as shared formula. + maBadTokens.insert(pShrdScTokArr); + return xRec; + } + + TokensType::iterator aIt = maRecMap.find(pShrdScTokArr); + if( aIt == maRecMap.end() ) + { + // create a new record + XclTokenArrayRef xTokArr = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_SHARED, *pShrdScTokArr, &rScPos ); + xRec = new XclExpShrfmla( xTokArr, rScPos ); + maRecMap[ pShrdScTokArr ] = xRec; + } + else + { + // extend existing record + OSL_ENSURE( aIt->second, "XclExpShrfmlaBuffer::CreateOrExtendShrfmla - missing record" ); + xRec = aIt->second; + xRec->ExtendRange( rScPos ); + } + + return xRec; +} + +// Multiple operations ======================================================== + +XclExpTableop::XclExpTableop( const ScAddress& rScPos, + const XclMultipleOpRefs& rRefs, sal_uInt8 nScMode ) : + XclExpRangeFmlaBase( EXC_ID3_TABLEOP, 16, rScPos ), + mnLastAppXclCol( static_cast< sal_uInt16 >( rScPos.Col() ) ), + mnColInpXclCol( static_cast< sal_uInt16 >( rRefs.maColFirstScPos.Col() ) ), + mnColInpXclRow( static_cast< sal_uInt16 >( rRefs.maColFirstScPos.Row() ) ), + mnRowInpXclCol( static_cast< sal_uInt16 >( rRefs.maRowFirstScPos.Col() ) ), + mnRowInpXclRow( static_cast< sal_uInt16 >( rRefs.maRowFirstScPos.Row() ) ), + mnScMode( nScMode ), + mbValid( false ) +{ +} + +bool XclExpTableop::TryExtend( const ScAddress& rScPos, const XclMultipleOpRefs& rRefs ) +{ + sal_uInt16 nXclCol = static_cast< sal_uInt16 >( rScPos.Col() ); + sal_uInt16 nXclRow = static_cast< sal_uInt16 >( rScPos.Row() ); + + bool bOk = IsAppendable( nXclCol, nXclRow ); + if( bOk ) + { + SCCOL nFirstScCol = static_cast< SCCOL >( maXclRange.maFirst.mnCol ); + SCROW nFirstScRow = static_cast< SCROW >( maXclRange.maFirst.mnRow ); + SCCOL nColInpScCol = static_cast< SCCOL >( mnColInpXclCol ); + SCROW nColInpScRow = static_cast< SCROW >( mnColInpXclRow ); + SCCOL nRowInpScCol = static_cast< SCCOL >( mnRowInpXclCol ); + SCROW nRowInpScRow = static_cast< SCROW >( mnRowInpXclRow ); + + bOk = ((mnScMode == 2) == rRefs.mbDblRefMode) && + (rScPos.Tab() == rRefs.maFmlaScPos.Tab()) && + (nColInpScCol == rRefs.maColFirstScPos.Col()) && + (nColInpScRow == rRefs.maColFirstScPos.Row()) && + (rScPos.Tab() == rRefs.maColFirstScPos.Tab()) && + (rScPos.Tab() == rRefs.maColRelScPos.Tab()); + + if( bOk ) switch( mnScMode ) + { + case 0: + bOk = (rScPos.Col() == rRefs.maFmlaScPos.Col()) && + (nFirstScRow == rRefs.maFmlaScPos.Row() + 1) && + (nFirstScCol == rRefs.maColRelScPos.Col() + 1) && + (rScPos.Row() == rRefs.maColRelScPos.Row()); + break; + case 1: + bOk = (nFirstScCol == rRefs.maFmlaScPos.Col() + 1) && + (rScPos.Row() == rRefs.maFmlaScPos.Row()) && + (rScPos.Col() == rRefs.maColRelScPos.Col()) && + (nFirstScRow == rRefs.maColRelScPos.Row() + 1); + break; + case 2: + bOk = (nFirstScCol == rRefs.maFmlaScPos.Col() + 1) && + (nFirstScRow == rRefs.maFmlaScPos.Row() + 1) && + (nFirstScCol == rRefs.maColRelScPos.Col() + 1) && + (rScPos.Row() == rRefs.maColRelScPos.Row()) && + (nRowInpScCol == rRefs.maRowFirstScPos.Col()) && + (nRowInpScRow == rRefs.maRowFirstScPos.Row()) && + (rScPos.Tab() == rRefs.maRowFirstScPos.Tab()) && + (rScPos.Col() == rRefs.maRowRelScPos.Col()) && + (nFirstScRow == rRefs.maRowRelScPos.Row() + 1) && + (rScPos.Tab() == rRefs.maRowRelScPos.Tab()); + break; + default: + bOk = false; + } + + if( bOk ) + { + // extend the cell range + OSL_ENSURE( IsAppendable( nXclCol, nXclRow ), "XclExpTableop::TryExtend - wrong cell address" ); + Extend( rScPos ); + mnLastAppXclCol = nXclCol; + } + } + + return bOk; +} + +void XclExpTableop::Finalize() +{ + // is the range complete? (last appended cell is in last column) + mbValid = maXclRange.maLast.mnCol == mnLastAppXclCol; + // if last row is incomplete, try to shorten the used range + if( !mbValid && (maXclRange.maFirst.mnRow < maXclRange.maLast.mnRow) ) + { + --maXclRange.maLast.mnRow; + mbValid = true; + } + + // check if referred cells are outside of own range + if( !mbValid ) + return; + + switch( mnScMode ) + { + case 0: + mbValid = (mnColInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) || + (mnColInpXclRow < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow); + break; + case 1: + mbValid = (mnColInpXclCol < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) || + (mnColInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow); + break; + case 2: + mbValid = ((mnColInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnColInpXclCol > maXclRange.maLast.mnCol) || + (mnColInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnColInpXclRow > maXclRange.maLast.mnRow)) && + ((mnRowInpXclCol + 1 < maXclRange.maFirst.mnCol) || (mnRowInpXclCol > maXclRange.maLast.mnCol) || + (mnRowInpXclRow + 1 < maXclRange.maFirst.mnRow) || (mnRowInpXclRow > maXclRange.maLast.mnRow)); + break; + } +} + +XclTokenArrayRef XclExpTableop::CreateCellTokenArray( const XclExpRoot& rRoot ) const +{ + XclExpFormulaCompiler& rFmlaComp = rRoot.GetFormulaCompiler(); + return mbValid ? + rFmlaComp.CreateSpecialRefFormula( EXC_TOKID_TBL, maBaseXclPos ) : + rFmlaComp.CreateErrorFormula( EXC_ERR_NA ); +} + +bool XclExpTableop::IsVolatile() const +{ + return true; +} + +void XclExpTableop::Save( XclExpStream& rStrm ) +{ + if( mbValid ) + XclExpRangeFmlaBase::Save( rStrm ); +} + +bool XclExpTableop::IsAppendable( sal_uInt16 nXclCol, sal_uInt16 nXclRow ) const +{ + return ((nXclCol == mnLastAppXclCol + 1) && (nXclRow == maXclRange.maFirst.mnRow)) || + ((nXclCol == mnLastAppXclCol + 1) && (nXclCol <= maXclRange.maLast.mnCol) && (nXclRow == maXclRange.maLast.mnRow)) || + ((mnLastAppXclCol == maXclRange.maLast.mnCol) && (nXclCol == maXclRange.maFirst.mnCol) && (nXclRow == maXclRange.maLast.mnRow + 1)); +} + +void XclExpTableop::WriteBody( XclExpStream& rStrm ) +{ + sal_uInt16 nFlags = EXC_TABLEOP_DEFAULTFLAGS; + ::set_flag( nFlags, EXC_TABLEOP_RECALC_ALWAYS, IsVolatile() ); + switch( mnScMode ) + { + case 1: ::set_flag( nFlags, EXC_TABLEOP_ROW ); break; + case 2: ::set_flag( nFlags, EXC_TABLEOP_BOTH ); break; + } + + WriteRangeAddress( rStrm ); + rStrm << nFlags; + if( mnScMode == 2 ) + rStrm << mnRowInpXclRow << mnRowInpXclCol << mnColInpXclRow << mnColInpXclCol; + else + rStrm << mnColInpXclRow << mnColInpXclCol << sal_uInt32( 0 ); +} + +XclExpTableopBuffer::XclExpTableopBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ) +{ +} + +XclExpTableopRef XclExpTableopBuffer::CreateOrExtendTableop( + const ScTokenArray& rScTokArr, const ScAddress& rScPos ) +{ + XclExpTableopRef xRec; + + // try to extract cell references of a multiple operations formula + XclMultipleOpRefs aRefs; + if (XclTokenArrayHelper::GetMultipleOpRefs(GetDoc(), aRefs, rScTokArr, rScPos)) + { + // try to find an existing TABLEOP record for this cell position + for( size_t nPos = 0, nSize = maTableopList.GetSize(); !xRec && (nPos < nSize); ++nPos ) + { + XclExpTableop* xTempRec = maTableopList.GetRecord( nPos ); + if( xTempRec->TryExtend( rScPos, aRefs ) ) + xRec = xTempRec; + } + + // no record found, or found record not extensible + if( !xRec ) + xRec = TryCreate( rScPos, aRefs ); + } + + return xRec; +} + +void XclExpTableopBuffer::Finalize() +{ + for( size_t nPos = 0, nSize = maTableopList.GetSize(); nPos < nSize; ++nPos ) + maTableopList.GetRecord( nPos )->Finalize(); +} + +XclExpTableopRef XclExpTableopBuffer::TryCreate( const ScAddress& rScPos, const XclMultipleOpRefs& rRefs ) +{ + sal_uInt8 nScMode = 0; + bool bOk = (rScPos.Tab() == rRefs.maFmlaScPos.Tab()) && + (rScPos.Tab() == rRefs.maColFirstScPos.Tab()) && + (rScPos.Tab() == rRefs.maColRelScPos.Tab()); + + if( bOk ) + { + if( rRefs.mbDblRefMode ) + { + nScMode = 2; + bOk = (rScPos.Col() == rRefs.maFmlaScPos.Col() + 1) && + (rScPos.Row() == rRefs.maFmlaScPos.Row() + 1) && + (rScPos.Col() == rRefs.maColRelScPos.Col() + 1) && + (rScPos.Row() == rRefs.maColRelScPos.Row()) && + (rScPos.Tab() == rRefs.maRowFirstScPos.Tab()) && + (rScPos.Col() == rRefs.maRowRelScPos.Col()) && + (rScPos.Row() == rRefs.maRowRelScPos.Row() + 1) && + (rScPos.Tab() == rRefs.maRowRelScPos.Tab()); + } + else if( (rScPos.Col() == rRefs.maFmlaScPos.Col()) && + (rScPos.Row() == rRefs.maFmlaScPos.Row() + 1) && + (rScPos.Col() == rRefs.maColRelScPos.Col() + 1) && + (rScPos.Row() == rRefs.maColRelScPos.Row()) ) + { + nScMode = 0; + } + else if( (rScPos.Col() == rRefs.maFmlaScPos.Col() + 1) && + (rScPos.Row() == rRefs.maFmlaScPos.Row()) && + (rScPos.Col() == rRefs.maColRelScPos.Col()) && + (rScPos.Row() == rRefs.maColRelScPos.Row() + 1) ) + { + nScMode = 1; + } + else + { + bOk = false; + } + } + + XclExpTableopRef xRec; + if( bOk ) + { + xRec = new XclExpTableop( rScPos, rRefs, nScMode ); + maTableopList.AppendRecord( xRec ); + } + + return xRec; +} + +// Cell records + +XclExpCellBase::XclExpCellBase( + sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos ) : + XclExpRecord( nRecId, nContSize + 4 ), + maXclPos( rXclPos ) +{ +} + +bool XclExpCellBase::IsMultiLineText() const +{ + return false; +} + +bool XclExpCellBase::TryMerge( const XclExpCellBase& /*rCell*/ ) +{ + return false; +} + +void XclExpCellBase::GetBlankXFIndexes( ScfUInt16Vec& /*rXFIndexes*/ ) const +{ + // default: do nothing +} + +void XclExpCellBase::RemoveUnusedBlankCells( const ScfUInt16Vec& /*rXFIndexes*/, size_t /*nStartAllNotFound*/ ) +{ + // default: do nothing +} + +// Single cell records ======================================================== + +XclExpSingleCellBase::XclExpSingleCellBase( + sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos, sal_uInt32 nXFId ) : + XclExpCellBase( nRecId, 2, rXclPos ), + maXFId( nXFId ), + mnContSize( nContSize ) +{ +} + +XclExpSingleCellBase::XclExpSingleCellBase( const XclExpRoot& rRoot, + sal_uInt16 nRecId, std::size_t nContSize, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_Int16 nScript, sal_uInt32 nForcedXFId ) : + XclExpCellBase( nRecId, 2, rXclPos ), + maXFId( nForcedXFId ), + mnContSize( nContSize ) +{ + if( GetXFId() == EXC_XFID_NOTFOUND ) + SetXFId( rRoot.GetXFBuffer().Insert( pPattern, nScript ) ); +} + +sal_uInt16 XclExpSingleCellBase::GetLastXclCol() const +{ + return GetXclCol(); +} + +sal_uInt32 XclExpSingleCellBase::GetFirstXFId() const +{ + return GetXFId(); +} + +bool XclExpSingleCellBase::IsEmpty() const +{ + return false; +} + +void XclExpSingleCellBase::ConvertXFIndexes( const XclExpRoot& rRoot ) +{ + maXFId.ConvertXFIndex( rRoot ); +} + +void XclExpSingleCellBase::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 ); + AddRecSize( mnContSize ); + XclExpCellBase::Save( rStrm ); +} + +void XclExpSingleCellBase::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast (GetXclRow()) << GetXclCol() << maXFId.mnXFIndex; + WriteContents( rStrm ); +} + +XclExpNumberCell::XclExpNumberCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, double fValue ) : + // #i41210# always use latin script for number cells - may look wrong for special number formats... + XclExpSingleCellBase( rRoot, EXC_ID3_NUMBER, 8, rXclPos, pPattern, ApiScriptType::LATIN, nForcedXFId ), + mfValue( fValue ) +{ +} + +static OString lcl_GetStyleId( const XclExpXmlStream& rStrm, sal_uInt32 nXFIndex ) +{ + return OString::number( rStrm.GetRoot().GetXFBuffer() + .GetXmlCellIndex( nXFIndex ) ); +} + +static OString lcl_GetStyleId( const XclExpXmlStream& rStrm, const XclExpCellBase& rCell ) +{ + sal_uInt32 nXFId = rCell.GetFirstXFId(); + sal_uInt16 nXFIndex = rStrm.GetRoot().GetXFBuffer().GetXFIndex( nXFId ); + return lcl_GetStyleId( rStrm, nXFIndex ); +} + +void XclExpNumberCell::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(), + XML_s, lcl_GetStyleId(rStrm, *this), + XML_t, "n" + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + rWorksheet->startElement(XML_v); + rWorksheet->write( mfValue ); + rWorksheet->endElement( XML_v ); + rWorksheet->endElement( XML_c ); +} + +void XclExpNumberCell::WriteContents( XclExpStream& rStrm ) +{ + rStrm << mfValue; +} + +XclExpBooleanCell::XclExpBooleanCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, bool bValue ) : + // #i41210# always use latin script for boolean cells + XclExpSingleCellBase( rRoot, EXC_ID3_BOOLERR, 2, rXclPos, pPattern, ApiScriptType::LATIN, nForcedXFId ), + mbValue( bValue ) +{ +} + +void XclExpBooleanCell::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(), + XML_s, lcl_GetStyleId(rStrm, *this), + XML_t, "b" + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + rWorksheet->startElement( XML_v ); + rWorksheet->write( mbValue ? "1" : "0" ); + rWorksheet->endElement( XML_v ); + rWorksheet->endElement( XML_c ); +} + +void XclExpBooleanCell::WriteContents( XclExpStream& rStrm ) +{ + rStrm << sal_uInt16( mbValue ? 1 : 0 ) << EXC_BOOLERR_BOOL; +} + +XclExpLabelCell::XclExpLabelCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, const OUString& rStr ) : + XclExpSingleCellBase( EXC_ID3_LABEL, 0, rXclPos, nForcedXFId ) +{ + sal_uInt16 nMaxLen = (rRoot.GetBiff() == EXC_BIFF8) ? EXC_STR_MAXLEN : EXC_LABEL_MAXLEN; + XclExpStringRef xText = XclExpStringHelper::CreateCellString( + rRoot, rStr, pPattern, XclStrFlags::NONE, nMaxLen); + Init( rRoot, pPattern, xText ); +} + +XclExpLabelCell::XclExpLabelCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, + const EditTextObject* pEditText, XclExpHyperlinkHelper& rLinkHelper ) : + XclExpSingleCellBase( EXC_ID3_LABEL, 0, rXclPos, nForcedXFId ) +{ + sal_uInt16 nMaxLen = (rRoot.GetBiff() == EXC_BIFF8) ? EXC_STR_MAXLEN : EXC_LABEL_MAXLEN; + + XclExpStringRef xText; + if (pEditText) + xText = XclExpStringHelper::CreateCellString( + rRoot, *pEditText, pPattern, rLinkHelper, XclStrFlags::NONE, nMaxLen); + else + xText = XclExpStringHelper::CreateCellString( + rRoot, OUString(), pPattern, XclStrFlags::NONE, nMaxLen); + + Init( rRoot, pPattern, xText ); +} + +bool XclExpLabelCell::IsMultiLineText() const +{ + return mbLineBreak || mxText->IsWrapped(); +} + +void XclExpLabelCell::Init( const XclExpRoot& rRoot, + const ScPatternAttr* pPattern, XclExpStringRef const & xText ) +{ + OSL_ENSURE( xText && xText->Len(), "XclExpLabelCell::XclExpLabelCell - empty string passed" ); + mxText = xText; + mnSstIndex = 0; + + const XclFormatRunVec& rFormats = mxText->GetFormats(); + // remove formatting of the leading run if the entire string + // is equally formatted + sal_uInt16 nXclFont = EXC_FONT_NOTFOUND; + if( rFormats.size() == 1 ) + nXclFont = mxText->RemoveLeadingFont(); + else + nXclFont = mxText->GetLeadingFont(); + + // create cell format + if( GetXFId() == EXC_XFID_NOTFOUND ) + { + OSL_ENSURE( nXclFont != EXC_FONT_NOTFOUND, "XclExpLabelCell::Init - leading font not found" ); + bool bForceLineBreak = mxText->IsWrapped(); + SetXFId( rRoot.GetXFBuffer().InsertWithFont( pPattern, ApiScriptType::WEAK, nXclFont, bForceLineBreak ) ); + } + + // get auto-wrap attribute from cell format + const XclExpXF* pXF = rRoot.GetXFBuffer().GetXFById( GetXFId() ); + mbLineBreak = pXF && pXF->GetAlignmentData().mbLineBreak; + + // initialize the record contents + switch( rRoot.GetBiff() ) + { + case EXC_BIFF5: + // BIFF5-BIFF7: create a LABEL or RSTRING record + OSL_ENSURE( mxText->Len() <= EXC_LABEL_MAXLEN, "XclExpLabelCell::XclExpLabelCell - string too long" ); + SetContSize( mxText->GetSize() ); + // formatted string is exported in an RSTRING record + if( mxText->IsRich() ) + { + OSL_ENSURE( mxText->GetFormatsCount() <= EXC_LABEL_MAXLEN, "XclExpLabelCell::WriteContents - too many formats" ); + mxText->LimitFormatCount( EXC_LABEL_MAXLEN ); + SetRecId( EXC_ID_RSTRING ); + SetContSize( GetContSize() + 1 + 2 * mxText->GetFormatsCount() ); + } + break; + case EXC_BIFF8: + // BIFF8+: create a LABELSST record + mnSstIndex = rRoot.GetSst().Insert( xText ); + SetRecId( EXC_ID_LABELSST ); + SetContSize( 4 ); + break; + default: DBG_ERROR_BIFF(); + } +} + +void XclExpLabelCell::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(), + XML_s, lcl_GetStyleId(rStrm, *this), + XML_t, "s" + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + rWorksheet->startElement( XML_v ); + rWorksheet->write( static_cast(mnSstIndex) ); + rWorksheet->endElement( XML_v ); + rWorksheet->endElement( XML_c ); +} + +void XclExpLabelCell::WriteContents( XclExpStream& rStrm ) +{ + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF5: + rStrm << *mxText; + if( mxText->IsRich() ) + { + rStrm << static_cast< sal_uInt8 >( mxText->GetFormatsCount() ); + mxText->WriteFormats( rStrm ); + } + break; + case EXC_BIFF8: + rStrm << mnSstIndex; + break; + default: DBG_ERROR_BIFF(); + } +} + +XclExpFormulaCell::XclExpFormulaCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, + const ScFormulaCell& rScFmlaCell, + XclExpArrayBuffer& rArrayBfr, + XclExpShrfmlaBuffer& rShrfmlaBfr, + XclExpTableopBuffer& rTableopBfr ) : + XclExpSingleCellBase( EXC_ID2_FORMULA, 0, rXclPos, nForcedXFId ), + mrScFmlaCell( const_cast< ScFormulaCell& >( rScFmlaCell ) ) +{ + // *** Find result number format overwriting cell number format *** ------- + + if( GetXFId() == EXC_XFID_NOTFOUND ) + { + SvNumberFormatter& rFormatter = rRoot.GetFormatter(); + XclExpNumFmtBuffer& rNumFmtBfr = rRoot.GetNumFmtBuffer(); + + // current cell number format + sal_uInt32 nScNumFmt = pPattern ? + pPattern->GetItemSet().Get( ATTR_VALUE_FORMAT ).GetValue() : + rNumFmtBfr.GetStandardFormat(); + + // alternative number format passed to XF buffer + sal_uInt32 nAltScNumFmt = NUMBERFORMAT_ENTRY_NOT_FOUND; + /* Xcl doesn't know Boolean number formats, we write + "TRUE";"FALSE" (language dependent). Don't do it for automatic + formula formats, because Excel gets them right. */ + /* #i8640# Don't set text format, if we have string results. */ + SvNumFormatType nFormatType = mrScFmlaCell.GetFormatType(); + if( ((nScNumFmt % SV_COUNTRY_LANGUAGE_OFFSET) == 0) && + (nFormatType != SvNumFormatType::LOGICAL) && + (nFormatType != SvNumFormatType::TEXT) ) + nAltScNumFmt = nScNumFmt; + /* If cell number format is Boolean and automatic formula + format is Boolean don't write that ugly special format. */ + else if( (nFormatType == SvNumFormatType::LOGICAL) && + (rFormatter.GetType( nScNumFmt ) == SvNumFormatType::LOGICAL) ) + nAltScNumFmt = rNumFmtBfr.GetStandardFormat(); + + // #i41420# find script type according to result type (always latin for numeric results) + sal_Int16 nScript = ApiScriptType::LATIN; + bool bForceLineBreak = false; + if( nFormatType == SvNumFormatType::TEXT ) + { + OUString aResult = mrScFmlaCell.GetString().getString(); + bForceLineBreak = mrScFmlaCell.IsMultilineResult(); + nScript = XclExpStringHelper::GetLeadingScriptType( rRoot, aResult ); + } + SetXFId( rRoot.GetXFBuffer().InsertWithNumFmt( pPattern, nScript, nAltScNumFmt, bForceLineBreak ) ); + } + + // *** Convert the formula token array *** -------------------------------- + + ScAddress aScPos( static_cast< SCCOL >( rXclPos.mnCol ), static_cast< SCROW >( rXclPos.mnRow ), rRoot.GetCurrScTab() ); + const ScTokenArray& rScTokArr = *mrScFmlaCell.GetCode(); + + // first try to create multiple operations + mxAddRec = rTableopBfr.CreateOrExtendTableop( rScTokArr, aScPos ); + + // no multiple operation found - try to create matrix formula + if( !mxAddRec ) + switch( mrScFmlaCell.GetMatrixFlag() ) + { + case ScMatrixMode::Formula: + { + // origin of the matrix - find the used matrix range + SCCOL nMatWidth; + SCROW nMatHeight; + mrScFmlaCell.GetMatColsRows( nMatWidth, nMatHeight ); + OSL_ENSURE( nMatWidth && nMatHeight, "XclExpFormulaCell::XclExpFormulaCell - empty matrix" ); + ScRange aMatScRange( aScPos ); + ScAddress& rMatEnd = aMatScRange.aEnd; + rMatEnd.IncCol( static_cast< SCCOL >( nMatWidth - 1 ) ); + rMatEnd.IncRow( static_cast< SCROW >( nMatHeight - 1 ) ); + // reduce to valid range (range keeps valid, because start position IS valid) + rRoot.GetAddressConverter().ValidateRange( aMatScRange, true ); + // create the ARRAY record + mxAddRec = rArrayBfr.CreateArray( rScTokArr, aMatScRange ); + } + break; + case ScMatrixMode::Reference: + { + // other formula cell covered by a matrix - find the ARRAY record + mxAddRec = rArrayBfr.FindArray(rScTokArr, aScPos); + // should always be found, if Calc document is not broken + OSL_ENSURE( mxAddRec, "XclExpFormulaCell::XclExpFormulaCell - no matrix found" ); + } + break; + default:; + } + + // no matrix found - try to create shared formula + if( !mxAddRec ) + mxAddRec = rShrfmlaBfr.CreateOrExtendShrfmla(mrScFmlaCell, aScPos); + + // no shared formula found - create a simple cell formula + if( !mxAddRec ) + mxTokArr = rRoot.GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CELL, rScTokArr, &aScPos ); +} + +void XclExpFormulaCell::Save( XclExpStream& rStrm ) +{ + // create token array for FORMULA cells with additional record + if( mxAddRec ) + mxTokArr = mxAddRec->CreateCellTokenArray( rStrm.GetRoot() ); + + // FORMULA record itself + OSL_ENSURE( mxTokArr, "XclExpFormulaCell::Save - missing token array" ); + if( !mxTokArr ) + mxTokArr = rStrm.GetRoot().GetFormulaCompiler().CreateErrorFormula( EXC_ERR_NA ); + SetContSize( 16 + mxTokArr->GetSize() ); + XclExpSingleCellBase::Save( rStrm ); + + // additional record (ARRAY, SHRFMLA, or TABLEOP), only for first FORMULA record + if( mxAddRec && mxAddRec->IsBasePos( GetXclCol(), GetXclRow() ) ) + mxAddRec->Save( rStrm ); + + // STRING record for string result + if( mxStringRec ) + mxStringRec->Save( rStrm ); +} + +void XclExpFormulaCell::SaveXml( XclExpXmlStream& rStrm ) +{ + const char* sType = nullptr; + OUString sValue; + XclXmlUtils::GetFormulaTypeAndValue( mrScFmlaCell, sType, sValue ); + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), GetXclPos()).getStr(), + XML_s, lcl_GetStyleId(rStrm, *this), + XML_t, sType + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + + bool bWriteFormula = true; + bool bTagStarted = false; + ScAddress aScPos( static_cast< SCCOL >( GetXclPos().mnCol ), + static_cast< SCROW >( GetXclPos().mnRow ), rStrm.GetRoot().GetCurrScTab() ); + + switch (mrScFmlaCell.GetMatrixFlag()) + { + case ScMatrixMode::NONE: + break; + case ScMatrixMode::Reference: + bWriteFormula = false; + break; + case ScMatrixMode::Formula: + { + // origin of the matrix - find the used matrix range + SCCOL nMatWidth; + SCROW nMatHeight; + mrScFmlaCell.GetMatColsRows( nMatWidth, nMatHeight ); + OSL_ENSURE( nMatWidth && nMatHeight, "XclExpFormulaCell::XclExpFormulaCell - empty matrix" ); + ScRange aMatScRange( aScPos ); + ScAddress& rMatEnd = aMatScRange.aEnd; + rMatEnd.IncCol( static_cast< SCCOL >( nMatWidth - 1 ) ); + rMatEnd.IncRow( static_cast< SCROW >( nMatHeight - 1 ) ); + // reduce to valid range (range keeps valid, because start position IS valid + rStrm.GetRoot().GetAddressConverter().ValidateRange( aMatScRange, true ); + + OStringBuffer sFmlaCellRange; + if (rStrm.GetRoot().GetDoc().ValidRange(aMatScRange)) + { + // calculate the cell range. + sFmlaCellRange.append( XclXmlUtils::ToOString( + rStrm.GetRoot().GetStringBuf(), aMatScRange.aStart ).getStr()); + sFmlaCellRange.append(":"); + sFmlaCellRange.append( XclXmlUtils::ToOString( + rStrm.GetRoot().GetStringBuf(), aMatScRange.aEnd ).getStr()); + } + + if ( aMatScRange.aStart.Col() == GetXclPos().mnCol && + aMatScRange.aStart.Row() == static_cast(GetXclPos().mnRow)) + { + rWorksheet->startElement( XML_f, + XML_aca, ToPsz( (mxTokArr && mxTokArr->IsVolatile()) || + (mxAddRec && mxAddRec->IsVolatile())), + XML_t, mxAddRec ? "array" : nullptr, + XML_ref, !sFmlaCellRange.isEmpty()? sFmlaCellRange.getStr() : nullptr + // OOXTODO: XML_dt2D, bool + // OOXTODO: XML_dtr, bool + // OOXTODO: XML_del1, bool + // OOXTODO: XML_del2, bool + // OOXTODO: XML_r1, ST_CellRef + // OOXTODO: XML_r2, ST_CellRef + // OOXTODO: XML_ca, bool + // OOXTODO: XML_si, uint + // OOXTODO: XML_bx bool + ); + bTagStarted = true; + } + } + break; + } + + if (bWriteFormula) + { + if (!bTagStarted) + { + rWorksheet->startElement( XML_f, + XML_aca, ToPsz( (mxTokArr && mxTokArr->IsVolatile()) || + (mxAddRec && mxAddRec->IsVolatile()) ) ); + } + rWorksheet->writeEscaped( XclXmlUtils::ToOUString( + rStrm.GetRoot().GetCompileFormulaContext(), mrScFmlaCell.aPos, mrScFmlaCell.GetCode(), + mrScFmlaCell.GetErrCode())); + rWorksheet->endElement( XML_f ); + } + + if( strcmp( sType, "inlineStr" ) == 0 ) + { + rWorksheet->startElement(XML_is); + rWorksheet->startElement(XML_t); + rWorksheet->writeEscaped( sValue ); + rWorksheet->endElement( XML_t ); + rWorksheet->endElement( XML_is ); + } + else + { + rWorksheet->startElement(XML_v); + rWorksheet->writeEscaped( sValue ); + rWorksheet->endElement( XML_v ); + } + rWorksheet->endElement( XML_c ); +} + +void XclExpFormulaCell::WriteContents( XclExpStream& rStrm ) +{ + FormulaError nScErrCode = mrScFmlaCell.GetErrCode(); + if( nScErrCode != FormulaError::NONE ) + { + rStrm << EXC_FORMULA_RES_ERROR << sal_uInt8( 0 ) + << XclTools::GetXclErrorCode( nScErrCode ) + << sal_uInt8( 0 ) << sal_uInt16( 0 ) + << sal_uInt16( 0xFFFF ); + } + else + { + // result of the formula + switch( mrScFmlaCell.GetFormatType() ) + { + case SvNumFormatType::NUMBER: + { + // either value or error code + rStrm << mrScFmlaCell.GetValue(); + } + break; + + case SvNumFormatType::TEXT: + { + OUString aResult = mrScFmlaCell.GetString().getString(); + if( !aResult.isEmpty() || (rStrm.GetRoot().GetBiff() <= EXC_BIFF5) ) + { + rStrm << EXC_FORMULA_RES_STRING; + mxStringRec = new XclExpStringRec( rStrm.GetRoot(), aResult ); + } + else + rStrm << EXC_FORMULA_RES_EMPTY; // BIFF8 only + rStrm << sal_uInt8( 0 ) << sal_uInt32( 0 ) << sal_uInt16( 0xFFFF ); + } + break; + + case SvNumFormatType::LOGICAL: + { + sal_uInt8 nXclValue = (mrScFmlaCell.GetValue() == 0.0) ? 0 : 1; + rStrm << EXC_FORMULA_RES_BOOL << sal_uInt8( 0 ) + << nXclValue << sal_uInt8( 0 ) << sal_uInt16( 0 ) + << sal_uInt16( 0xFFFF ); + } + break; + + default: + rStrm << mrScFmlaCell.GetValue(); + } + } + + // flags and formula token array + sal_uInt16 nFlags = EXC_FORMULA_DEFAULTFLAGS; + ::set_flag( nFlags, EXC_FORMULA_RECALC_ALWAYS, mxTokArr->IsVolatile() || (mxAddRec && mxAddRec->IsVolatile()) ); + ::set_flag( nFlags, EXC_FORMULA_SHARED, mxAddRec && (mxAddRec->GetRecId() == EXC_ID_SHRFMLA) ); + rStrm << nFlags << sal_uInt32( 0 ) << *mxTokArr; +} + +// Multiple cell records ====================================================== + +XclExpMultiCellBase::XclExpMultiCellBase( + sal_uInt16 nRecId, sal_uInt16 nMulRecId, std::size_t nContSize, const XclAddress& rXclPos ) : + XclExpCellBase( nRecId, 0, rXclPos ), + mnMulRecId( nMulRecId ), + mnContSize( nContSize ) +{ +} + +sal_uInt16 XclExpMultiCellBase::GetLastXclCol() const +{ + return GetXclCol() + GetCellCount() - 1; +} + +sal_uInt32 XclExpMultiCellBase::GetFirstXFId() const +{ + return maXFIds.empty() ? XclExpXFBuffer::GetDefCellXFId() : maXFIds.front().mnXFId; +} + +bool XclExpMultiCellBase::IsEmpty() const +{ + return maXFIds.empty(); +} + +void XclExpMultiCellBase::ConvertXFIndexes( const XclExpRoot& rRoot ) +{ + for( auto& rXFId : maXFIds ) + rXFId.ConvertXFIndex( rRoot ); +} + +void XclExpMultiCellBase::Save( XclExpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 ); + + XclExpMultiXFIdDeq::const_iterator aEnd = maXFIds.end(); + XclExpMultiXFIdDeq::const_iterator aRangeBeg = maXFIds.begin(); + XclExpMultiXFIdDeq::const_iterator aRangeEnd = aRangeBeg; + sal_uInt16 nBegXclCol = GetXclCol(); + sal_uInt16 nEndXclCol = nBegXclCol; + + while( aRangeEnd != aEnd ) + { + // find begin of next used XF range + aRangeBeg = aRangeEnd; + nBegXclCol = nEndXclCol; + while( (aRangeBeg != aEnd) && (aRangeBeg->mnXFIndex == EXC_XF_NOTFOUND) ) + { + nBegXclCol = nBegXclCol + aRangeBeg->mnCount; + ++aRangeBeg; + } + // find end of next used XF range + aRangeEnd = aRangeBeg; + nEndXclCol = nBegXclCol; + while( (aRangeEnd != aEnd) && (aRangeEnd->mnXFIndex != EXC_XF_NOTFOUND) ) + { + nEndXclCol = nEndXclCol + aRangeEnd->mnCount; + ++aRangeEnd; + } + + // export this range as a record + if( aRangeBeg != aRangeEnd ) + { + sal_uInt16 nCount = nEndXclCol - nBegXclCol; + bool bIsMulti = nCount > 1; + std::size_t nTotalSize = GetRecSize() + (2 + mnContSize) * nCount; + if( bIsMulti ) nTotalSize += 2; + + rStrm.StartRecord( bIsMulti ? mnMulRecId : GetRecId(), nTotalSize ); + rStrm << static_cast (GetXclRow()) << nBegXclCol; + + sal_uInt16 nRelCol = nBegXclCol - GetXclCol(); + for( XclExpMultiXFIdDeq::const_iterator aIt = aRangeBeg; aIt != aRangeEnd; ++aIt ) + { + for( sal_uInt16 nIdx = 0; nIdx < aIt->mnCount; ++nIdx ) + { + rStrm << aIt->mnXFIndex; + WriteContents( rStrm, nRelCol ); + ++nRelCol; + } + } + if( bIsMulti ) + rStrm << static_cast< sal_uInt16 >( nEndXclCol - 1 ); + rStrm.EndRecord(); + } + } +} + +void XclExpMultiCellBase::SaveXml( XclExpXmlStream& rStrm ) +{ + XclExpMultiXFIdDeq::const_iterator aEnd = maXFIds.end(); + XclExpMultiXFIdDeq::const_iterator aRangeBeg = maXFIds.begin(); + XclExpMultiXFIdDeq::const_iterator aRangeEnd = aRangeBeg; + sal_uInt16 nBegXclCol = GetXclCol(); + sal_uInt16 nEndXclCol = nBegXclCol; + + while( aRangeEnd != aEnd ) + { + // find begin of next used XF range + aRangeBeg = aRangeEnd; + nBegXclCol = nEndXclCol; + while( (aRangeBeg != aEnd) && (aRangeBeg->mnXFIndex == EXC_XF_NOTFOUND) ) + { + nBegXclCol = nBegXclCol + aRangeBeg->mnCount; + ++aRangeBeg; + } + // find end of next used XF range + aRangeEnd = aRangeBeg; + nEndXclCol = nBegXclCol; + while( (aRangeEnd != aEnd) && (aRangeEnd->mnXFIndex != EXC_XF_NOTFOUND) ) + { + nEndXclCol = nEndXclCol + aRangeEnd->mnCount; + ++aRangeEnd; + } + + // export this range as a record + if( aRangeBeg != aRangeEnd ) + { + sal_uInt16 nRelColIdx = nBegXclCol - GetXclCol(); + sal_Int32 nRelCol = 0; + for( XclExpMultiXFIdDeq::const_iterator aIt = aRangeBeg; aIt != aRangeEnd; ++aIt ) + { + for( sal_uInt16 nIdx = 0; nIdx < aIt->mnCount; ++nIdx ) + { + WriteXmlContents( + rStrm, + XclAddress( static_cast(nBegXclCol + nRelCol), GetXclRow() ), + aIt->mnXFIndex, + nRelColIdx ); + ++nRelCol; + ++nRelColIdx; + } + } + } + } +} + +sal_uInt16 XclExpMultiCellBase::GetCellCount() const +{ + return std::accumulate(maXFIds.begin(), maXFIds.end(), sal_uInt16(0), + [](const sal_uInt16& rSum, const XclExpMultiXFId& rXFId) { return rSum + rXFId.mnCount; }); +} + +void XclExpMultiCellBase::AppendXFId( const XclExpMultiXFId& rXFId ) +{ + if( maXFIds.empty() || (maXFIds.back().mnXFId != rXFId.mnXFId) ) + maXFIds.push_back( rXFId ); + else + maXFIds.back().mnCount += rXFId.mnCount; +} + +void XclExpMultiCellBase::AppendXFId( const XclExpRoot& rRoot, + const ScPatternAttr* pPattern, sal_uInt16 nScript, sal_uInt32 nForcedXFId, sal_uInt16 nCount ) +{ + sal_uInt32 nXFId = (nForcedXFId == EXC_XFID_NOTFOUND) ? + rRoot.GetXFBuffer().Insert( pPattern, nScript ) : nForcedXFId; + AppendXFId( XclExpMultiXFId( nXFId, nCount ) ); +} + +bool XclExpMultiCellBase::TryMergeXFIds( const XclExpMultiCellBase& rCell ) +{ + if( GetLastXclCol() + 1 == rCell.GetXclCol() ) + { + maXFIds.insert( maXFIds.end(), rCell.maXFIds.begin(), rCell.maXFIds.end() ); + return true; + } + return false; +} + +void XclExpMultiCellBase::GetXFIndexes( ScfUInt16Vec& rXFIndexes ) const +{ + OSL_ENSURE( GetLastXclCol() < rXFIndexes.size(), "XclExpMultiCellBase::GetXFIndexes - vector too small" ); + ScfUInt16Vec::iterator aDestIt = rXFIndexes.begin() + GetXclCol(); + for( const auto& rXFId : maXFIds ) + { + ::std::fill( aDestIt, aDestIt + rXFId.mnCount, rXFId.mnXFIndex ); + aDestIt += rXFId.mnCount; + } +} + +void XclExpMultiCellBase::RemoveUnusedXFIndexes( const ScfUInt16Vec& rXFIndexes, size_t nStartAllNotFound ) +{ + // save last column before calling maXFIds.clear() + sal_uInt16 nLastXclCol = GetLastXclCol(); + OSL_ENSURE( nLastXclCol < rXFIndexes.size(), "XclExpMultiCellBase::RemoveUnusedXFIndexes - XF index vector too small" ); + + // build new XF index vector, containing passed XF indexes + maXFIds.clear(); + // Process only all that possibly are not EXC_XF_NOTFOUND. + size_t nEnd = std::min(nLastXclCol + 1, nStartAllNotFound); + for( size_t i = GetXclCol(); i < nEnd; ++i ) + { + XclExpMultiXFId aXFId( 0 ); + // AppendXFId() tests XclExpXFIndex::mnXFId, set it too + aXFId.mnXFId = aXFId.mnXFIndex = rXFIndexes[ i ]; + AppendXFId( aXFId ); + } + + // remove leading and trailing unused XF indexes + if( !maXFIds.empty() && (maXFIds.front().mnXFIndex == EXC_XF_NOTFOUND) ) + { + SetXclCol( GetXclCol() + maXFIds.front().mnCount ); + maXFIds.erase(maXFIds.begin(), maXFIds.begin() + 1); + } + if( !maXFIds.empty() && (maXFIds.back().mnXFIndex == EXC_XF_NOTFOUND) ) + maXFIds.pop_back(); + + // The Save() function will skip all XF indexes equal to EXC_XF_NOTFOUND. +} + +sal_uInt16 XclExpMultiCellBase::GetStartColAllDefaultCell() const +{ + sal_uInt16 col = GetXclCol(); + sal_uInt16 nMaxNonDefCol = col; + for( const auto& rXFId : maXFIds ) + { + col += rXFId.mnCount; + if (rXFId.mnXFIndex != EXC_XF_DEFAULTCELL) + nMaxNonDefCol = col; + } + return nMaxNonDefCol; +} + +XclExpBlankCell::XclExpBlankCell( const XclAddress& rXclPos, const XclExpMultiXFId& rXFId ) : + XclExpMultiCellBase( EXC_ID3_BLANK, EXC_ID_MULBLANK, 0, rXclPos ) +{ + OSL_ENSURE( rXFId.mnCount > 0, "XclExpBlankCell::XclExpBlankCell - invalid count" ); + AppendXFId( rXFId ); +} + +XclExpBlankCell::XclExpBlankCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, sal_uInt16 nLastXclCol, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId ) : + XclExpMultiCellBase( EXC_ID3_BLANK, EXC_ID_MULBLANK, 0, rXclPos ) +{ + OSL_ENSURE( rXclPos.mnCol <= nLastXclCol, "XclExpBlankCell::XclExpBlankCell - invalid column range" ); + // #i46627# use default script type instead of ApiScriptType::WEAK + AppendXFId( rRoot, pPattern, rRoot.GetDefApiScript(), nForcedXFId, nLastXclCol - rXclPos.mnCol + 1 ); +} + +bool XclExpBlankCell::TryMerge( const XclExpCellBase& rCell ) +{ + const XclExpBlankCell* pBlankCell = dynamic_cast< const XclExpBlankCell* >( &rCell ); + return pBlankCell && TryMergeXFIds( *pBlankCell ); +} + +void XclExpBlankCell::GetBlankXFIndexes( ScfUInt16Vec& rXFIndexes ) const +{ + GetXFIndexes( rXFIndexes ); +} + +void XclExpBlankCell::RemoveUnusedBlankCells( const ScfUInt16Vec& rXFIndexes, size_t nStartAllNotFound ) +{ + RemoveUnusedXFIndexes( rXFIndexes, nStartAllNotFound ); +} + +void XclExpBlankCell::WriteContents( XclExpStream& /*rStrm*/, sal_uInt16 /*nRelCol*/ ) +{ +} + +void XclExpBlankCell::WriteXmlContents( XclExpXmlStream& rStrm, const XclAddress& rAddress, sal_uInt32 nXFId, sal_uInt16 /* nRelCol */ ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->singleElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), rAddress).getStr(), + XML_s, lcl_GetStyleId(rStrm, nXFId) ); +} + +XclExpRkCell::XclExpRkCell( + const XclExpRoot& rRoot, const XclAddress& rXclPos, + const ScPatternAttr* pPattern, sal_uInt32 nForcedXFId, sal_Int32 nRkValue ) : + XclExpMultiCellBase( EXC_ID_RK, EXC_ID_MULRK, 4, rXclPos ) +{ + // #i41210# always use latin script for number cells - may look wrong for special number formats... + AppendXFId( rRoot, pPattern, ApiScriptType::LATIN, nForcedXFId ); + maRkValues.push_back( nRkValue ); +} + +bool XclExpRkCell::TryMerge( const XclExpCellBase& rCell ) +{ + const XclExpRkCell* pRkCell = dynamic_cast< const XclExpRkCell* >( &rCell ); + if( pRkCell && TryMergeXFIds( *pRkCell ) ) + { + maRkValues.insert( maRkValues.end(), pRkCell->maRkValues.begin(), pRkCell->maRkValues.end() ); + return true; + } + return false; +} + +void XclExpRkCell::WriteXmlContents( XclExpXmlStream& rStrm, const XclAddress& rAddress, sal_uInt32 nXFId, sal_uInt16 nRelCol ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_c, + XML_r, XclXmlUtils::ToOString(rStrm.GetRoot().GetStringBuf(), rAddress).getStr(), + XML_s, lcl_GetStyleId(rStrm, nXFId), + XML_t, "n" + // OOXTODO: XML_cm, XML_vm, XML_ph + ); + rWorksheet->startElement( XML_v ); + rWorksheet->write( XclTools::GetDoubleFromRK( maRkValues[ nRelCol ] ) ); + rWorksheet->endElement( XML_v ); + rWorksheet->endElement( XML_c ); +} + +void XclExpRkCell::WriteContents( XclExpStream& rStrm, sal_uInt16 nRelCol ) +{ + OSL_ENSURE( nRelCol < maRkValues.size(), "XclExpRkCell::WriteContents - overflow error" ); + rStrm << maRkValues[ nRelCol ]; +} + +// Rows and Columns + +XclExpOutlineBuffer::XclExpOutlineBuffer( const XclExpRoot& rRoot, bool bRows ) : + mpScOLArray( nullptr ), + maLevelInfos( SC_OL_MAXDEPTH ), + mnCurrLevel( 0 ), + mbCurrCollapse( false ) +{ + if( const ScOutlineTable* pOutlineTable = rRoot.GetDoc().GetOutlineTable( rRoot.GetCurrScTab() ) ) + mpScOLArray = &(bRows ? pOutlineTable->GetRowArray() : pOutlineTable->GetColArray()); + + if( mpScOLArray ) + for( size_t nLevel = 0; nLevel < SC_OL_MAXDEPTH; ++nLevel ) + if( const ScOutlineEntry* pEntry = mpScOLArray->GetEntryByPos( nLevel, 0 ) ) + maLevelInfos[ nLevel ].mnScEndPos = pEntry->GetEnd(); +} + +void XclExpOutlineBuffer::UpdateColRow( SCCOLROW nScPos ) +{ + if( !mpScOLArray ) + return; + + // find open level index for passed position + size_t nNewOpenScLevel = 0; // new open level (0-based Calc index) + sal_uInt8 nNewLevel = 0; // new open level (1-based Excel index) + + if( mpScOLArray->FindTouchedLevel( nScPos, nScPos, nNewOpenScLevel ) ) + nNewLevel = static_cast< sal_uInt8 >( nNewOpenScLevel + 1 ); + // else nNewLevel keeps 0 to show that there are no groups + + mbCurrCollapse = false; + if( nNewLevel >= mnCurrLevel ) + { + // new level(s) opened, or no level closed - update all level infos + for( size_t nScLevel = 0; nScLevel <= nNewOpenScLevel; ++nScLevel ) + { + /* In each level: check if a new group is started (there may be + neighbored groups without gap - therefore check ALL levels). */ + if( maLevelInfos[ nScLevel ].mnScEndPos < nScPos ) + { + if( const ScOutlineEntry* pEntry = mpScOLArray->GetEntryByPos( nScLevel, nScPos ) ) + { + maLevelInfos[ nScLevel ].mnScEndPos = pEntry->GetEnd(); + maLevelInfos[ nScLevel ].mbHidden = pEntry->IsHidden(); + } + } + } + } + else + { + // level(s) closed - check if any of the closed levels are collapsed + // Calc uses 0-based level indexes + sal_uInt16 nOldOpenScLevel = mnCurrLevel - 1; + for( sal_uInt16 nScLevel = nNewOpenScLevel + 1; !mbCurrCollapse && (nScLevel <= nOldOpenScLevel); ++nScLevel ) + mbCurrCollapse = maLevelInfos[ nScLevel ].mbHidden; + } + + // cache new opened level + mnCurrLevel = nNewLevel; +} + +XclExpGuts::XclExpGuts( const XclExpRoot& rRoot ) : + XclExpRecord( EXC_ID_GUTS, 8 ), + mnColLevels( 0 ), + mnColWidth( 0 ), + mnRowLevels( 0 ), + mnRowWidth( 0 ) +{ + const ScOutlineTable* pOutlineTable = rRoot.GetDoc().GetOutlineTable( rRoot.GetCurrScTab() ); + if(!pOutlineTable) + return; + + // column outline groups + const ScOutlineArray& rColArray = pOutlineTable->GetColArray(); + mnColLevels = ulimit_cast< sal_uInt16 >( rColArray.GetDepth(), EXC_OUTLINE_MAX ); + if( mnColLevels ) + { + ++mnColLevels; + mnColWidth = 12 * mnColLevels + 5; + } + + // row outline groups + const ScOutlineArray& rRowArray = pOutlineTable->GetRowArray(); + mnRowLevels = ulimit_cast< sal_uInt16 >( rRowArray.GetDepth(), EXC_OUTLINE_MAX ); + if( mnRowLevels ) + { + ++mnRowLevels; + mnRowWidth = 12 * mnRowLevels + 5; + } +} + +void XclExpGuts::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnRowWidth << mnColWidth << mnRowLevels << mnColLevels; +} + +XclExpDimensions::XclExpDimensions( const XclExpRoot& rRoot ) : + mrRoot(rRoot), + mnFirstUsedXclRow( 0 ), + mnFirstFreeXclRow( 0 ), + mnFirstUsedXclCol( 0 ), + mnFirstFreeXclCol( 0 ) +{ + switch( rRoot.GetBiff() ) + { + case EXC_BIFF2: SetRecHeader( EXC_ID2_DIMENSIONS, 8 ); break; + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: SetRecHeader( EXC_ID3_DIMENSIONS, 10 ); break; + case EXC_BIFF8: SetRecHeader( EXC_ID3_DIMENSIONS, 14 ); break; + default: DBG_ERROR_BIFF(); + } +} + +void XclExpDimensions::SetDimensions( + sal_uInt16 nFirstUsedXclCol, sal_uInt32 nFirstUsedXclRow, + sal_uInt16 nFirstFreeXclCol, sal_uInt32 nFirstFreeXclRow ) +{ + mnFirstUsedXclRow = nFirstUsedXclRow; + mnFirstFreeXclRow = nFirstFreeXclRow; + mnFirstUsedXclCol = nFirstUsedXclCol; + mnFirstFreeXclCol = nFirstFreeXclCol; +} + +void XclExpDimensions::SaveXml( XclExpXmlStream& rStrm ) +{ + ScRange aRange; + aRange.aStart.SetRow( static_cast(mnFirstUsedXclRow) ); + aRange.aStart.SetCol( static_cast(mnFirstUsedXclCol) ); + + if( mnFirstFreeXclRow != mnFirstUsedXclRow && mnFirstFreeXclCol != mnFirstUsedXclCol ) + { + aRange.aEnd.SetRow( static_cast(mnFirstFreeXclRow-1) ); + aRange.aEnd.SetCol( static_cast(mnFirstFreeXclCol-1) ); + } + + aRange.PutInOrder(); + rStrm.GetCurrentStream()->singleElement( XML_dimension, + // To be compatible with MS Office 2007, + // we need full address notation format + // e.g. "A1:AMJ177" and not partial like: "1:177". + XML_ref, XclXmlUtils::ToOString(mrRoot.GetDoc(), aRange, true) ); +} + +void XclExpDimensions::WriteBody( XclExpStream& rStrm ) +{ + XclBiff eBiff = rStrm.GetRoot().GetBiff(); + if( eBiff == EXC_BIFF8 ) + rStrm << mnFirstUsedXclRow << mnFirstFreeXclRow; + else + rStrm << static_cast< sal_uInt16 >( mnFirstUsedXclRow ) << static_cast< sal_uInt16 >( mnFirstFreeXclRow ); + rStrm << mnFirstUsedXclCol << mnFirstFreeXclCol; + if( eBiff >= EXC_BIFF3 ) + rStrm << sal_uInt16( 0 ); +} + +namespace { + +double lclGetCChCorrection(const XclExpRoot& rRoot) +{ + // Convert the correction from 1/256ths of a character size to count of chars + // TODO: make to fit ECMA-376-1:2016 18.3.1.81 sheetFormatPr (Sheet Format Properties): + // 5 pixels are added to the base width: 2 for margin padding on each side, plus 1 for gridline + // So this should depend on rRoot.GetCharWidth(), not on font height + + tools::Long nFontHt = rRoot.GetFontBuffer().GetAppFontData().mnHeight; + return XclTools::GetXclDefColWidthCorrection(nFontHt) / 256.0; +} + +} // namespace + +XclExpDefcolwidth::XclExpDefcolwidth( const XclExpRoot& rRoot ) : + XclExpDoubleRecord(EXC_ID_DEFCOLWIDTH, EXC_DEFCOLWIDTH_DEF + lclGetCChCorrection(rRoot)), + XclExpRoot( rRoot ) +{ +} + +bool XclExpDefcolwidth::IsDefWidth( sal_uInt16 nXclColWidth ) const +{ + // This formula is taking number of characters with GetValue() + // and it is translating it into default column width. + // https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.column.aspx + double defaultColumnWidth = 256.0 * GetValue(); + + // exactly matched, if difference is less than 1/16 of a character to the left or to the right + return std::abs(defaultColumnWidth - nXclColWidth) < 256.0 * 1.0 / 16.0; +} + +void XclExpDefcolwidth::SetDefWidth( sal_uInt16 nXclColWidth, bool bXLS ) +{ + double fCCh = nXclColWidth / 256.0; + if (bXLS) + { + const double fCorrection = lclGetCChCorrection(GetRoot()); + const double fCorrectedCCh = fCCh - fCorrection; + // Now get the value which would be stored in XLS DefColWidth struct + double fCChRound = std::round(fCorrectedCCh); + // If default width was set to a value that is not representable as integral CCh between 0 + // and 255, then just ignore that value, and use an arbitrary default. That way, the stored + // default might not represent the most used column width (or any used column width), but + // that's OK, and it just means that those columns will explicitly store their width in + // 1/256ths of char, and have fUserSet in their ColInfo records. + if (fCChRound < 0.0 || fCChRound > 255.0 || std::abs(fCChRound - fCorrectedCCh) > 1.0 / 512) + fCChRound = 8.0; + fCCh = fCChRound + fCorrection; + } + + SetValue(fCCh); +} + +void XclExpDefcolwidth::Save(XclExpStream& rStrm) +{ + double fCorrectedCCh = GetValue() - lclGetCChCorrection(GetRoot()); + // Convert double to sal_uInt16 + XclExpUInt16Record aUInt16Rec(GetRecId(), + static_cast(std::round(fCorrectedCCh))); + aUInt16Rec.Save(rStrm); +} + +XclExpColinfo::XclExpColinfo( const XclExpRoot& rRoot, + SCCOL nScCol, SCROW nLastScRow, XclExpColOutlineBuffer& rOutlineBfr ) : + XclExpRecord( EXC_ID_COLINFO, 12 ), + XclExpRoot( rRoot ), + mbCustomWidth( false ), + mnWidth( 0 ), + mnScWidth( 0 ), + mnFlags( 0 ), + mnOutlineLevel( 0 ), + mnFirstXclCol( static_cast< sal_uInt16 >( nScCol ) ), + mnLastXclCol( static_cast< sal_uInt16 >( nScCol ) ) +{ + ScDocument& rDoc = GetDoc(); + SCTAB nScTab = GetCurrScTab(); + + // column default format + maXFId.mnXFId = GetXFBuffer().Insert( + rDoc.GetMostUsedPattern( nScCol, 0, nLastScRow, nScTab ), GetDefApiScript() ); + + // column width. If column is hidden then we should return real value (not zero) + sal_uInt16 nScWidth = rDoc.GetColWidth( nScCol, nScTab, false ); + mnWidth = XclTools::GetXclColumnWidth( nScWidth, GetCharWidth() ); + mnScWidth = convertTwipToMm100(nScWidth); + + // column flags + ::set_flag( mnFlags, EXC_COLINFO_HIDDEN, rDoc.ColHidden(nScCol, nScTab) ); + + // outline data + rOutlineBfr.Update( nScCol ); + ::set_flag( mnFlags, EXC_COLINFO_COLLAPSED, rOutlineBfr.IsCollapsed() ); + ::insert_value( mnFlags, rOutlineBfr.GetLevel(), 8, 3 ); + mnOutlineLevel = rOutlineBfr.GetLevel(); +} + +void XclExpColinfo::ConvertXFIndexes() +{ + maXFId.ConvertXFIndex( GetRoot() ); +} + +bool XclExpColinfo::IsDefault( const XclExpDefcolwidth& rDefColWidth ) +{ + mbCustomWidth = !rDefColWidth.IsDefWidth(mnWidth); + return (maXFId.mnXFIndex == EXC_XF_DEFAULTCELL) && + (mnFlags == 0) && + (mnOutlineLevel == 0) && + !mbCustomWidth; +} + +bool XclExpColinfo::TryMerge( const XclExpColinfo& rColInfo ) +{ + if( (maXFId.mnXFIndex == rColInfo.maXFId.mnXFIndex) && + (mnWidth == rColInfo.mnWidth) && + (mnFlags == rColInfo.mnFlags) && + (mnOutlineLevel == rColInfo.mnOutlineLevel) && + (mnLastXclCol + 1 == rColInfo.mnFirstXclCol) ) + { + mnLastXclCol = rColInfo.mnLastXclCol; + return true; + } + return false; +} + +void XclExpColinfo::WriteBody( XclExpStream& rStrm ) +{ + // if last column is equal to last possible column, Excel adds one more + sal_uInt16 nLastXclCol = mnLastXclCol; + if( nLastXclCol == static_cast< sal_uInt16 >( rStrm.GetRoot().GetMaxPos().Col() ) ) + ++nLastXclCol; + + rStrm << mnFirstXclCol + << nLastXclCol + << mnWidth + << maXFId.mnXFIndex + << mnFlags + << sal_uInt16( 0 ); +} + +void XclExpColinfo::SaveXml( XclExpXmlStream& rStrm ) +{ + const double nExcelColumnWidth = mnScWidth / convertTwipToMm100(GetCharWidth()); + + // tdf#101363 In MS specification the output value is set with double precision after delimiter: + // =Truncate(({width in pixels} - 5)/{Maximum Digit Width} * 100 + 0.5)/100 + // Explanation of magic numbers: + // 5 number - are 4 pixels of margin padding (two on each side), plus 1 pixel padding for the gridlines. + // It is unknown if it should be applied during LibreOffice export + // 100 number - used to limit precision to 0.01 with formula =Truncate( {value}*100+0.5 ) / 100 + // 0.5 number (0.005 to output value) - used to increase value before truncating, + // to avoid situation when 2.997 will be truncated to 2.99 and not to 3.00 + const double nTruncatedExcelColumnWidth = std::trunc( nExcelColumnWidth * 100.0 + 0.5 ) / 100.0; + rStrm.GetCurrentStream()->singleElement( XML_col, + // OOXTODO: XML_bestFit, + XML_collapsed, ToPsz( ::get_flag( mnFlags, EXC_COLINFO_COLLAPSED ) ), + XML_customWidth, ToPsz( mbCustomWidth ), + XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_COLINFO_HIDDEN ) ), + XML_outlineLevel, OString::number(mnOutlineLevel), + XML_max, OString::number(mnLastXclCol + 1), + XML_min, OString::number(mnFirstXclCol + 1), + // OOXTODO: XML_phonetic, + XML_style, lcl_GetStyleId(rStrm, maXFId.mnXFIndex), + XML_width, OString::number(nTruncatedExcelColumnWidth) ); +} + +XclExpColinfoBuffer::XclExpColinfoBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + maDefcolwidth( rRoot ), + maOutlineBfr( rRoot ), + mnHighestOutlineLevel( 0 ) +{ +} + +void XclExpColinfoBuffer::Initialize( SCROW nLastScRow ) +{ + + for( sal_uInt16 nScCol = 0, nLastScCol = GetMaxPos().Col(); nScCol <= nLastScCol; ++nScCol ) + { + maColInfos.AppendNewRecord( new XclExpColinfo( GetRoot(), nScCol, nLastScRow, maOutlineBfr ) ); + if( maOutlineBfr.GetLevel() > mnHighestOutlineLevel ) + { + mnHighestOutlineLevel = maOutlineBfr.GetLevel(); + } + } +} + +void XclExpColinfoBuffer::Finalize( ScfUInt16Vec& rXFIndexes, bool bXLS ) +{ + rXFIndexes.clear(); + rXFIndexes.reserve( maColInfos.GetSize() ); + + if( !maColInfos.IsEmpty()) + { + XclExpColinfo* xPrevRec = maColInfos.GetRecord( 0 ); + xPrevRec->ConvertXFIndexes(); + for( size_t nPos = 1; nPos < maColInfos.GetSize(); ++nPos ) + { + XclExpColinfo* xRec = maColInfos.GetRecord( nPos ); + xRec->ConvertXFIndexes(); + + // try to merge with previous record + if( xPrevRec->TryMerge( *xRec ) ) + maColInfos.InvalidateRecord( nPos ); + else + xPrevRec = xRec; + } + maColInfos.RemoveInvalidatedRecords(); + } + + // put XF indexes into passed vector, collect use count of all different widths + std::unordered_map< sal_uInt16, sal_uInt16 > aWidthMap; + sal_uInt16 nMaxColCount = 0; + sal_uInt16 nMaxUsedWidth = 0; + for( size_t nPos = 0; nPos < maColInfos.GetSize(); ++nPos ) + { + const XclExpColinfo* xRec = maColInfos.GetRecord( nPos ); + sal_uInt16 nColCount = xRec->GetColCount(); + + // add XF index to passed vector + rXFIndexes.resize( rXFIndexes.size() + nColCount, xRec->GetXFIndex() ); + + // collect use count of column width + sal_uInt16 nWidth = xRec->GetColWidth(); + sal_uInt16& rnMapCount = aWidthMap[ nWidth ]; + rnMapCount = rnMapCount + nColCount; + if( rnMapCount > nMaxColCount ) + { + nMaxColCount = rnMapCount; + nMaxUsedWidth = nWidth; + } + } + maDefcolwidth.SetDefWidth( nMaxUsedWidth, bXLS ); + + // remove all default COLINFO records + for( size_t nPos = 0; nPos < maColInfos.GetSize(); ++nPos ) + { + XclExpColinfo* xRec = maColInfos.GetRecord( nPos ); + if( xRec->IsDefault( maDefcolwidth ) ) + maColInfos.InvalidateRecord( nPos ); + } + maColInfos.RemoveInvalidatedRecords(); +} + +void XclExpColinfoBuffer::Save( XclExpStream& rStrm ) +{ + // DEFCOLWIDTH + maDefcolwidth.Save( rStrm ); + // COLINFO records + maColInfos.Save( rStrm ); +} + +void XclExpColinfoBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + if( maColInfos.IsEmpty() ) + return; + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_cols); + maColInfos.SaveXml( rStrm ); + rWorksheet->endElement( XML_cols ); +} + +XclExpDefaultRowData::XclExpDefaultRowData() : + mnFlags( EXC_DEFROW_DEFAULTFLAGS ), + mnHeight( EXC_DEFROW_DEFAULTHEIGHT ) +{ +} + +XclExpDefaultRowData::XclExpDefaultRowData( const XclExpRow& rRow ) : + mnFlags( EXC_DEFROW_DEFAULTFLAGS ), + mnHeight( rRow.GetHeight() ) +{ + ::set_flag( mnFlags, EXC_DEFROW_HIDDEN, rRow.IsHidden() ); + ::set_flag( mnFlags, EXC_DEFROW_UNSYNCED, rRow.IsUnsynced() ); +} + +static bool operator<( const XclExpDefaultRowData& rLeft, const XclExpDefaultRowData& rRight ) +{ + return (rLeft.mnHeight < rRight.mnHeight) || + ((rLeft.mnHeight == rRight.mnHeight) && (rLeft.mnFlags < rRight.mnFlags)); +} + +XclExpDefrowheight::XclExpDefrowheight() : + XclExpRecord( EXC_ID3_DEFROWHEIGHT, 4 ) +{ +} + +void XclExpDefrowheight::SetDefaultData( const XclExpDefaultRowData& rDefData ) +{ + maDefData = rDefData; +} + +void XclExpDefrowheight::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF3 ); + rStrm << maDefData.mnFlags << maDefData.mnHeight; +} + +XclExpRow::XclExpRow( const XclExpRoot& rRoot, sal_uInt32 nXclRow, + XclExpRowOutlineBuffer& rOutlineBfr, bool bAlwaysEmpty, bool bHidden, sal_uInt16 nHeight ) : + XclExpRecord( EXC_ID3_ROW, 16 ), + XclExpRoot( rRoot ), + mnXclRow( nXclRow ), + mnHeight( nHeight ), + mnFlags( EXC_ROW_DEFAULTFLAGS ), + mnXFIndex( EXC_XF_DEFAULTCELL ), + mnOutlineLevel( 0 ), + mnXclRowRpt( 1 ), + mnCurrentRow( nXclRow ), + mbAlwaysEmpty( bAlwaysEmpty ), + mbEnabled( true ) +{ + SCTAB nScTab = GetCurrScTab(); + SCROW nScRow = static_cast< SCROW >( mnXclRow ); + + // *** Row flags *** ------------------------------------------------------ + + CRFlags nRowFlags = GetDoc().GetRowFlags( nScRow, nScTab ); + bool bUserHeight( nRowFlags & CRFlags::ManualSize ); + ::set_flag( mnFlags, EXC_ROW_UNSYNCED, bUserHeight ); + ::set_flag( mnFlags, EXC_ROW_HIDDEN, bHidden ); + + // *** Outline data *** --------------------------------------------------- + + rOutlineBfr.Update( nScRow ); + ::set_flag( mnFlags, EXC_ROW_COLLAPSED, rOutlineBfr.IsCollapsed() ); + ::insert_value( mnFlags, rOutlineBfr.GetLevel(), 0, 3 ); + mnOutlineLevel = rOutlineBfr.GetLevel(); + + // *** Progress bar *** --------------------------------------------------- + + XclExpProgressBar& rProgress = GetProgressBar(); + rProgress.IncRowRecordCount(); + rProgress.Progress(); +} + +static size_t findFirstAllSameUntilEnd( const ScfUInt16Vec& rIndexes, sal_uInt16 value, + size_t searchStart = std::numeric_limits::max()) +{ + for( size_t i = std::min(rIndexes.size(), searchStart); i >= 1; --i ) + { + if( rIndexes[ i - 1 ] != value ) + return i; + } + return 0; +} + +void XclExpRow::AppendCell( XclExpCellRef const & xCell, bool bIsMergedBase ) +{ + OSL_ENSURE( !mbAlwaysEmpty, "XclExpRow::AppendCell - row is marked to be always empty" ); + // try to merge with last existing cell + InsertCell( xCell, maCellList.GetSize(), bIsMergedBase ); +} + +void XclExpRow::Finalize( const ScfUInt16Vec& rColXFIndexes, ScfUInt16Vec& aXFIndexes, size_t nStartColAllDefault, bool bProgress ) +{ + size_t nPos, nSize; + + // *** Convert XF identifiers *** ----------------------------------------- + + // additionally collect the blank XF indexes + size_t nColCount = GetMaxPos().Col() + 1; + OSL_ENSURE( rColXFIndexes.size() == nColCount, "XclExpRow::Finalize - wrong column XF index count" ); + + // The vector should be preset to all items being EXC_XF_NOTFOUND, to avoid repeated allocations + // and clearing. + assert( aXFIndexes.size() == nColCount ); + assert( aXFIndexes.front() == EXC_XF_NOTFOUND ); + assert( aXFIndexes.back() == EXC_XF_NOTFOUND ); + for( nPos = 0, nSize = maCellList.GetSize(); nPos < nSize; ++nPos ) + { + XclExpCellBase* pCell = maCellList.GetRecord( nPos ); + pCell->ConvertXFIndexes( GetRoot() ); + pCell->GetBlankXFIndexes( aXFIndexes ); + } + + // *** Fill gaps with BLANK/MULBLANK cell records *** --------------------- + + /* This is needed because nonexistent cells in Calc are not formatted at all, + but in Excel they would have the column default format. Blank cells that + are equal to the respective column default are removed later in this function. */ + if( !mbAlwaysEmpty ) + { + // XF identifier representing default cell XF + XclExpMultiXFId aXFId( XclExpXFBuffer::GetDefCellXFId() ); + aXFId.ConvertXFIndex( GetRoot() ); + + nPos = 0; + while( nPos <= maCellList.GetSize() ) // don't cache list size, may change in the loop + { + // get column index that follows previous cell + sal_uInt16 nFirstFreeXclCol = (nPos > 0) ? (maCellList.GetRecord( nPos - 1 )->GetLastXclCol() + 1) : 0; + // get own column index + sal_uInt16 nNextUsedXclCol = (nPos < maCellList.GetSize()) ? maCellList.GetRecord( nPos )->GetXclCol() : (GetMaxPos().Col() + 1); + + // is there a gap? + if( nFirstFreeXclCol < nNextUsedXclCol ) + { + aXFId.mnCount = nNextUsedXclCol - nFirstFreeXclCol; + XclExpCellRef xNewCell = new XclExpBlankCell( XclAddress( nFirstFreeXclCol, mnXclRow ), aXFId ); + // insert the cell, InsertCell() may merge it with existing BLANK records + InsertCell( xNewCell, nPos, false ); + // insert default XF indexes into aXFIndexes + for( size_t i = nFirstFreeXclCol; i < nNextUsedXclCol; ++i ) + aXFIndexes[ i ] = aXFId.mnXFIndex; + // don't step forward with nPos, InsertCell() may remove records + } + else + ++nPos; + } + } + + // *** Find default row format *** ---------------------------------------- + + // Often there will be many EXC_XF_DEFAULTCELL at the end, optimize by ignoring them. + size_t nStartSearchAllDefault = aXFIndexes.size(); + if( !maCellList.IsEmpty() && dynamic_cast< const XclExpBlankCell* >( maCellList.GetLastRecord())) + { + const XclExpBlankCell* pLastBlank = static_cast< const XclExpBlankCell* >( maCellList.GetLastRecord()); + assert(pLastBlank->GetLastXclCol() == aXFIndexes.size() - 1); + nStartSearchAllDefault = pLastBlank->GetStartColAllDefaultCell(); + } + size_t nStartAllDefault = findFirstAllSameUntilEnd( aXFIndexes, EXC_XF_DEFAULTCELL, nStartSearchAllDefault); + + // find most used XF index in the row + sal_uInt16 nRowXFIndex = EXC_XF_DEFAULTCELL; + const size_t nHalfIndexes = aXFIndexes.size() / 2; + if( nStartAllDefault > nHalfIndexes ) // Otherwise most are EXC_XF_DEFAULTCELL. + { + // Very likely the most common one is going to be the last one. + nRowXFIndex = aXFIndexes.back(); + size_t nStartLastSame = findFirstAllSameUntilEnd( aXFIndexes, nRowXFIndex ); + if( nStartLastSame > nHalfIndexes ) // No, find out the most used one by counting. + { + std::unordered_map< sal_uInt16, size_t > aIndexMap; + size_t nMaxXFCount = 0; + for( const auto& rXFIndex : aXFIndexes ) + { + if( rXFIndex != EXC_XF_NOTFOUND ) + { + size_t& rnCount = aIndexMap[ rXFIndex ]; + ++rnCount; + if( rnCount > nMaxXFCount ) + { + nRowXFIndex = rXFIndex; + nMaxXFCount = rnCount; + if (nMaxXFCount > nHalfIndexes) + { + // No other XF index can have a greater usage count, we + // don't need to loop through the remaining cells. + // Specifically for the tail of unused default + // cells/columns this makes a difference. + break; // for + } + } + } + } + } + } + + // decide whether to use the row default XF index or column default XF indexes + bool bUseColDefXFs = nRowXFIndex == EXC_XF_DEFAULTCELL; + if( !bUseColDefXFs ) + { + // count needed XF indexes for blank cells with and without row default XF index + size_t nXFCountWithRowDefXF = 0; + size_t nXFCountWithoutRowDefXF = 0; + ScfUInt16Vec::const_iterator aColIt = rColXFIndexes.begin(); + for( const auto& rXFIndex : aXFIndexes ) + { + sal_uInt16 nXFIndex = rXFIndex; + if( nXFIndex != EXC_XF_NOTFOUND ) + { + if( nXFIndex != nRowXFIndex ) + ++nXFCountWithRowDefXF; // with row default XF index + if( nXFIndex != *aColIt ) + ++nXFCountWithoutRowDefXF; // without row default XF index + } + ++aColIt; + } + + // use column XF indexes if this would cause less or equal number of BLANK records + bUseColDefXFs = nXFCountWithoutRowDefXF <= nXFCountWithRowDefXF; + } + + // *** Remove unused BLANK cell records *** ------------------------------- + + size_t maxStartAllNotFound; + if( bUseColDefXFs ) + { + size_t maxStartAllDefault = std::max( nStartAllDefault, nStartColAllDefault ); + // use column default XF indexes + // #i194#: remove cell XF indexes equal to column default XF indexes + for( size_t i = 0; i < maxStartAllDefault; ++i ) + { + if( aXFIndexes[ i ] == rColXFIndexes[ i ] ) + aXFIndexes[ i ] = EXC_XF_NOTFOUND; + } + // They can differ only up to maxNonDefault, in the rest they are the same. + for( size_t i = maxStartAllDefault; i < aXFIndexes.size(); ++i ) + aXFIndexes[ i ] = EXC_XF_NOTFOUND; + maxStartAllNotFound = maxStartAllDefault; + } + else + { + // use row default XF index + mnXFIndex = nRowXFIndex; + ::set_flag( mnFlags, EXC_ROW_USEDEFXF ); + // #98133#, #i194#, #i27407#: remove cell XF indexes equal to row default XF index + for( auto& rXFIndex : aXFIndexes ) + if( rXFIndex == nRowXFIndex ) + rXFIndex = EXC_XF_NOTFOUND; + maxStartAllNotFound = aXFIndexes.size(); + } + + // remove unused parts of BLANK/MULBLANK cell records + size_t nStartAllNotFound = findFirstAllSameUntilEnd( aXFIndexes, EXC_XF_NOTFOUND, maxStartAllNotFound ); + nPos = 0; + while( nPos < maCellList.GetSize() ) // do not cache list size, may change in the loop + { + XclExpCellBase* xCell = maCellList.GetRecord( nPos ); + xCell->RemoveUnusedBlankCells( aXFIndexes, nStartAllNotFound ); + if( xCell->IsEmpty() ) + maCellList.RemoveRecord( nPos ); + else + ++nPos; + } + // Ensure it's all EXC_XF_NOTFOUND again for next reuse. + for( size_t i = 0; i < nStartAllNotFound; ++i ) + aXFIndexes[ i ] = EXC_XF_NOTFOUND; + + // progress bar includes disabled rows; only update it in the lead thread. + if (bProgress) + GetProgressBar().Progress(); +} +sal_uInt16 XclExpRow::GetFirstUsedXclCol() const +{ + return maCellList.IsEmpty() ? 0 : maCellList.GetFirstRecord()->GetXclCol(); +} + +sal_uInt16 XclExpRow::GetFirstFreeXclCol() const +{ + return maCellList.IsEmpty() ? 0 : (maCellList.GetLastRecord()->GetLastXclCol() + 1); +} + +bool XclExpRow::IsDefaultable() const +{ + const sal_uInt16 nFlagsAlwaysMarkedAsDefault = EXC_ROW_DEFAULTFLAGS | EXC_ROW_HIDDEN | EXC_ROW_UNSYNCED; + return !::get_flag( mnFlags, static_cast< sal_uInt16 >( ~nFlagsAlwaysMarkedAsDefault ) ) && + IsEmpty(); +} + +void XclExpRow::DisableIfDefault( const XclExpDefaultRowData& rDefRowData ) +{ + mbEnabled = !IsDefaultable() || + (mnHeight != rDefRowData.mnHeight) || + (IsHidden() != rDefRowData.IsHidden()) || + (IsUnsynced() != rDefRowData.IsUnsynced()); +} + +void XclExpRow::WriteCellList( XclExpStream& rStrm ) +{ + OSL_ENSURE( mbEnabled || maCellList.IsEmpty(), "XclExpRow::WriteCellList - cells in disabled row" ); + maCellList.Save( rStrm ); +} + +void XclExpRow::Save( XclExpStream& rStrm ) +{ + if( mbEnabled ) + { + mnCurrentRow = mnXclRow; + for ( sal_uInt32 i = 0; i < mnXclRowRpt; ++i, ++mnCurrentRow ) + XclExpRecord::Save( rStrm ); + } +} + +void XclExpRow::InsertCell( XclExpCellRef xCell, size_t nPos, bool bIsMergedBase ) +{ + OSL_ENSURE( xCell, "XclExpRow::InsertCell - missing cell" ); + + /* If we have a multi-line text in a merged cell, and the resulting + row height has not been confirmed, we need to force the EXC_ROW_UNSYNCED + flag to be true to ensure Excel works correctly. */ + if( bIsMergedBase && xCell->IsMultiLineText() ) + ::set_flag( mnFlags, EXC_ROW_UNSYNCED ); + + // try to merge with previous cell, insert the new cell if not successful + XclExpCellBase* xPrevCell = maCellList.GetRecord( nPos - 1 ); + if( xPrevCell && xPrevCell->TryMerge( *xCell ) ) + xCell = xPrevCell; + else + maCellList.InsertRecord( xCell, nPos++ ); + // nPos points now to following cell + + // try to merge with following cell, remove it if successful + XclExpCellRef xNextCell = maCellList.GetRecord( nPos ); + if( xNextCell && xCell->TryMerge( *xNextCell ) ) + maCellList.RemoveRecord( nPos ); +} + +void XclExpRow::WriteBody( XclExpStream& rStrm ) +{ + rStrm << static_cast< sal_uInt16 >(mnCurrentRow) + << GetFirstUsedXclCol() + << GetFirstFreeXclCol() + << mnHeight + << sal_uInt32( 0 ) + << mnFlags + << mnXFIndex; +} + +void XclExpRow::SaveXml( XclExpXmlStream& rStrm ) +{ + if( !mbEnabled ) + return; + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + bool haveFormat = ::get_flag( mnFlags, EXC_ROW_USEDEFXF ); + mnCurrentRow = mnXclRow + 1; + for ( sal_uInt32 i=0; istartElement( XML_row, + XML_r, OString::number(mnCurrentRow++), + // OOXTODO: XML_spans, optional + XML_s, haveFormat ? lcl_GetStyleId( rStrm, mnXFIndex ).getStr() : nullptr, + XML_customFormat, ToPsz( haveFormat ), + XML_ht, OString::number(static_cast(mnHeight) / 20.0), + XML_hidden, ToPsz( ::get_flag( mnFlags, EXC_ROW_HIDDEN ) ), + XML_customHeight, ToPsz( ::get_flag( mnFlags, EXC_ROW_UNSYNCED ) ), + XML_outlineLevel, OString::number(mnOutlineLevel), + XML_collapsed, ToPsz( ::get_flag( mnFlags, EXC_ROW_COLLAPSED ) ) + // OOXTODO: XML_thickTop, bool + // OOXTODO: XML_thickBot, bool + // OOXTODO: XML_ph, bool + ); + // OOXTODO: XML_extLst + maCellList.SaveXml( rStrm ); + rWorksheet->endElement( XML_row ); + } +} + +XclExpRowBuffer::XclExpRowBuffer( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + maOutlineBfr( rRoot ), + maDimensions( rRoot ), + mnHighestOutlineLevel( 0 ) +{ +} + +void XclExpRowBuffer::AppendCell( XclExpCellRef const & xCell, bool bIsMergedBase ) +{ + OSL_ENSURE( xCell, "XclExpRowBuffer::AppendCell - missing cell" ); + GetOrCreateRow( xCell->GetXclRow(), false ).AppendCell( xCell, bIsMergedBase ); +} + +void XclExpRowBuffer::CreateRows( SCROW nFirstFreeScRow ) +{ + if( nFirstFreeScRow > 0 ) + GetOrCreateRow( ::std::max ( nFirstFreeScRow - 1, GetMaxPos().Row() ), true ); +} + +namespace { + +class RowFinalizeTask : public comphelper::ThreadTask +{ + bool mbProgress; + const ScfUInt16Vec& mrColXFIndexes; + size_t mnStartColAllDefault; + std::vector< XclExpRow * > maRows; +public: + RowFinalizeTask( const std::shared_ptr & pTag, + const ScfUInt16Vec& rColXFIndexes, + size_t nStartColAllDefault, + bool bProgress ) : + comphelper::ThreadTask( pTag ), + mbProgress( bProgress ), + mrColXFIndexes( rColXFIndexes ), + mnStartColAllDefault( nStartColAllDefault ) + {} + + void push_back( XclExpRow *pRow ) { maRows.push_back( pRow ); } + virtual void doWork() override + { + ScfUInt16Vec aXFIndexes( mrColXFIndexes.size(), EXC_XF_NOTFOUND ); + for (XclExpRow* p : maRows) + p->Finalize( mrColXFIndexes, aXFIndexes, mnStartColAllDefault, mbProgress ); + } +}; + +} + +void XclExpRowBuffer::Finalize( XclExpDefaultRowData& rDefRowData, + const ScfUInt16Vec& rColXFIndexes, + size_t nStartColAllDefault ) +{ + // *** Finalize all rows *** ---------------------------------------------- + + GetProgressBar().ActivateFinalRowsSegment(); + +#if 1 + // This is staggeringly slow, and each element operates only + // on its own data. + const size_t nRows = maRowMap.size(); + const size_t nThreads = nRows < 128 ? 1 : comphelper::ThreadPool::getPreferredConcurrency(); +#else + const size_t nThreads = 1; // globally disable multi-threading for now. +#endif + if (nThreads == 1) + { + ScfUInt16Vec aXFIndexes( rColXFIndexes.size(), EXC_XF_NOTFOUND ); + for (auto& rEntry : maRowMap) + rEntry.second->Finalize( rColXFIndexes, aXFIndexes, nStartColAllDefault, true ); + } + else + { + comphelper::ThreadPool &rPool = comphelper::ThreadPool::getSharedOptimalPool(); + std::shared_ptr pTag = comphelper::ThreadPool::createThreadTaskTag(); + std::vector> aTasks(nThreads); + for ( size_t i = 0; i < nThreads; i++ ) + aTasks[ i ].reset( new RowFinalizeTask( pTag, rColXFIndexes, nStartColAllDefault, i == 0 ) ); + + size_t nIdx = 0; + for ( const auto& rEntry : maRowMap ) + { + aTasks[ nIdx % nThreads ]->push_back( rEntry.second.get() ); + ++nIdx; + } + + for ( size_t i = 1; i < nThreads; i++ ) + rPool.pushTask( std::move(aTasks[ i ]) ); + + // Progress bar updates must be synchronous to avoid deadlock + aTasks[0]->doWork(); + + rPool.waitUntilDone(pTag); + } + + // *** Default row format *** --------------------------------------------- + + std::map< XclExpDefaultRowData, size_t > aDefRowMap; + + XclExpDefaultRowData aMaxDefData; + size_t nMaxDefCount = 0; + // only look for default format in existing rows, if there are more than unused + // if the row is hidden, then row xml must be created even if it not contain cells + XclExpRow* pPrev = nullptr; + std::vector< XclExpRow* > aRepeated; + for (const auto& rEntry : maRowMap) + { + const RowRef& rRow = rEntry.second; + if ( rRow->IsDefaultable() ) + { + XclExpDefaultRowData aDefData( *rRow ); + size_t& rnDefCount = aDefRowMap[ aDefData ]; + ++rnDefCount; + if( rnDefCount > nMaxDefCount ) + { + nMaxDefCount = rnDefCount; + aMaxDefData = aDefData; + } + } + if ( pPrev ) + { + if ( pPrev->IsDefaultable() ) + { + // if the previous row we processed is not + // defaultable then afaict the rows in between are + // not used ( and not repeatable ) + sal_uInt32 nRpt = rRow->GetXclRow() - pPrev->GetXclRow(); + if ( nRpt > 1 ) + aRepeated.push_back( pPrev ); + pPrev->SetXclRowRpt( nRpt ); + XclExpDefaultRowData aDefData( *pPrev ); + size_t& rnDefCount = aDefRowMap[ aDefData ]; + rnDefCount += ( pPrev->GetXclRowRpt() - 1 ); + if( rnDefCount > nMaxDefCount ) + { + nMaxDefCount = rnDefCount; + aMaxDefData = aDefData; + } + } + } + pPrev = rRow.get(); + } + // return the default row format to caller + rDefRowData = aMaxDefData; + + // now disable repeating extra (empty) rows that are equal to the default row + for (auto& rpRow : aRepeated) + { + if ( rpRow->GetXclRowRpt() > 1 + && rpRow->GetHeight() == rDefRowData.mnHeight + && rpRow->IsHidden() == rDefRowData.IsHidden() ) + { + rpRow->SetXclRowRpt( 1 ); + } + } + + // *** Disable unused ROW records, find used area *** --------------------- + + sal_uInt16 nFirstUsedXclCol = SAL_MAX_UINT16; + sal_uInt16 nFirstFreeXclCol = 0; + sal_uInt32 nFirstUsedXclRow = SAL_MAX_UINT32; + sal_uInt32 nFirstFreeXclRow = 0; + + for (const auto& rEntry : maRowMap) + { + const RowRef& rRow = rEntry.second; + // disable unused rows + rRow->DisableIfDefault( aMaxDefData ); + + // find used column range + if( !rRow->IsEmpty() ) // empty rows return (0...0) as used range + { + nFirstUsedXclCol = ::std::min( nFirstUsedXclCol, rRow->GetFirstUsedXclCol() ); + nFirstFreeXclCol = ::std::max( nFirstFreeXclCol, rRow->GetFirstFreeXclCol() ); + } + + // find used row range + if( rRow->IsEnabled() ) + { + sal_uInt32 nXclRow = rRow->GetXclRow(); + nFirstUsedXclRow = ::std::min< sal_uInt32 >( nFirstUsedXclRow, nXclRow ); + nFirstFreeXclRow = ::std::max< sal_uInt32 >( nFirstFreeXclRow, nXclRow + 1 ); + } + } + + // adjust start position, if there are no or only empty/disabled ROW records + nFirstUsedXclCol = ::std::min( nFirstUsedXclCol, nFirstFreeXclCol ); + nFirstUsedXclRow = ::std::min( nFirstUsedXclRow, nFirstFreeXclRow ); + + // initialize the DIMENSIONS record + maDimensions.SetDimensions( + nFirstUsedXclCol, nFirstUsedXclRow, nFirstFreeXclCol, nFirstFreeXclRow ); +} + +void XclExpRowBuffer::Save( XclExpStream& rStrm ) +{ + // DIMENSIONS record + maDimensions.Save( rStrm ); + + // save in blocks of 32 rows, each block contains first all ROWs, then all cells + size_t nSize = maRowMap.size(); + RowMap::iterator itr = maRowMap.begin(), itrEnd = maRowMap.end(); + RowMap::iterator itrBlkStart = maRowMap.begin(), itrBlkEnd = maRowMap.begin(); + sal_uInt16 nStartXclRow = (nSize == 0) ? 0 : itr->second->GetXclRow(); + + for (; itr != itrEnd; ++itr) + { + // find end of row block + itrBlkEnd = std::find_if_not(itrBlkEnd, itrEnd, + [&nStartXclRow](const RowMap::value_type& rRow) { return rRow.second->GetXclRow() - nStartXclRow < EXC_ROW_ROWBLOCKSIZE; }); + + // write the ROW records + std::for_each(itrBlkStart, itrBlkEnd, [&rStrm](const RowMap::value_type& rRow) { rRow.second->Save( rStrm ); }); + + // write the cell records + std::for_each(itrBlkStart, itrBlkEnd, [&rStrm](const RowMap::value_type& rRow) { rRow.second->WriteCellList( rStrm ); }); + + itrBlkStart = (itrBlkEnd == itrEnd) ? itrBlkEnd : itrBlkEnd++; + nStartXclRow += EXC_ROW_ROWBLOCKSIZE; + } +} + +void XclExpRowBuffer::SaveXml( XclExpXmlStream& rStrm ) +{ + if (std::none_of(maRowMap.begin(), maRowMap.end(), [](const RowMap::value_type& rRow) { return rRow.second->IsEnabled(); })) + { + rStrm.GetCurrentStream()->singleElement(XML_sheetData); + return; + } + + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_sheetData); + for (const auto& rEntry : maRowMap) + rEntry.second->SaveXml(rStrm); + rWorksheet->endElement( XML_sheetData ); +} + +XclExpRow& XclExpRowBuffer::GetOrCreateRow( sal_uInt32 nXclRow, bool bRowAlwaysEmpty ) +{ + // This is called rather often, so optimize for the most common case of saving row by row + // (so the requested row is often the last one in the map or belongs after the last one). + RowMap::iterator itr; + if(maRowMap.empty()) + itr = maRowMap.end(); + else + { + RowMap::reverse_iterator last = maRowMap.rbegin(); + if( last->first == nXclRow ) + return *last->second; + if( nXclRow > last->first ) + itr = maRowMap.end(); + else + itr = maRowMap.lower_bound( nXclRow ); + } + const bool bFound = itr != maRowMap.end(); + // bFoundHigher: nXclRow was identical to the previous entry, so not explicitly created earlier + // coverity[deref_iterator : FALSE] - clearly itr if only derefed if bFound which checks for valid itr + const bool bFoundHigher = bFound && itr->first != nXclRow; + if( bFound && !bFoundHigher ) + return *itr->second; + + size_t nFrom = 0; + RowRef pPrevEntry; + if( itr != maRowMap.begin() ) + { + --itr; + pPrevEntry = itr->second; + if( bFoundHigher ) + nFrom = nXclRow; + else + nFrom = itr->first + 1; + } + + const ScDocument& rDoc = GetRoot().GetDoc(); + const SCTAB nScTab = GetRoot().GetCurrScTab(); + // Do not repeatedly call RowHidden() / GetRowHeight() for same values. + bool bHidden = false; + SCROW lastSameHiddenRow = -1; + sal_uInt16 nHeight = 0; + SCROW lastSameHeightRow = -1; + // create the missing rows first + while( nFrom <= nXclRow ) + { + // only create RowMap entries if it is first row in spreadsheet, + // if it is the desired row, or for rows that differ from previous. + if( static_cast(nFrom) > lastSameHiddenRow ) + bHidden = rDoc.RowHidden(nFrom, nScTab, nullptr, &lastSameHiddenRow); + // Always get the actual row height even if the manual size flag is + // not set, to correctly export the heights of rows with wrapped + // texts. + if( static_cast(nFrom) > lastSameHeightRow ) + nHeight = rDoc.GetRowHeight(nFrom, nScTab, nullptr, &lastSameHeightRow, false); + if ( !pPrevEntry || ( nFrom == nXclRow ) || + ( maOutlineBfr.IsCollapsed() ) || + ( maOutlineBfr.GetLevel() != 0 ) || + ( bRowAlwaysEmpty && !pPrevEntry->IsEmpty() ) || + ( bHidden != pPrevEntry->IsHidden() ) || + ( nHeight != pPrevEntry->GetHeight() ) ) + { + if( maOutlineBfr.GetLevel() > mnHighestOutlineLevel ) + { + mnHighestOutlineLevel = maOutlineBfr.GetLevel(); + } + RowRef p = std::make_shared(GetRoot(), nFrom, maOutlineBfr, bRowAlwaysEmpty, bHidden, nHeight); + maRowMap.emplace(nFrom, p); + pPrevEntry = p; + } + ++nFrom; + } + itr = maRowMap.find(nXclRow); + return *itr->second; +} + +// Cell Table + +XclExpCellTable::XclExpCellTable( const XclExpRoot& rRoot ) : + XclExpRoot( rRoot ), + maColInfoBfr( rRoot ), + maRowBfr( rRoot ), + maArrayBfr( rRoot ), + maShrfmlaBfr( rRoot ), + maTableopBfr( rRoot ), + mxDefrowheight( new XclExpDefrowheight() ), + mxGuts( new XclExpGuts( rRoot ) ), + mxNoteList( new XclExpNoteList ), + mxMergedcells( new XclExpMergedcells( rRoot ) ), + mxHyperlinkList( new XclExpHyperlinkList ), + mxDval( new XclExpDval( rRoot ) ), + mxExtLst( new XclExtLst( rRoot ) ) +{ + ScDocument& rDoc = GetDoc(); + SCTAB nScTab = GetCurrScTab(); + SvNumberFormatter& rFormatter = GetFormatter(); + + // maximum sheet limits + SCCOL nMaxScCol = GetMaxPos().Col(); + SCROW nMaxScRow = GetMaxPos().Row(); + + // find used area (non-empty cells) + SCCOL nLastUsedScCol; + SCROW nLastUsedScRow; + rDoc.GetTableArea( nScTab, nLastUsedScCol, nLastUsedScRow ); + + if(nLastUsedScCol > nMaxScCol) + nLastUsedScCol = nMaxScCol; + + // check extra blank rows to avoid of losing their not default settings (workaround for tdf#41425) + nLastUsedScRow += 1000; + + if(nLastUsedScRow > nMaxScRow) + nLastUsedScRow = nMaxScRow; + + ScRange aUsedRange( 0, 0, nScTab, nLastUsedScCol, nLastUsedScRow, nScTab ); + GetAddressConverter().ValidateRange( aUsedRange, true ); + nLastUsedScRow = aUsedRange.aEnd.Row(); + + // first row without any set attributes (height/hidden/...) + SCROW nFirstUnflaggedScRow = rDoc.GetLastFlaggedRow( nScTab ) + 1; + + // find range of outlines + SCROW nFirstUngroupedScRow = 0; + if( const ScOutlineTable* pOutlineTable = rDoc.GetOutlineTable( nScTab ) ) + { + SCCOLROW nScStartPos, nScEndPos; + const ScOutlineArray& rRowArray = pOutlineTable->GetRowArray(); + rRowArray.GetRange( nScStartPos, nScEndPos ); + // +1 because open/close button is in next row in Excel, +1 for "end->first unused" + nFirstUngroupedScRow = static_cast< SCROW >( nScEndPos + 2 ); + } + + // column settings + /* #i30411# Files saved with SO7/OOo1.x with nonstandard default column + formatting cause big Excel files, because all rows from row 1 to row + 32000 are exported. Now, if the used area goes exactly to row 32000, + use this row as default and ignore all rows >32000. + #i59220# Tolerance of +-128 rows for inserted/removed rows. */ + if( (31871 <= nLastUsedScRow) && (nLastUsedScRow <= 32127) && (nFirstUnflaggedScRow < nLastUsedScRow) && (nFirstUngroupedScRow <= nLastUsedScRow) ) + nMaxScRow = nLastUsedScRow; + maColInfoBfr.Initialize( nMaxScRow ); + + // range for cell iterator + SCCOL nLastIterScCol = nMaxScCol; + SCROW nLastIterScRow = ulimit_cast< SCROW >( nLastUsedScRow, nMaxScRow ); + ScUsedAreaIterator aIt( rDoc, nScTab, 0, 0, nLastIterScCol, nLastIterScRow ); + + // activate the correct segment and sub segment at the progress bar + GetProgressBar().ActivateCreateRowsSegment(); + + for( bool bIt = aIt.GetNext(); bIt; bIt = aIt.GetNext() ) + { + SCCOL nScCol = aIt.GetStartCol(); + SCROW nScRow = aIt.GetRow(); + SCCOL nLastScCol = aIt.GetEndCol(); + ScAddress aScPos( nScCol, nScRow, nScTab ); + + XclAddress aXclPos( static_cast< sal_uInt16 >( nScCol ), static_cast< sal_uInt32 >( nScRow ) ); + sal_uInt16 nLastXclCol = static_cast< sal_uInt16 >( nLastScCol ); + + const ScRefCellValue& rScCell = aIt.GetCell(); + XclExpCellRef xCell; + + const ScPatternAttr* pPattern = aIt.GetPattern(); + + // handle overlapped merged cells before creating the cell record + sal_uInt32 nMergeBaseXFId = EXC_XFID_NOTFOUND; + bool bIsMergedBase = false; + if( pPattern ) + { + const SfxItemSet& rItemSet = pPattern->GetItemSet(); + // base cell in a merged range + const ScMergeAttr& rMergeItem = rItemSet.Get( ATTR_MERGE ); + bIsMergedBase = rMergeItem.IsMerged(); + /* overlapped cell in a merged range; in Excel all merged cells + must contain same XF index, for correct border */ + const ScMergeFlagAttr& rMergeFlagItem = rItemSet.Get( ATTR_MERGE_FLAG ); + if( rMergeFlagItem.IsOverlapped() ) + nMergeBaseXFId = mxMergedcells->GetBaseXFId( aScPos ); + } + + OUString aAddNoteText; // additional text to be appended to a note + + switch (rScCell.meType) + { + case CELLTYPE_VALUE: + { + double fValue = rScCell.mfValue; + + if (pPattern) + { + OUString aUrl = pPattern->GetItemSet().Get(ATTR_HYPERLINK).GetValue(); + if (!aUrl.isEmpty()) + { + rtl::Reference aLink = + new XclExpHyperlink(GetRoot(), SvxURLField(aUrl, aUrl), aScPos); + mxHyperlinkList->AppendRecord(aLink); + } + } + + // try to create a Boolean cell + if( pPattern && ((fValue == 0.0) || (fValue == 1.0)) ) + { + sal_uInt32 nScNumFmt = pPattern->GetItemSet().Get( ATTR_VALUE_FORMAT ).GetValue(); + if( rFormatter.GetType( nScNumFmt ) == SvNumFormatType::LOGICAL ) + xCell = new XclExpBooleanCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, fValue != 0.0 ); + } + + // try to create an RK value (compressed floating-point number) + sal_Int32 nRkValue; + if( !xCell && XclTools::GetRKFromDouble( nRkValue, fValue ) ) + xCell = new XclExpRkCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, nRkValue ); + + // else: simple floating-point number cell + if( !xCell ) + xCell = new XclExpNumberCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, fValue ); + } + break; + + case CELLTYPE_STRING: + { + xCell = new XclExpLabelCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, rScCell.mpString->getString()); + } + break; + + case CELLTYPE_EDIT: + { + XclExpHyperlinkHelper aLinkHelper( GetRoot(), aScPos ); + xCell = new XclExpLabelCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, rScCell.mpEditText, aLinkHelper); + + // add a single created HLINK record to the record list + if( aLinkHelper.HasLinkRecord() ) + mxHyperlinkList->AppendRecord( aLinkHelper.GetLinkRecord() ); + // add list of multiple URLs to the additional cell note text + if( aLinkHelper.HasMultipleUrls() ) + aAddNoteText = ScGlobal::addToken( aAddNoteText, aLinkHelper.GetUrlList(), '\n', 2 ); + } + break; + + case CELLTYPE_FORMULA: + { + if (pPattern) + { + OUString aUrl = pPattern->GetItemSet().Get(ATTR_HYPERLINK).GetValue(); + if (!aUrl.isEmpty()) + { + rtl::Reference aLink = + new XclExpHyperlink(GetRoot(), SvxURLField(aUrl, aUrl), aScPos); + mxHyperlinkList->AppendRecord(aLink); + } + } + + xCell = new XclExpFormulaCell( + GetRoot(), aXclPos, pPattern, nMergeBaseXFId, + *rScCell.mpFormula, maArrayBfr, maShrfmlaBfr, maTableopBfr); + } + break; + + default: + OSL_FAIL( "XclExpCellTable::XclExpCellTable - unknown cell type" ); + [[fallthrough]]; + case CELLTYPE_NONE: + { + xCell = new XclExpBlankCell( + GetRoot(), aXclPos, nLastXclCol, pPattern, nMergeBaseXFId ); + } + break; + } + + assert(xCell && "can only reach here with xCell set"); + + // insert the cell into the current row + maRowBfr.AppendCell( xCell, bIsMergedBase ); + + if ( !aAddNoteText.isEmpty() ) + mxNoteList->AppendNewRecord( new XclExpNote( GetRoot(), aScPos, nullptr, aAddNoteText ) ); + + // other sheet contents + if( pPattern ) + { + const SfxItemSet& rItemSet = pPattern->GetItemSet(); + + // base cell in a merged range + if( bIsMergedBase ) + { + const ScMergeAttr& rMergeItem = rItemSet.Get( ATTR_MERGE ); + ScRange aScRange( aScPos ); + aScRange.aEnd.IncCol( rMergeItem.GetColMerge() - 1 ); + aScRange.aEnd.IncRow( rMergeItem.GetRowMerge() - 1 ); + sal_uInt32 nXFId = xCell->GetFirstXFId(); + // blank cells merged vertically may occur repeatedly + OSL_ENSURE( (aScRange.aStart.Col() == aScRange.aEnd.Col()) || (nScCol == nLastScCol), + "XclExpCellTable::XclExpCellTable - invalid repeated blank merged cell" ); + for( SCCOL nIndex = nScCol; nIndex <= nLastScCol; ++nIndex ) + { + mxMergedcells->AppendRange( aScRange, nXFId ); + aScRange.aStart.IncCol(); + aScRange.aEnd.IncCol(); + } + } + + // data validation + if( ScfTools::CheckItem( rItemSet, ATTR_VALIDDATA, false ) ) + { + sal_uLong nScHandle = rItemSet.Get( ATTR_VALIDDATA ).GetValue(); + ScRange aScRange( aScPos ); + aScRange.aEnd.SetCol( nLastScCol ); + mxDval->InsertCellRange( aScRange, nScHandle ); + } + } + } + + // create missing row settings for rows anyhow flagged or with outlines + maRowBfr.CreateRows( ::std::max( nFirstUnflaggedScRow, nFirstUngroupedScRow ) ); +} + +void XclExpCellTable::Finalize(bool bXLS) +{ + // Finalize multiple operations. + maTableopBfr.Finalize(); + + /* Finalize column buffer. This calculates column default XF indexes from + the XF identifiers and fills a vector with these XF indexes. */ + ScfUInt16Vec aColXFIndexes; + maColInfoBfr.Finalize( aColXFIndexes, bXLS ); + + // Usually many indexes towards the end will be EXC_XF_DEFAULTCELL, find + // the index that starts all EXC_XF_DEFAULTCELL until the end. + size_t nStartColAllDefault = findFirstAllSameUntilEnd( aColXFIndexes, EXC_XF_DEFAULTCELL ); + + /* Finalize row buffer. This calculates all cell XF indexes from the XF + identifiers. Then the XF index vector aColXFIndexes (filled above) is + used to calculate the row default formats. With this, all unneeded blank + cell records (equal to row default or column default) will be removed. + The function returns the (most used) default row format in aDefRowData. */ + XclExpDefaultRowData aDefRowData; + maRowBfr.Finalize( aDefRowData, aColXFIndexes, nStartColAllDefault ); + + // Initialize the DEFROWHEIGHT record. + mxDefrowheight->SetDefaultData( aDefRowData ); +} + +XclExpRecordRef XclExpCellTable::CreateRecord( sal_uInt16 nRecId ) const +{ + XclExpRecordRef xRec; + switch( nRecId ) + { + case EXC_ID3_DIMENSIONS: xRec = new XclExpDelegatingRecord( &const_cast(&maRowBfr)->GetDimensions() ); break; + case EXC_ID2_DEFROWHEIGHT: xRec = mxDefrowheight; break; + case EXC_ID_GUTS: xRec = mxGuts; break; + case EXC_ID_NOTE: xRec = mxNoteList; break; + case EXC_ID_MERGEDCELLS: xRec = mxMergedcells; break; + case EXC_ID_HLINK: xRec = mxHyperlinkList; break; + case EXC_ID_DVAL: xRec = mxDval; break; + case EXC_ID_EXTLST: xRec = mxExtLst; break; + default: OSL_FAIL( "XclExpCellTable::CreateRecord - unknown record id" ); + } + return xRec; +} + +void XclExpCellTable::Save( XclExpStream& rStrm ) +{ + // DEFCOLWIDTH and COLINFOs + maColInfoBfr.Save( rStrm ); + // ROWs and cell records + maRowBfr.Save( rStrm ); +} + +void XclExpCellTable::SaveXml( XclExpXmlStream& rStrm ) +{ + // DEFAULT row height + XclExpDefaultRowData& rDefData = mxDefrowheight->GetDefaultData(); + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement( XML_sheetFormatPr, + // OOXTODO: XML_baseColWidth + XML_defaultColWidth, OString::number(maColInfoBfr.GetDefColWidth()), + // OOXTODO: XML_customHeight + // OOXTODO: XML_thickTop + // OOXTODO: XML_thickBottom + XML_defaultRowHeight, OString::number(static_cast (rDefData.mnHeight) / 20.0), + XML_zeroHeight, ToPsz( rDefData.IsHidden() ), + XML_outlineLevelRow, OString::number(maRowBfr.GetHighestOutlineLevel()), + XML_outlineLevelCol, OString::number(maColInfoBfr.GetHighestOutlineLevel()) ); + rWorksheet->endElement( XML_sheetFormatPr ); + + maColInfoBfr.SaveXml( rStrm ); + maRowBfr.SaveXml( rStrm ); + mxExtLst->SaveXml( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xeview.cxx b/sc/source/filter/excel/xeview.cxx new file mode 100644 index 000000000..d94a94407 --- /dev/null +++ b/sc/source/filter/excel/xeview.cxx @@ -0,0 +1,537 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ::oox; + +// Workbook view settings records ============================================= + +XclExpWindow1::XclExpWindow1( const XclExpRoot& rRoot ) + : XclExpRecord(EXC_ID_WINDOW1, 18) + , mnFlags(0) + , mnTabBarSize(600) +{ + const ScViewOptions& rViewOpt = rRoot.GetDoc().GetViewOptions(); + ::set_flag( mnFlags, EXC_WIN1_HOR_SCROLLBAR, rViewOpt.GetOption( VOPT_HSCROLL ) ); + ::set_flag( mnFlags, EXC_WIN1_VER_SCROLLBAR, rViewOpt.GetOption( VOPT_VSCROLL ) ); + ::set_flag( mnFlags, EXC_WIN1_TABBAR, rViewOpt.GetOption( VOPT_TABCONTROLS ) ); + + double fTabBarWidth = rRoot.GetExtDocOptions().GetDocSettings().mfTabBarWidth; + if( (0.0 <= fTabBarWidth) && (fTabBarWidth <= 1.0) ) + mnTabBarSize = static_cast< sal_uInt16 >( fTabBarWidth * 1000.0 + 0.5 ); +} + +void XclExpWindow1::SaveXml( XclExpXmlStream& rStrm ) +{ + const XclExpTabInfo& rTabInfo = rStrm.GetRoot().GetTabInfo(); + + rStrm.GetCurrentStream()->singleElement( XML_workbookView, + // OOXTODO: XML_visibility, // ST_visibility + // OOXTODO: XML_minimized, // bool + XML_showHorizontalScroll, ToPsz( ::get_flag( mnFlags, EXC_WIN1_HOR_SCROLLBAR ) ), + XML_showVerticalScroll, ToPsz( ::get_flag( mnFlags, EXC_WIN1_VER_SCROLLBAR ) ), + XML_showSheetTabs, ToPsz( ::get_flag( mnFlags, EXC_WIN1_TABBAR ) ), + XML_xWindow, "0", + XML_yWindow, "0", + XML_windowWidth, OString::number(0x4000), + XML_windowHeight, OString::number(0x2000), + XML_tabRatio, OString::number(mnTabBarSize), + XML_firstSheet, OString::number(rTabInfo.GetFirstVisXclTab()), + XML_activeTab, OString::number(rTabInfo.GetDisplayedXclTab()) + // OOXTODO: XML_autoFilterDateGrouping, // bool; AUTOFILTER12? 87Eh + ); +} + +void XclExpWindow1::WriteBody( XclExpStream& rStrm ) +{ + const XclExpTabInfo& rTabInfo = rStrm.GetRoot().GetTabInfo(); + + rStrm << sal_uInt16( 0 ) // X position of the window + << sal_uInt16( 0 ) // Y position of the window + << sal_uInt16( 0x4000 ) // width of the window + << sal_uInt16( 0x2000 ) // height of the window + << mnFlags + << rTabInfo.GetDisplayedXclTab() + << rTabInfo.GetFirstVisXclTab() + << rTabInfo.GetXclSelectedCount() + << mnTabBarSize; +} + +// Sheet view settings records ================================================ + +XclExpWindow2::XclExpWindow2( const XclExpRoot& rRoot, + const XclTabViewData& rData, sal_uInt32 nGridColorId ) : + XclExpRecord( EXC_ID_WINDOW2, (rRoot.GetBiff() == EXC_BIFF8) ? 18 : 10 ), + maGridColor( rData.maGridColor ), + mnGridColorId( nGridColorId ), + mnFlags( 0 ), + maFirstXclPos( rData.maFirstXclPos ), + mnNormalZoom( rData.mnNormalZoom ), + mnPageZoom( rData.mnPageZoom ) +{ + ::set_flag( mnFlags, EXC_WIN2_SHOWFORMULAS, rData.mbShowFormulas ); + ::set_flag( mnFlags, EXC_WIN2_SHOWGRID, rData.mbShowGrid ); + ::set_flag( mnFlags, EXC_WIN2_SHOWHEADINGS, rData.mbShowHeadings ); + ::set_flag( mnFlags, EXC_WIN2_FROZEN, rData.mbFrozenPanes ); + ::set_flag( mnFlags, EXC_WIN2_SHOWZEROS, rData.mbShowZeros ); + ::set_flag( mnFlags, EXC_WIN2_DEFGRIDCOLOR, rData.mbDefGridColor ); + ::set_flag( mnFlags, EXC_WIN2_MIRRORED, rData.mbMirrored ); + ::set_flag( mnFlags, EXC_WIN2_SHOWOUTLINE, rData.mbShowOutline ); + ::set_flag( mnFlags, EXC_WIN2_FROZENNOSPLIT, rData.mbFrozenPanes ); + ::set_flag( mnFlags, EXC_WIN2_SELECTED, rData.mbSelected ); + ::set_flag( mnFlags, EXC_WIN2_DISPLAYED, rData.mbDisplayed ); + ::set_flag( mnFlags, EXC_WIN2_PAGEBREAKMODE, rData.mbPageMode ); +} + +void XclExpWindow2::WriteBody( XclExpStream& rStrm ) +{ + const XclExpRoot& rRoot = rStrm.GetRoot(); + + rStrm << mnFlags + << maFirstXclPos; + + switch( rRoot.GetBiff() ) + { + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + rStrm << maGridColor; + break; + case EXC_BIFF8: + rStrm << rRoot.GetPalette().GetColorIndex( mnGridColorId ) + << sal_uInt16( 0 ) + << mnPageZoom + << mnNormalZoom + << sal_uInt32( 0 ); + break; + default: DBG_ERROR_BIFF(); + } +} + +XclExpScl::XclExpScl( sal_uInt16 nZoom ) : + XclExpRecord( EXC_ID_SCL, 4 ), + mnNum( nZoom ), + mnDenom( 100 ) +{ + Shorten( 2 ); + Shorten( 5 ); +} + +void XclExpScl::Shorten( sal_uInt16 nFactor ) +{ + while( (mnNum % nFactor == 0) && (mnDenom % nFactor == 0) ) + { + mnNum = mnNum / nFactor; + mnDenom = mnDenom / nFactor; + } +} + +void XclExpScl::WriteBody( XclExpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() >= EXC_BIFF4 ); + rStrm << mnNum << mnDenom; +} + +XclExpPane::XclExpPane( const XclTabViewData& rData ) : + XclExpRecord( EXC_ID_PANE, 10 ), + mnSplitX( rData.mnSplitX ), + mnSplitY( rData.mnSplitY ), + maSecondXclPos( rData.maSecondXclPos ), + mnActivePane( rData.mnActivePane ), + mbFrozenPanes( rData.mbFrozenPanes ) +{ + OSL_ENSURE( rData.IsSplit(), "XclExpPane::XclExpPane - no PANE record for unsplit view" ); +} + +static const char* lcl_GetActivePane( sal_uInt8 nActivePane ) +{ + switch( nActivePane ) + { + case EXC_PANE_TOPLEFT: return "topLeft"; + case EXC_PANE_TOPRIGHT: return "topRight"; + case EXC_PANE_BOTTOMLEFT: return "bottomLeft"; + case EXC_PANE_BOTTOMRIGHT: return "bottomRight"; + } + return "**error: lcl_GetActivePane"; +} + +void XclExpPane::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->singleElement( XML_pane, + XML_xSplit, OString::number(mnSplitX), + XML_ySplit, OString::number(mnSplitY), + XML_topLeftCell, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), maSecondXclPos ).getStr(), + XML_activePane, lcl_GetActivePane( mnActivePane ), + XML_state, mbFrozenPanes ? "frozen" : "split" ); +} + +void XclExpPane::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnSplitX + << static_cast( mnSplitY ) + << maSecondXclPos + << mnActivePane; + if( rStrm.GetRoot().GetBiff() >= EXC_BIFF5 ) + rStrm << sal_uInt8( 0 ); +} + +XclExpSelection::XclExpSelection( const XclTabViewData& rData, sal_uInt8 nPane ) : + XclExpRecord( EXC_ID_SELECTION, 15 ), + mnPane( nPane ) +{ + if( const XclSelectionData* pSelData = rData.GetSelectionData( nPane ) ) + maSelData = *pSelData; + + // find the cursor position in the selection list (or add it) + XclRangeList& rXclSel = maSelData.maXclSelection; + auto aIt = std::find_if(rXclSel.begin(), rXclSel.end(), + [this](const XclRange& rRange) { return rRange.Contains(maSelData.maXclCursor); }); + if (aIt != rXclSel.end()) + { + maSelData.mnCursorIdx = static_cast< sal_uInt16 >( std::distance(rXclSel.begin(), aIt) ); + } + else + { + /* Cursor cell not found in list? (e.g. inactive pane, or removed in + ConvertRangeList(), because Calc cursor on invalid pos) + -> insert the valid Excel cursor. */ + maSelData.mnCursorIdx = static_cast< sal_uInt16 >( rXclSel.size() ); + rXclSel.push_back( XclRange( maSelData.maXclCursor ) ); + } +} + +void XclExpSelection::SaveXml( XclExpXmlStream& rStrm ) +{ + rStrm.GetCurrentStream()->singleElement( XML_selection, + XML_pane, lcl_GetActivePane( mnPane ), + XML_activeCell, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), maSelData.maXclCursor ).getStr(), + XML_activeCellId, OString::number(maSelData.mnCursorIdx), + XML_sqref, XclXmlUtils::ToOString(rStrm.GetRoot().GetDoc(), maSelData.maXclSelection) ); +} + +void XclExpSelection::WriteBody( XclExpStream& rStrm ) +{ + rStrm << mnPane // pane for this selection + << maSelData.maXclCursor // cell cursor + << maSelData.mnCursorIdx; // index to range containing cursor + maSelData.maXclSelection.Write( rStrm, false ); +} + +XclExpTabBgColor::XclExpTabBgColor( const XclTabViewData& rTabViewData ) : + XclExpRecord( EXC_ID_SHEETEXT, 18 ), + mrTabViewData( rTabViewData ) +{ +} +//TODO Fix savexml... +/*void XclExpTabBgColor::SaveXml( XclExpXmlStream& rStrm ) +{ +}*/ + +void XclExpTabBgColor::WriteBody( XclExpStream& rStrm ) +{ + if ( mrTabViewData.IsDefaultTabBgColor() ) + return; + sal_uInt16 const rt = 0x0862; //rt + sal_uInt16 const grbitFrt = 0x0000; //grbit must be set to 0 + sal_uInt32 unused = 0x00000000; //Use twice... + sal_uInt32 const cb = 0x00000014; // Record Size, may be larger in future... + sal_uInt16 const reserved = 0x0000; //trailing bits are 0 + sal_uInt16 TabBgColorIndex; + XclExpPalette& rPal = rStrm.GetRoot().GetPalette(); + TabBgColorIndex = rPal.GetColorIndex(mrTabViewData.mnTabBgColorId); + if (TabBgColorIndex < 8 || TabBgColorIndex > 63 ) // only numbers 8 - 63 are valid numbers + TabBgColorIndex = 127; //Excel specs: 127 makes excel ignore tab color information. + rStrm << rt << grbitFrt << unused << unused << cb << TabBgColorIndex << reserved; +} + +// Sheet view settings ======================================================== + +namespace { + +/** Converts a Calc zoom factor into an Excel zoom factor. Returns 0 for a default zoom value. */ +sal_uInt16 lclGetXclZoom( tools::Long nScZoom, sal_uInt16 nDefXclZoom ) +{ + sal_uInt16 nXclZoom = limit_cast< sal_uInt16 >( nScZoom, EXC_ZOOM_MIN, EXC_ZOOM_MAX ); + return (nXclZoom == nDefXclZoom) ? 0 : nXclZoom; +} + +} // namespace + +XclExpTabViewSettings::XclExpTabViewSettings( const XclExpRoot& rRoot, SCTAB nScTab ) : + XclExpRoot( rRoot ), + mnGridColorId( XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWTEXT ) ), + mbHasTabSettings(false) +{ + // *** sheet flags *** + + const XclExpTabInfo& rTabInfo = GetTabInfo(); + maData.mbSelected = rTabInfo.IsSelectedTab( nScTab ); + maData.mbDisplayed = rTabInfo.IsDisplayedTab( nScTab ); + maData.mbMirrored = rTabInfo.IsMirroredTab( nScTab ); + + const ScViewOptions& rViewOpt = GetDoc().GetViewOptions(); + maData.mbShowFormulas = rViewOpt.GetOption( VOPT_FORMULAS ); + maData.mbShowHeadings = rViewOpt.GetOption( VOPT_HEADER ); + maData.mbShowZeros = rViewOpt.GetOption( VOPT_NULLVALS ); + maData.mbShowOutline = rViewOpt.GetOption( VOPT_OUTLINER ); + + // *** sheet options: cursor, selection, splits, grid color, zoom *** + + if( const ScExtTabSettings* pTabSett = GetExtDocOptions().GetTabSettings( nScTab ) ) + { + mbHasTabSettings = true; + const ScExtTabSettings& rTabSett = *pTabSett; + XclExpAddressConverter& rAddrConv = GetAddressConverter(); + + // first visible cell in top-left pane + if( (rTabSett.maFirstVis.Col() >= 0) && (rTabSett.maFirstVis.Row() >= 0) ) + maData.maFirstXclPos = rAddrConv.CreateValidAddress( rTabSett.maFirstVis, false ); + + // first visible cell in additional pane(s) + if( (rTabSett.maSecondVis.Col() >= 0) && (rTabSett.maSecondVis.Row() >= 0) ) + maData.maSecondXclPos = rAddrConv.CreateValidAddress( rTabSett.maSecondVis, false ); + + // active pane + switch( rTabSett.meActivePane ) + { + case SCEXT_PANE_TOPLEFT: maData.mnActivePane = EXC_PANE_TOPLEFT; break; + case SCEXT_PANE_TOPRIGHT: maData.mnActivePane = EXC_PANE_TOPRIGHT; break; + case SCEXT_PANE_BOTTOMLEFT: maData.mnActivePane = EXC_PANE_BOTTOMLEFT; break; + case SCEXT_PANE_BOTTOMRIGHT: maData.mnActivePane = EXC_PANE_BOTTOMRIGHT; break; + } + + // freeze/split position + maData.mbFrozenPanes = rTabSett.mbFrozenPanes; + if( maData.mbFrozenPanes ) + { + /* Frozen panes: handle split position as row/column positions. + #i35812# Excel uses number of visible rows/columns, Calc uses position of freeze. */ + SCCOL nFreezeScCol = rTabSett.maFreezePos.Col(); + if( (0 < nFreezeScCol) && (nFreezeScCol <= GetXclMaxPos().Col()) ) + maData.mnSplitX = static_cast< sal_uInt16 >( nFreezeScCol ) - maData.maFirstXclPos.mnCol; + SCROW nFreezeScRow = rTabSett.maFreezePos.Row(); + if( (0 < nFreezeScRow) && (nFreezeScRow <= GetXclMaxPos().Row()) ) + maData.mnSplitY = static_cast< sal_uInt32 >( nFreezeScRow ) - maData.maFirstXclPos.mnRow; + // if both splits are left out (address overflow), remove the frozen flag + maData.mbFrozenPanes = maData.IsSplit(); + + // #i20671# frozen panes: mostright/mostbottom pane is active regardless of cursor position + if( maData.HasPane( EXC_PANE_BOTTOMRIGHT ) ) + maData.mnActivePane = EXC_PANE_BOTTOMRIGHT; + else if( maData.HasPane( EXC_PANE_TOPRIGHT ) ) + maData.mnActivePane = EXC_PANE_TOPRIGHT; + else if( maData.HasPane( EXC_PANE_BOTTOMLEFT ) ) + maData.mnActivePane = EXC_PANE_BOTTOMLEFT; + } + else + { + // split window: position is in twips + maData.mnSplitX = static_cast(rTabSett.maSplitPos.X()); + maData.mnSplitY = static_cast(rTabSett.maSplitPos.Y()); + } + + // selection + CreateSelectionData( EXC_PANE_TOPLEFT, rTabSett.maCursor, rTabSett.maSelection ); + CreateSelectionData( EXC_PANE_TOPRIGHT, rTabSett.maCursor, rTabSett.maSelection ); + CreateSelectionData( EXC_PANE_BOTTOMLEFT, rTabSett.maCursor, rTabSett.maSelection ); + CreateSelectionData( EXC_PANE_BOTTOMRIGHT, rTabSett.maCursor, rTabSett.maSelection ); + + // grid color + const Color& rGridColor = rTabSett.maGridColor; + maData.mbDefGridColor = rGridColor == COL_AUTO; + if( !maData.mbDefGridColor ) + { + if( GetBiff() == EXC_BIFF8 ) + mnGridColorId = GetPalette().InsertColor( rGridColor, EXC_COLOR_GRID ); + else + maData.maGridColor = rGridColor; + } + maData.mbShowGrid = rTabSett.mbShowGrid; + + // view mode and zoom + maData.mbPageMode = (GetBiff() == EXC_BIFF8) && rTabSett.mbPageMode; + maData.mnNormalZoom = lclGetXclZoom( rTabSett.mnNormalZoom, EXC_WIN2_NORMALZOOM_DEF ); + maData.mnPageZoom = lclGetXclZoom( rTabSett.mnPageZoom, EXC_WIN2_PAGEZOOM_DEF ); + maData.mnCurrentZoom = maData.mbPageMode ? maData.mnPageZoom : maData.mnNormalZoom; + } + + // Tab Bg Color + if ( GetBiff() == EXC_BIFF8 && !GetDoc().IsDefaultTabBgColor(nScTab) ) + { + XclExpPalette& rPal = GetPalette(); + maData.maTabBgColor = GetDoc().GetTabBgColor(nScTab); + maData.mnTabBgColorId = rPal.InsertColor(maData.maTabBgColor, EXC_COLOR_TABBG, EXC_COLOR_NOTABBG ); + } +} + +void XclExpTabViewSettings::Save( XclExpStream& rStrm ) +{ + WriteWindow2( rStrm ); + WriteScl( rStrm ); + WritePane( rStrm ); + WriteSelection( rStrm, EXC_PANE_TOPLEFT ); + WriteSelection( rStrm, EXC_PANE_TOPRIGHT ); + WriteSelection( rStrm, EXC_PANE_BOTTOMLEFT ); + WriteSelection( rStrm, EXC_PANE_BOTTOMRIGHT ); + WriteTabBgColor( rStrm ); +} + +static void lcl_WriteSelection( XclExpXmlStream& rStrm, const XclTabViewData& rData, sal_uInt8 nPane ) +{ + if( rData.HasPane( nPane ) ) + XclExpSelection( rData, nPane ).SaveXml( rStrm ); +} + +static OString lcl_GetZoom( sal_uInt16 nZoom ) +{ + if( nZoom ) + return OString::number( nZoom ); + return "100"; +} + +void XclExpTabViewSettings::SaveXml( XclExpXmlStream& rStrm ) +{ + sax_fastparser::FSHelperPtr& rWorksheet = rStrm.GetCurrentStream(); + rWorksheet->startElement(XML_sheetViews); + + // handle missing viewdata at embedded XLSX OLE objects + if( !mbHasTabSettings && maData.mbSelected ) + { + SCCOL nPosLeft = rStrm.GetRoot().GetDoc().GetPosLeft(); + SCROW nPosTop = rStrm.GetRoot().GetDoc().GetPosTop(); + if (nPosLeft > 0 || nPosTop > 0) + { + ScAddress aLeftTop(nPosLeft, nPosTop, 0); + XclExpAddressConverter& rAddrConv = GetAddressConverter(); + maData.maFirstXclPos = rAddrConv.CreateValidAddress( aLeftTop, false ); + } + } + + rWorksheet->startElement( XML_sheetView, + // OOXTODO: XML_windowProtection, + XML_showFormulas, ToPsz( maData.mbShowFormulas ), + XML_showGridLines, ToPsz( maData.mbShowGrid ), + XML_showRowColHeaders, ToPsz( maData.mbShowHeadings ), + XML_showZeros, ToPsz( maData.mbShowZeros ), + XML_rightToLeft, ToPsz( maData.mbMirrored ), + XML_tabSelected, ToPsz( maData.mbSelected ), + // OOXTODO: XML_showRuler, + XML_showOutlineSymbols, ToPsz( maData.mbShowOutline ), + XML_defaultGridColor, mnGridColorId == XclExpPalette::GetColorIdFromIndex( EXC_COLOR_WINDOWTEXT ) ? "true" : "false", + // OOXTODO: XML_showWhiteSpace, + XML_view, maData.mbPageMode ? "pageBreakPreview" : "normal", // OOXTODO: pageLayout + XML_topLeftCell, XclXmlUtils::ToOString( rStrm.GetRoot().GetStringBuf(), maData.maFirstXclPos ).getStr(), + XML_colorId, OString::number(rStrm.GetRoot().GetPalette().GetColorIndex(mnGridColorId)), + XML_zoomScale, lcl_GetZoom(maData.mnCurrentZoom), + XML_zoomScaleNormal, lcl_GetZoom(maData.mnNormalZoom), + // OOXTODO: XML_zoomScaleSheetLayoutView, + XML_zoomScalePageLayoutView, lcl_GetZoom(maData.mnPageZoom), + XML_workbookViewId, "0" // OOXTODO? 0-based index of document(xl/workbook.xml)/workbook/bookviews/workbookView + // should always be 0, as we only generate 1 such element. + ); + if( maData.IsSplit() ) + { + XclExpPane aPane( maData ); + aPane.SaveXml( rStrm ); + } + lcl_WriteSelection( rStrm, maData, EXC_PANE_TOPLEFT ); + lcl_WriteSelection( rStrm, maData, EXC_PANE_TOPRIGHT ); + lcl_WriteSelection( rStrm, maData, EXC_PANE_BOTTOMLEFT ); + lcl_WriteSelection( rStrm, maData, EXC_PANE_BOTTOMRIGHT ); + rWorksheet->endElement( XML_sheetView ); + // OOXTODO: XML_extLst + rWorksheet->endElement( XML_sheetViews ); +} + +// private -------------------------------------------------------------------- + +void XclExpTabViewSettings::CreateSelectionData( sal_uInt8 nPane, + const ScAddress& rCursor, const ScRangeList& rSelection ) +{ + if( !maData.HasPane( nPane ) ) + return; + + XclSelectionData& rSelData = maData.CreateSelectionData( nPane ); + + // first step: use top-left visible cell as cursor + rSelData.maXclCursor.mnCol = ((nPane == EXC_PANE_TOPLEFT) || (nPane == EXC_PANE_BOTTOMLEFT)) ? + maData.maFirstXclPos.mnCol : maData.maSecondXclPos.mnCol; + rSelData.maXclCursor.mnRow = ((nPane == EXC_PANE_TOPLEFT) || (nPane == EXC_PANE_TOPRIGHT)) ? + maData.maFirstXclPos.mnRow : maData.maSecondXclPos.mnRow; + + // second step, active pane: create actual selection data with current cursor position + if( nPane == maData.mnActivePane ) + { + XclExpAddressConverter& rAddrConv = GetAddressConverter(); + // cursor position (keep top-left pane position from above, if rCursor is invalid) + if( (rCursor.Col() >= 0) && (rCursor.Row() >= 0) ) + rSelData.maXclCursor = rAddrConv.CreateValidAddress( rCursor, false ); + // selection + rAddrConv.ConvertRangeList( rSelData.maXclSelection, rSelection, false ); + } +} + +void XclExpTabViewSettings::WriteWindow2( XclExpStream& rStrm ) const +{ +// #i43553# GCC 3.3 parse error +// XclExpWindow2( GetRoot(), maData, mnGridColorId ).Save( rStrm ); + XclExpWindow2 aWindow2( GetRoot(), maData, mnGridColorId ); + aWindow2.Save( rStrm ); +} + +void XclExpTabViewSettings::WriteScl( XclExpStream& rStrm ) const +{ + if( maData.mnCurrentZoom != 0 ) + XclExpScl( maData.mnCurrentZoom ).Save( rStrm ); +} + +void XclExpTabViewSettings::WritePane( XclExpStream& rStrm ) const +{ + if( maData.IsSplit() ) +// #i43553# GCC 3.3 parse error +// XclExpPane( GetRoot(), maData ).Save( rStrm ); + { + XclExpPane aPane( maData ); + aPane.Save( rStrm ); + } +} + +void XclExpTabViewSettings::WriteSelection( XclExpStream& rStrm, sal_uInt8 nPane ) const +{ + if( maData.HasPane( nPane ) ) + XclExpSelection( maData, nPane ).Save( rStrm ); +} + +void XclExpTabViewSettings::WriteTabBgColor( XclExpStream& rStrm ) const +{ + if ( !maData.IsDefaultTabBgColor() ) + XclExpTabBgColor( maData ).Save( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xichart.cxx b/sc/source/filter/excel/xichart.cxx new file mode 100644 index 000000000..dc14076b7 --- /dev/null +++ b/sc/source/filter/excel/xichart.cxx @@ -0,0 +1,4415 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::util::XNumberFormatsSupplier; +using ::com::sun::star::drawing::XDrawPage; +using ::com::sun::star::drawing::XDrawPageSupplier; +using ::com::sun::star::drawing::XShape; + +using namespace ::com::sun::star::chart2; + +using ::com::sun::star::chart2::data::XDataProvider; +using ::com::sun::star::chart2::data::XDataReceiver; +using ::com::sun::star::chart2::data::XDataSequence; +using ::com::sun::star::chart2::data::XDataSink; +using ::com::sun::star::chart2::data::XLabeledDataSequence; +using ::com::sun::star::chart2::data::LabeledDataSequence; + +using ::formula::FormulaToken; +using ::formula::FormulaTokenArrayPlainIterator; +using ::std::unique_ptr; + +namespace cssc = ::com::sun::star::chart; +namespace cssc2 = ::com::sun::star::chart2; + +// Helpers ==================================================================== + +namespace { + +XclImpStream& operator>>( XclImpStream& rStrm, XclChRectangle& rRect ) +{ + rRect.mnX = rStrm.ReadInt32(); + rRect.mnY = rStrm.ReadInt32(); + rRect.mnWidth = rStrm.ReadInt32(); + rRect.mnHeight = rStrm.ReadInt32(); + return rStrm; +} + +void lclSetValueOrClearAny( Any& rAny, double fValue, bool bClear ) +{ + if( bClear ) + rAny.clear(); + else + rAny <<= fValue; +} + +void lclSetExpValueOrClearAny( Any& rAny, double fValue, bool bLogScale, bool bClear ) +{ + if( !bClear && bLogScale ) + fValue = pow( 10.0, fValue ); + lclSetValueOrClearAny( rAny, fValue, bClear ); +} + +double lclGetSerialDay( const XclImpRoot& rRoot, sal_uInt16 nValue, sal_uInt16 nTimeUnit ) +{ + switch( nTimeUnit ) + { + case EXC_CHDATERANGE_DAYS: + return nValue; + case EXC_CHDATERANGE_MONTHS: + return rRoot.GetDoubleFromDateTime( Date( 1, static_cast< sal_uInt16 >( 1 + nValue % 12 ), static_cast< sal_uInt16 >( rRoot.GetBaseYear() + nValue / 12 ) ) ); + case EXC_CHDATERANGE_YEARS: + return rRoot.GetDoubleFromDateTime( Date( 1, 1, static_cast< sal_uInt16 >( rRoot.GetBaseYear() + nValue ) ) ); + default: + OSL_ENSURE( false, "lclGetSerialDay - unexpected time unit" ); + } + return nValue; +} + +void lclConvertTimeValue( const XclImpRoot& rRoot, Any& rAny, sal_uInt16 nValue, bool bAuto, sal_uInt16 nTimeUnit ) +{ + if( bAuto ) + rAny.clear(); + else + rAny <<= lclGetSerialDay( rRoot, nValue, nTimeUnit ); +} + +sal_Int32 lclGetApiTimeUnit( sal_uInt16 nTimeUnit ) +{ + switch( nTimeUnit ) + { + case EXC_CHDATERANGE_DAYS: return cssc::TimeUnit::DAY; + case EXC_CHDATERANGE_MONTHS: return cssc::TimeUnit::MONTH; + case EXC_CHDATERANGE_YEARS: return cssc::TimeUnit::YEAR; + default: OSL_ENSURE( false, "lclGetApiTimeUnit - unexpected time unit" ); + } + return cssc::TimeUnit::DAY; +} + +void lclConvertTimeInterval( Any& rInterval, sal_uInt16 nValue, bool bAuto, sal_uInt16 nTimeUnit ) +{ + if( bAuto || (nValue == 0) ) + rInterval.clear(); + else + rInterval <<= cssc::TimeInterval( nValue, lclGetApiTimeUnit( nTimeUnit ) ); +} + +} // namespace + +// Common ===================================================================== + +/** Stores global data needed in various classes of the Chart import filter. */ +struct XclImpChRootData : public XclChRootData +{ + XclImpChChart& mrChartData; /// The chart data object. + + explicit XclImpChRootData( XclImpChChart& rChartData ) : mrChartData( rChartData ) {} +}; + +XclImpChRoot::XclImpChRoot( const XclImpRoot& rRoot, XclImpChChart& rChartData ) : + XclImpRoot( rRoot ), + mxChData( std::make_shared( rChartData ) ) +{ +} + +XclImpChRoot::~XclImpChRoot() +{ +} + +XclImpChChart& XclImpChRoot::GetChartData() const +{ + return mxChData->mrChartData; +} + +const XclChTypeInfo& XclImpChRoot::GetChartTypeInfo( XclChTypeId eType ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfo( eType ); +} + +const XclChTypeInfo& XclImpChRoot::GetChartTypeInfo( sal_uInt16 nRecId ) const +{ + return mxChData->mxTypeInfoProv->GetTypeInfoFromRecId( nRecId ); +} + +const XclChFormatInfo& XclImpChRoot::GetFormatInfo( XclChObjectType eObjType ) const +{ + return mxChData->mxFmtInfoProv->GetFormatInfo( eObjType ); +} + +Color XclImpChRoot::GetFontAutoColor() const +{ + return GetPalette().GetColor( EXC_COLOR_CHWINDOWTEXT ); +} + +Color XclImpChRoot::GetSeriesLineAutoColor( sal_uInt16 nFormatIdx ) const +{ + return GetPalette().GetColor( XclChartHelper::GetSeriesLineAutoColorIdx( nFormatIdx ) ); +} + +Color XclImpChRoot::GetSeriesFillAutoColor( sal_uInt16 nFormatIdx ) const +{ + const XclImpPalette& rPal = GetPalette(); + Color aColor = rPal.GetColor( XclChartHelper::GetSeriesFillAutoColorIdx( nFormatIdx ) ); + sal_uInt8 nTrans = XclChartHelper::GetSeriesFillAutoTransp( nFormatIdx ); + return ScfTools::GetMixedColor( aColor, rPal.GetColor( EXC_COLOR_CHWINDOWBACK ), nTrans ); +} + +void XclImpChRoot::InitConversion( const Reference& xChartDoc, const tools::Rectangle& rChartRect ) const +{ + // create formatting object tables + mxChData->InitConversion( GetRoot(), xChartDoc, rChartRect ); + + // lock the model to suppress any internal updates + if( xChartDoc.is() ) + xChartDoc->lockControllers(); + + SfxObjectShell* pDocShell = GetDocShell(); + Reference< XDataReceiver > xDataRec( xChartDoc, UNO_QUERY ); + if( pDocShell && xDataRec.is() ) + { + // create and register a data provider + Reference< XDataProvider > xDataProv( + ScfApiHelper::CreateInstance( pDocShell, SERVICE_CHART2_DATAPROVIDER ), UNO_QUERY ); + if( xDataProv.is() ) + xDataRec->attachDataProvider( xDataProv ); + // attach the number formatter + Reference< XNumberFormatsSupplier > xNumFmtSupp( pDocShell->GetModel(), UNO_QUERY ); + if( xNumFmtSupp.is() ) + xDataRec->attachNumberFormatsSupplier( xNumFmtSupp ); + } +} + +void XclImpChRoot::FinishConversion( XclImpDffConverter& rDffConv ) const +{ + rDffConv.Progress( EXC_CHART_PROGRESS_SIZE ); + // unlock the model + Reference< XModel > xModel = mxChData->mxChartDoc; + if( xModel.is() ) + xModel->unlockControllers(); + rDffConv.Progress( EXC_CHART_PROGRESS_SIZE ); + + mxChData->FinishConversion(); +} + +Reference< XDataProvider > XclImpChRoot::GetDataProvider() const +{ + return mxChData->mxChartDoc->getDataProvider(); +} + +Reference< XShape > XclImpChRoot::GetTitleShape( const XclChTextKey& rTitleKey ) const +{ + return mxChData->GetTitleShape( rTitleKey ); +} + +sal_Int32 XclImpChRoot::CalcHmmFromChartX( sal_Int32 nPosX ) const +{ + return static_cast< sal_Int32 >( mxChData->mfUnitSizeX * nPosX + mxChData->mnBorderGapX + 0.5 ); +} + +sal_Int32 XclImpChRoot::CalcHmmFromChartY( sal_Int32 nPosY ) const +{ + return static_cast< sal_Int32 >( mxChData->mfUnitSizeY * nPosY + mxChData->mnBorderGapY + 0.5 ); +} + +css::awt::Rectangle XclImpChRoot::CalcHmmFromChartRect( const XclChRectangle& rRect ) const +{ + return css::awt::Rectangle( + CalcHmmFromChartX( rRect.mnX ), + CalcHmmFromChartY( rRect.mnY ), + CalcHmmFromChartX( rRect.mnWidth ), + CalcHmmFromChartY( rRect.mnHeight ) ); +} + +double XclImpChRoot::CalcRelativeFromHmmX( sal_Int32 nPosX ) const +{ + const tools::Long nWidth = mxChData->maChartRect.GetWidth(); + if (!nWidth) + throw o3tl::divide_by_zero(); + return static_cast(nPosX) / nWidth; +} + +double XclImpChRoot::CalcRelativeFromHmmY( sal_Int32 nPosY ) const +{ + const tools::Long nHeight = mxChData->maChartRect.GetHeight(); + if (!nHeight) + throw o3tl::divide_by_zero(); + return static_cast(nPosY) / nHeight; +} + +double XclImpChRoot::CalcRelativeFromChartX( sal_Int32 nPosX ) const +{ + return CalcRelativeFromHmmX( CalcHmmFromChartX( nPosX ) ); +} + +double XclImpChRoot::CalcRelativeFromChartY( sal_Int32 nPosY ) const +{ + return CalcRelativeFromHmmY( CalcHmmFromChartY( nPosY ) ); +} + +void XclImpChRoot::ConvertLineFormat( ScfPropertySet& rPropSet, + const XclChLineFormat& rLineFmt, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteLineProperties( + rPropSet, *mxChData->mxLineDashTable, rLineFmt, ePropMode ); +} + +void XclImpChRoot::ConvertAreaFormat( ScfPropertySet& rPropSet, + const XclChAreaFormat& rAreaFmt, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteAreaProperties( rPropSet, rAreaFmt, ePropMode ); +} + +void XclImpChRoot::ConvertEscherFormat( ScfPropertySet& rPropSet, + const XclChEscherFormat& rEscherFmt, const XclChPicFormat* pPicFmt, + sal_uInt32 nDffFillType, XclChPropertyMode ePropMode ) const +{ + GetChartPropSetHelper().WriteEscherProperties( rPropSet, + *mxChData->mxGradientTable, *mxChData->mxBitmapTable, + rEscherFmt, pPicFmt, nDffFillType, ePropMode ); +} + +void XclImpChRoot::ConvertFont( ScfPropertySet& rPropSet, + sal_uInt16 nFontIdx, const Color* pFontColor ) const +{ + GetFontBuffer().WriteFontProperties( rPropSet, EXC_FONTPROPSET_CHART, nFontIdx, pFontColor ); +} + +void XclImpChRoot::ConvertPieRotation( ScfPropertySet& rPropSet, sal_uInt16 nAngle ) +{ + sal_Int32 nApiRot = (450 - (nAngle % 360)) % 360; + rPropSet.SetProperty( EXC_CHPROP_STARTINGANGLE, nApiRot ); +} + +XclImpChGroupBase::~XclImpChGroupBase() +{ +} + +void XclImpChGroupBase::ReadRecordGroup( XclImpStream& rStrm ) +{ + // read contents of the header record + ReadHeaderRecord( rStrm ); + + // only read sub records, if the next record is a CHBEGIN + if( rStrm.GetNextRecId() != EXC_ID_CHBEGIN ) + return; + + // read the CHBEGIN record, may be used for special initial processing + rStrm.StartNextRecord(); + ReadSubRecord( rStrm ); + + // read the nested records + bool bLoop = true; + while( bLoop && rStrm.StartNextRecord() ) + { + sal_uInt16 nRecId = rStrm.GetRecId(); + bLoop = nRecId != EXC_ID_CHEND; + // skip unsupported nested blocks + if( nRecId == EXC_ID_CHBEGIN ) + SkipBlock( rStrm ); + else + ReadSubRecord( rStrm ); + } + /* Returns with current CHEND record or unchanged stream, if no record + group present. In every case another call to StartNextRecord() will go + to next record of interest. */ +} + +void XclImpChGroupBase::SkipBlock( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecId() == EXC_ID_CHBEGIN, "XclImpChGroupBase::SkipBlock - no CHBEGIN record" ); + // do nothing if current record is not CHBEGIN + bool bLoop = rStrm.GetRecId() == EXC_ID_CHBEGIN; + while( bLoop && rStrm.StartNextRecord() ) + { + sal_uInt16 nRecId = rStrm.GetRecId(); + bLoop = nRecId != EXC_ID_CHEND; + // skip nested record groups + if( nRecId == EXC_ID_CHBEGIN ) + SkipBlock( rStrm ); + } +} + +// Frame formatting =========================================================== + +void XclImpChFramePos::ReadChFramePos( XclImpStream& rStrm ) +{ + maData.mnTLMode = rStrm.ReaduInt16(); + maData.mnBRMode = rStrm.ReaduInt16(); + /* According to the spec, the upper 16 bits of all members in the + rectangle are unused and may contain garbage. */ + maData.maRect.mnX = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnY = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnWidth = rStrm.ReadInt16(); rStrm.Ignore( 2 ); + maData.maRect.mnHeight = rStrm.ReadInt16(); rStrm.Ignore( 2 ); +} + +void XclImpChLineFormat::ReadChLineFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maColor; + maData.mnPattern = rStrm.ReaduInt16(); + maData.mnWeight = rStrm.ReadInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + // BIFF8: index into palette used instead of RGB data + maData.maColor = rRoot.GetPalette().GetColor( rStrm.ReaduInt16() ); +} + +void XclImpChLineFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + if( IsAuto() ) + { + XclChLineFormat aLineFmt; + aLineFmt.maColor = (eObjType == EXC_CHOBJTYPE_LINEARSERIES) ? + rRoot.GetSeriesLineAutoColor( nFormatIdx ) : + rRoot.GetPalette().GetColor( rFmtInfo.mnAutoLineColorIdx ); + aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + aLineFmt.mnWeight = rFmtInfo.mnAutoLineWeight; + rRoot.ConvertLineFormat( rPropSet, aLineFmt, rFmtInfo.mePropMode ); + } + else + { + rRoot.ConvertLineFormat( rPropSet, maData, rFmtInfo.mePropMode ); + } +} + +void XclImpChAreaFormat::ReadChAreaFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maPattColor >> maData.maBackColor; + maData.mnPattern = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + const XclImpPalette& rPal = rRoot.GetPalette(); + maData.maPattColor = rPal.GetColor( rStrm.ReaduInt16() ); + maData.maBackColor = rPal.GetColor( rStrm.ReaduInt16()); + } +} + +void XclImpChAreaFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + if( IsAuto() ) + { + XclChAreaFormat aAreaFmt; + aAreaFmt.maPattColor = (eObjType == EXC_CHOBJTYPE_FILLEDSERIES) ? + rRoot.GetSeriesFillAutoColor( nFormatIdx ) : + rRoot.GetPalette().GetColor( rFmtInfo.mnAutoPattColorIdx ); + aAreaFmt.mnPattern = EXC_PATT_SOLID; + rRoot.ConvertAreaFormat( rPropSet, aAreaFmt, rFmtInfo.mePropMode ); + } + else + { + rRoot.ConvertAreaFormat( rPropSet, maData, rFmtInfo.mePropMode ); + } +} + +XclImpChEscherFormat::XclImpChEscherFormat( const XclImpRoot& rRoot ) : + mnDffFillType( mso_fillSolid ) +{ + maData.mxItemSet = + std::make_shared( rRoot.GetDoc().GetDrawLayer()->GetItemPool() ); +} + +void XclImpChEscherFormat::ReadHeaderRecord( XclImpStream& rStrm ) +{ + // read from stream - CHESCHERFORMAT uses own ID for record continuation + XclImpDffPropSet aPropSet( rStrm.GetRoot() ); + rStrm.ResetRecord( true, rStrm.GetRecId() ); + rStrm >> aPropSet; + // get the data + aPropSet.FillToItemSet( *maData.mxItemSet ); + // get fill type from DFF property set + mnDffFillType = aPropSet.GetPropertyValue( DFF_Prop_fillType ); +} + +void XclImpChEscherFormat::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHPICFORMAT: + maPicFmt.mnBmpMode = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + maPicFmt.mnFlags = rStrm.ReaduInt16(); + maPicFmt.mfScale = rStrm.ReadDouble(); + break; + } +} + +void XclImpChEscherFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, bool bUsePicFmt ) const +{ + const XclChFormatInfo& rFmtInfo = rRoot.GetFormatInfo( eObjType ); + rRoot.ConvertEscherFormat( rPropSet, maData, bUsePicFmt ? &maPicFmt : nullptr, mnDffFillType, rFmtInfo.mePropMode ); +} + +XclImpChFrameBase::XclImpChFrameBase( const XclChFormatInfo& rFmtInfo ) +{ + if( !rFmtInfo.mbCreateDefFrame ) + return; + + switch( rFmtInfo.meDefFrameType ) + { + case EXC_CHFRAMETYPE_AUTO: + mxLineFmt = new XclImpChLineFormat(); + if( rFmtInfo.mbIsFrame ) + mxAreaFmt = std::make_shared(); + break; + case EXC_CHFRAMETYPE_INVISIBLE: + { + XclChLineFormat aLineFmt; + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_AUTO, false ); + aLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; + mxLineFmt = new XclImpChLineFormat( aLineFmt ); + if( rFmtInfo.mbIsFrame ) + { + XclChAreaFormat aAreaFmt; + ::set_flag( aAreaFmt.mnFlags, EXC_CHAREAFORMAT_AUTO, false ); + aAreaFmt.mnPattern = EXC_PATT_NONE; + mxAreaFmt = std::make_shared( aAreaFmt ); + } + } + break; + default: + OSL_FAIL( "XclImpChFrameBase::XclImpChFrameBase - unknown frame type" ); + } +} + +void XclImpChFrameBase::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHLINEFORMAT: + mxLineFmt = new XclImpChLineFormat(); + mxLineFmt->ReadChLineFormat( rStrm ); + break; + case EXC_ID_CHAREAFORMAT: + mxAreaFmt = std::make_shared(); + mxAreaFmt->ReadChAreaFormat( rStrm ); + break; + case EXC_ID_CHESCHERFORMAT: + mxEscherFmt = std::make_shared( rStrm.GetRoot() ); + mxEscherFmt->ReadRecordGroup( rStrm ); + break; + } +} + +void XclImpChFrameBase::ConvertLineBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx ) const +{ + if( mxLineFmt ) + mxLineFmt->Convert( rRoot, rPropSet, eObjType, nFormatIdx ); +} + +void XclImpChFrameBase::ConvertAreaBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx, bool bUsePicFmt ) const +{ + if( rRoot.GetFormatInfo( eObjType ).mbIsFrame ) + { + // CHESCHERFORMAT overrides CHAREAFORMAT (even if it is auto) + if( mxEscherFmt ) + mxEscherFmt->Convert( rRoot, rPropSet, eObjType, bUsePicFmt ); + else if( mxAreaFmt ) + mxAreaFmt->Convert( rRoot, rPropSet, eObjType, nFormatIdx ); + } +} + +void XclImpChFrameBase::ConvertFrameBase( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, XclChObjectType eObjType, sal_uInt16 nFormatIdx, bool bUsePicFmt ) const +{ + ConvertLineBase( rRoot, rPropSet, eObjType, nFormatIdx ); + ConvertAreaBase( rRoot, rPropSet, eObjType, nFormatIdx, bUsePicFmt ); +} + +XclImpChFrame::XclImpChFrame( const XclImpChRoot& rRoot, XclChObjectType eObjType ) : + XclImpChFrameBase( rRoot.GetFormatInfo( eObjType ) ), + XclImpChRoot( rRoot ), + meObjType( eObjType ) +{ +} + +void XclImpChFrame::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnFormat = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChFrame::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + const XclImpPalette& rPal = GetPalette(); + + if( rLineData.IsVisible() && (!mxLineFmt || !mxLineFmt->HasLine()) ) + { + // line formatting + XclChLineFormat aLineFmt; + aLineFmt.maColor = rPal.GetColor( rLineData.mnColorIdx ); + switch( rLineData.mnStyle ) + { + case EXC_OBJ_LINE_SOLID: aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; break; + case EXC_OBJ_LINE_DASH: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASH; break; + case EXC_OBJ_LINE_DOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DOT; break; + case EXC_OBJ_LINE_DASHDOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASHDOT; break; + case EXC_OBJ_LINE_DASHDOTDOT: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DASHDOTDOT; break; + case EXC_OBJ_LINE_MEDTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_MEDTRANS; break; + case EXC_OBJ_LINE_DARKTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_DARKTRANS; break; + case EXC_OBJ_LINE_LIGHTTRANS: aLineFmt.mnPattern = EXC_CHLINEFORMAT_LIGHTTRANS; break; + case EXC_OBJ_LINE_NONE: aLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; break; + default: aLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + } + switch( rLineData.mnWidth ) + { + case EXC_OBJ_LINE_HAIR: aLineFmt.mnWeight = EXC_CHLINEFORMAT_HAIR; break; + case EXC_OBJ_LINE_THIN: aLineFmt.mnWeight = EXC_CHLINEFORMAT_SINGLE; break; + case EXC_OBJ_LINE_MEDIUM: aLineFmt.mnWeight = EXC_CHLINEFORMAT_DOUBLE; break; + case EXC_OBJ_LINE_THICK: aLineFmt.mnWeight = EXC_CHLINEFORMAT_TRIPLE; break; + default: aLineFmt.mnWeight = EXC_CHLINEFORMAT_HAIR; + } + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_AUTO, rLineData.IsAuto() ); + mxLineFmt = new XclImpChLineFormat( aLineFmt ); + } + + if( rFillData.IsFilled() && (!mxAreaFmt || !mxAreaFmt->HasArea()) && !mxEscherFmt ) + { + // area formatting + XclChAreaFormat aAreaFmt; + aAreaFmt.maPattColor = rPal.GetColor( rFillData.mnPattColorIdx ); + aAreaFmt.maBackColor = rPal.GetColor( rFillData.mnBackColorIdx ); + aAreaFmt.mnPattern = rFillData.mnPattern; + ::set_flag( aAreaFmt.mnFlags, EXC_CHAREAFORMAT_AUTO, rFillData.IsAuto() ); + mxAreaFmt = std::make_shared( aAreaFmt ); + } +} + +void XclImpChFrame::Convert( ScfPropertySet& rPropSet, bool bUsePicFmt ) const +{ + ConvertFrameBase( GetChRoot(), rPropSet, meObjType, EXC_CHDATAFORMAT_UNKNOWN, bUsePicFmt ); +} + +// Source links =============================================================== + +namespace { + +/** Creates a labeled data sequence object, adds link for series title if present. */ +Reference< XLabeledDataSequence > lclCreateLabeledDataSequence( + const XclImpChSourceLinkRef& xValueLink, const OUString& rValueRole, + const XclImpChSourceLink* pTitleLink = nullptr ) +{ + // create data sequence for values and title + Reference< XDataSequence > xValueSeq; + if( xValueLink ) + xValueSeq = xValueLink->CreateDataSequence( rValueRole ); + Reference< XDataSequence > xTitleSeq; + if( pTitleLink ) + xTitleSeq = pTitleLink->CreateDataSequence( EXC_CHPROP_ROLE_LABEL ); + + // create the labeled data sequence, if values or title are present + Reference< XLabeledDataSequence > xLabeledSeq; + if( xValueSeq.is() || xTitleSeq.is() ) + xLabeledSeq = LabeledDataSequence::create(comphelper::getProcessComponentContext()); + if( xLabeledSeq.is() ) + { + if( xValueSeq.is() ) + xLabeledSeq->setValues( xValueSeq ); + if( xTitleSeq.is() ) + xLabeledSeq->setLabel( xTitleSeq ); + } + return xLabeledSeq; +} + +} // namespace + +XclImpChSourceLink::XclImpChSourceLink( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +XclImpChSourceLink::~XclImpChSourceLink() +{ +} + +void XclImpChSourceLink::ReadChSourceLink( XclImpStream& rStrm ) +{ + maData.mnDestType = rStrm.ReaduInt8(); + maData.mnLinkType = rStrm.ReaduInt8(); + maData.mnFlags = rStrm.ReaduInt16(); + maData.mnNumFmtIdx = rStrm.ReaduInt16(); + + mxTokenArray.reset(); + if( GetLinkType() == EXC_CHSRCLINK_WORKSHEET ) + { + // read token array + XclTokenArray aXclTokArr; + rStrm >> aXclTokArr; + + // convert BIFF formula tokens to Calc token array + if( std::unique_ptr pTokens = GetFormulaCompiler().CreateFormula( EXC_FMLATYPE_CHART, aXclTokArr ) ) + mxTokenArray = std::move( pTokens ); + } + + // try to read a following CHSTRING record + if( (rStrm.GetNextRecId() == EXC_ID_CHSTRING) && rStrm.StartNextRecord() ) + { + mxString = std::make_shared(); + rStrm.Ignore( 2 ); + mxString->Read( rStrm, XclStrFlags::EightBitLength | XclStrFlags::SeparateFormats ); + } +} + +void XclImpChSourceLink::SetString( const OUString& rString ) +{ + if( !mxString ) + mxString = std::make_shared(); + mxString->SetText( rString ); +} + +void XclImpChSourceLink::SetTextFormats( XclFormatRunVec&& rFormats ) +{ + if( mxString ) + mxString->SetFormats( std::move(rFormats) ); +} + +sal_uInt16 XclImpChSourceLink::GetCellCount() const +{ + sal_uInt32 nCellCount = 0; + if( mxTokenArray ) + { + FormulaTokenArrayPlainIterator aIter(*mxTokenArray); + for( const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next() ) + { + switch( pToken->GetType() ) + { + case ::formula::svSingleRef: + case ::formula::svExternalSingleRef: + // single cell + ++nCellCount; + break; + case ::formula::svDoubleRef: + case ::formula::svExternalDoubleRef: + { + // cell range + const ScComplexRefData& rComplexRef = *pToken->GetDoubleRef(); + ScAddress aAbs1 = rComplexRef.Ref1.toAbs(GetRoot().GetDoc(), ScAddress()); + ScAddress aAbs2 = rComplexRef.Ref2.toAbs(GetRoot().GetDoc(), ScAddress()); + sal_uInt32 nTabs = static_cast(aAbs2.Tab() - aAbs1.Tab() + 1); + sal_uInt32 nCols = static_cast(aAbs2.Col() - aAbs1.Col() + 1); + sal_uInt32 nRows = static_cast(aAbs2.Row() - aAbs1.Row() + 1); + nCellCount += nCols * nRows * nTabs; + } + break; + default: ; + } + } + } + return limit_cast< sal_uInt16 >( nCellCount ); +} + +void XclImpChSourceLink::ConvertNumFmt( ScfPropertySet& rPropSet, bool bPercent ) const +{ + bool bLinkToSource = ::get_flag( maData.mnFlags, EXC_CHSRCLINK_NUMFMT ); + sal_uInt32 nScNumFmt = bLinkToSource ? GetNumFmtBuffer().GetScFormat( maData.mnNumFmtIdx ) : NUMBERFORMAT_ENTRY_NOT_FOUND; + OUString aPropName = bPercent ? OUString( EXC_CHPROP_PERCENTAGENUMFMT ) : OUString( EXC_CHPROP_NUMBERFORMAT ); + if( nScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND ) + rPropSet.SetProperty( aPropName, static_cast< sal_Int32 >( nScNumFmt ) ); + else + // restore 'link to source' at data point (series may contain manual number format) + rPropSet.SetAnyProperty( aPropName, Any() ); +} + +Reference< XDataSequence > XclImpChSourceLink::CreateDataSequence( const OUString& rRole ) const +{ + Reference< XDataSequence > xDataSeq; + Reference< XDataProvider > xDataProv = GetDataProvider(); + if( xDataProv.is() ) + { + if ( mxTokenArray ) + { + ScCompiler aComp( GetDoc(), ScAddress(), *mxTokenArray, GetDoc().GetGrammar() ); + OUStringBuffer aRangeRep; + aComp.CreateStringFromTokenArray( aRangeRep ); + try + { + xDataSeq = xDataProv->createDataSequenceByRangeRepresentation( aRangeRep.makeStringAndClear() ); + // set sequence role + ScfPropertySet aSeqProp( xDataSeq ); + aSeqProp.SetProperty( EXC_CHPROP_ROLE, rRole ); + } + catch( Exception& ) + { + // OSL_FAIL( "XclImpChSourceLink::CreateDataSequence - cannot create data sequence" ); + } + } + else if( rRole == EXC_CHPROP_ROLE_LABEL && mxString && !mxString->GetText().isEmpty() ) + { + try + { + OUString aString("\""); + xDataSeq = xDataProv->createDataSequenceByRangeRepresentation( aString + mxString->GetText() + aString ); + // set sequence role + ScfPropertySet aSeqProp( xDataSeq ); + aSeqProp.SetProperty( EXC_CHPROP_ROLE, rRole ); + } + catch( Exception& ) { } + } + } + return xDataSeq; +} + +Sequence< Reference< XFormattedString > > XclImpChSourceLink::CreateStringSequence( + const XclImpChRoot& rRoot, sal_uInt16 nLeadFontIdx, const Color& rLeadFontColor ) const +{ + ::std::vector< Reference< XFormattedString > > aStringVec; + if( mxString ) + { + for( XclImpStringIterator aIt( *mxString ); aIt.Is(); ++aIt ) + { + Reference< css::chart2::XFormattedString2 > xFmtStr = css::chart2::FormattedString::create( comphelper::getProcessComponentContext() ); + // set text data + xFmtStr->setString( aIt.GetPortionText() ); + + // set font formatting and font color + ScfPropertySet aStringProp( xFmtStr ); + sal_uInt16 nFontIdx = aIt.GetPortionFont(); + if( (nFontIdx == EXC_FONT_NOTFOUND) && (aIt.GetPortionIndex() == 0) ) + // leading unformatted portion - use passed font settings + rRoot.ConvertFont( aStringProp, nLeadFontIdx, &rLeadFontColor ); + else + rRoot.ConvertFont( aStringProp, nFontIdx ); + + // add string to vector of strings + aStringVec.emplace_back(xFmtStr ); + } + } + return ScfApiHelper::VectorToSequence( aStringVec ); +} + +void XclImpChSourceLink::FillSourceLink( ::std::vector< ScTokenRef >& rTokens ) const +{ + if( !mxTokenArray ) + // no links to fill. + return; + + FormulaTokenArrayPlainIterator aIter(*mxTokenArray); + for (FormulaToken* p = aIter.First(); p; p = aIter.Next()) + { + ScTokenRef pToken(p->Clone()); + if (ScRefTokenHelper::isRef(pToken)) + // This is a reference token. Store it. + ScRefTokenHelper::join(&GetRoot().GetDoc(), rTokens, pToken, ScAddress()); + } +} + +// Text ======================================================================= + +XclImpChFontBase::~XclImpChFontBase() +{ +} + +void XclImpChFontBase::ConvertFontBase( const XclImpChRoot& rRoot, ScfPropertySet& rPropSet ) const +{ + Color aFontColor = GetFontColor(); + rRoot.ConvertFont( rPropSet, GetFontIndex(), &aFontColor ); +} + +void XclImpChFontBase::ConvertRotationBase( ScfPropertySet& rPropSet, bool bSupportsStacked ) const +{ + XclChPropSetHelper::WriteRotationProperties( rPropSet, GetRotation(), bSupportsStacked ); +} + +XclImpChFont::XclImpChFont() : + mnFontIdx( EXC_FONT_NOTFOUND ) +{ +} + +void XclImpChFont::ReadChFont( XclImpStream& rStrm ) +{ + mnFontIdx = rStrm.ReaduInt16(); +} + +XclImpChText::XclImpChText( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChText::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnHAlign = rStrm.ReaduInt8(); + maData.mnVAlign = rStrm.ReaduInt8(); + maData.mnBackMode = rStrm.ReaduInt16(); + rStrm >> maData.maTextColor + >> maData.maRect; + maData.mnFlags = rStrm.ReaduInt16(); + + if( GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + maData.maTextColor = GetPalette().GetColor( rStrm.ReaduInt16() ); + // placement and rotation + maData.mnFlags2 = rStrm.ReaduInt16(); + maData.mnRotation = rStrm.ReaduInt16(); + } + else + { + // BIFF2-BIFF7: get rotation from text orientation + sal_uInt8 nOrient = ::extract_value< sal_uInt8 >( maData.mnFlags, 8, 3 ); + maData.mnRotation = XclTools::GetXclRotFromOrient( nOrient ); + } +} + +void XclImpChText::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHFONT: + mxFont = std::make_shared(); + mxFont->ReadChFont( rStrm ); + break; + case EXC_ID_CHFORMATRUNS: + if( GetBiff() == EXC_BIFF8 ) + XclImpString::ReadFormats( rStrm, maFormats ); + break; + case EXC_ID_CHSOURCELINK: + mxSrcLink = std::make_shared( GetChRoot() ); + mxSrcLink->ReadChSourceLink( rStrm ); + break; + case EXC_ID_CHFRAME: + mxFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_TEXT ); + mxFrame->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHOBJECTLINK: + maObjLink.mnTarget = rStrm.ReaduInt16(); + maObjLink.maPointPos.mnSeriesIdx = rStrm.ReaduInt16(); + maObjLink.maPointPos.mnPointIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHFRLABELPROPS: + ReadChFrLabelProps( rStrm ); + break; + case EXC_ID_CHEND: + if( mxSrcLink && !maFormats.empty() ) + mxSrcLink->SetTextFormats( std::vector(maFormats) ); + break; + } +} + +sal_uInt16 XclImpChText::GetFontIndex() const +{ + return mxFont ? mxFont->GetFontIndex() : EXC_FONT_NOTFOUND; +} + +Color XclImpChText::GetFontColor() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTEXT_AUTOCOLOR ) ? GetFontAutoColor() : maData.maTextColor; +} + +sal_uInt16 XclImpChText::GetRotation() const +{ + return maData.mnRotation; +} + +void XclImpChText::SetString( const OUString& rString ) +{ + if( !mxSrcLink ) + mxSrcLink = std::make_shared( GetChRoot() ); + mxSrcLink->SetString( rString ); +} + +void XclImpChText::UpdateText( const XclImpChText* pParentText ) +{ + if( !pParentText ) + return; + + // update missing members + if( !mxFrame ) + mxFrame = pParentText->mxFrame; + if( !mxFont ) + { + mxFont = pParentText->mxFont; + // text color is taken from CHTEXT record, not from font in CHFONT + ::set_flag( maData.mnFlags, EXC_CHTEXT_AUTOCOLOR, ::get_flag( pParentText->maData.mnFlags, EXC_CHTEXT_AUTOCOLOR ) ); + maData.maTextColor = pParentText->maData.maTextColor; + } +} + +void XclImpChText::UpdateDataLabel( bool bCateg, bool bValue, bool bPercent ) +{ + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEG, bCateg ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWVALUE, bValue ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWPERCENT, bPercent ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_SHOWCATEGPERC, bCateg && bPercent ); + ::set_flag( maData.mnFlags, EXC_CHTEXT_DELETED, !bCateg && !bValue && !bPercent ); +} + +void XclImpChText::ConvertFont( ScfPropertySet& rPropSet ) const +{ + ConvertFontBase( GetChRoot(), rPropSet ); +} + +void XclImpChText::ConvertRotation( ScfPropertySet& rPropSet, bool bSupportsStacked ) const +{ + ConvertRotationBase( rPropSet, bSupportsStacked ); +} + +void XclImpChText::ConvertFrame( ScfPropertySet& rPropSet ) const +{ + if( mxFrame ) + mxFrame->Convert( rPropSet ); +} + +void XclImpChText::ConvertNumFmt( ScfPropertySet& rPropSet, bool bPercent ) const +{ + if( mxSrcLink ) + mxSrcLink->ConvertNumFmt( rPropSet, bPercent ); +} + +void XclImpChText::ConvertDataLabel( ScfPropertySet& rPropSet, const XclChTypeInfo& rTypeInfo, const ScfPropertySet* pGlobalPropSet ) const +{ + // existing CHFRLABELPROPS record wins over flags from CHTEXT + sal_uInt16 nShowFlags = mxLabelProps ? mxLabelProps->mnFlags : maData.mnFlags; + sal_uInt16 SHOWANYCATEG = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWCATEG : (EXC_CHTEXT_SHOWCATEGPERC | EXC_CHTEXT_SHOWCATEG); + sal_uInt16 SHOWANYVALUE = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWVALUE : EXC_CHTEXT_SHOWVALUE; + sal_uInt16 SHOWANYPERCENT = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWPERCENT : (EXC_CHTEXT_SHOWPERCENT | EXC_CHTEXT_SHOWCATEGPERC); + sal_uInt16 SHOWANYBUBBLE = mxLabelProps ? EXC_CHFRLABELPROPS_SHOWBUBBLE : EXC_CHTEXT_SHOWBUBBLE; + + // get raw flags for label values + bool bShowNone = IsDeleted(); + bool bShowCateg = !bShowNone && ::get_flag( nShowFlags, SHOWANYCATEG ); + bool bShowPercent = !bShowNone && ::get_flag( nShowFlags, SHOWANYPERCENT ); + bool bShowValue = !bShowNone && ::get_flag( nShowFlags, SHOWANYVALUE ); + bool bShowBubble = !bShowNone && ::get_flag( nShowFlags, SHOWANYBUBBLE ); + + // adjust to Chart2 behaviour + if( rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES ) + bShowValue = bShowBubble; // Chart2 bubble charts show bubble size if 'ShowValue' is set + + // other flags + bool bShowAny = bShowValue || bShowPercent || bShowCateg; + bool bShowSymbol = bShowAny && ::get_flag( maData.mnFlags, EXC_CHTEXT_SHOWSYMBOL ); + + // create API struct for label values, set API label separator + cssc2::DataPointLabel aPointLabel( bShowValue, bShowPercent, bShowCateg, bShowSymbol, false, false ); + rPropSet.SetProperty( EXC_CHPROP_LABEL, aPointLabel ); + OUString aSep = mxLabelProps ? mxLabelProps->maSeparator : OUString('\n'); + if( aSep.isEmpty() ) + aSep = "; "; + rPropSet.SetStringProperty( EXC_CHPROP_LABELSEPARATOR, aSep ); + + // text properties of attached label + if( !bShowAny ) + return; + + ConvertFont( rPropSet ); + ConvertRotation( rPropSet, false ); + // label placement + using namespace cssc::DataLabelPlacement; + sal_Int32 nPlacement = rTypeInfo.mnDefaultLabelPos; + switch( ::extract_value< sal_uInt16 >( maData.mnFlags2, 0, 4 ) ) + { + case EXC_CHTEXT_POS_DEFAULT: nPlacement = rTypeInfo.mnDefaultLabelPos; break; + case EXC_CHTEXT_POS_OUTSIDE: nPlacement = OUTSIDE; break; + case EXC_CHTEXT_POS_INSIDE: nPlacement = INSIDE; break; + case EXC_CHTEXT_POS_CENTER: nPlacement = CENTER; break; + case EXC_CHTEXT_POS_AXIS: nPlacement = NEAR_ORIGIN; break; + case EXC_CHTEXT_POS_ABOVE: nPlacement = TOP; break; + case EXC_CHTEXT_POS_BELOW: nPlacement = BOTTOM; break; + case EXC_CHTEXT_POS_LEFT: nPlacement = LEFT; break; + case EXC_CHTEXT_POS_RIGHT: nPlacement = RIGHT; break; + case EXC_CHTEXT_POS_AUTO: nPlacement = AVOID_OVERLAP; break; + } + sal_Int32 nGlobalPlacement = 0; + if ( ( nPlacement == rTypeInfo.mnDefaultLabelPos ) && pGlobalPropSet && + pGlobalPropSet->GetProperty( nGlobalPlacement, EXC_CHPROP_LABELPLACEMENT ) ) + nPlacement = nGlobalPlacement; + + rPropSet.SetProperty( EXC_CHPROP_LABELPLACEMENT, nPlacement ); + // label number format (percentage format wins over value format) + if( bShowPercent || bShowValue ) + ConvertNumFmt( rPropSet, bShowPercent ); +} + +Reference< XTitle > XclImpChText::CreateTitle() const +{ + Reference< XTitle > xTitle; + if( mxSrcLink && mxSrcLink->HasString() ) + { + // create the formatted strings + Sequence< Reference< XFormattedString > > aStringSeq( + mxSrcLink->CreateStringSequence( GetChRoot(), GetFontIndex(), GetFontColor() ) ); + if( aStringSeq.hasElements() ) + { + // create the title object + xTitle.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_TITLE ), UNO_QUERY ); + if( xTitle.is() ) + { + // set the formatted strings + xTitle->setText( aStringSeq ); + // more title formatting properties + ScfPropertySet aTitleProp( xTitle ); + ConvertFrame( aTitleProp ); + ConvertRotation( aTitleProp, true ); + } + } + } + return xTitle; +} + +void XclImpChText::ConvertTitlePosition( const XclChTextKey& rTitleKey ) const +{ + if( !mxFramePos ) return; + + const XclChFramePos& rPosData = mxFramePos->GetFramePosData(); + OSL_ENSURE( (rPosData.mnTLMode == EXC_CHFRAMEPOS_PARENT) && (rPosData.mnBRMode == EXC_CHFRAMEPOS_PARENT), + "XclImpChText::ConvertTitlePosition - unexpected frame position mode" ); + + /* Check if title is moved manually. To get the actual position of the + title, we do some kind of hack and use the values from the CHTEXT + record, effectively ignoring the contents of the CHFRAMEPOS record + which contains the position relative to the default title position + (according to the spec, the CHFRAMEPOS supersedes the CHTEXT record). + Especially when it comes to axis titles, things would become very + complicated here, because the relative title position is stored in a + measurement unit that is dependent on the size of the inner plot area, + the interpretation of the X and Y coordinate is dependent on the + direction of the axis, and in 3D charts, and the title default + positions are dependent on the 3D view settings (rotation, elevation, + and perspective). Thus, it is easier to assume that the creator has + written out the correct absolute position and size of the title in the + CHTEXT record. This is assured by checking that the shape size stored + in the CHTEXT record is non-zero. */ + if( !((rPosData.mnTLMode == EXC_CHFRAMEPOS_PARENT) && + ((rPosData.maRect.mnX != 0) || (rPosData.maRect.mnY != 0)) && + (maData.maRect.mnWidth > 0) && (maData.maRect.mnHeight > 0)) ) + return; + + try + { + Reference< XShape > xTitleShape( GetTitleShape( rTitleKey ), UNO_SET_THROW ); + // the call to XShape.getSize() may recalc the chart view + css::awt::Size aTitleSize = xTitleShape->getSize(); + // rotated titles need special handling... + Degree100 nScRot = XclTools::GetScRotation( GetRotation(), 0_deg100 ); + double fRad = toRadians(nScRot); + double fSin = fabs( sin( fRad ) ); + // calculate the title position from the values in the CHTEXT record + css::awt::Point aTitlePos( + CalcHmmFromChartX( maData.maRect.mnX ), + CalcHmmFromChartY( maData.maRect.mnY ) ); + // add part of height to X direction, if title is rotated down (clockwise) + if( nScRot > 18000_deg100 ) + aTitlePos.X += static_cast< sal_Int32 >( fSin * aTitleSize.Height + 0.5 ); + // add part of width to Y direction, if title is rotated up (counterclockwise) + else if( nScRot > 0_deg100 ) + aTitlePos.Y += static_cast< sal_Int32 >( fSin * aTitleSize.Width + 0.5 ); + // set the resulting position at the title shape + xTitleShape->setPosition( aTitlePos ); + } + catch( Exception& ) + { + } +} + +void XclImpChText::ReadChFrLabelProps( XclImpStream& rStrm ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + mxLabelProps = std::make_shared(); + sal_uInt16 nSepLen; + rStrm.Ignore( 12 ); + mxLabelProps->mnFlags = rStrm.ReaduInt16(); + nSepLen = rStrm.ReaduInt16(); + if( nSepLen > 0 ) + mxLabelProps->maSeparator = rStrm.ReadUniString( nSepLen ); + } +} + +namespace { + +void lclUpdateText( XclImpChTextRef& rxText, const XclImpChText* xDefText ) +{ + if (rxText) + rxText->UpdateText( xDefText ); + else if (xDefText) + { + rxText = std::make_shared(*xDefText); + } +} + +void lclFinalizeTitle( XclImpChTextRef& rxTitle, const XclImpChText* pDefText, const OUString& rAutoTitle ) +{ + /* Do not update a title, if it is not visible (if rxTitle is null). + Existing reference indicates enabled title. */ + if( rxTitle ) + { + if( !rxTitle->HasString() ) + rxTitle->SetString( rAutoTitle ); + if( rxTitle->HasString() ) + rxTitle->UpdateText(pDefText); + else + rxTitle.reset(); + } +} + +} // namespace + +// Data series ================================================================ + +void XclImpChMarkerFormat::ReadChMarkerFormat( XclImpStream& rStrm ) +{ + rStrm >> maData.maLineColor >> maData.maFillColor; + maData.mnMarkerType = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + + const XclImpRoot& rRoot = rStrm.GetRoot(); + if( rRoot.GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + const XclImpPalette& rPal = rRoot.GetPalette(); + maData.maLineColor = rPal.GetColor( rStrm.ReaduInt16() ); + maData.maFillColor = rPal.GetColor( rStrm.ReaduInt16() ); + // marker size + maData.mnMarkerSize = rStrm.ReaduInt32(); + } +} + +void XclImpChMarkerFormat::Convert( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx, sal_Int16 nLineWeight ) const +{ + if( IsAuto() ) + { + XclChMarkerFormat aMarkerFmt; + // line and fill color of the symbol are equal to series line color + //TODO: Excel sets no fill color for specific symbols (e.g. cross) + aMarkerFmt.maLineColor = aMarkerFmt.maFillColor = rRoot.GetSeriesLineAutoColor( nFormatIdx ); + switch( nLineWeight ) + { + case EXC_CHLINEFORMAT_HAIR: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_HAIRSIZE; break; + case EXC_CHLINEFORMAT_SINGLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_SINGLESIZE; break; + case EXC_CHLINEFORMAT_DOUBLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_DOUBLESIZE; break; + case EXC_CHLINEFORMAT_TRIPLE: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_TRIPLESIZE; break; + default: aMarkerFmt.mnMarkerSize = EXC_CHMARKERFORMAT_SINGLESIZE; + } + aMarkerFmt.mnMarkerType = XclChartHelper::GetAutoMarkerType( nFormatIdx ); + XclChPropSetHelper::WriteMarkerProperties( rPropSet, aMarkerFmt ); + } + else + { + XclChPropSetHelper::WriteMarkerProperties( rPropSet, maData ); + } +} + +void XclImpChMarkerFormat::ConvertColor( const XclImpChRoot& rRoot, + ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) const +{ + Color aLineColor = IsAuto() ? rRoot.GetSeriesLineAutoColor( nFormatIdx ) : maData.maFillColor; + rPropSet.SetColorProperty( EXC_CHPROP_COLOR, aLineColor ); +} + +XclImpChPieFormat::XclImpChPieFormat() : + mnPieDist( 0 ) +{ +} + +void XclImpChPieFormat::ReadChPieFormat( XclImpStream& rStrm ) +{ + mnPieDist = rStrm.ReaduInt16(); +} + +void XclImpChPieFormat::Convert( ScfPropertySet& rPropSet ) const +{ + double fApiDist = ::std::min< double >( mnPieDist / 100.0, 1.0 ); + rPropSet.SetProperty( EXC_CHPROP_OFFSET, fApiDist ); +} + +XclImpChSeriesFormat::XclImpChSeriesFormat() : + mnFlags( 0 ) +{ +} + +void XclImpChSeriesFormat::ReadChSeriesFormat( XclImpStream& rStrm ) +{ + mnFlags = rStrm.ReaduInt16(); +} + +void XclImpCh3dDataFormat::ReadCh3dDataFormat( XclImpStream& rStrm ) +{ + maData.mnBase = rStrm.ReaduInt8(); + maData.mnTop = rStrm.ReaduInt8(); +} + +void XclImpCh3dDataFormat::Convert( ScfPropertySet& rPropSet ) const +{ + using namespace ::com::sun::star::chart2::DataPointGeometry3D; + sal_Int32 nApiType = (maData.mnBase == EXC_CH3DDATAFORMAT_RECT) ? + ((maData.mnTop == EXC_CH3DDATAFORMAT_STRAIGHT) ? CUBOID : PYRAMID) : + ((maData.mnTop == EXC_CH3DDATAFORMAT_STRAIGHT) ? CYLINDER : CONE); + rPropSet.SetProperty( EXC_CHPROP_GEOMETRY3D, nApiType ); +} + +XclImpChAttachedLabel::XclImpChAttachedLabel( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + mnFlags( 0 ) +{ +} + +void XclImpChAttachedLabel::ReadChAttachedLabel( XclImpStream& rStrm ) +{ + mnFlags = rStrm.ReaduInt16(); +} + +XclImpChTextRef XclImpChAttachedLabel::CreateDataLabel( const XclImpChText* pParent ) const +{ + const sal_uInt16 EXC_CHATTLABEL_SHOWANYVALUE = EXC_CHATTLABEL_SHOWVALUE; + const sal_uInt16 EXC_CHATTLABEL_SHOWANYPERCENT = EXC_CHATTLABEL_SHOWPERCENT | EXC_CHATTLABEL_SHOWCATEGPERC; + const sal_uInt16 EXC_CHATTLABEL_SHOWANYCATEG = EXC_CHATTLABEL_SHOWCATEG | EXC_CHATTLABEL_SHOWCATEGPERC; + + XclImpChTextRef xLabel; + if ( pParent ) + xLabel = std::make_shared( *pParent ); + else + xLabel = std::make_shared( GetChRoot() ); + xLabel->UpdateDataLabel( + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYCATEG ), + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYVALUE ), + ::get_flag( mnFlags, EXC_CHATTLABEL_SHOWANYPERCENT ) ); + return xLabel; +} + +XclImpChDataFormat::XclImpChDataFormat( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChDataFormat::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.maPointPos.mnPointIdx = rStrm.ReaduInt16(); + maData.maPointPos.mnSeriesIdx = rStrm.ReaduInt16(); + maData.mnFormatIdx = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChDataFormat::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHMARKERFORMAT: + mxMarkerFmt = std::make_shared(); + mxMarkerFmt->ReadChMarkerFormat( rStrm ); + break; + case EXC_ID_CHPIEFORMAT: + mxPieFmt = std::make_shared(); + mxPieFmt->ReadChPieFormat( rStrm ); + break; + case EXC_ID_CHSERIESFORMAT: + mxSeriesFmt = std::make_shared(); + mxSeriesFmt->ReadChSeriesFormat( rStrm ); + break; + case EXC_ID_CH3DDATAFORMAT: + mx3dDataFmt = std::make_shared(); + mx3dDataFmt->ReadCh3dDataFormat( rStrm ); + break; + case EXC_ID_CHATTACHEDLABEL: + mxAttLabel = std::make_shared( GetChRoot() ); + mxAttLabel->ReadChAttachedLabel( rStrm ); + break; + default: + XclImpChFrameBase::ReadSubRecord( rStrm ); + } +} + +void XclImpChDataFormat::SetPointPos( const XclChDataPointPos& rPointPos, sal_uInt16 nFormatIdx ) +{ + maData.maPointPos = rPointPos; + maData.mnFormatIdx = nFormatIdx; +} + +void XclImpChDataFormat::UpdateGroupFormat( const XclChExtTypeInfo& rTypeInfo ) +{ + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); +} + +void XclImpChDataFormat::UpdateSeriesFormat( const XclChExtTypeInfo& rTypeInfo, const XclImpChDataFormat* pGroupFmt ) +{ + // update missing formats from passed chart type group format + if( pGroupFmt ) + { + if( !mxLineFmt ) + mxLineFmt = pGroupFmt->mxLineFmt; + if( !mxAreaFmt && !mxEscherFmt ) + { + mxAreaFmt = pGroupFmt->mxAreaFmt; + mxEscherFmt = pGroupFmt->mxEscherFmt; + } + if( !mxMarkerFmt ) + mxMarkerFmt = pGroupFmt->mxMarkerFmt; + if( !mxPieFmt ) + mxPieFmt = pGroupFmt->mxPieFmt; + if( !mxSeriesFmt ) + mxSeriesFmt = pGroupFmt->mxSeriesFmt; + if( !mx3dDataFmt ) + mx3dDataFmt = pGroupFmt->mx3dDataFmt; + if( !mxAttLabel ) + mxAttLabel = pGroupFmt->mxAttLabel; + } + + /* Create missing but required formats. Existing line, area, and marker + format objects are needed to create automatic series formatting. */ + if( !mxLineFmt ) + mxLineFmt = new XclImpChLineFormat(); + if( !mxAreaFmt && !mxEscherFmt ) + mxAreaFmt = std::make_shared(); + if( !mxMarkerFmt ) + mxMarkerFmt = std::make_shared(); + + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); + // update data label + UpdateDataLabel( pGroupFmt ); +} + +void XclImpChDataFormat::UpdatePointFormat( const XclChExtTypeInfo& rTypeInfo, const XclImpChDataFormat* pSeriesFmt ) +{ + // remove formats if they are automatic in this and in the passed series format + if( pSeriesFmt ) + { + if( IsAutoLine() && pSeriesFmt->IsAutoLine() ) + mxLineFmt.clear(); + if( IsAutoArea() && pSeriesFmt->IsAutoArea() ) + mxAreaFmt.reset(); + if( IsAutoMarker() && pSeriesFmt->IsAutoMarker() ) + mxMarkerFmt.reset(); + mxSeriesFmt.reset(); + } + + // Excel ignores 3D bar format for single data points + mx3dDataFmt.reset(); + // remove point line formats for linear chart types, TODO: implement in OOChart + if( !rTypeInfo.IsSeriesFrameFormat() ) + mxLineFmt.clear(); + + // remove formats not used for the current chart type + RemoveUnusedFormats( rTypeInfo ); + // update data label + UpdateDataLabel( pSeriesFmt ); +} + +void XclImpChDataFormat::UpdateTrendLineFormat() +{ + if( !mxLineFmt ) + mxLineFmt = new XclImpChLineFormat(); + mxAreaFmt.reset(); + mxEscherFmt.reset(); + mxMarkerFmt.reset(); + mxPieFmt.reset(); + mxSeriesFmt.reset(); + mx3dDataFmt.reset(); + mxAttLabel.reset(); + // update data label + UpdateDataLabel( nullptr ); +} + +void XclImpChDataFormat::Convert( ScfPropertySet& rPropSet, const XclChExtTypeInfo& rTypeInfo, const ScfPropertySet* pGlobalPropSet ) const +{ + /* Line and area format. + #i71810# If the data points are filled with bitmaps, textures, or + patterns, then only bar charts will use the CHPICFORMAT record to + determine stacking/stretching mode. All other chart types ignore this + record and always use the property 'fill-type' from the DFF property + set (stretched for bitmaps, and stacked for textures and patterns). */ + bool bUsePicFmt = rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_BAR; + ConvertFrameBase( GetChRoot(), rPropSet, rTypeInfo.GetSeriesObjectType(), maData.mnFormatIdx, bUsePicFmt ); + + // #i83151# only hair lines in 3D charts with filled data points + if( rTypeInfo.mb3dChart && rTypeInfo.IsSeriesFrameFormat() && mxLineFmt && mxLineFmt->HasLine() ) + rPropSet.SetProperty< sal_Int32 >( "BorderWidth", 0 ); + + // other formatting + if( mxMarkerFmt ) + mxMarkerFmt->Convert( GetChRoot(), rPropSet, maData.mnFormatIdx, GetLineWeight() ); + if( mxPieFmt ) + mxPieFmt->Convert( rPropSet ); + if( mx3dDataFmt ) + mx3dDataFmt->Convert( rPropSet ); + if( mxLabel ) + mxLabel->ConvertDataLabel( rPropSet, rTypeInfo, pGlobalPropSet ); + + // 3D settings + rPropSet.SetProperty< sal_Int16 >( EXC_CHPROP_PERCENTDIAGONAL, 0 ); + + /* Special case: set marker color as line color, if series line is not + visible. This makes the color visible in the marker area. + TODO: remove this if OOChart supports own colors in markers. */ + if( !rTypeInfo.IsSeriesFrameFormat() && !HasLine() && mxMarkerFmt ) + mxMarkerFmt->ConvertColor( GetChRoot(), rPropSet, maData.mnFormatIdx ); +} + +void XclImpChDataFormat::ConvertLine( ScfPropertySet& rPropSet, XclChObjectType eObjType ) const +{ + ConvertLineBase( GetChRoot(), rPropSet, eObjType ); +} + +void XclImpChDataFormat::ConvertArea( ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) const +{ + ConvertAreaBase( GetChRoot(), rPropSet, EXC_CHOBJTYPE_FILLEDSERIES, nFormatIdx ); +} + +void XclImpChDataFormat::RemoveUnusedFormats( const XclChExtTypeInfo& rTypeInfo ) +{ + // data point marker only in linear 2D charts + if( rTypeInfo.IsSeriesFrameFormat() ) + mxMarkerFmt.reset(); + // pie format only in pie/donut charts + if( rTypeInfo.meTypeCateg != EXC_CHTYPECATEG_PIE ) + mxPieFmt.reset(); + // 3D format only in 3D bar charts + if( !rTypeInfo.mb3dChart || (rTypeInfo.meTypeCateg != EXC_CHTYPECATEG_BAR) ) + mx3dDataFmt.reset(); +} + +void XclImpChDataFormat::UpdateDataLabel( const XclImpChDataFormat* pParentFmt ) +{ + /* CHTEXT groups linked to data labels override existing CHATTACHEDLABEL + records. Only if there is a CHATTACHEDLABEL record without a CHTEXT + group, the contents of the CHATTACHEDLABEL record are used. In this + case a new CHTEXT group is created and filled with the settings from + the CHATTACHEDLABEL record. */ + const XclImpChText* pDefText = nullptr; + if (pParentFmt) + pDefText = pParentFmt->GetDataLabel(); + if (!pDefText) + pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_DATALABEL ); + if (mxLabel) + mxLabel->UpdateText(pDefText); + else if (mxAttLabel) + mxLabel = mxAttLabel->CreateDataLabel( pDefText ); +} + +XclImpChSerTrendLine::XclImpChSerTrendLine( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChSerTrendLine::ReadChSerTrendLine( XclImpStream& rStrm ) +{ + maData.mnLineType = rStrm.ReaduInt8(); + maData.mnOrder = rStrm.ReaduInt8(); + maData.mfIntercept = rStrm.ReadDouble(); + maData.mnShowEquation = rStrm.ReaduInt8(); + maData.mnShowRSquared = rStrm.ReaduInt8(); + maData.mfForecastFor = rStrm.ReadDouble(); + maData.mfForecastBack = rStrm.ReadDouble(); +} + +Reference< XRegressionCurve > XclImpChSerTrendLine::CreateRegressionCurve() const +{ + // trend line type + Reference< XRegressionCurve > xRegCurve; + switch( maData.mnLineType ) + { + case EXC_CHSERTREND_POLYNOMIAL: + if( maData.mnOrder == 1 ) + { + xRegCurve = LinearRegressionCurve::create( comphelper::getProcessComponentContext() ); + } else { + xRegCurve = PolynomialRegressionCurve::create( comphelper::getProcessComponentContext() ); + } + break; + case EXC_CHSERTREND_EXPONENTIAL: + xRegCurve = ExponentialRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_LOGARITHMIC: + xRegCurve = LogarithmicRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_POWER: + xRegCurve = PotentialRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + case EXC_CHSERTREND_MOVING_AVG: + xRegCurve = MovingAverageRegressionCurve::create( comphelper::getProcessComponentContext() ); + break; + } + + // trend line formatting + if( xRegCurve.is() && mxDataFmt ) + { + ScfPropertySet aPropSet( xRegCurve ); + mxDataFmt->ConvertLine( aPropSet, EXC_CHOBJTYPE_TRENDLINE ); + + aPropSet.SetProperty(EXC_CHPROP_CURVENAME, maTrendLineName); + aPropSet.SetProperty(EXC_CHPROP_POLYNOMIAL_DEGREE, static_cast (maData.mnOrder) ); + aPropSet.SetProperty(EXC_CHPROP_MOVING_AVERAGE_PERIOD, static_cast (maData.mnOrder) ); + aPropSet.SetProperty(EXC_CHPROP_EXTRAPOLATE_FORWARD, maData.mfForecastFor); + aPropSet.SetProperty(EXC_CHPROP_EXTRAPOLATE_BACKWARD, maData.mfForecastBack); + + bool bForceIntercept = std::isfinite(maData.mfIntercept); + aPropSet.SetProperty(EXC_CHPROP_FORCE_INTERCEPT, bForceIntercept); + if (bForceIntercept) + { + aPropSet.SetProperty(EXC_CHPROP_INTERCEPT_VALUE, maData.mfIntercept); + } + + // #i83100# show equation and correlation coefficient + ScfPropertySet aLabelProp( xRegCurve->getEquationProperties() ); + aLabelProp.SetBoolProperty( EXC_CHPROP_SHOWEQUATION, maData.mnShowEquation != 0 ); + aLabelProp.SetBoolProperty( EXC_CHPROP_SHOWCORRELATION, maData.mnShowRSquared != 0 ); + + // #i83100# formatting of the equation text box + if (const XclImpChText* pLabel = mxDataFmt->GetDataLabel()) + { + pLabel->ConvertFont( aLabelProp ); + pLabel->ConvertFrame( aLabelProp ); + pLabel->ConvertNumFmt( aLabelProp, false ); + } + } + + return xRegCurve; +} + +XclImpChSerErrorBar::XclImpChSerErrorBar( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChSerErrorBar::ReadChSerErrorBar( XclImpStream& rStrm ) +{ + maData.mnBarType = rStrm.ReaduInt8(); + maData.mnSourceType = rStrm.ReaduInt8(); + maData.mnLineEnd = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + maData.mfValue = rStrm.ReadDouble(); + maData.mnValueCount = rStrm.ReaduInt16(); +} + +void XclImpChSerErrorBar::SetSeriesData( XclImpChSourceLinkRef const & xValueLink, XclImpChDataFormatRef const & xDataFmt ) +{ + mxValueLink = xValueLink; + mxDataFmt = xDataFmt; +} + +Reference< XLabeledDataSequence > XclImpChSerErrorBar::CreateValueSequence() const +{ + return lclCreateLabeledDataSequence( mxValueLink, XclChartHelper::GetErrorBarValuesRole( maData.mnBarType ) ); +} + +Reference< XPropertySet > XclImpChSerErrorBar::CreateErrorBar( const XclImpChSerErrorBar* pPosBar, const XclImpChSerErrorBar* pNegBar ) +{ + Reference< XPropertySet > xErrorBar; + + if( const XclImpChSerErrorBar* pPrimaryBar = pPosBar ? pPosBar : pNegBar ) + { + xErrorBar.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_ERRORBAR ), UNO_QUERY ); + ScfPropertySet aBarProp( xErrorBar ); + + // plus/minus bars visible? + aBarProp.SetBoolProperty( EXC_CHPROP_SHOWPOSITIVEERROR, pPosBar != nullptr ); + aBarProp.SetBoolProperty( EXC_CHPROP_SHOWNEGATIVEERROR, pNegBar != nullptr ); + + // type of displayed error + switch( pPrimaryBar->maData.mnSourceType ) + { + case EXC_CHSERERR_PERCENT: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::RELATIVE ); + aBarProp.SetProperty( EXC_CHPROP_POSITIVEERROR, pPrimaryBar->maData.mfValue ); + aBarProp.SetProperty( EXC_CHPROP_NEGATIVEERROR, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_FIXED: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::ABSOLUTE ); + aBarProp.SetProperty( EXC_CHPROP_POSITIVEERROR, pPrimaryBar->maData.mfValue ); + aBarProp.SetProperty( EXC_CHPROP_NEGATIVEERROR, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_STDDEV: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::STANDARD_DEVIATION ); + aBarProp.SetProperty( EXC_CHPROP_WEIGHT, pPrimaryBar->maData.mfValue ); + break; + case EXC_CHSERERR_STDERR: + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::STANDARD_ERROR ); + break; + case EXC_CHSERERR_CUSTOM: + { + aBarProp.SetProperty( EXC_CHPROP_ERRORBARSTYLE, cssc::ErrorBarStyle::FROM_DATA ); + // attach data sequences to error bar + Reference< XDataSink > xDataSink( xErrorBar, UNO_QUERY ); + if( xDataSink.is() ) + { + // create vector of all value sequences + ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; + // add positive values + if( pPosBar ) + { + Reference< XLabeledDataSequence > xValueSeq = pPosBar->CreateValueSequence(); + if( xValueSeq.is() ) + aLabeledSeqVec.push_back( xValueSeq ); + } + // add negative values + if( pNegBar ) + { + Reference< XLabeledDataSequence > xValueSeq = pNegBar->CreateValueSequence(); + if( xValueSeq.is() ) + aLabeledSeqVec.push_back( xValueSeq ); + } + // attach labeled data sequences to series + if( aLabeledSeqVec.empty() ) + xErrorBar.clear(); + else + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + } + } + break; + default: + xErrorBar.clear(); + } + + // error bar formatting + if( pPrimaryBar->mxDataFmt && xErrorBar.is() ) + pPrimaryBar->mxDataFmt->ConvertLine( aBarProp, EXC_CHOBJTYPE_ERRORBAR ); + } + + return xErrorBar; +} + +XclImpChSeries::XclImpChSeries( const XclImpChRoot& rRoot, sal_uInt16 nSeriesIdx ) : + XclImpChRoot( rRoot ), + mnGroupIdx( EXC_CHSERGROUP_NONE ), + mnSeriesIdx( nSeriesIdx ), + mnParentIdx( EXC_CHSERIES_INVALID ), + mbLabelDeleted( false ) +{ +} + +void XclImpChSeries::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnCategType = rStrm.ReaduInt16(); + maData.mnValueType = rStrm.ReaduInt16(); + maData.mnCategCount = rStrm.ReaduInt16(); + maData.mnValueCount = rStrm.ReaduInt16(); + if( GetBiff() == EXC_BIFF8 ) + { + maData.mnBubbleType = rStrm.ReaduInt16(); + maData.mnBubbleCount = rStrm.ReaduInt16(); + } +} + +void XclImpChSeries::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHSOURCELINK: + ReadChSourceLink( rStrm ); + break; + case EXC_ID_CHDATAFORMAT: + ReadChDataFormat( rStrm ); + break; + case EXC_ID_CHSERGROUP: + mnGroupIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHSERPARENT: + ReadChSerParent( rStrm ); + break; + case EXC_ID_CHSERTRENDLINE: + ReadChSerTrendLine( rStrm ); + break; + case EXC_ID_CHSERERRORBAR: + ReadChSerErrorBar( rStrm ); + break; + case EXC_ID_CHLEGENDEXCEPTION: + ReadChLegendException( rStrm ); + break; + } +} + +void XclImpChSeries::SetDataFormat( const XclImpChDataFormatRef& xDataFmt ) +{ + if (!xDataFmt) + return; + + sal_uInt16 nPointIdx = xDataFmt->GetPointPos().mnPointIdx; + if (nPointIdx == EXC_CHDATAFORMAT_ALLPOINTS) + { + if (mxSeriesFmt) + // Don't overwrite the existing format. + return; + + mxSeriesFmt = xDataFmt; + if (HasParentSeries()) + return; + + XclImpChTypeGroupRef pTypeGroup = GetChartData().GetTypeGroup(mnGroupIdx); + if (pTypeGroup) + pTypeGroup->SetUsedFormatIndex(xDataFmt->GetFormatIdx()); + + return; + } + + if (nPointIdx >= EXC_CHDATAFORMAT_MAXPOINTCOUNT) + // Above the max point count. Bail out. + return; + + XclImpChDataFormatMap::iterator itr = maPointFmts.lower_bound(nPointIdx); + if (itr == maPointFmts.end() || maPointFmts.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert it. + itr = maPointFmts.insert(itr, XclImpChDataFormatMap::value_type(nPointIdx, xDataFmt)); + } +} + +void XclImpChSeries::SetDataLabel( const XclImpChTextRef& xLabel ) +{ + if (!xLabel) + return; + + sal_uInt16 nPointIdx = xLabel->GetPointPos().mnPointIdx; + if ((nPointIdx != EXC_CHDATAFORMAT_ALLPOINTS) && (nPointIdx >= EXC_CHDATAFORMAT_MAXPOINTCOUNT)) + // Above the maximum allowed data points. Bail out. + return; + + XclImpChTextMap::iterator itr = maLabels.lower_bound(nPointIdx); + if (itr == maLabels.end() || maLabels.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert it. + itr = maLabels.insert(itr, XclImpChTextMap::value_type(nPointIdx, xLabel)); + } +} + +void XclImpChSeries::AddChildSeries( const XclImpChSeries& rSeries ) +{ + OSL_ENSURE( !HasParentSeries(), "XclImpChSeries::AddChildSeries - not allowed for child series" ); + if (&rSeries == this) + { + SAL_WARN("sc.filter", "self add attempt"); + return; + } + + /* In Excel, trend lines and error bars are stored as own series. In Calc, + these are properties of the parent series. This function adds the + settings of the passed series to this series. */ + maTrendLines.insert( maTrendLines.end(), rSeries.maTrendLines.begin(), rSeries.maTrendLines.end() ); + for (auto const& it : rSeries.m_ErrorBars) + { + m_ErrorBars.insert(std::make_pair(it.first, std::make_unique(*it.second))); + } +} + +void XclImpChSeries::FinalizeDataFormats() +{ + if( HasParentSeries() ) + { + // *** series is a child series, e.g. trend line or error bar *** + + // create missing series format + if( !mxSeriesFmt ) + mxSeriesFmt = CreateDataFormat( EXC_CHDATAFORMAT_ALLPOINTS, 0 ); + + if( mxSeriesFmt ) + { + // #i83100# set text label format, e.g. for trend line equations + XclImpChTextRef xLabel; + XclImpChTextMap::iterator itr = maLabels.find(EXC_CHDATAFORMAT_ALLPOINTS); + if (itr != maLabels.end()) + xLabel = itr->second; + mxSeriesFmt->SetDataLabel(xLabel); + // create missing automatic formats + mxSeriesFmt->UpdateTrendLineFormat(); + } + + // copy series formatting to child objects + for (auto const& trendLine : maTrendLines) + { + trendLine->SetDataFormat(mxSeriesFmt); + if (mxTitleLink && mxTitleLink->HasString()) + { + trendLine->SetTrendlineName(mxTitleLink->GetString()); + } + } + for (auto const& it : m_ErrorBars) + { + it.second->SetSeriesData( mxValueLink, mxSeriesFmt ); + } + } + else if( XclImpChTypeGroup* pTypeGroup = GetChartData().GetTypeGroup( mnGroupIdx ).get() ) + { + // *** series is a regular data series *** + + // create missing series format + if( !mxSeriesFmt ) + { + // #i51639# use a new unused format index to create series default format + sal_uInt16 nFormatIdx = pTypeGroup->PopUnusedFormatIndex(); + mxSeriesFmt = CreateDataFormat( EXC_CHDATAFORMAT_ALLPOINTS, nFormatIdx ); + } + + // set text labels to data formats + for (auto const& label : maLabels) + { + sal_uInt16 nPointIdx = label.first; + if (nPointIdx == EXC_CHDATAFORMAT_ALLPOINTS) + { + if (!mxSeriesFmt) + mxSeriesFmt = CreateDataFormat(nPointIdx, EXC_CHDATAFORMAT_DEFAULT); + mxSeriesFmt->SetDataLabel(label.second); + } + else if (nPointIdx < EXC_CHDATAFORMAT_MAXPOINTCOUNT) + { + XclImpChDataFormatRef p; + XclImpChDataFormatMap::iterator itr = maPointFmts.lower_bound(nPointIdx); + if (itr == maPointFmts.end() || maPointFmts.key_comp()(nPointIdx, itr->first)) + { + // No object exists at this point index position. Insert + // a new one. + p = CreateDataFormat(nPointIdx, EXC_CHDATAFORMAT_DEFAULT); + itr = maPointFmts.insert( + itr, XclImpChDataFormatMap::value_type(nPointIdx, p)); + } + else + p = itr->second; + p->SetDataLabel(label.second); + } + } + + // update series format (copy missing formatting from group default format) + if( mxSeriesFmt ) + mxSeriesFmt->UpdateSeriesFormat( pTypeGroup->GetTypeInfo(), pTypeGroup->GetGroupFormat().get() ); + + // update data point formats (removes unchanged automatic formatting) + for (auto const& pointFormat : maPointFmts) + pointFormat.second->UpdatePointFormat( pTypeGroup->GetTypeInfo(), mxSeriesFmt.get() ); + } +} + +namespace { + +/** Returns the property set of the specified data point. */ +ScfPropertySet lclGetPointPropSet( Reference< XDataSeries > const & xDataSeries, sal_uInt16 nPointIdx ) +{ + ScfPropertySet aPropSet; + try + { + aPropSet.Set( xDataSeries->getDataPointByIndex( static_cast< sal_Int32 >( nPointIdx ) ) ); + } + catch( Exception& ) + { + OSL_FAIL( "lclGetPointPropSet - no data point property set" ); + } + return aPropSet; +} + +} // namespace + +Reference< XLabeledDataSequence > XclImpChSeries::CreateValueSequence( const OUString& rValueRole ) const +{ + return lclCreateLabeledDataSequence( mxValueLink, rValueRole, mxTitleLink.get() ); +} + +Reference< XLabeledDataSequence > XclImpChSeries::CreateCategSequence( const OUString& rCategRole ) const +{ + return lclCreateLabeledDataSequence( mxCategLink, rCategRole ); +} + +Reference< XDataSeries > XclImpChSeries::CreateDataSeries() const +{ + Reference< XDataSeries > xDataSeries; + if( const XclImpChTypeGroup* pTypeGroup = GetChartData().GetTypeGroup( mnGroupIdx ).get() ) + { + const XclChExtTypeInfo& rTypeInfo = pTypeGroup->GetTypeInfo(); + + // create the data series object + xDataSeries.set( ScfApiHelper::CreateInstance( SERVICE_CHART2_DATASERIES ), UNO_QUERY ); + + // attach data and title sequences to series + Reference< XDataSink > xDataSink( xDataSeries, UNO_QUERY ); + if( xDataSink.is() ) + { + // create vector of all value sequences + ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; + // add Y values + Reference< XLabeledDataSequence > xYValueSeq = + CreateValueSequence( EXC_CHPROP_ROLE_YVALUES ); + if( xYValueSeq.is() ) + aLabeledSeqVec.push_back( xYValueSeq ); + // add X values + if( !rTypeInfo.mbCategoryAxis ) + { + Reference< XLabeledDataSequence > xXValueSeq = + CreateCategSequence( EXC_CHPROP_ROLE_XVALUES ); + if( xXValueSeq.is() ) + aLabeledSeqVec.push_back( xXValueSeq ); + // add size values of bubble charts + if( rTypeInfo.meTypeId == EXC_CHTYPEID_BUBBLES ) + { + Reference< XLabeledDataSequence > xSizeValueSeq = + lclCreateLabeledDataSequence( mxBubbleLink, EXC_CHPROP_ROLE_SIZEVALUES, mxTitleLink.get() ); + if( xSizeValueSeq.is() ) + aLabeledSeqVec.push_back( xSizeValueSeq ); + } + } + // attach labeled data sequences to series + if( !aLabeledSeqVec.empty() ) + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + } + + // series formatting + ScfPropertySet aSeriesProp( xDataSeries ); + if( mxSeriesFmt ) + mxSeriesFmt->Convert( aSeriesProp, rTypeInfo ); + + if (mbLabelDeleted) + aSeriesProp.SetProperty(EXC_CHPROP_SHOWLEGENDENTRY, false); + + // trend lines + ConvertTrendLines( xDataSeries ); + + // error bars + Reference< XPropertySet > xErrorBarX = CreateErrorBar( EXC_CHSERERR_XPLUS, EXC_CHSERERR_XMINUS ); + if( xErrorBarX.is() ) + aSeriesProp.SetProperty( EXC_CHPROP_ERRORBARX, xErrorBarX ); + Reference< XPropertySet > xErrorBarY = CreateErrorBar( EXC_CHSERERR_YPLUS, EXC_CHSERERR_YMINUS ); + if( xErrorBarY.is() ) + aSeriesProp.SetProperty( EXC_CHPROP_ERRORBARY, xErrorBarY ); + + // own area formatting for every data point (TODO: varying line color not supported) + bool bVarPointFmt = pTypeGroup->HasVarPointFormat() && rTypeInfo.IsSeriesFrameFormat(); + aSeriesProp.SetBoolProperty( EXC_CHPROP_VARYCOLORSBY, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE ); + // #i91271# always set area formatting for every point in pie/doughnut charts + if (mxSeriesFmt && mxValueLink && ((bVarPointFmt && mxSeriesFmt->IsAutoArea()) || (rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE))) + { + for( sal_uInt16 nPointIdx = 0, nPointCount = mxValueLink->GetCellCount(); nPointIdx < nPointCount; ++nPointIdx ) + { + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, nPointIdx ); + mxSeriesFmt->ConvertArea( aPointProp, bVarPointFmt ? nPointIdx : mnSeriesIdx ); + } + } + + // data point formatting + for (auto const& pointFormat : maPointFmts) + { + ScfPropertySet aPointProp = lclGetPointPropSet( xDataSeries, pointFormat.first ); + pointFormat.second->Convert( aPointProp, rTypeInfo, &aSeriesProp ); + } + } + return xDataSeries; +} + +void XclImpChSeries::FillAllSourceLinks( ::std::vector< ScTokenRef >& rTokens ) const +{ + if( mxValueLink ) + mxValueLink->FillSourceLink( rTokens ); + if( mxCategLink ) + mxCategLink->FillSourceLink( rTokens ); + if( mxTitleLink ) + mxTitleLink->FillSourceLink( rTokens ); + if( mxBubbleLink ) + mxBubbleLink->FillSourceLink( rTokens ); +} + +void XclImpChSeries::ReadChSourceLink( XclImpStream& rStrm ) +{ + XclImpChSourceLinkRef xSrcLink = std::make_shared( GetChRoot() ); + xSrcLink->ReadChSourceLink( rStrm ); + switch( xSrcLink->GetDestType() ) + { + case EXC_CHSRCLINK_TITLE: mxTitleLink = xSrcLink; break; + case EXC_CHSRCLINK_VALUES: mxValueLink = xSrcLink; break; + case EXC_CHSRCLINK_CATEGORY: mxCategLink = xSrcLink; break; + case EXC_CHSRCLINK_BUBBLES: mxBubbleLink = xSrcLink; break; + } +} + +void XclImpChSeries::ReadChDataFormat( XclImpStream& rStrm ) +{ + // #i51639# chart stores all data formats and assigns them later to the series + GetChartData().ReadChDataFormat( rStrm ); +} + +void XclImpChSeries::ReadChSerParent( XclImpStream& rStrm ) +{ + mnParentIdx = rStrm.ReaduInt16(); + // index to parent series is 1-based, convert it to 0-based + if( mnParentIdx > 0 ) + --mnParentIdx; + else + mnParentIdx = EXC_CHSERIES_INVALID; +} + +void XclImpChSeries::ReadChSerTrendLine( XclImpStream& rStrm ) +{ + XclImpChSerTrendLineRef xTrendLine = std::make_shared( GetChRoot() ); + xTrendLine->ReadChSerTrendLine( rStrm ); + maTrendLines.push_back( xTrendLine ); +} + +void XclImpChSeries::ReadChSerErrorBar( XclImpStream& rStrm ) +{ + unique_ptr pErrorBar(new XclImpChSerErrorBar(GetChRoot())); + pErrorBar->ReadChSerErrorBar(rStrm); + sal_uInt8 nBarType = pErrorBar->GetBarType(); + m_ErrorBars.insert(std::make_pair(nBarType, std::move(pErrorBar))); +} + +XclImpChDataFormatRef XclImpChSeries::CreateDataFormat( sal_uInt16 nPointIdx, sal_uInt16 nFormatIdx ) +{ + XclImpChDataFormatRef xDataFmt = std::make_shared( GetChRoot() ); + xDataFmt->SetPointPos( XclChDataPointPos( mnSeriesIdx, nPointIdx ), nFormatIdx ); + return xDataFmt; +} + +void XclImpChSeries::ConvertTrendLines( Reference< XDataSeries > const & xDataSeries ) const +{ + Reference< XRegressionCurveContainer > xRegCurveCont( xDataSeries, UNO_QUERY ); + if( !xRegCurveCont.is() ) + return; + + for (auto const& trendLine : maTrendLines) + { + try + { + Reference< XRegressionCurve > xRegCurve = trendLine->CreateRegressionCurve(); + if( xRegCurve.is() ) + { + xRegCurveCont->addRegressionCurve( xRegCurve ); + } + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChSeries::ConvertTrendLines - cannot add regression curve" ); + } + } +} + +Reference< XPropertySet > XclImpChSeries::CreateErrorBar( sal_uInt8 nPosBarId, sal_uInt8 nNegBarId ) const +{ + XclImpChSerErrorBarMap::const_iterator itrPosBar = m_ErrorBars.find(nPosBarId); + XclImpChSerErrorBarMap::const_iterator itrNegBar = m_ErrorBars.find(nNegBarId); + XclImpChSerErrorBarMap::const_iterator itrEnd = m_ErrorBars.end(); + if (itrPosBar == itrEnd || itrNegBar == itrEnd) + return Reference(); + + return XclImpChSerErrorBar::CreateErrorBar(itrPosBar->second.get(), itrNegBar->second.get()); +} + +void XclImpChSeries::ReadChLegendException(XclImpStream& rStrm) +{ + rStrm.Ignore(2); + sal_uInt16 nFlags = rStrm.ReaduInt16(); + mbLabelDeleted = (nFlags & EXC_CHLEGENDEXCEPTION_DELETED); +} + +// Chart type groups ========================================================== + +XclImpChType::XclImpChType( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + mnRecId( EXC_ID_CHUNKNOWN ), + maTypeInfo( rRoot.GetChartTypeInfo( EXC_CHTYPEID_UNKNOWN ) ) +{ +} + +void XclImpChType::ReadChType( XclImpStream& rStrm ) +{ + sal_uInt16 nRecId = rStrm.GetRecId(); + bool bKnownType = true; + + switch( nRecId ) + { + case EXC_ID_CHBAR: + maData.mnOverlap = rStrm.ReadInt16(); + maData.mnGap = rStrm.ReadInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + break; + + case EXC_ID_CHLINE: + case EXC_ID_CHAREA: + case EXC_ID_CHRADARLINE: + case EXC_ID_CHRADARAREA: + maData.mnFlags = rStrm.ReaduInt16(); + break; + + case EXC_ID_CHPIE: + maData.mnRotation = rStrm.ReaduInt16(); + maData.mnPieHole = rStrm.ReaduInt16(); + if( GetBiff() == EXC_BIFF8 ) + maData.mnFlags = rStrm.ReaduInt16(); + else + maData.mnFlags = 0; + break; + + case EXC_ID_CHPIEEXT: + maData.mnRotation = 0; + maData.mnPieHole = 0; + maData.mnFlags = 0; + break; + + case EXC_ID_CHSCATTER: + if( GetBiff() == EXC_BIFF8 ) + { + maData.mnBubbleSize = rStrm.ReaduInt16(); + maData.mnBubbleType = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + } + else + maData.mnFlags = 0; + break; + + case EXC_ID_CHSURFACE: + maData.mnFlags = rStrm.ReaduInt16(); + break; + + default: + bKnownType = false; + } + + if( bKnownType ) + mnRecId = nRecId; +} + +void XclImpChType::Finalize( bool bStockChart ) +{ + switch( mnRecId ) + { + case EXC_ID_CHLINE: + maTypeInfo = GetChartTypeInfo( bStockChart ? + EXC_CHTYPEID_STOCK : EXC_CHTYPEID_LINE ); + break; + case EXC_ID_CHBAR: + maTypeInfo = GetChartTypeInfo( ::get_flagvalue( + maData.mnFlags, EXC_CHBAR_HORIZONTAL, + EXC_CHTYPEID_HORBAR, EXC_CHTYPEID_BAR ) ); + break; + case EXC_ID_CHPIE: + maTypeInfo = GetChartTypeInfo( (maData.mnPieHole > 0) ? + EXC_CHTYPEID_DONUT : EXC_CHTYPEID_PIE ); + break; + case EXC_ID_CHSCATTER: + maTypeInfo = GetChartTypeInfo( ::get_flagvalue( + maData.mnFlags, EXC_CHSCATTER_BUBBLES, + EXC_CHTYPEID_BUBBLES, EXC_CHTYPEID_SCATTER ) ); + break; + default: + maTypeInfo = GetChartTypeInfo( mnRecId ); + } + + switch( maTypeInfo.meTypeId ) + { + case EXC_CHTYPEID_PIEEXT: + case EXC_CHTYPEID_BUBBLES: + case EXC_CHTYPEID_SURFACE: + case EXC_CHTYPEID_UNKNOWN: + GetTracer().TraceChartUnKnownType(); + break; + default:; + } +} + +bool XclImpChType::IsStacked() const +{ + bool bStacked = false; + if( maTypeInfo.mbSupportsStacking ) switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_LINE: + bStacked = + ::get_flag( maData.mnFlags, EXC_CHLINE_STACKED ) && + !::get_flag( maData.mnFlags, EXC_CHLINE_PERCENT ); + break; + case EXC_CHTYPECATEG_BAR: + bStacked = + ::get_flag( maData.mnFlags, EXC_CHBAR_STACKED ) && + !::get_flag( maData.mnFlags, EXC_CHBAR_PERCENT ); + break; + default:; + } + return bStacked; +} + +bool XclImpChType::IsPercent() const +{ + bool bPercent = false; + if( maTypeInfo.mbSupportsStacking ) switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_LINE: + bPercent = + ::get_flag( maData.mnFlags, EXC_CHLINE_STACKED ) && + ::get_flag( maData.mnFlags, EXC_CHLINE_PERCENT ); + break; + case EXC_CHTYPECATEG_BAR: + bPercent = + ::get_flag( maData.mnFlags, EXC_CHBAR_STACKED ) && + ::get_flag( maData.mnFlags, EXC_CHBAR_PERCENT ); + break; + default:; + } + return bPercent; +} + +bool XclImpChType::HasCategoryLabels() const +{ + // radar charts disable category labels in chart type, not in CHTICK of X axis + return (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_RADAR) || ::get_flag( maData.mnFlags, EXC_CHRADAR_AXISLABELS ); +} + +Reference< XCoordinateSystem > XclImpChType::CreateCoordSystem( bool b3dChart ) const +{ + // create the coordinate system object + Reference< css::uno::XComponentContext > xContext = comphelper::getProcessComponentContext(); + Reference< XCoordinateSystem > xCoordSystem; + if( maTypeInfo.mbPolarCoordSystem ) + { + if( b3dChart ) + xCoordSystem = css::chart2::PolarCoordinateSystem3d::create(xContext); + else + xCoordSystem = css::chart2::PolarCoordinateSystem2d::create(xContext); + } + else + { + if( b3dChart ) + xCoordSystem = css::chart2::CartesianCoordinateSystem3d::create(xContext); + else + xCoordSystem = css::chart2::CartesianCoordinateSystem2d::create(xContext); + } + + // swap X and Y axis + if( maTypeInfo.mbSwappedAxesSet ) + { + ScfPropertySet aCoordSysProp( xCoordSystem ); + aCoordSysProp.SetBoolProperty( EXC_CHPROP_SWAPXANDYAXIS, true ); + } + + return xCoordSystem; +} + +Reference< XChartType > XclImpChType::CreateChartType( Reference< XDiagram > const & xDiagram, bool b3dChart ) const +{ + OUString aService = OUString::createFromAscii( maTypeInfo.mpcServiceName ); + Reference< XChartType > xChartType( ScfApiHelper::CreateInstance( aService ), UNO_QUERY ); + + // additional properties + switch( maTypeInfo.meTypeCateg ) + { + case EXC_CHTYPECATEG_BAR: + { + ScfPropertySet aTypeProp( xChartType ); + Sequence< sal_Int32 > aInt32Seq{ -maData.mnOverlap, -maData.mnOverlap }; + aTypeProp.SetProperty( EXC_CHPROP_OVERLAPSEQ, aInt32Seq ); + aInt32Seq = { maData.mnGap, maData.mnGap }; + aTypeProp.SetProperty( EXC_CHPROP_GAPWIDTHSEQ, aInt32Seq ); + } + break; + case EXC_CHTYPECATEG_PIE: + { + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetBoolProperty( EXC_CHPROP_USERINGS, maTypeInfo.meTypeId == EXC_CHTYPEID_DONUT ); + /* #i85166# starting angle of first pie slice. 3D pie charts use Y + rotation setting in view3D element. Of-pie charts do not + support pie rotation. */ + if( !b3dChart && (maTypeInfo.meTypeId != EXC_CHTYPEID_PIEEXT) ) + { + ScfPropertySet aDiaProp( xDiagram ); + XclImpChRoot::ConvertPieRotation( aDiaProp, maData.mnRotation ); + } + } + break; + default:; + } + + return xChartType; +} + +void XclImpChChart3d::ReadChChart3d( XclImpStream& rStrm ) +{ + maData.mnRotation = rStrm.ReaduInt16(); + maData.mnElevation = rStrm.ReadInt16(); + maData.mnEyeDist = rStrm.ReaduInt16(); + maData.mnRelHeight = rStrm.ReaduInt16(); + maData.mnRelDepth = rStrm.ReaduInt16(); + maData.mnDepthGap = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChChart3d::Convert( ScfPropertySet& rPropSet, bool b3dWallChart ) const +{ + namespace cssd = ::com::sun::star::drawing; + +// #i104057# do not assert this, written by broken external generators +// OSL_ENSURE( ::get_flag( maData.mnFlags, EXC_CHCHART3D_HASWALLS ) == b3dWallChart, "XclImpChChart3d::Convert - wrong wall flag" ); + + sal_Int32 nRotationY = 0; + sal_Int32 nRotationX = 0; + sal_Int32 nPerspective = 15; + bool bRightAngled = false; + cssd::ProjectionMode eProjMode = cssd::ProjectionMode_PERSPECTIVE; + Color aAmbientColor, aLightColor; + + if( b3dWallChart ) + { + // Y rotation (Excel [0..359], Chart2 [-179,180]) + nRotationY = NormAngle180(maData.mnRotation); + // X rotation a.k.a. elevation (Excel [-90..90], Chart2 [-179,180]) + nRotationX = limit_cast< sal_Int32, sal_Int32 >( maData.mnElevation, -90, 90 ); + // perspective (Excel and Chart2 [0,100]) + nPerspective = limit_cast< sal_Int32, sal_Int32 >( maData.mnEyeDist, 0, 100 ); + // right-angled axes + bRightAngled = !::get_flag( maData.mnFlags, EXC_CHCHART3D_REAL3D ); + // projection mode (parallel axes, if right-angled, #i90360# or if perspective is at 0%) + bool bParallel = bRightAngled || (nPerspective == 0); + eProjMode = bParallel ? cssd::ProjectionMode_PARALLEL : cssd::ProjectionMode_PERSPECTIVE; + // ambient color (Gray 20%) + aAmbientColor = Color( 204, 204, 204 ); + // light color (Gray 60%) + aLightColor = Color( 102, 102, 102 ); + } + else + { + // Y rotation not used in pie charts, but 'first pie slice angle' + nRotationY = 0; + XclImpChRoot::ConvertPieRotation( rPropSet, maData.mnRotation ); + // X rotation a.k.a. elevation (map Excel [10..80] to Chart2 [-80,-10]) + nRotationX = limit_cast< sal_Int32, sal_Int32 >( maData.mnElevation, 10, 80 ) - 90; + // perspective (Excel and Chart2 [0,100]) + nPerspective = limit_cast< sal_Int32, sal_Int32 >( maData.mnEyeDist, 0, 100 ); + // no right-angled axes in pie charts, but parallel projection + bRightAngled = false; + eProjMode = cssd::ProjectionMode_PARALLEL; + // ambient color (Gray 30%) + aAmbientColor = Color( 179, 179, 179 ); + // light color (Gray 70%) + aLightColor = Color( 76, 76, 76 ); + } + + // properties + rPropSet.SetProperty( EXC_CHPROP_3DRELATIVEHEIGHT, static_cast(maData.mnRelHeight / 2)); // seems to be 200%, change to 100% + rPropSet.SetProperty( EXC_CHPROP_ROTATIONVERTICAL, nRotationY ); + rPropSet.SetProperty( EXC_CHPROP_ROTATIONHORIZONTAL, nRotationX ); + rPropSet.SetProperty( EXC_CHPROP_PERSPECTIVE, nPerspective ); + rPropSet.SetBoolProperty( EXC_CHPROP_RIGHTANGLEDAXES, bRightAngled ); + rPropSet.SetProperty( EXC_CHPROP_D3DSCENEPERSPECTIVE, eProjMode ); + + // light settings + rPropSet.SetProperty( EXC_CHPROP_D3DSCENESHADEMODE, cssd::ShadeMode_FLAT ); + rPropSet.SetColorProperty( EXC_CHPROP_D3DSCENEAMBIENTCOLOR, aAmbientColor ); + rPropSet.SetBoolProperty( EXC_CHPROP_D3DSCENELIGHTON1, false ); + rPropSet.SetBoolProperty( EXC_CHPROP_D3DSCENELIGHTON2, true ); + rPropSet.SetColorProperty( EXC_CHPROP_D3DSCENELIGHTCOLOR2, aLightColor ); + rPropSet.SetProperty( EXC_CHPROP_D3DSCENELIGHTDIR2, cssd::Direction3D( 0.2, 0.4, 1.0 ) ); +} + +XclImpChLegend::XclImpChLegend( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChLegend::ReadHeaderRecord( XclImpStream& rStrm ) +{ + rStrm >> maData.maRect; + maData.mnDockMode = rStrm.ReaduInt8(); + maData.mnSpacing = rStrm.ReaduInt8(); + maData.mnFlags = rStrm.ReaduInt16(); + + // trace unsupported features + if( GetTracer().IsEnabled() ) + { + if( maData.mnDockMode == EXC_CHLEGEND_NOTDOCKED ) + GetTracer().TraceChartLegendPosition(); + if( ::get_flag( maData.mnFlags, EXC_CHLEGEND_DATATABLE ) ) + GetTracer().TraceChartDataTable(); + } +} + +void XclImpChLegend::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHTEXT: + mxText = std::make_shared( GetChRoot() ); + mxText->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHFRAME: + mxFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_LEGEND ); + mxFrame->ReadRecordGroup( rStrm ); + break; + } +} + +void XclImpChLegend::Finalize() +{ + // legend default formatting differs in OOChart and Excel, missing frame means automatic + if( !mxFrame ) + mxFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_LEGEND ); + // Update text formatting. If mxText is empty, the passed default text is used. + lclUpdateText( mxText, GetChartData().GetDefaultText( EXC_CHTEXTTYPE_LEGEND ) ); +} + +Reference< XLegend > XclImpChLegend::CreateLegend() const +{ + Reference< XLegend > xLegend( ScfApiHelper::CreateInstance( SERVICE_CHART2_LEGEND ), UNO_QUERY ); + if( xLegend.is() ) + { + ScfPropertySet aLegendProp( xLegend ); + aLegendProp.SetBoolProperty( EXC_CHPROP_SHOW, true ); + + // frame properties + if( mxFrame ) + mxFrame->Convert( aLegendProp ); + // text properties + if( mxText ) + mxText->ConvertFont( aLegendProp ); + + /* Legend position and size. Default positions are used only if the + plot area is positioned automatically (Excel sets the plot area to + manual mode, if the legend is moved or resized). With manual plot + areas, Excel ignores the value in maData.mnDockMode completely. */ + cssc2::LegendPosition eApiPos = cssc2::LegendPosition_LINE_END; + cssc::ChartLegendExpansion eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + if( !GetChartData().IsManualPlotArea() ) switch( maData.mnDockMode ) + { + case EXC_CHLEGEND_LEFT: + eApiPos = cssc2::LegendPosition_LINE_START; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + break; + case EXC_CHLEGEND_RIGHT: + // top-right not supported + case EXC_CHLEGEND_CORNER: + eApiPos = cssc2::LegendPosition_LINE_END; + eApiExpand = cssc::ChartLegendExpansion_HIGH; + break; + case EXC_CHLEGEND_TOP: + eApiPos = cssc2::LegendPosition_PAGE_START; + eApiExpand = cssc::ChartLegendExpansion_WIDE; + break; + case EXC_CHLEGEND_BOTTOM: + eApiPos = cssc2::LegendPosition_PAGE_END; + eApiExpand = cssc::ChartLegendExpansion_WIDE; + break; + } + + // no automatic position/size: try to find the correct position and size + if( GetChartData().IsManualPlotArea() || maData.mnDockMode == EXC_CHLEGEND_NOTDOCKED ) + { + const XclChFramePos* pFramePos = mxFramePos ? &mxFramePos->GetFramePosData() : nullptr; + + /* Legend position. Only the settings from the CHFRAMEPOS record + are used by Excel, the position in the CHLEGEND record will be + ignored. */ + if( pFramePos ) + { + RelativePosition aRelPos( + CalcRelativeFromChartX( pFramePos->maRect.mnX ), + CalcRelativeFromChartY( pFramePos->maRect.mnY ), + css::drawing::Alignment_TOP_LEFT ); + aLegendProp.SetProperty( EXC_CHPROP_RELATIVEPOSITION, aRelPos ); + } + else + { + // no manual position/size found, just go for the default + eApiPos = cssc2::LegendPosition_LINE_END; + } + + /* Legend size. The member mnBRMode specifies whether size is + automatic or changes manually. Manual size is given in points, + not in chart units. */ + if( pFramePos && (pFramePos->mnBRMode == EXC_CHFRAMEPOS_ABSSIZE_POINTS) && + (pFramePos->maRect.mnWidth > 0) && (pFramePos->maRect.mnHeight > 0) ) + { + eApiExpand = cssc::ChartLegendExpansion_CUSTOM; + sal_Int32 nWidthHmm = o3tl::convert(pFramePos->maRect.mnWidth, o3tl::Length::pt, o3tl::Length::mm100); + sal_Int32 nHeightHmm = o3tl::convert(pFramePos->maRect.mnHeight, o3tl::Length::pt, o3tl::Length::mm100); + RelativeSize aRelSize( CalcRelativeFromHmmX( nWidthHmm ), CalcRelativeFromHmmY( nHeightHmm ) ); + aLegendProp.SetProperty( EXC_CHPROP_RELATIVESIZE, aRelSize ); + } + else + { + // automatic size: determine entry direction from flags + eApiExpand = ::get_flagvalue( maData.mnFlags, EXC_CHLEGEND_STACKED, + cssc::ChartLegendExpansion_HIGH, cssc::ChartLegendExpansion_WIDE ); + } + } + aLegendProp.SetProperty( EXC_CHPROP_ANCHORPOSITION, eApiPos ); + aLegendProp.SetProperty( EXC_CHPROP_EXPANSION, eApiExpand ); + } + return xLegend; +} + +XclImpChDropBar::XclImpChDropBar( sal_uInt16 nDropBar ) : + mnDropBar( nDropBar ), + mnBarDist( 0 ) +{ +} + +void XclImpChDropBar::ReadHeaderRecord( XclImpStream& rStrm ) +{ + mnBarDist = rStrm.ReaduInt16(); +} + +void XclImpChDropBar::Convert( const XclImpChRoot& rRoot, ScfPropertySet& rPropSet ) const +{ + XclChObjectType eObjType = EXC_CHOBJTYPE_BACKGROUND; + switch( mnDropBar ) + { + case EXC_CHDROPBAR_UP: eObjType = EXC_CHOBJTYPE_WHITEDROPBAR; break; + case EXC_CHDROPBAR_DOWN: eObjType = EXC_CHOBJTYPE_BLACKDROPBAR; break; + } + ConvertFrameBase( rRoot, rPropSet, eObjType ); +} + +XclImpChTypeGroup::XclImpChTypeGroup( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ), + maType( rRoot ), + maTypeInfo( maType.GetTypeInfo() ) +{ + // Initialize unused format indexes set. At this time, all formats are unused. + for( sal_uInt16 nFormatIdx = 0; nFormatIdx <= EXC_CHSERIES_MAXSERIES; ++nFormatIdx ) + maUnusedFormats.insert( maUnusedFormats.end(), nFormatIdx ); +} + +void XclImpChTypeGroup::ReadHeaderRecord( XclImpStream& rStrm ) +{ + rStrm.Ignore( 16 ); + maData.mnFlags = rStrm.ReaduInt16(); + maData.mnGroupIdx = rStrm.ReaduInt16(); +} + +void XclImpChTypeGroup::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHCHART3D: + mxChart3d = std::make_shared(); + mxChart3d->ReadChChart3d( rStrm ); + break; + case EXC_ID_CHLEGEND: + mxLegend = std::make_shared( GetChRoot() ); + mxLegend->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHDEFAULTTEXT: + GetChartData().ReadChDefaultText( rStrm ); + break; + case EXC_ID_CHDROPBAR: + ReadChDropBar( rStrm ); + break; + case EXC_ID_CHCHARTLINE: + ReadChChartLine( rStrm ); + break; + case EXC_ID_CHDATAFORMAT: + ReadChDataFormat( rStrm ); + break; + default: + maType.ReadChType( rStrm ); + } +} + +void XclImpChTypeGroup::Finalize() +{ + // check and set valid chart type + bool bStockChart = + (maType.GetRecId() == EXC_ID_CHLINE) && // must be a line chart + !mxChart3d && // must be a 2d chart + m_ChartLines.find(EXC_CHCHARTLINE_HILO) != m_ChartLines.end() && // must contain hi-lo lines + (maSeries.size() == static_cast(HasDropBars() ? 4 : 3)); // correct series count + maType.Finalize( bStockChart ); + + // extended type info + maTypeInfo.Set( maType.GetTypeInfo(), static_cast< bool >(mxChart3d), false ); + + // reverse series order for some unstacked 2D chart types + if( maTypeInfo.mbReverseSeries && !Is3dChart() && !maType.IsStacked() && !maType.IsPercent() ) + ::std::reverse( maSeries.begin(), maSeries.end() ); + + // update chart type group format, may depend on chart type finalized above + if( mxGroupFmt ) + mxGroupFmt->UpdateGroupFormat( maTypeInfo ); +} + +void XclImpChTypeGroup::AddSeries( XclImpChSeriesRef const & xSeries ) +{ + if( xSeries ) + maSeries.push_back( xSeries ); + // store first inserted series separately, series order may be reversed later + if( !mxFirstSeries ) + mxFirstSeries = xSeries; +} + +void XclImpChTypeGroup::SetUsedFormatIndex( sal_uInt16 nFormatIdx ) +{ + maUnusedFormats.erase( nFormatIdx ); +} + +sal_uInt16 XclImpChTypeGroup::PopUnusedFormatIndex() +{ + OSL_ENSURE( !maUnusedFormats.empty(), "XclImpChTypeGroup::PopUnusedFormatIndex - no more format indexes available" ); + sal_uInt16 nFormatIdx = maUnusedFormats.empty() ? 0 : *maUnusedFormats.begin(); + SetUsedFormatIndex( nFormatIdx ); + return nFormatIdx; +} + +bool XclImpChTypeGroup::HasVarPointFormat() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTYPEGROUP_VARIEDCOLORS ) && + ((maTypeInfo.meVarPointMode == EXC_CHVARPOINT_MULTI) || // multiple series allowed + ((maTypeInfo.meVarPointMode == EXC_CHVARPOINT_SINGLE) && // or exactly 1 series? + (maSeries.size() == 1))); +} + +bool XclImpChTypeGroup::HasConnectorLines() const +{ + // existence of connector lines (only in stacked bar charts) + if ( !(maType.IsStacked() || maType.IsPercent()) || (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_BAR) ) + return false; + XclImpChLineFormatMap::const_iterator aConLine = m_ChartLines.find(EXC_CHCHARTLINE_CONNECT); + return (aConLine != m_ChartLines.end() && aConLine->second.HasLine()); +} + +OUString XclImpChTypeGroup::GetSingleSeriesTitle() const +{ + // no automatic title for series with trendlines or error bars + // pie charts always show an automatic title, even if more series exist + return (mxFirstSeries && !mxFirstSeries->HasChildSeries() && (maTypeInfo.mbSingleSeriesVis || (maSeries.size() == 1))) ? + mxFirstSeries->GetTitle() : OUString(); +} + +void XclImpChTypeGroup::ConvertChart3d( ScfPropertySet& rPropSet ) const +{ + if( mxChart3d ) + mxChart3d->Convert( rPropSet, Is3dWallChart() ); +} + +Reference< XCoordinateSystem > XclImpChTypeGroup::CreateCoordSystem() const +{ + return maType.CreateCoordSystem( Is3dChart() ); +} + +Reference< XChartType > XclImpChTypeGroup::CreateChartType( Reference< XDiagram > const & xDiagram, sal_Int32 nApiAxesSetIdx ) const +{ + OSL_ENSURE( IsValidGroup(), "XclImpChTypeGroup::CreateChartType - type group without series" ); + + // create the chart type object + Reference< XChartType > xChartType = maType.CreateChartType( xDiagram, Is3dChart() ); + + // bar chart connector lines + if( HasConnectorLines() ) + { + ScfPropertySet aDiaProp( xDiagram ); + aDiaProp.SetBoolProperty( EXC_CHPROP_CONNECTBARS, true ); + } + + /* Stock chart needs special processing. Create one 'big' series with + data sequences of different roles. */ + if( maTypeInfo.meTypeId == EXC_CHTYPEID_STOCK ) + CreateStockSeries( xChartType, nApiAxesSetIdx ); + else + CreateDataSeries( xChartType, nApiAxesSetIdx ); + + return xChartType; +} + +Reference< XLabeledDataSequence > XclImpChTypeGroup::CreateCategSequence() const +{ + Reference< XLabeledDataSequence > xLabeledSeq; + // create category sequence from first visible series + if( mxFirstSeries ) + xLabeledSeq = mxFirstSeries->CreateCategSequence( EXC_CHPROP_ROLE_CATEG ); + return xLabeledSeq; +} + +void XclImpChTypeGroup::ReadChDropBar( XclImpStream& rStrm ) +{ + if (m_DropBars.find(EXC_CHDROPBAR_UP) == m_DropBars.end()) + { + unique_ptr p(new XclImpChDropBar(EXC_CHDROPBAR_UP)); + p->ReadRecordGroup(rStrm); + m_DropBars.insert(std::make_pair(EXC_CHDROPBAR_UP, std::move(p))); + } + else if (m_DropBars.find(EXC_CHDROPBAR_DOWN) == m_DropBars.end()) + { + unique_ptr p(new XclImpChDropBar(EXC_CHDROPBAR_DOWN)); + p->ReadRecordGroup(rStrm); + m_DropBars.insert(std::make_pair(EXC_CHDROPBAR_DOWN, std::move(p))); + } +} + +void XclImpChTypeGroup::ReadChChartLine( XclImpStream& rStrm ) +{ + sal_uInt16 nLineId = rStrm.ReaduInt16(); + if( (rStrm.GetNextRecId() == EXC_ID_CHLINEFORMAT) && rStrm.StartNextRecord() ) + { + XclImpChLineFormat aLineFmt; + aLineFmt.ReadChLineFormat( rStrm ); + m_ChartLines[ nLineId ] = aLineFmt; + } +} + +void XclImpChTypeGroup::ReadChDataFormat( XclImpStream& rStrm ) +{ + // global series and data point format + XclImpChDataFormatRef xDataFmt = std::make_shared( GetChRoot() ); + xDataFmt->ReadRecordGroup( rStrm ); + const XclChDataPointPos& rPos = xDataFmt->GetPointPos(); + if( (rPos.mnSeriesIdx == 0) && (rPos.mnPointIdx == 0) && + (xDataFmt->GetFormatIdx() == EXC_CHDATAFORMAT_DEFAULT) ) + mxGroupFmt = xDataFmt; +} + +void XclImpChTypeGroup::InsertDataSeries( Reference< XChartType > const & xChartType, + Reference< XDataSeries > const & xSeries, sal_Int32 nApiAxesSetIdx ) const +{ + Reference< XDataSeriesContainer > xSeriesCont( xChartType, UNO_QUERY ); + if( !(xSeriesCont.is() && xSeries.is()) ) + return; + + // series stacking mode + cssc2::StackingDirection eStacking = cssc2::StackingDirection_NO_STACKING; + // stacked overrides deep-3d + if( maType.IsStacked() || maType.IsPercent() ) + eStacking = cssc2::StackingDirection_Y_STACKING; + else if( Is3dDeepChart() ) + eStacking = cssc2::StackingDirection_Z_STACKING; + + // additional series properties + ScfPropertySet aSeriesProp( xSeries ); + aSeriesProp.SetProperty( EXC_CHPROP_STACKINGDIR, eStacking ); + aSeriesProp.SetProperty( EXC_CHPROP_ATTAXISINDEX, nApiAxesSetIdx ); + + // insert series into container + try + { + xSeriesCont->addDataSeries( xSeries ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChTypeGroup::InsertDataSeries - cannot add data series" ); + } +} + +void XclImpChTypeGroup::CreateDataSeries( Reference< XChartType > const & xChartType, sal_Int32 nApiAxesSetIdx ) const +{ + bool bSpline = false; + for (auto const& elem : maSeries) + { + Reference< XDataSeries > xDataSeries = elem->CreateDataSeries(); + InsertDataSeries( xChartType, xDataSeries, nApiAxesSetIdx ); + bSpline |= elem->HasSpline(); + } + // spline - TODO: set at single series (#i66858#) + if( bSpline && !maTypeInfo.IsSeriesFrameFormat() && (maTypeInfo.meTypeCateg != EXC_CHTYPECATEG_RADAR) ) + { + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetProperty( EXC_CHPROP_CURVESTYLE, css::chart2::CurveStyle_CUBIC_SPLINES ); + } +} + +void XclImpChTypeGroup::CreateStockSeries( Reference< XChartType > const & xChartType, sal_Int32 nApiAxesSetIdx ) const +{ + // create the data series object + Reference< XDataSeries > xDataSeries( ScfApiHelper::CreateInstance( SERVICE_CHART2_DATASERIES ), UNO_QUERY ); + Reference< XDataSink > xDataSink( xDataSeries, UNO_QUERY ); + if( !xDataSink.is() ) + return; + + // create a list of data sequences from all series + ::std::vector< Reference< XLabeledDataSequence > > aLabeledSeqVec; + OSL_ENSURE( maSeries.size() >= 3, "XclImpChTypeGroup::CreateChartType - missing stock series" ); + int nRoleIdx = (maSeries.size() == 3) ? 1 : 0; + for( const auto& rxSeries : maSeries ) + { + // create a data sequence with a specific role + OUString aRole; + switch( nRoleIdx ) + { + case 0: aRole = EXC_CHPROP_ROLE_OPENVALUES; break; + case 1: aRole = EXC_CHPROP_ROLE_HIGHVALUES; break; + case 2: aRole = EXC_CHPROP_ROLE_LOWVALUES; break; + case 3: aRole = EXC_CHPROP_ROLE_CLOSEVALUES; break; + } + Reference< XLabeledDataSequence > xDataSeq = rxSeries->CreateValueSequence( aRole ); + if( xDataSeq.is() ) + aLabeledSeqVec.push_back( xDataSeq ); + ++nRoleIdx; + if (nRoleIdx >= 4) + break; + } + + // attach labeled data sequences to series and insert series into chart type + xDataSink->setData( ScfApiHelper::VectorToSequence( aLabeledSeqVec ) ); + + // formatting of special stock chart elements + ScfPropertySet aTypeProp( xChartType ); + aTypeProp.SetBoolProperty( EXC_CHPROP_JAPANESE, HasDropBars() ); + aTypeProp.SetBoolProperty( EXC_CHPROP_SHOWFIRST, HasDropBars() ); + aTypeProp.SetBoolProperty( EXC_CHPROP_SHOWHIGHLOW, true ); + // hi-lo line format + XclImpChLineFormatMap::const_iterator aHiLoLine = m_ChartLines.find( EXC_CHCHARTLINE_HILO ); + if (aHiLoLine != m_ChartLines.end()) + { + ScfPropertySet aSeriesProp( xDataSeries ); + aHiLoLine->second.Convert( GetChRoot(), aSeriesProp, EXC_CHOBJTYPE_HILOLINE ); + } + // white dropbar format + XclImpChDropBarMap::const_iterator itr = m_DropBars.find(EXC_CHDROPBAR_UP); + Reference xWhitePropSet; + if (itr != m_DropBars.end() && aTypeProp.GetProperty(xWhitePropSet, EXC_CHPROP_WHITEDAY)) + { + ScfPropertySet aBarProp( xWhitePropSet ); + itr->second->Convert(GetChRoot(), aBarProp); + } + // black dropbar format + itr = m_DropBars.find(EXC_CHDROPBAR_DOWN); + Reference xBlackPropSet; + if (itr != m_DropBars.end() && aTypeProp.GetProperty(xBlackPropSet, EXC_CHPROP_BLACKDAY)) + { + ScfPropertySet aBarProp( xBlackPropSet ); + itr->second->Convert(GetChRoot(), aBarProp); + } + + // insert the series into the chart type object + InsertDataSeries( xChartType, xDataSeries, nApiAxesSetIdx ); +} + +// Axes ======================================================================= + +XclImpChLabelRange::XclImpChLabelRange( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChLabelRange::ReadChLabelRange( XclImpStream& rStrm ) +{ + maLabelData.mnCross = rStrm.ReaduInt16(); + maLabelData.mnLabelFreq = rStrm.ReaduInt16(); + maLabelData.mnTickFreq = rStrm.ReaduInt16(); + maLabelData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChLabelRange::ReadChDateRange( XclImpStream& rStrm ) +{ + maDateData.mnMinDate = rStrm.ReaduInt16(); + maDateData.mnMaxDate = rStrm.ReaduInt16(); + maDateData.mnMajorStep = rStrm.ReaduInt16(); + maDateData.mnMajorUnit = rStrm.ReaduInt16(); + maDateData.mnMinorStep = rStrm.ReaduInt16(); + maDateData.mnMinorUnit = rStrm.ReaduInt16(); + maDateData.mnBaseUnit = rStrm.ReaduInt16(); + maDateData.mnCross = rStrm.ReaduInt16(); + maDateData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChLabelRange::Convert( ScfPropertySet& rPropSet, ScaleData& rScaleData, bool bMirrorOrient ) const +{ + // automatic axis type detection + rScaleData.AutoDateAxis = ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTODATE ); + + // the flag EXC_CHDATERANGE_DATEAXIS specifies whether this is a date axis + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ) ) + { + /* Chart2 requires axis type CATEGORY for automatic category/date axis + (even if it is a date axis currently). */ + rScaleData.AxisType = rScaleData.AutoDateAxis ? cssc2::AxisType::CATEGORY : cssc2::AxisType::DATE; + rScaleData.Scaling = css::chart2::LinearScaling::create( comphelper::getProcessComponentContext() ); + /* Min/max values depend on base time unit, they specify the number of + days, months, or years starting from null date. */ + lclConvertTimeValue( GetRoot(), rScaleData.Minimum, maDateData.mnMinDate, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMIN ), maDateData.mnBaseUnit ); + lclConvertTimeValue( GetRoot(), rScaleData.Maximum, maDateData.mnMaxDate, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAX ), maDateData.mnBaseUnit ); + // increment + cssc::TimeIncrement& rTimeIncrement = rScaleData.TimeIncrement; + lclConvertTimeInterval( rTimeIncrement.MajorTimeInterval, maDateData.mnMajorStep, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMAJOR ), maDateData.mnMajorUnit ); + lclConvertTimeInterval( rTimeIncrement.MinorTimeInterval, maDateData.mnMinorStep, ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOMINOR ), maDateData.mnMinorUnit ); + // base unit + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOBASE ) ) + rTimeIncrement.TimeResolution.clear(); + else + rTimeIncrement.TimeResolution <<= lclGetApiTimeUnit( maDateData.mnBaseUnit ); + } + else + { + // do not overlap text unless all labels are visible + rPropSet.SetBoolProperty( EXC_CHPROP_TEXTOVERLAP, maLabelData.mnLabelFreq == 1 ); + // do not break text into several lines unless all labels are visible + rPropSet.SetBoolProperty( EXC_CHPROP_TEXTBREAK, maLabelData.mnLabelFreq == 1 ); + // do not stagger labels in two lines + rPropSet.SetProperty( EXC_CHPROP_ARRANGEORDER, cssc::ChartAxisArrangeOrderType_SIDE_BY_SIDE ); + } + + // reverse order + bool bReverse = ::get_flag( maLabelData.mnFlags, EXC_CHLABELRANGE_REVERSE ) != bMirrorOrient; + rScaleData.Orientation = bReverse ? cssc2::AxisOrientation_REVERSE : cssc2::AxisOrientation_MATHEMATICAL; + + //TODO #i58731# show n-th category +} + +void XclImpChLabelRange::ConvertAxisPosition( ScfPropertySet& rPropSet, bool b3dChart ) const +{ + /* Crossing mode (max-cross flag overrides other crossing settings). Excel + does not move the Y axis in 3D charts, regardless of actual settings. + But: the Y axis has to be moved to "end", if the X axis is mirrored, + to keep it at the left end of the chart. */ + bool bMaxCross = ::get_flag( maLabelData.mnFlags, b3dChart ? EXC_CHLABELRANGE_REVERSE : EXC_CHLABELRANGE_MAXCROSS ); + cssc::ChartAxisPosition eAxisPos = bMaxCross ? cssc::ChartAxisPosition_END : cssc::ChartAxisPosition_VALUE; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERPOSITION, eAxisPos ); + + // crossing position (depending on axis type text/date) + if( ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_DATEAXIS ) ) + { + bool bAutoCross = ::get_flag( maDateData.mnFlags, EXC_CHDATERANGE_AUTOCROSS ); + /* Crossing position value depends on base time unit, it specifies the + number of days, months, or years from null date. Note that Excel + 2007/2010 write broken BIFF8 files, they always stores the number + of days regardless of the base time unit (and they are reading it + the same way, thus wrongly displaying files written by Excel + 97-2003). This filter sticks to the correct behaviour of Excel + 97-2003. */ + double fCrossingPos = bAutoCross ? 1.0 : lclGetSerialDay( GetRoot(), maDateData.mnCross, maDateData.mnBaseUnit ); + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); + } + else + { + double fCrossingPos = b3dChart ? 1.0 : maLabelData.mnCross; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); + } +} + +XclImpChValueRange::XclImpChValueRange( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChValueRange::ReadChValueRange( XclImpStream& rStrm ) +{ + maData.mfMin = rStrm.ReadDouble(); + maData.mfMax = rStrm.ReadDouble(); + maData.mfMajorStep = rStrm.ReadDouble(); + maData.mfMinorStep = rStrm.ReadDouble(); + maData.mfCross = rStrm.ReadDouble(); + maData.mnFlags = rStrm.ReaduInt16(); +} + +void XclImpChValueRange::Convert( ScaleData& rScaleData, bool bMirrorOrient ) const +{ + // scaling algorithm + const bool bLogScale = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE ); + if( bLogScale ) + rScaleData.Scaling = css::chart2::LogarithmicScaling::create( comphelper::getProcessComponentContext() ); + else + rScaleData.Scaling = css::chart2::LinearScaling::create( comphelper::getProcessComponentContext() ); + + // min/max + lclSetExpValueOrClearAny( rScaleData.Minimum, maData.mfMin, bLogScale, ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMIN ) ); + lclSetExpValueOrClearAny( rScaleData.Maximum, maData.mfMax, bLogScale, ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAX ) ); + + // increment + bool bAutoMajor = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMAJOR ); + bool bAutoMinor = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOMINOR ); + // major increment + IncrementData& rIncrementData = rScaleData.IncrementData; + lclSetValueOrClearAny( rIncrementData.Distance, maData.mfMajorStep, bAutoMajor ); + // minor increment + Sequence< SubIncrement >& rSubIncrementSeq = rIncrementData.SubIncrements; + rSubIncrementSeq.realloc( 1 ); + Any& rIntervalCount = rSubIncrementSeq.getArray()[ 0 ].IntervalCount; + rIntervalCount.clear(); + if( bLogScale ) + { + if( !bAutoMinor ) + rIntervalCount <<= sal_Int32( 9 ); + } + else if( !bAutoMajor && !bAutoMinor && (0.0 < maData.mfMinorStep) && (maData.mfMinorStep <= maData.mfMajorStep) ) + { + double fCount = maData.mfMajorStep / maData.mfMinorStep + 0.5; + if( (1.0 <= fCount) && (fCount < 1001.0) ) + rIntervalCount <<= static_cast< sal_Int32 >( fCount ); + } + else if( bAutoMinor ) + { + // tdf#114168 If minor unit is not set then set interval to 5, as MS Excel do. + rIntervalCount <<= static_cast< sal_Int32 >( 5 ); + } + + // reverse order + bool bReverse = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_REVERSE ) != bMirrorOrient; + rScaleData.Orientation = bReverse ? cssc2::AxisOrientation_REVERSE : cssc2::AxisOrientation_MATHEMATICAL; +} + +void XclImpChValueRange::ConvertAxisPosition( ScfPropertySet& rPropSet ) const +{ + bool bMaxCross = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_MAXCROSS ); + bool bAutoCross = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_AUTOCROSS ); + bool bLogScale = ::get_flag( maData.mnFlags, EXC_CHVALUERANGE_LOGSCALE ); + + // crossing mode (max-cross flag overrides other crossing settings) + cssc::ChartAxisPosition eAxisPos = bMaxCross ? cssc::ChartAxisPosition_END : cssc::ChartAxisPosition_VALUE; + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERPOSITION, eAxisPos ); + + // crossing position + double fCrossingPos = bAutoCross ? 0.0 : maData.mfCross; + if( bLogScale ) fCrossingPos = pow( 10.0, fCrossingPos ); + rPropSet.SetProperty( EXC_CHPROP_CROSSOVERVALUE, fCrossingPos ); +} + +namespace { + +sal_Int32 lclGetApiTickmarks( sal_uInt8 nXclTickPos ) +{ + using namespace ::com::sun::star::chart2::TickmarkStyle; + sal_Int32 nApiTickmarks = css::chart2::TickmarkStyle::NONE; + ::set_flag( nApiTickmarks, INNER, ::get_flag( nXclTickPos, EXC_CHTICK_INSIDE ) ); + ::set_flag( nApiTickmarks, OUTER, ::get_flag( nXclTickPos, EXC_CHTICK_OUTSIDE ) ); + return nApiTickmarks; +} + +cssc::ChartAxisLabelPosition lclGetApiLabelPosition( sal_Int8 nXclLabelPos ) +{ + using namespace ::com::sun::star::chart; + switch( nXclLabelPos ) + { + case EXC_CHTICK_LOW: return ChartAxisLabelPosition_OUTSIDE_START; + case EXC_CHTICK_HIGH: return ChartAxisLabelPosition_OUTSIDE_END; + case EXC_CHTICK_NEXT: return ChartAxisLabelPosition_NEAR_AXIS; + } + return ChartAxisLabelPosition_NEAR_AXIS; +} + +} // namespace + +XclImpChTick::XclImpChTick( const XclImpChRoot& rRoot ) : + XclImpChRoot( rRoot ) +{ +} + +void XclImpChTick::ReadChTick( XclImpStream& rStrm ) +{ + maData.mnMajor = rStrm.ReaduInt8(); + maData.mnMinor = rStrm.ReaduInt8(); + maData.mnLabelPos = rStrm.ReaduInt8(); + maData.mnBackMode = rStrm.ReaduInt8(); + rStrm.Ignore( 16 ); + rStrm >> maData.maTextColor; + maData.mnFlags = rStrm.ReaduInt16(); + + if( GetBiff() == EXC_BIFF8 ) + { + // BIFF8: index into palette used instead of RGB data + maData.maTextColor = GetPalette().GetColor( rStrm.ReaduInt16() ); + // rotation + maData.mnRotation = rStrm.ReaduInt16(); + } + else + { + // BIFF2-BIFF7: get rotation from text orientation + sal_uInt8 nOrient = ::extract_value< sal_uInt8 >( maData.mnFlags, 2, 3 ); + maData.mnRotation = XclTools::GetXclRotFromOrient( nOrient ); + } +} + +Color XclImpChTick::GetFontColor() const +{ + return ::get_flag( maData.mnFlags, EXC_CHTICK_AUTOCOLOR ) ? GetFontAutoColor() : maData.maTextColor; +} + +sal_uInt16 XclImpChTick::GetRotation() const +{ + /* n#720443: Ignore auto-rotation if there is a suggested rotation. + * Better fix would be to improve our axis auto rotation algorithm. + */ + if( maData.mnRotation != EXC_ROT_NONE ) + return maData.mnRotation; + return ::get_flag( maData.mnFlags, EXC_CHTICK_AUTOROT ) ? EXC_CHART_AUTOROTATION : maData.mnRotation; +} + +void XclImpChTick::Convert( ScfPropertySet& rPropSet ) const +{ + rPropSet.SetProperty( EXC_CHPROP_MAJORTICKS, lclGetApiTickmarks( maData.mnMajor ) ); + rPropSet.SetProperty( EXC_CHPROP_MINORTICKS, lclGetApiTickmarks( maData.mnMinor ) ); + rPropSet.SetProperty( EXC_CHPROP_LABELPOSITION, lclGetApiLabelPosition( maData.mnLabelPos ) ); + rPropSet.SetProperty( EXC_CHPROP_MARKPOSITION, cssc::ChartAxisMarkPosition_AT_AXIS ); +} + +XclImpChAxis::XclImpChAxis( const XclImpChRoot& rRoot, sal_uInt16 nAxisType ) : + XclImpChRoot( rRoot ), + mnNumFmtIdx( EXC_FORMAT_NOTFOUND ) +{ + maData.mnType = nAxisType; +} + +void XclImpChAxis::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnType = rStrm.ReaduInt16(); +} + +void XclImpChAxis::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHLABELRANGE: + mxLabelRange = std::make_shared( GetChRoot() ); + mxLabelRange->ReadChLabelRange( rStrm ); + break; + case EXC_ID_CHDATERANGE: + if( !mxLabelRange ) + mxLabelRange = std::make_shared( GetChRoot() ); + mxLabelRange->ReadChDateRange( rStrm ); + break; + case EXC_ID_CHVALUERANGE: + mxValueRange = std::make_shared( GetChRoot() ); + mxValueRange->ReadChValueRange( rStrm ); + break; + case EXC_ID_CHFORMAT: + mnNumFmtIdx = rStrm.ReaduInt16(); + break; + case EXC_ID_CHTICK: + mxTick = std::make_shared( GetChRoot() ); + mxTick->ReadChTick( rStrm ); + break; + case EXC_ID_CHFONT: + mxFont = std::make_shared(); + mxFont->ReadChFont( rStrm ); + break; + case EXC_ID_CHAXISLINE: + ReadChAxisLine( rStrm ); + break; + } +} + +void XclImpChAxis::Finalize() +{ + // add default scaling, needed e.g. to adjust rotation direction of pie and radar charts + if( !mxLabelRange ) + mxLabelRange = std::make_shared( GetChRoot() ); + if( !mxValueRange ) + mxValueRange = std::make_shared( GetChRoot() ); + // remove invisible grid lines completely + if( mxMajorGrid && !mxMajorGrid->HasLine() ) + mxMajorGrid.clear(); + if( mxMinorGrid && !mxMinorGrid->HasLine() ) + mxMinorGrid.clear(); + // default tick settings different in OOChart and Excel + if( !mxTick ) + mxTick = std::make_shared( GetChRoot() ); + // #i4140# different default axis line color + if( !mxAxisLine ) + { + XclChLineFormat aLineFmt; + // set "show axis" flag, default if line format record is missing + ::set_flag( aLineFmt.mnFlags, EXC_CHLINEFORMAT_SHOWAXIS ); + mxAxisLine = new XclImpChLineFormat( aLineFmt ); + } + // add wall/floor frame for 3d charts + if( !mxWallFrame ) + CreateWallFrame(); +} + +sal_uInt16 XclImpChAxis::GetFontIndex() const +{ + return mxFont ? mxFont->GetFontIndex() : EXC_FONT_NOTFOUND; +} + +Color XclImpChAxis::GetFontColor() const +{ + return mxTick ? mxTick->GetFontColor() : GetFontAutoColor(); +} + +sal_uInt16 XclImpChAxis::GetRotation() const +{ + return mxTick ? mxTick->GetRotation() : EXC_CHART_AUTOROTATION; +} + +Reference< XAxis > XclImpChAxis::CreateAxis( const XclImpChTypeGroup& rTypeGroup, const XclImpChAxis* pCrossingAxis ) const +{ + // create the axis object (always) + Reference< XAxis > xAxis( ScfApiHelper::CreateInstance( SERVICE_CHART2_AXIS ), UNO_QUERY ); + if( xAxis.is() ) + { + ScfPropertySet aAxisProp( xAxis ); + // #i58688# axis enabled + aAxisProp.SetBoolProperty( EXC_CHPROP_SHOW, !mxAxisLine || mxAxisLine->IsShowAxis() ); + + // axis line properties + if( mxAxisLine ) + mxAxisLine->Convert( GetChRoot(), aAxisProp, EXC_CHOBJTYPE_AXISLINE ); + // axis ticks properties + if( mxTick ) + mxTick->Convert( aAxisProp ); + + // axis caption text -------------------------------------------------- + + // radar charts disable their category labels via chart type, not via axis + bool bHasLabels = (!mxTick || mxTick->HasLabels()) && + ((GetAxisType() != EXC_CHAXIS_X) || rTypeGroup.HasCategoryLabels()); + aAxisProp.SetBoolProperty( EXC_CHPROP_DISPLAYLABELS, bHasLabels ); + if( bHasLabels ) + { + // font settings from CHFONT record or from default text + if( mxFont ) + ConvertFontBase( GetChRoot(), aAxisProp ); + else if( const XclImpChText* pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_AXISLABEL ) ) + pDefText->ConvertFont( aAxisProp ); + // label text rotation + ConvertRotationBase( aAxisProp, true ); + // number format + bool bLinkNumberFmtToSource = true; + if ( mnNumFmtIdx != EXC_FORMAT_NOTFOUND ) + { + sal_uInt32 nScNumFmt = GetNumFmtBuffer().GetScFormat( mnNumFmtIdx ); + if( nScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + aAxisProp.SetProperty( EXC_CHPROP_NUMBERFORMAT, static_cast< sal_Int32 >( nScNumFmt ) ); + bLinkNumberFmtToSource = false; + } + } + + aAxisProp.SetProperty( EXC_CHPROP_NUMBERFORMAT_LINKSRC, bLinkNumberFmtToSource ); + } + + // axis scaling and increment ----------------------------------------- + + const XclChExtTypeInfo& rTypeInfo = rTypeGroup.GetTypeInfo(); + ScaleData aScaleData = xAxis->getScaleData(); + // set axis type + switch( GetAxisType() ) + { + case EXC_CHAXIS_X: + if( rTypeInfo.mbCategoryAxis ) + { + aScaleData.AxisType = cssc2::AxisType::CATEGORY; + aScaleData.Categories = rTypeGroup.CreateCategSequence(); + } + else + aScaleData.AxisType = cssc2::AxisType::REALNUMBER; + break; + case EXC_CHAXIS_Y: + aScaleData.AxisType = rTypeGroup.IsPercent() ? + cssc2::AxisType::PERCENT : cssc2::AxisType::REALNUMBER; + break; + case EXC_CHAXIS_Z: + aScaleData.AxisType = cssc2::AxisType::SERIES; + break; + } + // axis scaling settings, dependent on axis type + switch( aScaleData.AxisType ) + { + case cssc2::AxisType::CATEGORY: + case cssc2::AxisType::SERIES: + // #i71684# radar charts have reversed rotation direction + if (mxLabelRange) + mxLabelRange->Convert( aAxisProp, aScaleData, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_RADAR ); + else + SAL_WARN("sc.filter", "missing LabelRange"); + break; + case cssc2::AxisType::REALNUMBER: + case cssc2::AxisType::PERCENT: + // #i85167# pie/donut charts have reversed rotation direction (at Y axis!) + if (mxValueRange) + mxValueRange->Convert( aScaleData, rTypeInfo.meTypeCateg == EXC_CHTYPECATEG_PIE ); + else + SAL_WARN("sc.filter", "missing ValueRange"); + break; + default: + OSL_FAIL( "XclImpChAxis::CreateAxis - unknown axis type" ); + } + + /* Do not set a value to the Origin member anymore (will be done via + new axis properties 'CrossoverPosition' and 'CrossoverValue'). */ + aScaleData.Origin.clear(); + + // write back + xAxis->setScaleData( aScaleData ); + + // grid --------------------------------------------------------------- + + // main grid + ScfPropertySet aGridProp( xAxis->getGridProperties() ); + aGridProp.SetBoolProperty( EXC_CHPROP_SHOW, static_cast(mxMajorGrid) ); + if( mxMajorGrid ) + mxMajorGrid->Convert( GetChRoot(), aGridProp, EXC_CHOBJTYPE_GRIDLINE ); + // sub grid + Sequence< Reference< XPropertySet > > aSubGridPropSeq = xAxis->getSubGridProperties(); + if( aSubGridPropSeq.hasElements() ) + { + ScfPropertySet aSubGridProp( aSubGridPropSeq[ 0 ] ); + aSubGridProp.SetBoolProperty( EXC_CHPROP_SHOW, static_cast(mxMinorGrid) ); + if( mxMinorGrid ) + mxMinorGrid->Convert( GetChRoot(), aSubGridProp, EXC_CHOBJTYPE_GRIDLINE ); + } + + // position of crossing axis ------------------------------------------ + + if( pCrossingAxis ) + pCrossingAxis->ConvertAxisPosition( aAxisProp, rTypeGroup ); + } + return xAxis; +} + +void XclImpChAxis::ConvertWall( ScfPropertySet& rPropSet ) const +{ + // #i71810# walls and floor in 3D charts use the CHPICFORMAT record for bitmap mode + if( mxWallFrame ) + mxWallFrame->Convert( rPropSet, true ); +} + +void XclImpChAxis::ConvertAxisPosition( ScfPropertySet& rPropSet, const XclImpChTypeGroup& rTypeGroup ) const +{ + if( ((GetAxisType() == EXC_CHAXIS_X) && rTypeGroup.GetTypeInfo().mbCategoryAxis) || (GetAxisType() == EXC_CHAXIS_Z) ) + { + if (mxLabelRange) + mxLabelRange->ConvertAxisPosition( rPropSet, rTypeGroup.Is3dChart() ); + else + SAL_WARN("sc.filter", "missing LabelRange"); + } + else + { + if (mxValueRange) + mxValueRange->ConvertAxisPosition( rPropSet ); + else + SAL_WARN("sc.filter", "missing ValueRange"); + } +} + +void XclImpChAxis::ReadChAxisLine( XclImpStream& rStrm ) +{ + XclImpChLineFormatRef* pxLineFmt = nullptr; + bool bWallFrame = false; + switch( rStrm.ReaduInt16() ) + { + case EXC_CHAXISLINE_AXISLINE: pxLineFmt = &mxAxisLine; break; + case EXC_CHAXISLINE_MAJORGRID: pxLineFmt = &mxMajorGrid; break; + case EXC_CHAXISLINE_MINORGRID: pxLineFmt = &mxMinorGrid; break; + case EXC_CHAXISLINE_WALLS: bWallFrame = true; break; + } + if( bWallFrame ) + CreateWallFrame(); + + bool bLoop = pxLineFmt || bWallFrame; + while( bLoop ) + { + sal_uInt16 nRecId = rStrm.GetNextRecId(); + bLoop = ((nRecId == EXC_ID_CHLINEFORMAT) || + (nRecId == EXC_ID_CHAREAFORMAT) || + (nRecId == EXC_ID_CHESCHERFORMAT)) + && rStrm.StartNextRecord(); + if( bLoop ) + { + if( pxLineFmt && (nRecId == EXC_ID_CHLINEFORMAT) ) + { + (*pxLineFmt) = new XclImpChLineFormat(); + (*pxLineFmt)->ReadChLineFormat( rStrm ); + } + else if( bWallFrame && mxWallFrame ) + { + mxWallFrame->ReadSubRecord( rStrm ); + } + } + } +} + +void XclImpChAxis::CreateWallFrame() +{ + switch( GetAxisType() ) + { + case EXC_CHAXIS_X: + mxWallFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_WALL3D ); + break; + case EXC_CHAXIS_Y: + mxWallFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_FLOOR3D ); + break; + default: + mxWallFrame.reset(); + } +} + +XclImpChAxesSet::XclImpChAxesSet( const XclImpChRoot& rRoot, sal_uInt16 nAxesSetId ) : + XclImpChRoot( rRoot ) +{ + maData.mnAxesSetId = nAxesSetId; +} + +void XclImpChAxesSet::ReadHeaderRecord( XclImpStream& rStrm ) +{ + maData.mnAxesSetId = rStrm.ReaduInt16(); + rStrm >> maData.maRect; +} + +void XclImpChAxesSet::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAMEPOS: + mxFramePos = std::make_shared(); + mxFramePos->ReadChFramePos( rStrm ); + break; + case EXC_ID_CHAXIS: + ReadChAxis( rStrm ); + break; + case EXC_ID_CHTEXT: + ReadChText( rStrm ); + break; + case EXC_ID_CHPLOTFRAME: + ReadChPlotFrame( rStrm ); + break; + case EXC_ID_CHTYPEGROUP: + ReadChTypeGroup( rStrm ); + break; + } +} + +void XclImpChAxesSet::Finalize() +{ + if( IsValidAxesSet() ) + { + // finalize chart type groups, erase empty groups without series + XclImpChTypeGroupMap aValidGroups; + for (auto const& typeGroup : maTypeGroups) + { + XclImpChTypeGroupRef xTypeGroup = typeGroup.second; + xTypeGroup->Finalize(); + if( xTypeGroup->IsValidGroup() ) + aValidGroups.emplace(typeGroup.first, xTypeGroup); + } + maTypeGroups.swap( aValidGroups ); + } + + // invalid chart type groups are deleted now, check again with IsValidAxesSet() + if( !IsValidAxesSet() ) + return; + + // always create missing axis objects + if( !mxXAxis ) + mxXAxis = std::make_shared( GetChRoot(), EXC_CHAXIS_X ); + if( !mxYAxis ) + mxYAxis = std::make_shared( GetChRoot(), EXC_CHAXIS_Y ); + if( !mxZAxis && GetFirstTypeGroup()->Is3dDeepChart() ) + mxZAxis = std::make_shared( GetChRoot(), EXC_CHAXIS_Z ); + + // finalize axes + if( mxXAxis ) mxXAxis->Finalize(); + if( mxYAxis ) mxYAxis->Finalize(); + if( mxZAxis ) mxZAxis->Finalize(); + + // finalize axis titles + const XclImpChText* pDefText = GetChartData().GetDefaultText( EXC_CHTEXTTYPE_AXISTITLE ); + OUString aAutoTitle(ScResId(STR_AXISTITLE)); + lclFinalizeTitle( mxXAxisTitle, pDefText, aAutoTitle ); + lclFinalizeTitle( mxYAxisTitle, pDefText, aAutoTitle ); + lclFinalizeTitle( mxZAxisTitle, pDefText, aAutoTitle ); + + // #i47745# missing plot frame -> invisible border and area + if( !mxPlotFrame ) + mxPlotFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_PLOTFRAME ); +} + +XclImpChTypeGroupRef XclImpChAxesSet::GetTypeGroup( sal_uInt16 nGroupIdx ) const +{ + XclImpChTypeGroupMap::const_iterator itr = maTypeGroups.find(nGroupIdx); + return itr == maTypeGroups.end() ? XclImpChTypeGroupRef() : itr->second; +} + +XclImpChTypeGroupRef XclImpChAxesSet::GetFirstTypeGroup() const +{ + XclImpChTypeGroupRef xTypeGroup; + if( !maTypeGroups.empty() ) + xTypeGroup = maTypeGroups.begin()->second; + return xTypeGroup; +} + +XclImpChLegendRef XclImpChAxesSet::GetLegend() const +{ + XclImpChLegendRef xLegend; + for( const auto& rEntry : maTypeGroups ) + { + xLegend = rEntry.second->GetLegend(); + if (xLegend) + break; + } + return xLegend; +} + +OUString XclImpChAxesSet::GetSingleSeriesTitle() const +{ + return (maTypeGroups.size() == 1) ? maTypeGroups.begin()->second->GetSingleSeriesTitle() : OUString(); +} + +void XclImpChAxesSet::Convert( Reference< XDiagram > const & xDiagram ) const +{ + if( !(IsValidAxesSet() && xDiagram.is()) ) + return; + + // diagram background formatting + if( GetAxesSetId() == EXC_CHAXESSET_PRIMARY ) + ConvertBackground( xDiagram ); + + // create the coordinate system, this inserts all chart types and series + Reference< XCoordinateSystem > xCoordSystem = CreateCoordSystem( xDiagram ); + if( !xCoordSystem.is() ) + return; + + // insert coordinate system, if not already done + try + { + Reference< XCoordinateSystemContainer > xCoordSystemCont( xDiagram, UNO_QUERY_THROW ); + Sequence< Reference< XCoordinateSystem > > aCoordSystems = xCoordSystemCont->getCoordinateSystems(); + if( !aCoordSystems.hasElements() ) + xCoordSystemCont->addCoordinateSystem( xCoordSystem ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::Convert - cannot insert coordinate system" ); + } + + // create the axes with grids and axis titles and insert them into the diagram + ConvertAxis( mxXAxis, mxXAxisTitle, xCoordSystem, mxYAxis.get() ); + ConvertAxis( mxYAxis, mxYAxisTitle, xCoordSystem, mxXAxis.get() ); + ConvertAxis( mxZAxis, mxZAxisTitle, xCoordSystem, nullptr ); +} + +void XclImpChAxesSet::ConvertTitlePositions() const +{ + if( mxXAxisTitle ) + mxXAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_X ) ); + if( mxYAxisTitle ) + mxYAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_Y ) ); + if( mxZAxisTitle ) + mxZAxisTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, maData.mnAxesSetId, EXC_CHAXIS_Z ) ); +} + +void XclImpChAxesSet::ReadChAxis( XclImpStream& rStrm ) +{ + XclImpChAxisRef xAxis = std::make_shared( GetChRoot() ); + xAxis->ReadRecordGroup( rStrm ); + + switch( xAxis->GetAxisType() ) + { + case EXC_CHAXIS_X: mxXAxis = xAxis; break; + case EXC_CHAXIS_Y: mxYAxis = xAxis; break; + case EXC_CHAXIS_Z: mxZAxis = xAxis; break; + } +} + +void XclImpChAxesSet::ReadChText( XclImpStream& rStrm ) +{ + XclImpChTextRef xText = std::make_shared( GetChRoot() ); + xText->ReadRecordGroup( rStrm ); + + switch( xText->GetLinkTarget() ) + { + case EXC_CHOBJLINK_XAXIS: mxXAxisTitle = xText; break; + case EXC_CHOBJLINK_YAXIS: mxYAxisTitle = xText; break; + case EXC_CHOBJLINK_ZAXIS: mxZAxisTitle = xText; break; + } +} + +void XclImpChAxesSet::ReadChPlotFrame( XclImpStream& rStrm ) +{ + if( (rStrm.GetNextRecId() == EXC_ID_CHFRAME) && rStrm.StartNextRecord() ) + { + mxPlotFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_PLOTFRAME ); + mxPlotFrame->ReadRecordGroup( rStrm ); + } +} + +void XclImpChAxesSet::ReadChTypeGroup( XclImpStream& rStrm ) +{ + XclImpChTypeGroupRef xTypeGroup = std::make_shared( GetChRoot() ); + xTypeGroup->ReadRecordGroup( rStrm ); + sal_uInt16 nGroupIdx = xTypeGroup->GetGroupIdx(); + XclImpChTypeGroupMap::iterator itr = maTypeGroups.lower_bound(nGroupIdx); + if (itr != maTypeGroups.end() && !maTypeGroups.key_comp()(nGroupIdx, itr->first)) + // Overwrite the existing element. + itr->second = xTypeGroup; + else + maTypeGroups.insert( + itr, XclImpChTypeGroupMap::value_type(nGroupIdx, xTypeGroup)); +} + +Reference< XCoordinateSystem > XclImpChAxesSet::CreateCoordSystem( Reference< XDiagram > const & xDiagram ) const +{ + Reference< XCoordinateSystem > xCoordSystem; + + /* Try to get existing coordinate system. For now, all series from primary + and secondary axes sets are inserted into one coordinate system. Later, + this should be changed to use one coordinate system for each axes set. */ + Reference< XCoordinateSystemContainer > xCoordSystemCont( xDiagram, UNO_QUERY ); + if( xCoordSystemCont.is() ) + { + Sequence< Reference< XCoordinateSystem > > aCoordSystems = xCoordSystemCont->getCoordinateSystems(); + OSL_ENSURE( aCoordSystems.getLength() <= 1, "XclImpChAxesSet::CreateCoordSystem - too many existing coordinate systems" ); + if( aCoordSystems.hasElements() ) + xCoordSystem = aCoordSystems[ 0 ]; + } + + // create the coordinate system according to the first chart type + if( !xCoordSystem.is() ) + { + XclImpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + if( xTypeGroup ) + { + xCoordSystem = xTypeGroup->CreateCoordSystem(); + // convert 3d chart settings + ScfPropertySet aDiaProp( xDiagram ); + xTypeGroup->ConvertChart3d( aDiaProp ); + } + } + + /* Create XChartType objects for all chart type groups. Each group will + add its series to the data provider attached to the chart document. */ + Reference< XChartTypeContainer > xChartTypeCont( xCoordSystem, UNO_QUERY ); + if( xChartTypeCont.is() ) + { + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + for( const auto& rEntry : maTypeGroups ) + { + try + { + Reference< XChartType > xChartType = rEntry.second->CreateChartType( xDiagram, nApiAxesSetIdx ); + if( xChartType.is() ) + xChartTypeCont->addChartType( xChartType ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::CreateCoordSystem - cannot add chart type" ); + } + } + } + + return xCoordSystem; +} + +void XclImpChAxesSet::ConvertAxis( + XclImpChAxisRef const & xChAxis, XclImpChTextRef const & xChAxisTitle, + Reference< XCoordinateSystem > const & xCoordSystem, const XclImpChAxis* pCrossingAxis ) const +{ + if( !xChAxis ) + return; + + // create and attach the axis object + Reference< XAxis > xAxis = CreateAxis( *xChAxis, pCrossingAxis ); + if( !xAxis.is() ) + return; + + // create and attach the axis title + if( xChAxisTitle ) try + { + Reference< XTitled > xTitled( xAxis, UNO_QUERY_THROW ); + Reference< XTitle > xTitle( xChAxisTitle->CreateTitle(), UNO_SET_THROW ); + xTitled->setTitleObject( xTitle ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::ConvertAxis - cannot set axis title" ); + } + + // insert axis into coordinate system + try + { + sal_Int32 nApiAxisDim = xChAxis->GetApiAxisDimension(); + sal_Int32 nApiAxesSetIdx = GetApiAxesSetIndex(); + xCoordSystem->setAxisByDimension( nApiAxisDim, xAxis, nApiAxesSetIdx ); + } + catch( Exception& ) + { + OSL_FAIL( "XclImpChAxesSet::ConvertAxis - cannot set axis" ); + } +} + +Reference< XAxis > XclImpChAxesSet::CreateAxis( const XclImpChAxis& rChAxis, const XclImpChAxis* pCrossingAxis ) const +{ + Reference< XAxis > xAxis; + if( const XclImpChTypeGroup* pTypeGroup = GetFirstTypeGroup().get() ) + xAxis = rChAxis.CreateAxis( *pTypeGroup, pCrossingAxis ); + return xAxis; +} + +void XclImpChAxesSet::ConvertBackground( Reference< XDiagram > const & xDiagram ) const +{ + XclImpChTypeGroupRef xTypeGroup = GetFirstTypeGroup(); + if( xTypeGroup && xTypeGroup->Is3dWallChart() ) + { + // wall/floor formatting (3D charts) + if( mxXAxis ) + { + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxXAxis->ConvertWall( aWallProp ); + } + if( mxYAxis ) + { + ScfPropertySet aFloorProp( xDiagram->getFloor() ); + mxYAxis->ConvertWall( aFloorProp ); + } + } + else if( mxPlotFrame ) + { + // diagram background formatting + ScfPropertySet aWallProp( xDiagram->getWall() ); + mxPlotFrame->Convert( aWallProp ); + } +} + +// The chart object =========================================================== + +XclImpChChart::XclImpChChart( const XclImpRoot& rRoot ) : + XclImpChRoot( rRoot, *this ) +{ + mxPrimAxesSet = std::make_shared( GetChRoot(), EXC_CHAXESSET_PRIMARY ); + mxSecnAxesSet = std::make_shared( GetChRoot(), EXC_CHAXESSET_SECONDARY ); +} + +XclImpChChart::~XclImpChChart() +{ +} + +void XclImpChChart::ReadHeaderRecord( XclImpStream& rStrm ) +{ + // coordinates are stored as 16.16 fixed point + rStrm >> maRect; +} + +void XclImpChChart::ReadSubRecord( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_CHFRAME: + mxFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + mxFrame->ReadRecordGroup( rStrm ); + break; + case EXC_ID_CHSERIES: + ReadChSeries( rStrm ); + break; + case EXC_ID_CHPROPERTIES: + ReadChProperties( rStrm ); + break; + case EXC_ID_CHDEFAULTTEXT: + ReadChDefaultText( rStrm ); + break; + case EXC_ID_CHAXESSET: + ReadChAxesSet( rStrm ); + break; + case EXC_ID_CHTEXT: + ReadChText( rStrm ); + break; + case EXC_ID_CHEND: + Finalize(); // finalize the entire chart object + break; + } +} + +void XclImpChChart::ReadChDefaultText( XclImpStream& rStrm ) +{ + sal_uInt16 nTextId = rStrm.ReaduInt16(); + if( (rStrm.GetNextRecId() == EXC_ID_CHTEXT) && rStrm.StartNextRecord() ) + { + unique_ptr pText(new XclImpChText(GetChRoot())); + pText->ReadRecordGroup(rStrm); + m_DefTexts.insert(std::make_pair(nTextId, std::move(pText))); + } +} + +void XclImpChChart::ReadChDataFormat( XclImpStream& rStrm ) +{ + XclImpChDataFormatRef xDataFmt = std::make_shared( GetChRoot() ); + xDataFmt->ReadRecordGroup( rStrm ); + if( xDataFmt->GetPointPos().mnSeriesIdx <= EXC_CHSERIES_MAXSERIES ) + { + const XclChDataPointPos& rPos = xDataFmt->GetPointPos(); + XclImpChDataFormatMap::iterator itr = maDataFmts.lower_bound(rPos); + if (itr == maDataFmts.end() || maDataFmts.key_comp()(rPos, itr->first)) + // No element exists for this data point. Insert it. + maDataFmts.insert( + itr, XclImpChDataFormatMap::value_type(rPos, xDataFmt)); + + /* Do not overwrite existing data format group, Excel always uses the + first data format group occurring in any CHSERIES group. */ + } +} + +void XclImpChChart::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + if( !mxFrame ) + mxFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + mxFrame->UpdateObjFrame( rLineData, rFillData ); +} + +XclImpChTypeGroupRef XclImpChChart::GetTypeGroup( sal_uInt16 nGroupIdx ) const +{ + XclImpChTypeGroupRef xTypeGroup = mxPrimAxesSet->GetTypeGroup( nGroupIdx ); + if( !xTypeGroup ) xTypeGroup = mxSecnAxesSet->GetTypeGroup( nGroupIdx ); + if( !xTypeGroup ) xTypeGroup = mxPrimAxesSet->GetFirstTypeGroup(); + return xTypeGroup; +} + +const XclImpChText* XclImpChChart::GetDefaultText( XclChTextType eTextType ) const +{ + sal_uInt16 nDefTextId = EXC_CHDEFTEXT_GLOBAL; + bool bBiff8 = GetBiff() == EXC_BIFF8; + switch( eTextType ) + { + case EXC_CHTEXTTYPE_TITLE: nDefTextId = EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_LEGEND: nDefTextId = EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_AXISTITLE: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_AXISLABEL: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + case EXC_CHTEXTTYPE_DATALABEL: nDefTextId = bBiff8 ? EXC_CHDEFTEXT_AXESSET : EXC_CHDEFTEXT_GLOBAL; break; + } + + XclImpChTextMap::const_iterator const itr = m_DefTexts.find(nDefTextId); + return itr == m_DefTexts.end() ? nullptr : itr->second.get(); +} + +bool XclImpChChart::IsManualPlotArea() const +{ + // there is no real automatic mode in BIFF5 charts + return (GetBiff() <= EXC_BIFF5) || ::get_flag( maProps.mnFlags, EXC_CHPROPS_USEMANPLOTAREA ); +} + +void XclImpChChart::Convert( const Reference& xChartDoc, + XclImpDffConverter& rDffConv, const OUString& rObjName, const tools::Rectangle& rChartRect ) const +{ + // initialize conversion (locks the model to suppress any internal updates) + InitConversion( xChartDoc, rChartRect ); + + // chart frame formatting + if( mxFrame ) + { + ScfPropertySet aFrameProp( xChartDoc->getPageBackground() ); + mxFrame->Convert( aFrameProp ); + } + + // chart title + if( mxTitle ) try + { + Reference< XTitled > xTitled( xChartDoc, UNO_QUERY_THROW ); + Reference< XTitle > xTitle( mxTitle->CreateTitle(), UNO_SET_THROW ); + xTitled->setTitleObject( xTitle ); + } + catch( Exception& ) + { + } + + /* Create the diagram object and attach it to the chart document. Currently, + one diagram is used to carry all coordinate systems and data series. */ + Reference< XDiagram > xDiagram = CreateDiagram(); + xChartDoc->setFirstDiagram( xDiagram ); + + // coordinate systems and chart types, convert axis settings + mxPrimAxesSet->Convert( xDiagram ); + mxSecnAxesSet->Convert( xDiagram ); + + // legend + if( xDiagram.is() && mxLegend ) + xDiagram->setLegend( mxLegend->CreateLegend() ); + + /* Following all conversions needing the old Chart1 API that involves full + initialization of the chart view. */ + Reference< cssc::XChartDocument > xChart1Doc( xChartDoc, UNO_QUERY ); + if( xChart1Doc.is() ) + { + Reference< cssc::XDiagram > xDiagram1 = xChart1Doc->getDiagram(); + + /* Set the 'IncludeHiddenCells' property via the old API as only this + ensures that the data provider and all created sequences get this + flag correctly. */ + ScfPropertySet aDiaProp( xDiagram1 ); + bool bShowVisCells = ::get_flag( maProps.mnFlags, EXC_CHPROPS_SHOWVISIBLEONLY ); + aDiaProp.SetBoolProperty( EXC_CHPROP_INCLUDEHIDDENCELLS, !bShowVisCells ); + + // plot area position and size (there is no real automatic mode in BIFF5 charts) + XclImpChFramePosRef xPlotAreaPos = mxPrimAxesSet->GetPlotAreaFramePos(); + if( IsManualPlotArea() && xPlotAreaPos ) try + { + const XclChFramePos& rFramePos = xPlotAreaPos->GetFramePosData(); + if( (rFramePos.mnTLMode == EXC_CHFRAMEPOS_PARENT) && (rFramePos.mnBRMode == EXC_CHFRAMEPOS_PARENT) ) + { + Reference< cssc::XDiagramPositioning > xPositioning( xDiagram1, UNO_QUERY_THROW ); + css::awt::Rectangle aDiagramRect = CalcHmmFromChartRect( rFramePos.maRect ); + // for pie charts, always set inner plot area size to exclude the data labels as Excel does + const XclImpChTypeGroup* pFirstTypeGroup = mxPrimAxesSet->GetFirstTypeGroup().get(); + if( pFirstTypeGroup && (pFirstTypeGroup->GetTypeInfo().meTypeCateg == EXC_CHTYPECATEG_PIE) ) + xPositioning->setDiagramPositionExcludingAxes( aDiagramRect ); + else if( pFirstTypeGroup && pFirstTypeGroup->Is3dChart() ) + xPositioning->setDiagramPositionIncludingAxesAndAxisTitles( aDiagramRect ); + else + xPositioning->setDiagramPositionIncludingAxes( aDiagramRect ); + } + } + catch( Exception& ) + { + } + + // positions of all title objects + if( mxTitle ) + mxTitle->ConvertTitlePosition( XclChTextKey( EXC_CHTEXTTYPE_TITLE ) ); + mxPrimAxesSet->ConvertTitlePositions(); + mxSecnAxesSet->ConvertTitlePositions(); + } + + // unlock the model + FinishConversion( rDffConv ); + + // start listening to this chart + ScDocument& rDoc = GetRoot().GetDoc(); + ScChartListenerCollection* pChartCollection = rDoc.GetChartListenerCollection(); + if(!pChartCollection) + return; + + std::vector< ScTokenRef > aRefTokens; + for( const auto& rxSeries : maSeries ) + rxSeries->FillAllSourceLinks( aRefTokens ); + if( !aRefTokens.empty() ) + { + ::std::unique_ptr< ScChartListener > xListener( new ScChartListener( rObjName, rDoc, std::move(aRefTokens) ) ); + xListener->SetUsed( true ); + xListener->StartListeningTo(); + pChartCollection->insert( xListener.release() ); + } +} + +void XclImpChChart::ReadChSeries( XclImpStream& rStrm ) +{ + sal_uInt16 nNewSeriesIdx = static_cast< sal_uInt16 >( maSeries.size() ); + XclImpChSeriesRef xSeries = std::make_shared( GetChRoot(), nNewSeriesIdx ); + xSeries->ReadRecordGroup( rStrm ); + maSeries.push_back( xSeries ); +} + +void XclImpChChart::ReadChProperties( XclImpStream& rStrm ) +{ + maProps.mnFlags = rStrm.ReaduInt16(); + maProps.mnEmptyMode = rStrm.ReaduInt8(); +} + +void XclImpChChart::ReadChAxesSet( XclImpStream& rStrm ) +{ + XclImpChAxesSetRef xAxesSet = std::make_shared( GetChRoot(), EXC_CHAXESSET_NONE ); + xAxesSet->ReadRecordGroup( rStrm ); + switch( xAxesSet->GetAxesSetId() ) + { + case EXC_CHAXESSET_PRIMARY: mxPrimAxesSet = xAxesSet; break; + case EXC_CHAXESSET_SECONDARY: mxSecnAxesSet = xAxesSet; break; + } +} + +void XclImpChChart::ReadChText( XclImpStream& rStrm ) +{ + XclImpChTextRef xText = std::make_shared( GetChRoot() ); + xText->ReadRecordGroup( rStrm ); + switch( xText->GetLinkTarget() ) + { + case EXC_CHOBJLINK_TITLE: + mxTitle = xText; + break; + case EXC_CHOBJLINK_DATA: + { + sal_uInt16 nSeriesIdx = xText->GetPointPos().mnSeriesIdx; + if( nSeriesIdx < maSeries.size() ) + maSeries[ nSeriesIdx ]->SetDataLabel( xText ); + } + break; + } +} + +void XclImpChChart::Finalize() +{ + // finalize series (must be done first) + FinalizeSeries(); + // #i49218# legend may be attached to primary or secondary axes set + mxLegend = mxPrimAxesSet->GetLegend(); + if( !mxLegend ) + mxLegend = mxSecnAxesSet->GetLegend(); + if( mxLegend ) + mxLegend->Finalize(); + // axes sets, updates chart type group default formats -> must be called before FinalizeDataFormats() + mxPrimAxesSet->Finalize(); + mxSecnAxesSet->Finalize(); + // formatting of all series + FinalizeDataFormats(); + // #i47745# missing frame -> invisible border and area + if( !mxFrame ) + mxFrame = std::make_shared( GetChRoot(), EXC_CHOBJTYPE_BACKGROUND ); + // chart title + FinalizeTitle(); +} + +void XclImpChChart::FinalizeSeries() +{ + for( const XclImpChSeriesRef& xSeries : maSeries ) + { + if( xSeries->HasParentSeries() ) + { + /* Process child series (trend lines and error bars). Data of + child series will be set at the connected parent series. */ + if( xSeries->GetParentIdx() < maSeries.size() ) + maSeries[ xSeries->GetParentIdx() ]->AddChildSeries( *xSeries ); + } + else + { + // insert the series into the related chart type group + if( XclImpChTypeGroup* pTypeGroup = GetTypeGroup( xSeries->GetGroupIdx() ).get() ) + pTypeGroup->AddSeries( xSeries ); + } + } +} + +void XclImpChChart::FinalizeDataFormats() +{ + /* #i51639# (part 1): CHDATAFORMAT groups are part of CHSERIES groups. + Each CHDATAFORMAT group specifies the series and data point it is + assigned to. This makes it possible to have a data format that is + related to another series, e.g. a CHDATAFORMAT group for series 2 is + part of a CHSERIES group that describes series 1. Therefore the chart + itself has collected all CHDATAFORMAT groups to be able to store data + format groups for series that have not been imported at that time. This + loop finally assigns these groups to the related series. */ + for( const auto& [rPos, rDataFmt] : maDataFmts ) + { + sal_uInt16 nSeriesIdx = rPos.mnSeriesIdx; + if( nSeriesIdx < maSeries.size() ) + maSeries[ nSeriesIdx ]->SetDataFormat( rDataFmt ); + } + + /* #i51639# (part 2): Finalize data formats of all series. This adds for + example missing CHDATAFORMAT groups for entire series that are needed + for automatic colors of lines and areas. */ + for( auto& rxSeries : maSeries ) + rxSeries->FinalizeDataFormats(); +} + +void XclImpChChart::FinalizeTitle() +{ + // special handling for auto-generated title + OUString aAutoTitle; + if( !mxTitle || (!mxTitle->IsDeleted() && !mxTitle->HasString()) ) + { + // automatic title from first series name (if there are no series on secondary axes set) + if( !mxSecnAxesSet->IsValidAxesSet() ) + aAutoTitle = mxPrimAxesSet->GetSingleSeriesTitle(); + if( mxTitle || (!aAutoTitle.isEmpty()) ) + { + if( !mxTitle ) + mxTitle = std::make_shared( GetChRoot() ); + if( aAutoTitle.isEmpty() ) + aAutoTitle = ScResId(STR_CHARTTITLE); + } + } + + // will reset mxTitle, if it does not contain a string and no auto title exists + lclFinalizeTitle( mxTitle, GetDefaultText( EXC_CHTEXTTYPE_TITLE ), aAutoTitle ); +} + +Reference< XDiagram > XclImpChChart::CreateDiagram() const +{ + // create a diagram object + Reference< XDiagram > xDiagram( ScfApiHelper::CreateInstance( SERVICE_CHART2_DIAGRAM ), UNO_QUERY ); + + // convert global chart settings + ScfPropertySet aDiaProp( xDiagram ); + + // treatment of missing values + using namespace cssc::MissingValueTreatment; + sal_Int32 nMissingValues = LEAVE_GAP; + switch( maProps.mnEmptyMode ) + { + case EXC_CHPROPS_EMPTY_SKIP: nMissingValues = LEAVE_GAP; break; + case EXC_CHPROPS_EMPTY_ZERO: nMissingValues = USE_ZERO; break; + case EXC_CHPROPS_EMPTY_INTERPOLATE: nMissingValues = CONTINUE; break; + } + aDiaProp.SetProperty( EXC_CHPROP_MISSINGVALUETREATMENT, nMissingValues ); + + return xDiagram; +} + +XclImpChartDrawing::XclImpChartDrawing( const XclImpRoot& rRoot, bool bOwnTab ) : + XclImpDrawing( rRoot, bOwnTab ), // sheet charts may contain OLE objects + mnScTab( rRoot.GetCurrScTab() ), + mbOwnTab( bOwnTab ) +{ +} + +void XclImpChartDrawing::ConvertObjects( XclImpDffConverter& rDffConv, + const Reference< XModel >& rxModel, const tools::Rectangle& rChartRect ) +{ + maChartRect = rChartRect; // needed in CalcAnchorRect() callback + + SdrModel* pSdrModel = nullptr; + SdrPage* pSdrPage = nullptr; + if( mbOwnTab ) + { + // chart sheet: insert all shapes into the sheet, not into the chart object + pSdrModel = GetDoc().GetDrawLayer(); + pSdrPage = GetSdrPage( mnScTab ); + } + else + { + // embedded chart object: insert all shapes into the chart + try + { + Reference< XDrawPageSupplier > xDrawPageSupp( rxModel, UNO_QUERY_THROW ); + Reference< XDrawPage > xDrawPage( xDrawPageSupp->getDrawPage(), UNO_SET_THROW ); + pSdrPage = ::GetSdrPageFromXDrawPage( xDrawPage ); + pSdrModel = pSdrPage ? &pSdrPage->getSdrModelFromSdrPage() : nullptr; + } + catch( Exception& ) + { + } + } + + if( pSdrModel && pSdrPage ) + ImplConvertObjects( rDffConv, *pSdrModel, *pSdrPage ); +} + +tools::Rectangle XclImpChartDrawing::CalcAnchorRect( const XclObjAnchor& rAnchor, bool bDffAnchor ) const +{ + /* In objects with DFF client anchor, the position of the shape is stored + in the cell address components of the client anchor. In old BIFF3-BIFF5 + objects, the position is stored in the offset components of the anchor. */ + tools::Rectangle aRect( + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maFirst.mnCol : rAnchor.mnLX ) / EXC_CHART_TOTALUNITS * maChartRect.GetWidth() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maFirst.mnRow : rAnchor.mnTY ) / EXC_CHART_TOTALUNITS * maChartRect.GetHeight() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maLast.mnCol : rAnchor.mnRX ) / EXC_CHART_TOTALUNITS * maChartRect.GetWidth() + 0.5 ), + static_cast< tools::Long >( static_cast< double >( bDffAnchor ? rAnchor.maLast.mnRow : rAnchor.mnBY ) / EXC_CHART_TOTALUNITS * maChartRect.GetHeight() + 0.5 ) ); + aRect.Justify(); + // move shapes into chart area for sheet charts + if( mbOwnTab ) + aRect.Move( maChartRect.Left(), maChartRect.Top() ); + return aRect; +} + +void XclImpChartDrawing::OnObjectInserted( const XclImpDrawObjBase& ) +{ +} + +XclImpChart::XclImpChart( const XclImpRoot& rRoot, bool bOwnTab ) : + XclImpRoot( rRoot ), + mbOwnTab( bOwnTab ), + mbIsPivotChart( false ) +{ +} + +XclImpChart::~XclImpChart() +{ +} + +void XclImpChart::ReadChartSubStream( XclImpStream& rStrm ) +{ + XclImpPageSettings& rPageSett = GetPageSettings(); + XclImpTabViewSettings& rTabViewSett = GetTabViewSettings(); + + bool bLoop = true; + while( bLoop && rStrm.StartNextRecord() ) + { + // page settings - only for charts in entire sheet + if( mbOwnTab ) switch( rStrm.GetRecId() ) + { + case EXC_ID_HORPAGEBREAKS: + case EXC_ID_VERPAGEBREAKS: rPageSett.ReadPageBreaks( rStrm ); break; + case EXC_ID_HEADER: + case EXC_ID_FOOTER: rPageSett.ReadHeaderFooter( rStrm ); break; + case EXC_ID_LEFTMARGIN: + case EXC_ID_RIGHTMARGIN: + case EXC_ID_TOPMARGIN: + case EXC_ID_BOTTOMMARGIN: rPageSett.ReadMargin( rStrm ); break; + case EXC_ID_PRINTHEADERS: rPageSett.ReadPrintHeaders( rStrm ); break; + case EXC_ID_PRINTGRIDLINES: rPageSett.ReadPrintGridLines( rStrm ); break; + case EXC_ID_HCENTER: + case EXC_ID_VCENTER: rPageSett.ReadCenter( rStrm ); break; + case EXC_ID_SETUP: rPageSett.ReadSetup( rStrm ); break; + case EXC_ID8_IMGDATA: rPageSett.ReadImgData( rStrm ); break; + + case EXC_ID_WINDOW2: rTabViewSett.ReadWindow2( rStrm, true );break; + case EXC_ID_SCL: rTabViewSett.ReadScl( rStrm ); break; + + case EXC_ID_SHEETEXT: //0x0862 + { + // FIXME: do not need to pass palette, XclImpTabVieSettings is derived from root + XclImpPalette& rPal = GetPalette(); + rTabViewSett.ReadTabBgColor( rStrm, rPal); + } + break; + + case EXC_ID_CODENAME: ReadCodeName( rStrm, false ); break; + } + + // common records + switch( rStrm.GetRecId() ) + { + case EXC_ID_EOF: bLoop = false; break; + + // #i31882# ignore embedded chart objects + case EXC_ID2_BOF: + case EXC_ID3_BOF: + case EXC_ID4_BOF: + case EXC_ID5_BOF: XclTools::SkipSubStream( rStrm ); break; + + case EXC_ID_CHCHART: ReadChChart( rStrm ); break; + + case EXC_ID8_CHPIVOTREF: + GetTracer().TracePivotChartExists(); + mbIsPivotChart = true; + break; + + // BIFF specific records + default: switch( GetBiff() ) + { + case EXC_BIFF5: switch( rStrm.GetRecId() ) + { + case EXC_ID_OBJ: GetChartDrawing().ReadObj( rStrm ); break; + } + break; + case EXC_BIFF8: switch( rStrm.GetRecId() ) + { + case EXC_ID_MSODRAWING: GetChartDrawing().ReadMsoDrawing( rStrm ); break; + // #i61786# weird documents: OBJ without MSODRAWING -> read in BIFF5 format + case EXC_ID_OBJ: GetChartDrawing().ReadObj( rStrm ); break; + } + break; + default:; + } + } + } +} + +void XclImpChart::UpdateObjFrame( const XclObjLineData& rLineData, const XclObjFillData& rFillData ) +{ + if( !mxChartData ) + mxChartData = std::make_shared( GetRoot() ); + mxChartData->UpdateObjFrame( rLineData, rFillData ); +} + +std::size_t XclImpChart::GetProgressSize() const +{ + return + (mxChartData ? XclImpChChart::GetProgressSize() : 0) + + (mxChartDrawing ? mxChartDrawing->GetProgressSize() : 0); +} + +void XclImpChart::Convert( Reference< XModel > const & xModel, XclImpDffConverter& rDffConv, const OUString& rObjName, const tools::Rectangle& rChartRect ) const +{ + Reference< XChartDocument > xChartDoc( xModel, UNO_QUERY ); + if( xChartDoc.is() ) + { + if( mxChartData ) + mxChartData->Convert( xChartDoc, rDffConv, rObjName, rChartRect ); + if( mxChartDrawing ) + mxChartDrawing->ConvertObjects( rDffConv, xModel, rChartRect ); + } +} + +XclImpChartDrawing& XclImpChart::GetChartDrawing() +{ + if( !mxChartDrawing ) + mxChartDrawing = std::make_shared( GetRoot(), mbOwnTab ); + return *mxChartDrawing; +} + +void XclImpChart::ReadChChart( XclImpStream& rStrm ) +{ + mxChartData = std::make_shared( GetRoot() ); + mxChartData->ReadRecordGroup( rStrm ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xicontent.cxx b/sc/source/filter/excel/xicontent.cxx new file mode 100644 index 000000000..872632a1c --- /dev/null +++ b/sc/source/filter/excel/xicontent.cxx @@ -0,0 +1,1442 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +using ::com::sun::star::uno::Sequence; +using ::std::unique_ptr; + +// Shared string table ======================================================== + +XclImpSst::XclImpSst( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpSst::ReadSst( XclImpStream& rStrm ) +{ + rStrm.Ignore( 4 ); + sal_uInt32 nStrCount = rStrm.ReaduInt32(); + auto nBytesAvailable = rStrm.GetRecLeft(); + if (nStrCount > nBytesAvailable) + { + SAL_WARN("sc.filter", "xls claimed to have " << nStrCount << " strings, but only " << nBytesAvailable << " bytes available, truncating"); + nStrCount = nBytesAvailable; + } + maStrings.clear(); + maStrings.reserve(nStrCount); + while( (nStrCount > 0) && rStrm.IsValid() ) + { + XclImpString aString; + aString.Read( rStrm ); + maStrings.push_back( aString ); + --nStrCount; + } +} + +const XclImpString* XclImpSst::GetString( sal_uInt32 nSstIndex ) const +{ + return (nSstIndex < maStrings.size()) ? &maStrings[ nSstIndex ] : nullptr; +} + +// Hyperlinks ================================================================= + +namespace { + +/** Reads character array and stores it into rString. + @param nChars Number of following characters (not byte count!). + @param b16Bit true = 16-bit characters, false = 8-bit characters. */ +void lclAppendString32( OUString& rString, XclImpStream& rStrm, sal_uInt32 nChars, bool b16Bit ) +{ + sal_uInt16 nReadChars = ulimit_cast< sal_uInt16 >( nChars ); + rString += rStrm.ReadRawUniString( nReadChars, b16Bit ); + // ignore remaining chars + std::size_t nIgnore = nChars - nReadChars; + if( b16Bit ) + nIgnore *= 2; + rStrm.Ignore( nIgnore ); +} + +/** Reads 32-bit string length and the character array and stores it into rString. + @param b16Bit true = 16-bit characters, false = 8-bit characters. */ +void lclAppendString32( OUString& rString, XclImpStream& rStrm, bool b16Bit ) +{ + lclAppendString32( rString, rStrm, rStrm.ReaduInt32(), b16Bit ); +} + +/** Reads 32-bit string length and ignores following 16-bit character array. */ +void lclIgnoreString32( XclImpStream& rStrm ) +{ + sal_uInt32 nChars = rStrm.ReaduInt32(); + nChars *= 2; + rStrm.Ignore( nChars ); +} + +/** Converts a path to an absolute path. + @param rPath The source path. The resulting path is returned here. + @param nLevel Number of parent directories to add in front of the path. */ +void lclGetAbsPath( OUString& rPath, sal_uInt16 nLevel, const SfxObjectShell* pDocShell ) +{ + OUStringBuffer aTmpStr; + while( nLevel ) + { + aTmpStr.append( "../" ); + --nLevel; + } + aTmpStr.append( rPath ); + + if( pDocShell ) + { + bool bWasAbs = false; + rPath = pDocShell->GetMedium()->GetURLObject().smartRel2Abs( aTmpStr.makeStringAndClear(), bWasAbs ).GetMainURL( INetURLObject::DecodeMechanism::NONE ); + // full path as stored in SvxURLField must be encoded + } + else + rPath = aTmpStr.makeStringAndClear(); +} + +/** Inserts the URL into a text cell. Does not modify value or formula cells. */ +void lclInsertUrl( XclImpRoot& rRoot, const OUString& rUrl, SCCOL nScCol, SCROW nScRow, SCTAB nScTab ) +{ + ScDocumentImport& rDoc = rRoot.GetDocImport(); + ScAddress aScPos( nScCol, nScRow, nScTab ); + ScRefCellValue aCell(rDoc.getDoc(), aScPos); + switch( aCell.meType ) + { + // #i54261# hyperlinks in string cells + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + { + sal_uInt32 nNumFmt = rDoc.getDoc().GetNumberFormat(rDoc.getDoc().GetNonThreadedContext(), aScPos); + SvNumberFormatter* pFormatter = rDoc.getDoc().GetFormatTable(); + const Color* pColor; + OUString aDisplText = ScCellFormat::GetString(aCell, nNumFmt, &pColor, *pFormatter, rDoc.getDoc()); + if (aDisplText.isEmpty()) + aDisplText = rUrl; + + ScEditEngineDefaulter& rEE = rRoot.GetEditEngine(); + SvxURLField aUrlField( rUrl, aDisplText, SvxURLFormat::AppDefault ); + + if( aCell.meType == CELLTYPE_EDIT ) + { + const EditTextObject* pEditObj = aCell.mpEditText; + rEE.SetTextCurrentDefaults( *pEditObj ); + rEE.QuickInsertField( SvxFieldItem( aUrlField, EE_FEATURE_FIELD ), ESelection( 0, 0, EE_PARA_ALL, 0 ) ); + } + else + { + rEE.SetTextCurrentDefaults( OUString() ); + rEE.QuickInsertField( SvxFieldItem( aUrlField, EE_FEATURE_FIELD ), ESelection() ); + if( const ScPatternAttr* pPattern = rDoc.getDoc().GetPattern( aScPos.Col(), aScPos.Row(), nScTab ) ) + { + SfxItemSet aItemSet( rEE.GetEmptyItemSet() ); + pPattern->FillEditItemSet( &aItemSet ); + rEE.QuickSetAttribs( aItemSet, ESelection( 0, 0, EE_PARA_ALL, 0 ) ); + } + } + + // The cell will own the text object instance. + rDoc.setEditCell(aScPos, rEE.CreateTextObject()); + } + break; + + default: + // Handle other cell types e.g. formulas ( and ? ) that have associated + // hyperlinks. + // Ideally all hyperlinks should be treated as below. For the moment, + // given the current absence of ods support lets just handle what we + // previously didn't handle the new way. + // Unfortunately we won't be able to preserve such hyperlinks when + // saving to ods. Note: when we are able to save such hyperlinks to ods + // we should handle *all* imported hyperlinks as below ( e.g. as cell + // attribute ) for better interoperability. + { + SfxStringItem aItem( ATTR_HYPERLINK, rUrl ); + rDoc.getDoc().ApplyAttr(nScCol, nScRow, nScTab, aItem); + break; + } + } +} + +} // namespace + +void XclImpHyperlink::ReadHlink( XclImpStream& rStrm ) +{ + XclRange aXclRange( ScAddress::UNINITIALIZED ); + rStrm >> aXclRange; + // #i80006# Excel silently ignores invalid hi-byte of column index (TODO: everywhere?) + aXclRange.maFirst.mnCol &= 0xFF; + aXclRange.maLast.mnCol &= 0xFF; + OUString aString = ReadEmbeddedData( rStrm ); + if ( !aString.isEmpty() ) + rStrm.GetRoot().GetXFRangeBuffer().SetHyperlink( aXclRange, aString ); +} + +OUString XclImpHyperlink::ReadEmbeddedData( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + SfxObjectShell* pDocShell = rRoot.GetDocShell(); + + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + XclGuid aGuid; + rStrm >> aGuid; + rStrm.Ignore( 4 ); + sal_uInt32 nFlags = rStrm.ReaduInt32(); + + OSL_ENSURE( aGuid == XclTools::maGuidStdLink, "XclImpHyperlink::ReadEmbeddedData - unknown header GUID" ); + + ::std::unique_ptr< OUString > xLongName; // link / file name + ::std::unique_ptr< OUString > xShortName; // 8.3-representation of file name + ::std::unique_ptr< OUString > xTextMark; // text mark + + // description (ignore) + if( ::get_flag( nFlags, EXC_HLINK_DESCR ) ) + lclIgnoreString32( rStrm ); + // target frame (ignore) !! DESCR/FRAME - is this the right order? (never seen them together) + if( ::get_flag( nFlags, EXC_HLINK_FRAME ) ) + lclIgnoreString32( rStrm ); + + // URL fields are zero-terminated - do not let the stream replace them + // in the lclAppendString32() with the '?' character. + rStrm.SetNulSubstChar( '\0' ); + + // UNC path + if( ::get_flag( nFlags, EXC_HLINK_UNC ) ) + { + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, true ); + lclGetAbsPath( *xLongName, 0, pDocShell ); + } + // file link or URL + else if( ::get_flag( nFlags, EXC_HLINK_BODY ) ) + { + rStrm >> aGuid; + + if( aGuid == XclTools::maGuidFileMoniker ) + { + sal_uInt16 nLevel = rStrm.ReaduInt16(); // counter for level to climb down in path + xShortName.reset( new OUString ); + lclAppendString32( *xShortName, rStrm, false ); + rStrm.Ignore( 24 ); + + sal_uInt32 nStrLen = rStrm.ReaduInt32(); + if( nStrLen ) + { + nStrLen = rStrm.ReaduInt32(); + nStrLen /= 2; // it's byte count here... + rStrm.Ignore( 2 ); + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, nStrLen, true ); + lclGetAbsPath( *xLongName, nLevel, pDocShell ); + } + else + lclGetAbsPath( *xShortName, nLevel, pDocShell ); + } + else if( aGuid == XclTools::maGuidUrlMoniker ) + { + sal_uInt32 nStrLen = rStrm.ReaduInt32(); + nStrLen /= 2; // it's byte count here... + xLongName.reset( new OUString ); + lclAppendString32( *xLongName, rStrm, nStrLen, true ); + if( !::get_flag( nFlags, EXC_HLINK_ABS ) ) + lclGetAbsPath( *xLongName, 0, pDocShell ); + } + else + { + OSL_FAIL( "XclImpHyperlink::ReadEmbeddedData - unknown content GUID" ); + } + } + + // text mark + if( ::get_flag( nFlags, EXC_HLINK_MARK ) ) + { + xTextMark.reset( new OUString ); + lclAppendString32( *xTextMark, rStrm, true ); + } + + rStrm.SetNulSubstChar(); // back to default + + OSL_ENSURE( rStrm.GetRecLeft() == 0, "XclImpHyperlink::ReadEmbeddedData - record size mismatch" ); + + if (!xLongName && xShortName) + xLongName = std::move(xShortName); + else if (!xLongName && xTextMark) + xLongName.reset( new OUString ); + + if (xLongName) + { + if (xTextMark) + { + if( xLongName->isEmpty() ) + { + sal_Int32 nSepPos = xTextMark->lastIndexOf( '!' ); + if( nSepPos > 0 ) + { + // Do not attempt to blindly convert '#SheetName!A1' to + // '#SheetName.A1', it can be #SheetName!R1C1 as well. + // Hyperlink handler has to handle all, but prefer + // '#SheetName.A1' if possible. + if (nSepPos < xTextMark->getLength() - 1) + { + ScDocument& rDoc = rRoot.GetDoc(); + ScRange aRange; + if ((aRange.ParseAny( xTextMark->copy( nSepPos + 1 ), rDoc, formula::FormulaGrammar::CONV_XL_R1C1) + & ScRefFlags::VALID) == ScRefFlags::ZERO) + xTextMark.reset( new OUString( xTextMark->replaceAt( nSepPos, 1, rtl::OUStringChar( '.' )))); + } + } + } + xLongName.reset( new OUString( *xLongName + "#" + *xTextMark ) ); + } + return( *xLongName ); + } + return( OUString() ); +} + +void XclImpHyperlink::ConvertToValidTabName(OUString& rUrl) +{ + sal_Int32 n = rUrl.getLength(); + if (n < 4) + // Needs at least 4 characters. + return; + + if (rUrl[0] != '#') + // the 1st character must be '#'. + return; + + OUStringBuffer aNewUrl("#"); + OUStringBuffer aTabName; + + bool bInQuote = false; + bool bQuoteTabName = false; + for( sal_Int32 i = 1; i < n; ++i ) + { + sal_Unicode c = rUrl[i]; + if (c == '\'') + { + if (bInQuote && i+1 < n && rUrl[i+1] == '\'') + { + // Two consecutive single quotes ('') signify a single literal + // quite. When this occurs, the whole table name needs to be + // quoted. + bQuoteTabName = true; + aTabName.append(c).append(c); + ++i; + continue; + } + + bInQuote = !bInQuote; + if (!bInQuote && !aTabName.isEmpty()) + { + if (bQuoteTabName) + aNewUrl.append("'"); + aNewUrl.append(aTabName); + if (bQuoteTabName) + aNewUrl.append("'"); + } + } + else if (bInQuote) + aTabName.append(c); + else + aNewUrl.append(c); + } + + if (bInQuote) + // It should be outside the quotes! + return; + + // All is good. Pass the new URL. + rUrl = aNewUrl.makeStringAndClear(); +} + +void XclImpHyperlink::InsertUrl( XclImpRoot& rRoot, const XclRange& rXclRange, const OUString& rUrl ) +{ + OUString aUrl(rUrl); + ConvertToValidTabName(aUrl); + + SCTAB nScTab = rRoot.GetCurrScTab(); + ScRange aScRange( ScAddress::UNINITIALIZED ); + if( rRoot.GetAddressConverter().ConvertRange( aScRange, rXclRange, nScTab, nScTab, true ) ) + { + SCCOL nScCol1, nScCol2; + SCROW nScRow1, nScRow2; + aScRange.GetVars( nScCol1, nScRow1, nScTab, nScCol2, nScRow2, nScTab ); + + if (utl::ConfigManager::IsFuzzing()) + { + SCROW nRows = nScRow2 - nScRow1; + if (nRows > 1024) + { + SAL_WARN("sc.filter", "for fuzzing performance, clamped hyperlink apply range end row from " << nScRow2 << " to " << nScRow1 + 1024); + nScRow2 = nScRow1 + 1024; + } + } + + for( SCCOL nScCol = nScCol1; nScCol <= nScCol2; ++nScCol ) + for( SCROW nScRow = nScRow1; nScRow <= nScRow2; ++nScRow ) + lclInsertUrl( rRoot, aUrl, nScCol, nScRow, nScTab ); + } +} + +// Label ranges =============================================================== + +void XclImpLabelranges::ReadLabelranges( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + ScDocument& rDoc = rRoot.GetDoc(); + SCTAB nScTab = rRoot.GetCurrScTab(); + XclImpAddressConverter& rAddrConv = rRoot.GetAddressConverter(); + ScRangePairListRef xLabelRangesRef; + + XclRangeList aRowXclRanges, aColXclRanges; + rStrm >> aRowXclRanges >> aColXclRanges; + + // row label ranges + ScRangeList aRowScRanges; + rAddrConv.ConvertRangeList( aRowScRanges, aRowXclRanges, nScTab, false ); + xLabelRangesRef = rDoc.GetRowNameRangesRef(); + for ( size_t i = 0, nRanges = aRowScRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = aRowScRanges[ i ]; + ScRange aDataRange( rScRange ); + if( aDataRange.aEnd.Col() < rDoc.MaxCol() ) + { + aDataRange.aStart.SetCol( aDataRange.aEnd.Col() + 1 ); + aDataRange.aEnd.SetCol( rDoc.MaxCol() ); + } + else if( aDataRange.aStart.Col() > 0 ) + { + aDataRange.aEnd.SetCol( aDataRange.aStart.Col() - 1 ); + aDataRange.aStart.SetCol( 0 ); + } + xLabelRangesRef->Append( ScRangePair( rScRange, aDataRange ) ); + } + + // column label ranges + ScRangeList aColScRanges; + rAddrConv.ConvertRangeList( aColScRanges, aColXclRanges, nScTab, false ); + xLabelRangesRef = rDoc.GetColNameRangesRef(); + + for ( size_t i = 0, nRanges = aColScRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = aColScRanges[ i ]; + ScRange aDataRange( rScRange ); + if( aDataRange.aEnd.Row() < rDoc.MaxRow() ) + { + aDataRange.aStart.SetRow( aDataRange.aEnd.Row() + 1 ); + aDataRange.aEnd.SetRow( rDoc.MaxRow() ); + } + else if( aDataRange.aStart.Row() > 0 ) + { + aDataRange.aEnd.SetRow( aDataRange.aStart.Row() - 1 ); + aDataRange.aStart.SetRow( 0 ); + } + xLabelRangesRef->Append( ScRangePair( rScRange, aDataRange ) ); + } +} + +// Conditional formatting ===================================================== + +XclImpCondFormat::XclImpCondFormat( const XclImpRoot& rRoot, sal_uInt32 nFormatIndex ) : + XclImpRoot( rRoot ), + mnFormatIndex( nFormatIndex ), + mnCondCount( 0 ), + mnCondIndex( 0 ) +{ +} + +XclImpCondFormat::~XclImpCondFormat() +{ +} + +void XclImpCondFormat::ReadCondfmt( XclImpStream& rStrm ) +{ + OSL_ENSURE( !mnCondCount, "XclImpCondFormat::ReadCondfmt - already initialized" ); + XclRangeList aXclRanges; + mnCondCount = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + rStrm >> aXclRanges; + GetAddressConverter().ConvertRangeList( maRanges, aXclRanges, GetCurrScTab(), true ); +} + +void XclImpCondFormat::ReadCF( XclImpStream& rStrm ) +{ + if( mnCondIndex >= mnCondCount ) + { + OSL_FAIL( "XclImpCondFormat::ReadCF - CF without leading CONDFMT" ); + return; + } + + // entire conditional format outside of valid range? + if( maRanges.empty() ) + return; + + sal_uInt8 nType = rStrm.ReaduInt8(); + sal_uInt8 nOperator = rStrm.ReaduInt8(); + sal_uInt16 nFmlaSize1 = rStrm.ReaduInt16(); + sal_uInt16 nFmlaSize2 = rStrm.ReaduInt16(); + sal_uInt32 nFlags = rStrm.ReaduInt32(); + rStrm.Ignore( 2 ); //nFlagsExtended + + // *** mode and comparison operator *** + + ScConditionMode eMode = ScConditionMode::NONE; + switch( nType ) + { + case EXC_CF_TYPE_CELL: + { + switch( nOperator ) + { + case EXC_CF_CMP_BETWEEN: eMode = ScConditionMode::Between; break; + case EXC_CF_CMP_NOT_BETWEEN: eMode = ScConditionMode::NotBetween; break; + case EXC_CF_CMP_EQUAL: eMode = ScConditionMode::Equal; break; + case EXC_CF_CMP_NOT_EQUAL: eMode = ScConditionMode::NotEqual; break; + case EXC_CF_CMP_GREATER: eMode = ScConditionMode::Greater; break; + case EXC_CF_CMP_LESS: eMode = ScConditionMode::Less; break; + case EXC_CF_CMP_GREATER_EQUAL: eMode = ScConditionMode::EqGreater; break; + case EXC_CF_CMP_LESS_EQUAL: eMode = ScConditionMode::EqLess; break; + default: + SAL_INFO( + "sc.filter", "unknown CF comparison " << nOperator); + } + } + break; + + case EXC_CF_TYPE_FMLA: + eMode = ScConditionMode::Direct; + break; + + default: + SAL_INFO("sc.filter", "unknown CF mode " << nType); + return; + } + + // *** create style sheet *** + + OUString aStyleName( XclTools::GetCondFormatStyleName( GetCurrScTab(), mnFormatIndex, mnCondIndex ) ); + SfxItemSet& rStyleItemSet = ScfTools::MakeCellStyleSheet( GetStyleSheetPool(), aStyleName, true ).GetItemSet(); + + const XclImpPalette& rPalette = GetPalette(); + + // number format + + if( get_flag( nFlags, EXC_CF_BLOCK_NUMFMT ) ) + { + XclImpNumFmtBuffer& rNumFmtBuffer = GetRoot().GetNumFmtBuffer(); + bool bIFmt = get_flag( nFlags, EXC_CF_IFMT_USER ); + sal_uInt16 nFormat = rNumFmtBuffer.ReadCFFormat( rStrm, bIFmt ); + rNumFmtBuffer.FillToItemSet( rStyleItemSet, nFormat ); + } + + // *** font block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_FONT ) ) + { + XclImpFont aFont( GetRoot() ); + aFont.ReadCFFontBlock( rStrm ); + aFont.FillToItemSet( rStyleItemSet, XclFontItemType::Cell ); + } + + // alignment + if( get_flag( nFlags, EXC_CF_BLOCK_ALIGNMENT ) ) + { + XclImpCellAlign aAlign; + sal_uInt16 nAlign(0); + sal_uInt16 nAlignMisc(0); + nAlign = rStrm.ReaduInt16(); + nAlignMisc = rStrm.ReaduInt16(); + aAlign.FillFromCF( nAlign, nAlignMisc ); + aAlign.FillToItemSet( rStyleItemSet, nullptr ); + rStrm.Ignore(4); + } + + // *** border block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_BORDER ) ) + { + sal_uInt16 nLineStyle(0); + sal_uInt32 nLineColor(0); + nLineStyle = rStrm.ReaduInt16(); + nLineColor = rStrm.ReaduInt32(); + rStrm.Ignore( 2 ); + + XclImpCellBorder aBorder; + aBorder.FillFromCF8( nLineStyle, nLineColor, nFlags ); + aBorder.FillToItemSet( rStyleItemSet, rPalette ); + } + + // *** pattern block *** + + if( ::get_flag( nFlags, EXC_CF_BLOCK_AREA ) ) + { + sal_uInt16 nPattern(0), nColor(0); + nPattern = rStrm.ReaduInt16(); + nColor = rStrm.ReaduInt16(); + + XclImpCellArea aArea; + aArea.FillFromCF8( nPattern, nColor, nFlags ); + aArea.FillToItemSet( rStyleItemSet, rPalette ); + } + + if( get_flag( nFlags, EXC_CF_BLOCK_PROTECTION ) ) + { + sal_uInt16 nCellProt; + nCellProt = rStrm.ReaduInt16(); + XclImpCellProt aCellProt; + aCellProt.FillFromXF3(nCellProt); + aCellProt.FillToItemSet( rStyleItemSet ); + } + + // *** formulas *** + + const ScAddress& rPos = maRanges.front().aStart; // assured above that maRanges is not empty + ExcelToSc& rFmlaConv = GetOldFmlaConverter(); + + ::std::unique_ptr< ScTokenArray > xTokArr1; + if( nFmlaSize1 > 0 ) + { + std::unique_ptr pTokArr; + rFmlaConv.Reset( rPos ); + rFmlaConv.Convert( pTokArr, rStrm, nFmlaSize1, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + { + xTokArr1 = std::move( pTokArr ); + GetDoc().CheckLinkFormulaNeedingCheck( *xTokArr1); + } + } + + ::std::unique_ptr< ScTokenArray > xTokArr2; + if( nFmlaSize2 > 0 ) + { + std::unique_ptr pTokArr; + rFmlaConv.Reset( rPos ); + rFmlaConv.Convert( pTokArr, rStrm, nFmlaSize2, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + { + xTokArr2 = std::move( pTokArr ); + GetDoc().CheckLinkFormulaNeedingCheck( *xTokArr2); + } + } + + // *** create the Calc conditional formatting *** + + const ScAddress aPos(rPos); //in case maRanges.Join invalidates it + + if( !mxScCondFmt ) + { + mxScCondFmt.reset( new ScConditionalFormat( 0/*nKey*/, &GetDoc() ) ); + if(maRanges.size() > 1) + maRanges.Join(maRanges[0], true); + mxScCondFmt->SetRange(maRanges); + } + + ScCondFormatEntry* pEntry = new ScCondFormatEntry(eMode, xTokArr1.get(), xTokArr2.get(), GetDoc(), aPos, aStyleName); + mxScCondFmt->AddEntry( pEntry ); + ++mnCondIndex; +} + +void XclImpCondFormat::Apply() +{ + if( mxScCondFmt ) + { + ScDocument& rDoc = GetDoc(); + + SCTAB nTab = maRanges.front().aStart.Tab(); + sal_uLong nKey = rDoc.AddCondFormat( mxScCondFmt->Clone(), nTab ); + + rDoc.AddCondFormatData( maRanges, nTab, nKey ); + } +} + +XclImpCondFormatManager::XclImpCondFormatManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpCondFormatManager::ReadCondfmt( XclImpStream& rStrm ) +{ + XclImpCondFormat* pFmt = new XclImpCondFormat( GetRoot(), maCondFmtList.size() ); + pFmt->ReadCondfmt( rStrm ); + maCondFmtList.push_back( std::unique_ptr(pFmt) ); +} + +void XclImpCondFormatManager::ReadCF( XclImpStream& rStrm ) +{ + OSL_ENSURE( !maCondFmtList.empty(), "XclImpCondFormatManager::ReadCF - CF without leading CONDFMT" ); + if( !maCondFmtList.empty() ) + maCondFmtList.back()->ReadCF( rStrm ); +} + +void XclImpCondFormatManager::Apply() +{ + for( auto& rxFmt : maCondFmtList ) + rxFmt->Apply(); + maCondFmtList.clear(); +} + +// Data Validation ============================================================ + +XclImpValidationManager::DVItem::DVItem( const ScRangeList& rRanges, const ScValidationData& rValidData ) : + maRanges(rRanges), maValidData(rValidData) {} + +XclImpValidationManager::XclImpValidationManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpValidationManager::ReadDval( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + sal_uInt32 nObjId(0); + rStrm.Ignore( 10 ); + nObjId = rStrm.ReaduInt32(); + if( nObjId != EXC_DVAL_NOOBJ ) + { + OSL_ENSURE( nObjId <= 0xFFFF, "XclImpValidation::ReadDval - invalid object ID" ); + rRoot.GetCurrSheetDrawing().SetSkipObj( static_cast< sal_uInt16 >( nObjId ) ); + } +} + +void XclImpValidationManager::ReadDV( XclImpStream& rStrm ) +{ + const XclImpRoot& rRoot = rStrm.GetRoot(); + OSL_ENSURE_BIFF( rRoot.GetBiff() == EXC_BIFF8 ); + + ScDocument& rDoc = rRoot.GetDoc(); + SCTAB nScTab = rRoot.GetCurrScTab(); + ExcelToSc& rFmlaConv = rRoot.GetOldFmlaConverter(); + + // flags + sal_uInt32 nFlags = rStrm.ReaduInt32(); + + // message strings + /* Empty strings are single NUL characters in Excel (string length is 1). + -> Do not let the stream replace them with '?' characters. */ + rStrm.SetNulSubstChar( '\0' ); + OUString aPromptTitle( rStrm.ReadUniString() ); + OUString aErrorTitle( rStrm.ReadUniString() ); + OUString aPromptMessage( rStrm.ReadUniString() ); + OUString aErrorMessage( rStrm.ReadUniString() ); + rStrm.SetNulSubstChar(); // back to default + + // formula(s) + if ( rStrm.GetRecLeft() <= 8 ) + // Not enough bytes left in the record. Bail out. + return; + + // first formula + // string list is single tStr token with NUL separators -> replace them with LF + rStrm.SetNulSubstChar( '\n' ); + ::std::unique_ptr< ScTokenArray > xTokArr1; + + // We can't import the formula directly because we need the range + sal_uInt16 nLenFormula1 = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + XclImpStreamPos aPosFormula1; + rStrm.StorePosition(aPosFormula1); + rStrm.Ignore(nLenFormula1); + + // second formula + ::std::unique_ptr< ScTokenArray > xTokArr2; + + sal_uInt16 nLenFormula2 = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + XclImpStreamPos aPosFormula2; + rStrm.StorePosition(aPosFormula2); + rStrm.Ignore(nLenFormula2); + + // read all cell ranges + XclRangeList aXclRanges; + rStrm >> aXclRanges; + + // convert to Calc range list + ScRangeList aScRanges; + rRoot.GetAddressConverter().ConvertRangeList( aScRanges, aXclRanges, nScTab, true ); + + // only continue if there are valid ranges + if ( aScRanges.empty() ) + return; + + ScRange aCombinedRange = aScRanges.Combine(); + + XclImpStreamPos aCurrentPos; + rStrm.StorePosition(aCurrentPos); + rStrm.RestorePosition(aPosFormula1); + if( nLenFormula1 > 0 ) + { + std::unique_ptr pTokArr; + rFmlaConv.Reset(aCombinedRange.aStart); + rFmlaConv.Convert( pTokArr, rStrm, nLenFormula1, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + xTokArr1 = std::move( pTokArr ); + } + rStrm.SetNulSubstChar(); // back to default + if (nLenFormula2 > 0) + { + rStrm.RestorePosition(aPosFormula2); + std::unique_ptr pTokArr; + rFmlaConv.Reset(aCombinedRange.aStart); + rFmlaConv.Convert( pTokArr, rStrm, nLenFormula2, false, FT_CondFormat ); + // formula converter owns pTokArr -> create a copy of the token array + if( pTokArr ) + xTokArr2 = std::move( pTokArr ); + } + + rStrm.RestorePosition(aCurrentPos); + + bool bIsValid = true; // valid settings in flags field + + ScValidationMode eValMode = SC_VALID_ANY; + switch( nFlags & EXC_DV_MODE_MASK ) + { + case EXC_DV_MODE_ANY: eValMode = SC_VALID_ANY; break; + case EXC_DV_MODE_WHOLE: eValMode = SC_VALID_WHOLE; break; + case EXC_DV_MODE_DECIMAL: eValMode = SC_VALID_DECIMAL; break; + case EXC_DV_MODE_LIST: eValMode = SC_VALID_LIST; break; + case EXC_DV_MODE_DATE: eValMode = SC_VALID_DATE; break; + case EXC_DV_MODE_TIME: eValMode = SC_VALID_TIME; break; + case EXC_DV_MODE_TEXTLEN: eValMode = SC_VALID_TEXTLEN; break; + case EXC_DV_MODE_CUSTOM: eValMode = SC_VALID_CUSTOM; break; + default: bIsValid = false; + } + rRoot.GetTracer().TraceDVType(eValMode == SC_VALID_CUSTOM); + + ScConditionMode eCondMode = ScConditionMode::Between; + switch( nFlags & EXC_DV_COND_MASK ) + { + case EXC_DV_COND_BETWEEN: eCondMode = ScConditionMode::Between; break; + case EXC_DV_COND_NOTBETWEEN:eCondMode = ScConditionMode::NotBetween; break; + case EXC_DV_COND_EQUAL: eCondMode = ScConditionMode::Equal; break; + case EXC_DV_COND_NOTEQUAL: eCondMode = ScConditionMode::NotEqual; break; + case EXC_DV_COND_GREATER: eCondMode = ScConditionMode::Greater; break; + case EXC_DV_COND_LESS: eCondMode = ScConditionMode::Less; break; + case EXC_DV_COND_EQGREATER: eCondMode = ScConditionMode::EqGreater; break; + case EXC_DV_COND_EQLESS: eCondMode = ScConditionMode::EqLess; break; + default: bIsValid = false; + } + + if ( !bIsValid ) + // No valid validation found. Bail out. + return; + + // The default value for comparison is _BETWEEN. However, custom + // rules are a formula, and thus the comparator should be ignored + // and only a true or false from the formula is evaluated. In Calc, + // formulas use comparison SC_COND_DIRECT. + if( eValMode == SC_VALID_CUSTOM ) + { + eCondMode = ScConditionMode::Direct; + } + + // first range for base address for relative references + const ScRange& rScRange = aScRanges.front(); // aScRanges is not empty + + // process string list of a list validity (convert to list of string tokens) + if( xTokArr1 && (eValMode == SC_VALID_LIST) && ::get_flag( nFlags, EXC_DV_STRINGLIST ) ) + XclTokenArrayHelper::ConvertStringToList(*xTokArr1, rDoc.GetSharedStringPool(), '\n'); + + maDVItems.push_back( + std::make_unique(aScRanges, ScValidationData(eValMode, eCondMode, xTokArr1.get(), xTokArr2.get(), rDoc, rScRange.aStart))); + DVItem& rItem = *maDVItems.back(); + + rItem.maValidData.SetIgnoreBlank( ::get_flag( nFlags, EXC_DV_IGNOREBLANK ) ); + rItem.maValidData.SetListType( ::get_flagvalue( nFlags, EXC_DV_SUPPRESSDROPDOWN, css::sheet::TableValidationVisibility::INVISIBLE, css::sheet::TableValidationVisibility::UNSORTED ) ); + + // *** prompt box *** + if( !aPromptTitle.isEmpty() || !aPromptMessage.isEmpty() ) + { + // set any text stored in the record + rItem.maValidData.SetInput( aPromptTitle, aPromptMessage ); + if( !::get_flag( nFlags, EXC_DV_SHOWPROMPT ) ) + rItem.maValidData.ResetInput(); + } + + // *** error box *** + ScValidErrorStyle eErrStyle = SC_VALERR_STOP; + switch( nFlags & EXC_DV_ERROR_MASK ) + { + case EXC_DV_ERROR_WARNING: eErrStyle = SC_VALERR_WARNING; break; + case EXC_DV_ERROR_INFO: eErrStyle = SC_VALERR_INFO; break; + } + // set texts and error style + rItem.maValidData.SetError( aErrorTitle, aErrorMessage, eErrStyle ); + if( !::get_flag( nFlags, EXC_DV_SHOWERROR ) ) + rItem.maValidData.ResetError(); +} + +void XclImpValidationManager::Apply() +{ + ScDocument& rDoc = GetRoot().GetDoc(); + for (const auto& rxDVItem : maDVItems) + { + DVItem& rItem = *rxDVItem; + // set the handle ID + sal_uLong nHandle = rDoc.AddValidationEntry( rItem.maValidData ); + ScPatternAttr aPattern( rDoc.GetPool() ); + aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALIDDATA, nHandle ) ); + + // apply all ranges + for ( size_t i = 0, nRanges = rItem.maRanges.size(); i < nRanges; ++i ) + { + const ScRange & rScRange = rItem.maRanges[ i ]; + rDoc.ApplyPatternAreaTab( rScRange.aStart.Col(), rScRange.aStart.Row(), + rScRange.aEnd.Col(), rScRange.aEnd.Row(), rScRange.aStart.Tab(), aPattern ); + } + } + maDVItems.clear(); +} + +// Web queries ================================================================ + +XclImpWebQuery::XclImpWebQuery( const ScRange& rDestRange ) : + maDestRange( rDestRange ), + meMode( xlWQUnknown ), + mnRefresh( 0 ) +{ +} + +void XclImpWebQuery::ReadParamqry( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags = rStrm.ReaduInt16(); + sal_uInt16 nType = ::extract_value< sal_uInt16 >( nFlags, 0, 3 ); + if( !((nType == EXC_PQRYTYPE_WEBQUERY) && ::get_flag( nFlags, EXC_PQRY_WEBQUERY )) ) + return; + + if( ::get_flag( nFlags, EXC_PQRY_TABLES ) ) + { + meMode = xlWQAllTables; + maTables = ScfTools::GetHTMLTablesName(); + } + else + { + meMode = xlWQDocument; + maTables = ScfTools::GetHTMLDocName(); + } +} + +void XclImpWebQuery::ReadWqstring( XclImpStream& rStrm ) +{ + maURL = rStrm.ReadUniString(); +} + +void XclImpWebQuery::ReadWqsettings( XclImpStream& rStrm ) +{ + rStrm.Ignore( 10 ); + sal_uInt16 nFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + mnRefresh = rStrm.ReaduInt16(); + + if( ::get_flag( nFlags, EXC_WQSETT_SPECTABLES ) && (meMode == xlWQAllTables) ) + meMode = xlWQSpecTables; +} + +void XclImpWebQuery::ReadWqtables( XclImpStream& rStrm ) +{ + if( meMode != xlWQSpecTables ) + return; + + rStrm.Ignore( 4 ); + OUString aTables( rStrm.ReadUniString() ); + + const sal_Unicode cSep = ';'; + static const OUStringLiteral aQuotedPairs( u"\"\"" ); + maTables.clear(); + for ( sal_Int32 nStringIx {aTables.isEmpty() ? -1 : 0}; nStringIx>=0; ) + { + OUString aToken( ScStringUtil::GetQuotedToken( aTables, 0, aQuotedPairs, ',', nStringIx ) ); + sal_Int32 nTabNum = CharClass::isAsciiNumeric( aToken ) ? aToken.toInt32() : 0; + if( nTabNum > 0 ) + maTables = ScGlobal::addToken( maTables, ScfTools::GetNameFromHTMLIndex( static_cast< sal_uInt32 >( nTabNum ) ), cSep ); + else + { + ScGlobal::EraseQuotes( aToken, '"', false ); + if( !aToken.isEmpty() ) + maTables = ScGlobal::addToken( maTables, ScfTools::GetNameFromHTMLName( aToken ), cSep ); + } + } +} + +void XclImpWebQuery::Apply( ScDocument& rDoc, const OUString& rFilterName ) +{ + if( !maURL.isEmpty() && (meMode != xlWQUnknown) && rDoc.GetDocumentShell() ) + { + ScAreaLink* pLink = new ScAreaLink( rDoc.GetDocumentShell(), + maURL, rFilterName, OUString(), maTables, maDestRange, mnRefresh * 60UL ); + rDoc.GetLinkManager()->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, + maURL, &rFilterName, &maTables ); + } +} + +XclImpWebQueryBuffer::XclImpWebQueryBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpWebQueryBuffer::ReadQsi( XclImpStream& rStrm ) +{ + if( GetBiff() == EXC_BIFF8 ) + { + rStrm.Ignore( 10 ); + OUString aXclName( rStrm.ReadUniString() ); + + // #i64794# Excel replaces spaces with underscores + aXclName = aXclName.replaceAll( " ", "_" ); + + // find the defined name used in Calc + if( const XclImpName* pName = GetNameManager().FindName( aXclName, GetCurrScTab() ) ) + { + if( const ScRangeData* pRangeData = pName->GetScRangeData() ) + { + ScRange aRange; + if( pRangeData->IsReference( aRange ) ) + maWQList.emplace_back( aRange ); + } + } + } + else + { + DBG_ERROR_BIFF(); + } +} + +void XclImpWebQueryBuffer::ReadParamqry( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadParamqry( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqstring( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqstring( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqsettings( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqsettings( rStrm ); +} + +void XclImpWebQueryBuffer::ReadWqtables( XclImpStream& rStrm ) +{ + if (!maWQList.empty()) + maWQList.back().ReadWqtables( rStrm ); +} + +void XclImpWebQueryBuffer::Apply() +{ + ScDocument& rDoc = GetDoc(); + for( auto& rQuery : maWQList ) + rQuery.Apply( rDoc, EXC_WEBQRY_FILTER ); +} + +// Decryption ================================================================= + +namespace { + +XclImpDecrypterRef lclReadFilepass5( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + OSL_ENSURE( rStrm.GetRecLeft() == 4, "lclReadFilepass5 - wrong record size" ); + if( rStrm.GetRecLeft() == 4 ) + { + sal_uInt16 nKey(0), nHash(0); + nKey = rStrm.ReaduInt16(); + nHash = rStrm.ReaduInt16(); + xDecr = std::make_shared( nKey, nHash ); + } + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8_Standard( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + OSL_ENSURE( rStrm.GetRecLeft() == 48, "lclReadFilepass8 - wrong record size" ); + if( rStrm.GetRecLeft() == 48 ) + { + std::vector aSalt(16); + std::vector aVerifier(16); + std::vector aVerifierHash(16); + rStrm.Read(aSalt.data(), 16); + rStrm.Read(aVerifier.data(), 16); + rStrm.Read(aVerifierHash.data(), 16); + xDecr = std::make_shared(std::move(aSalt), std::move(aVerifier), std::move(aVerifierHash)); + } + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8_Strong(XclImpStream& rStream) +{ + //It is possible there are other variants in existence but these + //are the defaults I get with Excel 2013 + XclImpDecrypterRef xDecr; + + msfilter::RC4EncryptionInfo info; + + info.header.flags = rStream.ReaduInt32(); + if (oox::getFlag( info.header.flags, msfilter::ENCRYPTINFO_EXTERNAL)) + return xDecr; + + sal_uInt32 nHeaderSize = rStream.ReaduInt32(); + sal_uInt32 actualHeaderSize = sizeof(info.header); + + if( nHeaderSize < actualHeaderSize ) + return xDecr; + + info.header.flags = rStream.ReaduInt32(); + info.header.sizeExtra = rStream.ReaduInt32(); + info.header.algId = rStream.ReaduInt32(); + info.header.algIdHash = rStream.ReaduInt32(); + info.header.keyBits = rStream.ReaduInt32(); + info.header.providedType = rStream.ReaduInt32(); + info.header.reserved1 = rStream.ReaduInt32(); + info.header.reserved2 = rStream.ReaduInt32(); + + rStream.Ignore(nHeaderSize - actualHeaderSize); + + info.verifier.saltSize = rStream.ReaduInt32(); + if (info.verifier.saltSize != msfilter::SALT_LENGTH) + return xDecr; + rStream.Read(&info.verifier.salt, sizeof(info.verifier.salt)); + rStream.Read(&info.verifier.encryptedVerifier, sizeof(info.verifier.encryptedVerifier)); + + info.verifier.encryptedVerifierHashSize = rStream.ReaduInt32(); + if (info.verifier.encryptedVerifierHashSize != RTL_DIGEST_LENGTH_SHA1) + return xDecr; + rStream.Read(&info.verifier.encryptedVerifierHash, info.verifier.encryptedVerifierHashSize); + + // check flags and algorithm IDs, required are AES128 and SHA-1 + if (!oox::getFlag(info.header.flags, msfilter::ENCRYPTINFO_CRYPTOAPI)) + return xDecr; + + if (oox::getFlag(info.header.flags, msfilter::ENCRYPTINFO_AES)) + return xDecr; + + if (info.header.algId != msfilter::ENCRYPT_ALGO_RC4) + return xDecr; + + // hash algorithm ID 0 defaults to SHA-1 too + if (info.header.algIdHash != 0 && info.header.algIdHash != msfilter::ENCRYPT_HASH_SHA1) + return xDecr; + + xDecr = std::make_shared( + std::vector(info.verifier.salt, + info.verifier.salt + SAL_N_ELEMENTS(info.verifier.salt)), + std::vector(info.verifier.encryptedVerifier, + info.verifier.encryptedVerifier + SAL_N_ELEMENTS(info.verifier.encryptedVerifier)), + std::vector(info.verifier.encryptedVerifierHash, + info.verifier.encryptedVerifierHash + SAL_N_ELEMENTS(info.verifier.encryptedVerifierHash))); + + return xDecr; +} + +XclImpDecrypterRef lclReadFilepass8( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + + sal_uInt16 nMode = rStrm.ReaduInt16(); + switch( nMode ) + { + case EXC_FILEPASS_BIFF5: + xDecr = lclReadFilepass5( rStrm ); + break; + + case EXC_FILEPASS_BIFF8: + { + sal_uInt32 nVersion = rStrm.ReaduInt32(); + if (nVersion == msfilter::VERSION_INFO_1997_FORMAT) + { + //A Version structure where Version.vMajor MUST be 0x0001, + //and Version.vMinor MUST be 0x0001. + xDecr = lclReadFilepass8_Standard(rStrm); + } + else if (nVersion == msfilter::VERSION_INFO_2007_FORMAT || + nVersion == msfilter::VERSION_INFO_2007_FORMAT_SP2) + { + //Version.vMajor MUST be 0x0002, 0x0003 or 0x0004 and + //Version.vMinor MUST be 0x0002. + xDecr = lclReadFilepass8_Strong(rStrm); + } + else + OSL_FAIL("lclReadFilepass8 - unknown BIFF8 encryption sub mode"); + } + break; + + default: + OSL_FAIL( "lclReadFilepass8 - unknown encryption mode" ); + } + + return xDecr; +} + +} // namespace + +const ErrCode& XclImpDecryptHelper::ReadFilepass( XclImpStream& rStrm ) +{ + XclImpDecrypterRef xDecr; + rStrm.DisableDecryption(); + + // read the FILEPASS record and create a new decrypter object + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: xDecr = lclReadFilepass5( rStrm ); break; + case EXC_BIFF8: xDecr = lclReadFilepass8( rStrm ); break; + default: DBG_ERROR_BIFF(); + }; + + // set decrypter at import stream + rStrm.SetDecrypter( xDecr ); + + // request and verify a password (decrypter implements IDocPasswordVerifier) + if( xDecr ) + rStrm.GetRoot().RequestEncryptionData( *xDecr ); + + // return error code (success, wrong password, etc.) + return xDecr ? xDecr->GetError() : EXC_ENCR_ERROR_UNSUPP_CRYPT; +} + +// Document protection ======================================================== + +XclImpDocProtectBuffer::XclImpDocProtectBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mnPassHash(0x0000), + mbDocProtect(false), + mbWinProtect(false) +{ +} + +void XclImpDocProtectBuffer::ReadDocProtect( XclImpStream& rStrm ) +{ + mbDocProtect = rStrm.ReaduInt16() != 0; +} + +void XclImpDocProtectBuffer::ReadWinProtect( XclImpStream& rStrm ) +{ + mbWinProtect = rStrm.ReaduInt16() != 0; +} + +void XclImpDocProtectBuffer::ReadPasswordHash( XclImpStream& rStrm ) +{ + rStrm.EnableDecryption(); + mnPassHash = rStrm.ReaduInt16(); +} + +void XclImpDocProtectBuffer::Apply() const +{ + if (!mbDocProtect && !mbWinProtect) + // Excel requires either the structure or windows protection is set. + // If neither is set then the document is not protected at all. + return; + + unique_ptr pProtect(new ScDocProtection); + pProtect->setProtected(true); + + if (mnPassHash) + { + // 16-bit password hash. + Sequence aPass{sal_Int8(mnPassHash >> 8), sal_Int8(mnPassHash & 0xFF)}; + pProtect->setPasswordHash(aPass, PASSHASH_XL); + } + + // document protection options + pProtect->setOption(ScDocProtection::STRUCTURE, mbDocProtect); + pProtect->setOption(ScDocProtection::WINDOWS, mbWinProtect); + + GetDoc().SetDocProtection(pProtect.get()); +} + +// Sheet Protection =========================================================== + +XclImpSheetProtectBuffer::Sheet::Sheet() : + mbProtected(false), + mnPasswordHash(0x0000), + mnOptions(0x4400) +{ +} + +XclImpSheetProtectBuffer::Sheet::Sheet(const Sheet& r) : + mbProtected(r.mbProtected), + mnPasswordHash(r.mnPasswordHash), + mnOptions(r.mnOptions) +{ +} + +XclImpSheetProtectBuffer::XclImpSheetProtectBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpSheetProtectBuffer::ReadProtect( XclImpStream& rStrm, SCTAB nTab ) +{ + if ( rStrm.ReaduInt16() ) + { + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mbProtected = true; + } +} + +void XclImpSheetProtectBuffer::ReadOptions( XclImpStream& rStrm, SCTAB nTab ) +{ + // The flag size specifies the size of bytes that follows that stores + // feature data. If -1 it depends on the feature type imported earlier. + // For enhanced protection data, the size is always 4. For the most xls + // documents out there this value is almost always -1. + sal_Int32 nFlagSize = rStrm.ReadInt32(); + if (nFlagSize != -1) + return; + + // There are actually 4 bytes to read, but the upper 2 bytes currently + // don't store any bits. + sal_uInt16 nOptions = rStrm.ReaduInt16(); + + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mnOptions = nOptions; +} + +void XclImpSheetProtectBuffer::AppendEnhancedProtection( const ScEnhancedProtection & rProt, SCTAB nTab ) +{ + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->maEnhancedProtections.push_back( rProt); +} + +void XclImpSheetProtectBuffer::ReadPasswordHash( XclImpStream& rStrm, SCTAB nTab ) +{ + sal_uInt16 nHash = rStrm.ReaduInt16(); + Sheet* pSheet = GetSheetItem(nTab); + if (pSheet) + pSheet->mnPasswordHash = nHash; +} + +void XclImpSheetProtectBuffer::Apply() const +{ + for (const auto& [rTab, rSheet] : maProtectedSheets) + { + if (!rSheet.mbProtected) + // This sheet is (for whatever reason) not protected. + continue; + + unique_ptr pProtect(new ScTableProtection); + pProtect->setProtected(true); + + // 16-bit hash password + const sal_uInt16 nHash = rSheet.mnPasswordHash; + if (nHash) + { + Sequence aPass{sal_Int8(nHash >> 8), sal_Int8(nHash & 0xFF)}; + pProtect->setPasswordHash(aPass, PASSHASH_XL); + } + + // sheet protection options + const sal_uInt16 nOptions = rSheet.mnOptions; + pProtect->setOption( ScTableProtection::OBJECTS, (nOptions & 0x0001) ); + pProtect->setOption( ScTableProtection::SCENARIOS, (nOptions & 0x0002) ); + pProtect->setOption( ScTableProtection::FORMAT_CELLS, (nOptions & 0x0004) ); + pProtect->setOption( ScTableProtection::FORMAT_COLUMNS, (nOptions & 0x0008) ); + pProtect->setOption( ScTableProtection::FORMAT_ROWS, (nOptions & 0x0010) ); + pProtect->setOption( ScTableProtection::INSERT_COLUMNS, (nOptions & 0x0020) ); + pProtect->setOption( ScTableProtection::INSERT_ROWS, (nOptions & 0x0040) ); + pProtect->setOption( ScTableProtection::INSERT_HYPERLINKS, (nOptions & 0x0080) ); + pProtect->setOption( ScTableProtection::DELETE_COLUMNS, (nOptions & 0x0100) ); + pProtect->setOption( ScTableProtection::DELETE_ROWS, (nOptions & 0x0200) ); + pProtect->setOption( ScTableProtection::SELECT_LOCKED_CELLS, (nOptions & 0x0400) ); + pProtect->setOption( ScTableProtection::SORT, (nOptions & 0x0800) ); + pProtect->setOption( ScTableProtection::AUTOFILTER, (nOptions & 0x1000) ); + pProtect->setOption( ScTableProtection::PIVOT_TABLES, (nOptions & 0x2000) ); + pProtect->setOption( ScTableProtection::SELECT_UNLOCKED_CELLS, (nOptions & 0x4000) ); + + // Enhanced protection containing editable ranges and permissions. + pProtect->setEnhancedProtection( std::vector(rSheet.maEnhancedProtections) ); + + // all done. now commit. + GetDoc().SetTabProtection(rTab, pProtect.get()); + } +} + +XclImpSheetProtectBuffer::Sheet* XclImpSheetProtectBuffer::GetSheetItem( SCTAB nTab ) +{ + ProtectedSheetMap::iterator itr = maProtectedSheets.find(nTab); + if (itr == maProtectedSheets.end()) + { + // new sheet + if ( !maProtectedSheets.emplace( nTab, Sheet() ).second ) + return nullptr; + + itr = maProtectedSheets.find(nTab); + } + + return &itr->second; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiescher.cxx b/sc/source/filter/excel/xiescher.cxx new file mode 100644 index 000000000..1de9da95d --- /dev/null +++ b/sc/source/filter/excel/xiescher.cxx @@ -0,0 +1,4453 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using ::com::sun::star::uno::Any; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::beans::NamedValue; +using ::com::sun::star::lang::XMultiServiceFactory; +using ::com::sun::star::container::XIndexContainer; +using ::com::sun::star::container::XNameContainer; +using ::com::sun::star::frame::XModel; +using ::com::sun::star::awt::XControlModel; +using ::com::sun::star::embed::XEmbeddedObject; +using ::com::sun::star::embed::XEmbedPersist; +using ::com::sun::star::drawing::XControlShape; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::form::XFormComponent; +using ::com::sun::star::form::XFormsSupplier; +using ::com::sun::star::form::binding::XBindableValue; +using ::com::sun::star::form::binding::XValueBinding; +using ::com::sun::star::form::binding::XListEntrySink; +using ::com::sun::star::form::binding::XListEntrySource; +using ::com::sun::star::script::ScriptEventDescriptor; +using ::com::sun::star::script::XEventAttacherManager; +using ::com::sun::star::table::CellAddress; +using ::com::sun::star::table::CellRangeAddress; + +// Drawing objects ============================================================ + +XclImpDrawObjBase::XclImpDrawObjBase( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mnObjId( EXC_OBJ_INVALID_ID ), + mnTab( 0 ), + mnObjType( EXC_OBJTYPE_UNKNOWN ), + mnDffShapeId( 0 ), + mnDffFlags( ShapeFlag::NONE ), + mbHasAnchor( false ), + mbHidden( false ), + mbVisible( true ), + mbPrintable( true ), + mbAreaObj( false ), + mbAutoMargin( true ), + mbSimpleMacro( true ), + mbProcessSdr( true ), + mbInsertSdr( true ), + mbCustomDff( false ), + mbNotifyMacroEventRead( false ) +{ +} + +XclImpDrawObjBase::~XclImpDrawObjBase() +{ +} + +XclImpDrawObjRef XclImpDrawObjBase::ReadObj3( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + if( rStrm.GetRecLeft() >= 30 ) + { + sal_uInt16 nObjType; + rStrm.Ignore( 4 ); + nObjType = rStrm.ReaduInt16(); + switch( nObjType ) + { + case EXC_OBJTYPE_GROUP: xDrawObj= std::make_shared( rRoot ); break; + case EXC_OBJTYPE_LINE: xDrawObj= std::make_shared( rRoot ); break; + case EXC_OBJTYPE_RECTANGLE: xDrawObj= std::make_shared( rRoot ); break; + case EXC_OBJTYPE_OVAL: xDrawObj= std::make_shared( rRoot ); break; + case EXC_OBJTYPE_ARC: xDrawObj= std::make_shared( rRoot ); break; + case EXC_OBJTYPE_CHART: xDrawObj= std::make_shared( rRoot ); break; + case EXC_OBJTYPE_TEXT: xDrawObj= std::make_shared( rRoot ); break; + case EXC_OBJTYPE_BUTTON: xDrawObj= std::make_shared( rRoot ); break; + case EXC_OBJTYPE_PICTURE: xDrawObj= std::make_shared( rRoot ); break; + default: + SAL_WARN("sc.filter", "XclImpDrawObjBase::ReadObj3 - unknown object type 0x" << std::hex << nObjType ); + rRoot.GetTracer().TraceUnsupportedObjects(); + } + } + + if (!xDrawObj) + { + xDrawObj = std::make_shared(rRoot); + } + + xDrawObj->mnTab = rRoot.GetCurrScTab(); + xDrawObj->ImplReadObj3( rStrm ); + return xDrawObj; +} + +XclImpDrawObjRef XclImpDrawObjBase::ReadObj4( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + if( rStrm.GetRecLeft() >= 30 ) + { + sal_uInt16 nObjType; + rStrm.Ignore( 4 ); + nObjType = rStrm.ReaduInt16(); + switch( nObjType ) + { + case EXC_OBJTYPE_GROUP: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_LINE: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_RECTANGLE: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_OVAL: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_ARC: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_CHART: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_TEXT: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_BUTTON: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_PICTURE: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_POLYGON: xDrawObj = std::make_shared( rRoot ); break; + default: + SAL_WARN("sc.filter", "XclImpDrawObjBase::ReadObj4 - unknown object type 0x" << std::hex << nObjType ); + rRoot.GetTracer().TraceUnsupportedObjects(); + } + } + + if (!xDrawObj) + { + xDrawObj = std::make_shared(rRoot); + } + + xDrawObj->mnTab = rRoot.GetCurrScTab(); + xDrawObj->ImplReadObj4( rStrm ); + return xDrawObj; +} + +XclImpDrawObjRef XclImpDrawObjBase::ReadObj5( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + if( rStrm.GetRecLeft() >= 34 ) + { + sal_uInt16 nObjType(EXC_OBJTYPE_UNKNOWN); + rStrm.Ignore( 4 ); + nObjType = rStrm.ReaduInt16(); + switch( nObjType ) + { + case EXC_OBJTYPE_GROUP: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_LINE: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_RECTANGLE: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_OVAL: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_ARC: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_CHART: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_TEXT: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_BUTTON: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_PICTURE: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_POLYGON: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_CHECKBOX: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_OPTIONBUTTON: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_EDIT: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_LABEL: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_DIALOG: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_SPIN: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_SCROLLBAR: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_LISTBOX: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_GROUPBOX: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_DROPDOWN: xDrawObj = std::make_shared( rRoot ); break; + default: + SAL_WARN("sc.filter", "XclImpDrawObjBase::ReadObj5 - unknown object type 0x" << std::hex << nObjType ); + rRoot.GetTracer().TraceUnsupportedObjects(); + xDrawObj = std::make_shared( rRoot ); + } + } + + OSL_ENSURE(xDrawObj, "object import failed"); + + if (xDrawObj) + { + xDrawObj->mnTab = rRoot.GetCurrScTab(); + xDrawObj->ImplReadObj5( rStrm ); + } + return xDrawObj; +} + +XclImpDrawObjRef XclImpDrawObjBase::ReadObj8( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + if( rStrm.GetRecLeft() >= 10 ) + { + sal_uInt16 nSubRecId(0), nSubRecSize(0), nObjType(0); + nSubRecId = rStrm.ReaduInt16(); + nSubRecSize = rStrm.ReaduInt16(); + nObjType = rStrm.ReaduInt16(); + OSL_ENSURE( nSubRecId == EXC_ID_OBJCMO, "XclImpDrawObjBase::ReadObj8 - OBJCMO subrecord expected" ); + if( (nSubRecId == EXC_ID_OBJCMO) && (nSubRecSize >= 6) ) + { + switch( nObjType ) + { + // in BIFF8, all simple objects support text + case EXC_OBJTYPE_LINE: + case EXC_OBJTYPE_ARC: + xDrawObj = std::make_shared( rRoot ); + // lines and arcs may be 2-dimensional + xDrawObj->SetAreaObj( false ); + break; + + // in BIFF8, all simple objects support text + case EXC_OBJTYPE_RECTANGLE: + case EXC_OBJTYPE_OVAL: + case EXC_OBJTYPE_POLYGON: + case EXC_OBJTYPE_DRAWING: + case EXC_OBJTYPE_TEXT: + xDrawObj = std::make_shared( rRoot ); + break; + + case EXC_OBJTYPE_GROUP: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_CHART: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_BUTTON: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_PICTURE: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_CHECKBOX: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_OPTIONBUTTON: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_EDIT: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_LABEL: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_DIALOG: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_SPIN: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_SCROLLBAR: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_LISTBOX: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_GROUPBOX: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_DROPDOWN: xDrawObj = std::make_shared( rRoot ); break; + case EXC_OBJTYPE_NOTE: xDrawObj = std::make_shared( rRoot ); break; + + default: + SAL_WARN("sc.filter", "XclImpDrawObjBase::ReadObj8 - unknown object type 0x" << std::hex << nObjType ); + rRoot.GetTracer().TraceUnsupportedObjects(); + } + } + } + + if (!xDrawObj) //ensure placeholder for unknown or broken records + { + SAL_WARN( "sc.filter", "XclImpDrawObjBase::ReadObj8 import failed, substituting placeholder"); + xDrawObj = std::make_shared( rRoot ); + } + + xDrawObj->mnTab = rRoot.GetCurrScTab(); + xDrawObj->ImplReadObj8( rStrm ); + return xDrawObj; +} + +void XclImpDrawObjBase::SetAnchor( const XclObjAnchor& rAnchor ) +{ + maAnchor = rAnchor; + mbHasAnchor = true; +} + +void XclImpDrawObjBase::SetDffData( + const DffObjData& rDffObjData, const OUString& rObjName, const OUString& rHyperlink, + bool bVisible, bool bAutoMargin ) +{ + mnDffShapeId = rDffObjData.nShapeId; + mnDffFlags = rDffObjData.nSpFlags; + maObjName = rObjName; + maHyperlink = rHyperlink; + mbVisible = bVisible; + mbAutoMargin = bAutoMargin; +} + +OUString XclImpDrawObjBase::GetObjName() const +{ + /* #i51348# Always return a non-empty name. Create English + default names depending on the object type. This is not implemented as + virtual functions in derived classes, as class type and object type may + not match. */ + return maObjName.isEmpty() ? GetObjectManager().GetDefaultObjName(*this) : maObjName; +} + +const XclObjAnchor* XclImpDrawObjBase::GetAnchor() const +{ + return mbHasAnchor ? &maAnchor : nullptr; +} + +bool XclImpDrawObjBase::IsValidSize( const tools::Rectangle& rAnchorRect ) const +{ + // XclObjAnchor rounds up the width, width of 3 is the result of an Excel width of 0 + return mbAreaObj ? + ((rAnchorRect.GetWidth() > 3) && (rAnchorRect.GetHeight() > 1)) : + ((rAnchorRect.GetWidth() > 3) || (rAnchorRect.GetHeight() > 1)); +} + +ScRange XclImpDrawObjBase::GetUsedArea( SCTAB nScTab ) const +{ + ScRange aScUsedArea( ScAddress::INITIALIZE_INVALID ); + // #i44077# object inserted -> update used area for OLE object import + if( mbHasAnchor && GetAddressConverter().ConvertRange( aScUsedArea, maAnchor, nScTab, nScTab, false ) ) + { + // reduce range, if object ends directly on borders between two columns or rows + if( (maAnchor.mnRX == 0) && (aScUsedArea.aStart.Col() < aScUsedArea.aEnd.Col()) ) + aScUsedArea.aEnd.IncCol( -1 ); + if( (maAnchor.mnBY == 0) && (aScUsedArea.aStart.Row() < aScUsedArea.aEnd.Row()) ) + aScUsedArea.aEnd.IncRow( -1 ); + } + return aScUsedArea; +} + +std::size_t XclImpDrawObjBase::GetProgressSize() const +{ + return DoGetProgressSize(); +} + +SdrObjectUniquePtr XclImpDrawObjBase::CreateSdrObject( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect, bool bIsDff ) const +{ + SdrObjectUniquePtr xSdrObj; + if( bIsDff && !mbCustomDff ) + { + rDffConv.Progress( GetProgressSize() ); + } + else + { + xSdrObj = DoCreateSdrObj( rDffConv, rAnchorRect ); + + //added for exporting OCX control + /* mnObjType value set should be as below table: + 0x0000 Group 0x0001 Line + 0x0002 Rectangle 0x0003 Oval + 0x0004 Arc 0x0005 Chart + 0x0006 Text 0x0009 Polygon + +-----------------------------------------------------+ + OCX ==>| 0x0008 Picture | + +-----------------------------------------------------+ + | 0x0007 Button | + | 0x000B Checkbox 0x000C Radio button | + | 0x000D Edit box 0x000E Label | + TBX ==> | 0x000F Dialog box 0x0010 Spin control | + | 0x0011 Scrollbar 0x0012 List | + | 0x0013 Group box 0x0014 Dropdown list | + +-----------------------------------------------------+ + 0x0019 Note 0x001E OfficeArt object + */ + if( xSdrObj && xSdrObj->IsUnoObj() && + ( (mnObjType < 25 && mnObjType > 10) || mnObjType == 7 || mnObjType == 8 ) ) + { + SdrUnoObj* pSdrUnoObj = dynamic_cast< SdrUnoObj* >( xSdrObj.get() ); + if( pSdrUnoObj != nullptr ) + { + const Reference< XControlModel >& xCtrlModel = pSdrUnoObj->GetUnoControlModel(); + Reference< XPropertySet > xPropSet(xCtrlModel,UNO_QUERY); + static constexpr OUStringLiteral sPropertyName(u"ControlTypeinMSO"); + + enum { eCreateFromOffice = 0, eCreateFromMSTBXControl, eCreateFromMSOCXControl }; + + if( mnObjType == 7 || (mnObjType < 25 && mnObjType > 10) )//TBX + { + try + { + //Need summary type for export. Detail type(checkbox, button ...) has been contained by mnObjType + const sal_Int16 nTBXControlType = eCreateFromMSTBXControl ; + xPropSet->setPropertyValue(sPropertyName, Any(nTBXControlType)); + } + catch(const Exception&) + { + SAL_WARN("sc.filter", "XclImpDrawObjBase::CreateSdrObject, this control can't be set the property ControlTypeinMSO!"); + } + } + if( mnObjType == 8 )//OCX + { + //Need summary type for export + static constexpr OUStringLiteral sObjIdPropertyName(u"ObjIDinMSO"); + const XclImpPictureObj* const pObj = dynamic_cast< const XclImpPictureObj* const >(this); + if( pObj != nullptr && pObj->IsOcxControl() ) + { + try + { + const sal_Int16 nOCXControlType = eCreateFromMSOCXControl; + xPropSet->setPropertyValue(sPropertyName, Any(nOCXControlType)); + //Detail type(checkbox, button ...) + xPropSet->setPropertyValue(sObjIdPropertyName, Any(sal_uInt16(mnObjId))); + } + catch(const Exception&) + { + SAL_WARN("sc.filter", "XclImpDrawObjBase::CreateSdrObject, this control can't be set the property ObjIDinMSO!"); + } + } + } + + } + } + } + return xSdrObj; +} + +void XclImpDrawObjBase::NotifyMacroEventRead() +{ + if (mbNotifyMacroEventRead) + return; + SfxObjectShell* pDocShell = GetDocShell(); + if (!pDocShell) + return; + comphelper::DocumentInfo::notifyMacroEventRead(pDocShell->GetModel()); + mbNotifyMacroEventRead = true; +} + +void XclImpDrawObjBase::PreProcessSdrObject( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) +{ + // default: front layer, derived classes may have to set other layer in DoPreProcessSdrObj() + rSdrObj.NbcSetLayer( SC_LAYER_FRONT ); + + // set object name (GetObjName() will always return a non-empty name) + rSdrObj.SetName( GetObjName() ); + + // #i39167# full width for all objects regardless of horizontal alignment + rSdrObj.SetMergedItem( SdrTextHorzAdjustItem( SDRTEXTHORZADJUST_BLOCK ) ); + + // automatic text margin + if( mbAutoMargin ) + { + sal_Int32 nMargin = rDffConv.GetDefaultTextMargin(); + rSdrObj.SetMergedItem( makeSdrTextLeftDistItem( nMargin ) ); + rSdrObj.SetMergedItem( makeSdrTextRightDistItem( nMargin ) ); + rSdrObj.SetMergedItem( makeSdrTextUpperDistItem( nMargin ) ); + rSdrObj.SetMergedItem( makeSdrTextLowerDistItem( nMargin ) ); + } + + // macro and hyperlink + // removed oracle/sun check for mbSimpleMacro ( no idea what its for ) + if (!maMacroName.isEmpty()) + { + if( ScMacroInfo* pInfo = ScDrawLayer::GetMacroInfo( &rSdrObj, true ) ) + { + OUString sMacro = XclTools::GetSbMacroUrl(maMacroName, GetDocShell()); + if (!sMacro.isEmpty()) + NotifyMacroEventRead(); + pInfo->SetMacro(sMacro); + } + } + if (!maHyperlink.isEmpty()) + rSdrObj.setHyperlink(maHyperlink); + + // call virtual function for object type specific processing + DoPreProcessSdrObj( rDffConv, rSdrObj ); +} + +void XclImpDrawObjBase::PostProcessSdrObject( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + // call virtual function for object type specific processing + DoPostProcessSdrObj( rDffConv, rSdrObj ); +} + +// protected ------------------------------------------------------------------ + +void XclImpDrawObjBase::ReadName5( XclImpStream& rStrm, sal_uInt16 nNameLen ) +{ + maObjName.clear(); + if( nNameLen > 0 ) + { + // name length field is repeated before the name + maObjName = rStrm.ReadByteString( false ); + // skip padding byte for word boundaries + if( rStrm.GetRecPos() & 1 ) rStrm.Ignore( 1 ); + } +} + +void XclImpDrawObjBase::ReadMacro3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + maMacroName.clear(); + rStrm.Ignore( nMacroSize ); + // skip padding byte for word boundaries, not contained in nMacroSize + if( rStrm.GetRecPos() & 1 ) rStrm.Ignore( 1 ); +} + +void XclImpDrawObjBase::ReadMacro4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + maMacroName.clear(); + rStrm.Ignore( nMacroSize ); +} + +void XclImpDrawObjBase::ReadMacro5( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + maMacroName.clear(); + rStrm.Ignore( nMacroSize ); +} + +void XclImpDrawObjBase::ReadMacro8( XclImpStream& rStrm ) +{ + maMacroName.clear(); + if( rStrm.GetRecLeft() <= 6 ) + return; + + // macro is stored in a tNameXR token containing a link to a defined name + sal_uInt16 nFmlaSize; + nFmlaSize = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + OSL_ENSURE( nFmlaSize == 7, "XclImpDrawObjBase::ReadMacro - unexpected formula size" ); + if( nFmlaSize == 7 ) + { + sal_uInt8 nTokenId; + sal_uInt16 nExtSheet, nExtName; + nTokenId = rStrm.ReaduInt8(); + nExtSheet = rStrm.ReaduInt16(); + nExtName = rStrm.ReaduInt16(); + OSL_ENSURE( nTokenId == XclTokenArrayHelper::GetTokenId( EXC_TOKID_NAMEX, EXC_TOKCLASS_REF ), + "XclImpDrawObjBase::ReadMacro - tNameXR token expected" ); + if( nTokenId == XclTokenArrayHelper::GetTokenId( EXC_TOKID_NAMEX, EXC_TOKCLASS_REF ) ) + maMacroName = GetLinkManager().GetMacroName( nExtSheet, nExtName ); + } +} + +void XclImpDrawObjBase::ConvertLineStyle( SdrObject& rSdrObj, const XclObjLineData& rLineData ) const +{ + if( rLineData.IsAuto() ) + { + XclObjLineData aAutoData; + aAutoData.mnAuto = 0; + ConvertLineStyle( rSdrObj, aAutoData ); + } + else + { + tools::Long nLineWidth = 35 * ::std::min( rLineData.mnWidth, EXC_OBJ_LINE_THICK ); + rSdrObj.SetMergedItem( XLineWidthItem( nLineWidth ) ); + rSdrObj.SetMergedItem( XLineColorItem( OUString(), GetPalette().GetColor( rLineData.mnColorIdx ) ) ); + rSdrObj.SetMergedItem( XLineJointItem( css::drawing::LineJoint_MITER ) ); + + sal_uLong nDotLen = ::std::max< sal_uLong >( 70 * rLineData.mnWidth, 35 ); + sal_uLong nDashLen = 3 * nDotLen; + sal_uLong nDist = 2 * nDotLen; + + switch( rLineData.mnStyle ) + { + default: + case EXC_OBJ_LINE_SOLID: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_SOLID ) ); + break; + case EXC_OBJ_LINE_DASH: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) ); + rSdrObj.SetMergedItem( XLineDashItem( OUString(), XDash( css::drawing::DashStyle_RECT, 0, nDotLen, 1, nDashLen, nDist ) ) ); + break; + case EXC_OBJ_LINE_DOT: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) ); + rSdrObj.SetMergedItem( XLineDashItem( OUString(), XDash( css::drawing::DashStyle_RECT, 1, nDotLen, 0, nDashLen, nDist ) ) ); + break; + case EXC_OBJ_LINE_DASHDOT: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) ); + rSdrObj.SetMergedItem( XLineDashItem( OUString(), XDash( css::drawing::DashStyle_RECT, 1, nDotLen, 1, nDashLen, nDist ) ) ); + break; + case EXC_OBJ_LINE_DASHDOTDOT: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_DASH ) ); + rSdrObj.SetMergedItem( XLineDashItem( OUString(), XDash( css::drawing::DashStyle_RECT, 2, nDotLen, 1, nDashLen, nDist ) ) ); + break; + case EXC_OBJ_LINE_MEDTRANS: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_SOLID ) ); + rSdrObj.SetMergedItem( XLineTransparenceItem( 50 ) ); + break; + case EXC_OBJ_LINE_DARKTRANS: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_SOLID ) ); + rSdrObj.SetMergedItem( XLineTransparenceItem( 25 ) ); + break; + case EXC_OBJ_LINE_LIGHTTRANS: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_SOLID ) ); + rSdrObj.SetMergedItem( XLineTransparenceItem( 75 ) ); + break; + case EXC_OBJ_LINE_NONE: + rSdrObj.SetMergedItem( XLineStyleItem( drawing::LineStyle_NONE ) ); + break; + } + } +} + +void XclImpDrawObjBase::ConvertFillStyle( SdrObject& rSdrObj, const XclObjFillData& rFillData ) const +{ + if( rFillData.IsAuto() ) + { + XclObjFillData aAutoData; + aAutoData.mnAuto = 0; + ConvertFillStyle( rSdrObj, aAutoData ); + } + else if( rFillData.mnPattern == EXC_PATT_NONE ) + { + rSdrObj.SetMergedItem( XFillStyleItem( drawing::FillStyle_NONE ) ); + } + else + { + Color aPattColor = GetPalette().GetColor( rFillData.mnPattColorIdx ); + Color aBackColor = GetPalette().GetColor( rFillData.mnBackColorIdx ); + if( (rFillData.mnPattern == EXC_PATT_SOLID) || (aPattColor == aBackColor) ) + { + rSdrObj.SetMergedItem( XFillStyleItem( drawing::FillStyle_SOLID ) ); + rSdrObj.SetMergedItem( XFillColorItem( OUString(), aPattColor ) ); + } + else + { + static const sal_uInt8 sppnPatterns[][ 8 ] = + { + { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, + { 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD }, + { 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22 }, + { 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00 }, + { 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC }, + { 0x33, 0x66, 0xCC, 0x99, 0x33, 0x66, 0xCC, 0x99 }, + { 0xCC, 0x66, 0x33, 0x99, 0xCC, 0x66, 0x33, 0x99 }, + { 0xCC, 0xCC, 0x33, 0x33, 0xCC, 0xCC, 0x33, 0x33 }, + { 0xCC, 0xFF, 0x33, 0xFF, 0xCC, 0xFF, 0x33, 0xFF }, + { 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00 }, + { 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88 }, + { 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88 }, + { 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11 }, + { 0xFF, 0x11, 0x11, 0x11, 0xFF, 0x11, 0x11, 0x11 }, + { 0xAA, 0x44, 0xAA, 0x11, 0xAA, 0x44, 0xAA, 0x11 }, + { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }, + { 0x80, 0x00, 0x08, 0x00, 0x80, 0x00, 0x08, 0x00 } + }; + const sal_uInt8* const pnPattern = sppnPatterns[std::min(rFillData.mnPattern - 2, SAL_N_ELEMENTS(sppnPatterns) - 1)]; + // create 2-colored 8x8 DIB + SvMemoryStream aMemStrm; + aMemStrm.WriteUInt32( 12 ).WriteInt16( 8 ).WriteInt16( 8 ).WriteUInt16( 1 ).WriteUInt16( 1 ); + aMemStrm.WriteUChar( 0xFF ).WriteUChar( 0xFF ).WriteUChar( 0xFF ); + aMemStrm.WriteUChar( 0x00 ).WriteUChar( 0x00 ).WriteUChar( 0x00 ); + for( size_t nIdx = 0; nIdx < 8; ++nIdx ) + aMemStrm.WriteUInt32( pnPattern[ nIdx ] ); // 32-bit little-endian + aMemStrm.Seek( STREAM_SEEK_TO_BEGIN ); + Bitmap aBitmap; + (void)ReadDIB(aBitmap, aMemStrm, false); + + XOBitmap aXOBitmap(( BitmapEx(aBitmap) )); + aXOBitmap.Bitmap2Array(); + if( aXOBitmap.GetBackgroundColor() == COL_BLACK ) + ::std::swap( aPattColor, aBackColor ); + aXOBitmap.SetPixelColor( aPattColor ); + aXOBitmap.SetBackgroundColor( aBackColor ); + aXOBitmap.Array2Bitmap(); + aBitmap = aXOBitmap.GetBitmap().GetBitmap(); + + rSdrObj.SetMergedItem(XFillStyleItem(drawing::FillStyle_BITMAP)); + rSdrObj.SetMergedItem(XFillBitmapItem(OUString(), Graphic(BitmapEx(aBitmap)))); + } + } +} + +void XclImpDrawObjBase::ConvertFrameStyle( SdrObject& rSdrObj, sal_uInt16 nFrameFlags ) const +{ + if( ::get_flag( nFrameFlags, EXC_OBJ_FRAME_SHADOW ) ) + { + rSdrObj.SetMergedItem( makeSdrShadowItem( true ) ); + rSdrObj.SetMergedItem( makeSdrShadowXDistItem( 35 ) ); + rSdrObj.SetMergedItem( makeSdrShadowYDistItem( 35 ) ); + rSdrObj.SetMergedItem( makeSdrShadowColorItem( GetPalette().GetColor( EXC_COLOR_WINDOWTEXT ) ) ); + } +} + +Color XclImpDrawObjBase::GetSolidLineColor( const XclObjLineData& rLineData ) const +{ + Color aColor( COL_TRANSPARENT ); + if( rLineData.IsAuto() ) + { + XclObjLineData aAutoData; + aAutoData.mnAuto = 0; + aColor = GetSolidLineColor( aAutoData ); + } + else if( rLineData.mnStyle != EXC_OBJ_LINE_NONE ) + { + aColor = GetPalette().GetColor( rLineData.mnColorIdx ); + } + return aColor; +} + +Color XclImpDrawObjBase::GetSolidFillColor( const XclObjFillData& rFillData ) const +{ + Color aColor( COL_TRANSPARENT ); + if( rFillData.IsAuto() ) + { + XclObjFillData aAutoData; + aAutoData.mnAuto = 0; + aColor = GetSolidFillColor( aAutoData ); + } + else if( rFillData.mnPattern != EXC_PATT_NONE ) + { + Color aPattColor = GetPalette().GetColor( rFillData.mnPattColorIdx ); + Color aBackColor = GetPalette().GetColor( rFillData.mnBackColorIdx ); + aColor = XclTools::GetPatternColor( aPattColor, aBackColor, rFillData.mnPattern ); + } + return aColor; +} + +void XclImpDrawObjBase::DoReadObj3( XclImpStream&, sal_uInt16 ) +{ +} + +void XclImpDrawObjBase::DoReadObj4( XclImpStream&, sal_uInt16 ) +{ +} + +void XclImpDrawObjBase::DoReadObj5( XclImpStream&, sal_uInt16, sal_uInt16 ) +{ +} + +void XclImpDrawObjBase::DoReadObj8SubRec( XclImpStream&, sal_uInt16, sal_uInt16 ) +{ +} + +std::size_t XclImpDrawObjBase::DoGetProgressSize() const +{ + return 1; +} + +SdrObjectUniquePtr XclImpDrawObjBase::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& ) const +{ + rDffConv.Progress( GetProgressSize() ); + return nullptr; +} + +void XclImpDrawObjBase::DoPreProcessSdrObj( XclImpDffConverter&, SdrObject& ) const +{ + // trace if object is not printable + if( !IsPrintable() ) + GetTracer().TraceObjectNotPrintable(); +} + +void XclImpDrawObjBase::DoPostProcessSdrObj( XclImpDffConverter&, SdrObject& ) const +{ +} + +void XclImpDrawObjBase::ImplReadObj3( XclImpStream& rStrm ) +{ + // back to offset 4 (ignore object count field) + rStrm.Seek( 4 ); + + sal_uInt16 nObjFlags, nMacroSize; + mnObjType = rStrm.ReaduInt16(); + mnObjId = rStrm.ReaduInt16(); + nObjFlags = rStrm.ReaduInt16(); + rStrm >> maAnchor; + nMacroSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + + mbHasAnchor = true; + mbHidden = ::get_flag( nObjFlags, EXC_OBJ_HIDDEN ); + mbVisible = ::get_flag( nObjFlags, EXC_OBJ_VISIBLE ); + DoReadObj3( rStrm, nMacroSize ); +} + +void XclImpDrawObjBase::ImplReadObj4( XclImpStream& rStrm ) +{ + // back to offset 4 (ignore object count field) + rStrm.Seek( 4 ); + + sal_uInt16 nObjFlags, nMacroSize; + mnObjType = rStrm.ReaduInt16(); + mnObjId = rStrm.ReaduInt16(); + nObjFlags = rStrm.ReaduInt16(); + rStrm >> maAnchor; + nMacroSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + + mbHasAnchor = true; + mbHidden = ::get_flag( nObjFlags, EXC_OBJ_HIDDEN ); + mbVisible = ::get_flag( nObjFlags, EXC_OBJ_VISIBLE ); + mbPrintable = ::get_flag( nObjFlags, EXC_OBJ_PRINTABLE ); + DoReadObj4( rStrm, nMacroSize ); +} + +void XclImpDrawObjBase::ImplReadObj5( XclImpStream& rStrm ) +{ + // back to offset 4 (ignore object count field) + rStrm.Seek( 4 ); + + sal_uInt16 nObjFlags, nMacroSize, nNameLen; + mnObjType = rStrm.ReaduInt16(); + mnObjId = rStrm.ReaduInt16(); + nObjFlags = rStrm.ReaduInt16(); + rStrm >> maAnchor; + nMacroSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + nNameLen = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + + mbHasAnchor = true; + mbHidden = ::get_flag( nObjFlags, EXC_OBJ_HIDDEN ); + mbVisible = ::get_flag( nObjFlags, EXC_OBJ_VISIBLE ); + mbPrintable = ::get_flag( nObjFlags, EXC_OBJ_PRINTABLE ); + DoReadObj5( rStrm, nNameLen, nMacroSize ); +} + +void XclImpDrawObjBase::ImplReadObj8( XclImpStream& rStrm ) +{ + // back to beginning + rStrm.Seek( EXC_REC_SEEK_TO_BEGIN ); + + bool bLoop = true; + while (bLoop) + { + if (rStrm.GetRecLeft() < 4) + break; + + sal_uInt16 nSubRecId = rStrm.ReaduInt16(); + sal_uInt16 nSubRecSize = rStrm.ReaduInt16(); + rStrm.PushPosition(); + // sometimes the last subrecord has an invalid length (OBJLBSDATA) -> min() + nSubRecSize = static_cast< sal_uInt16 >( ::std::min< std::size_t >( nSubRecSize, rStrm.GetRecLeft() ) ); + + switch( nSubRecId ) + { + case EXC_ID_OBJCMO: + OSL_ENSURE( rStrm.GetRecPos() == 4, "XclImpDrawObjBase::ImplReadObj8 - unexpected OBJCMO subrecord" ); + if( (rStrm.GetRecPos() == 4) && (nSubRecSize >= 6) ) + { + sal_uInt16 nObjFlags; + mnObjType = rStrm.ReaduInt16(); + mnObjId = rStrm.ReaduInt16( ); + nObjFlags = rStrm.ReaduInt16( ); + mbPrintable = ::get_flag( nObjFlags, EXC_OBJCMO_PRINTABLE ); + } + break; + case EXC_ID_OBJMACRO: + ReadMacro8( rStrm ); + break; + case EXC_ID_OBJEND: + bLoop = false; + break; + default: + DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } + + rStrm.PopPosition(); + rStrm.Ignore( nSubRecSize ); + } + + /* Call DoReadObj8SubRec() with EXC_ID_OBJEND for further stream + processing (e.g. charts), even if the OBJEND subrecord is missing. */ + DoReadObj8SubRec( rStrm, EXC_ID_OBJEND, 0 ); + + /* Pictures that Excel reads from BIFF5 and writes to BIFF8 still have the + IMGDATA record following the OBJ record (but they use the image data + stored in DFF). The IMGDATA record may be continued by several CONTINUE + records. But the last CONTINUE record may be in fact an MSODRAWING + record that contains the DFF data of the next drawing object! So we + have to skip just enough CONTINUE records to look at the next + MSODRAWING/CONTINUE record. */ + if( !((rStrm.GetNextRecId() == EXC_ID3_IMGDATA) && rStrm.StartNextRecord()) ) + return; + + rStrm.Ignore( 4 ); + sal_uInt32 nDataSize = rStrm.ReaduInt32(); + nDataSize -= rStrm.GetRecLeft(); + // skip following CONTINUE records until IMGDATA ends + while (true) + { + if (!nDataSize) + break; + if (rStrm.GetNextRecId() != EXC_ID_CONT) + break; + if (!rStrm.StartNextRecord()) + break; + OSL_ENSURE( nDataSize >= rStrm.GetRecLeft(), "XclImpDrawObjBase::ImplReadObj8 - CONTINUE too long" ); + nDataSize -= ::std::min< sal_uInt32 >( rStrm.GetRecLeft(), nDataSize ); + } + OSL_ENSURE( nDataSize == 0, "XclImpDrawObjBase::ImplReadObj8 - missing CONTINUE records" ); + // next record may be MSODRAWING or CONTINUE or anything else +} + +void XclImpDrawObjVector::InsertGrouped( XclImpDrawObjRef const & xDrawObj ) +{ + if( !mObjs.empty() ) + if( XclImpGroupObj* pGroupObj = dynamic_cast< XclImpGroupObj* >( mObjs.back().get() ) ) + if( pGroupObj->TryInsert( xDrawObj ) ) + return; + mObjs.push_back( xDrawObj ); +} + +std::size_t XclImpDrawObjVector::GetProgressSize() const +{ + return std::accumulate(mObjs.begin(), mObjs.end(), std::size_t(0), + [](const std::size_t& rSum, const XclImpDrawObjRef& rxObj) { return rSum + rxObj->GetProgressSize(); }); +} + +XclImpPhObj::XclImpPhObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ) +{ + SetProcessSdrObj( false ); +} + +XclImpGroupObj::XclImpGroupObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ), + mnFirstUngrouped( 0 ) +{ +} + +bool XclImpGroupObj::TryInsert( XclImpDrawObjRef const & xDrawObj ) +{ + if( xDrawObj->GetObjId() == mnFirstUngrouped ) + return false; + // insert into own list or into nested group + maChildren.InsertGrouped( xDrawObj ); + return true; +} + +void XclImpGroupObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm.Ignore( 4 ); + mnFirstUngrouped = rStrm.ReaduInt16(); + rStrm.Ignore( 16 ); + ReadMacro3( rStrm, nMacroSize ); +} + +void XclImpGroupObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm.Ignore( 4 ); + mnFirstUngrouped = rStrm.ReaduInt16(); + rStrm.Ignore( 16 ); + ReadMacro4( rStrm, nMacroSize ); +} + +void XclImpGroupObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + rStrm.Ignore( 4 ); + mnFirstUngrouped = rStrm.ReaduInt16(); + rStrm.Ignore( 16 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); +} + +std::size_t XclImpGroupObj::DoGetProgressSize() const +{ + return XclImpDrawObjBase::DoGetProgressSize() + maChildren.GetProgressSize(); +} + +SdrObjectUniquePtr XclImpGroupObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& /*rAnchorRect*/ ) const +{ + std::unique_ptr xSdrObj( + new SdrObjGroup( + *GetDoc().GetDrawLayer())); + // child objects in BIFF2-BIFF5 have absolute size, not needed to pass own anchor rectangle + SdrObjList& rObjList = *xSdrObj->GetSubList(); // SdrObjGroup always returns existing sublist + for( const auto& rxChild : maChildren ) + rDffConv.ProcessObject( rObjList, *rxChild ); + rDffConv.Progress(); + return xSdrObj; +} + +XclImpLineObj::XclImpLineObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ), + mnArrows( 0 ), + mnStartPoint( EXC_OBJ_LINE_TL ) +{ + SetAreaObj( false ); +} + +void XclImpLineObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm >> maLineData; + mnArrows = rStrm.ReaduInt16(); + mnStartPoint = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadMacro3( rStrm, nMacroSize ); +} + +void XclImpLineObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm >> maLineData; + mnArrows = rStrm.ReaduInt16(); + mnStartPoint = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadMacro4( rStrm, nMacroSize ); +} + +void XclImpLineObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + rStrm >> maLineData; + mnArrows = rStrm.ReaduInt16(); + mnStartPoint = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); +} + +SdrObjectUniquePtr XclImpLineObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + ::basegfx::B2DPolygon aB2DPolygon; + switch( mnStartPoint ) + { + default: + case EXC_OBJ_LINE_TL: + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Left(), rAnchorRect.Top() ) ); + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Right(), rAnchorRect.Bottom() ) ); + break; + case EXC_OBJ_LINE_TR: + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Right(), rAnchorRect.Top() ) ); + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Left(), rAnchorRect.Bottom() ) ); + break; + case EXC_OBJ_LINE_BR: + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Right(), rAnchorRect.Bottom() ) ); + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Left(), rAnchorRect.Top() ) ); + break; + case EXC_OBJ_LINE_BL: + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Left(), rAnchorRect.Bottom() ) ); + aB2DPolygon.append( ::basegfx::B2DPoint( rAnchorRect.Right(), rAnchorRect.Top() ) ); + break; + } + SdrObjectUniquePtr xSdrObj( + new SdrPathObj( + *GetDoc().GetDrawLayer(), + SdrObjKind::Line, + ::basegfx::B2DPolyPolygon(aB2DPolygon))); + ConvertLineStyle( *xSdrObj, maLineData ); + + // line ends + sal_uInt8 nArrowType = ::extract_value< sal_uInt8 >( mnArrows, 0, 4 ); + bool bLineStart = false; + bool bLineEnd = false; + bool bFilled = false; + switch( nArrowType ) + { + case EXC_OBJ_ARROW_OPEN: bLineStart = false; bLineEnd = true; bFilled = false; break; + case EXC_OBJ_ARROW_OPENBOTH: bLineStart = true; bLineEnd = true; bFilled = false; break; + case EXC_OBJ_ARROW_FILLED: bLineStart = false; bLineEnd = true; bFilled = true; break; + case EXC_OBJ_ARROW_FILLEDBOTH: bLineStart = true; bLineEnd = true; bFilled = true; break; + } + if( bLineStart || bLineEnd ) + { + sal_uInt8 nArrowWidth = ::extract_value< sal_uInt8 >( mnArrows, 4, 4 ); + double fArrowWidth = 3.0; + switch( nArrowWidth ) + { + case EXC_OBJ_ARROW_NARROW: fArrowWidth = 2.0; break; + case EXC_OBJ_ARROW_MEDIUM: fArrowWidth = 3.0; break; + case EXC_OBJ_ARROW_WIDE: fArrowWidth = 5.0; break; + } + + sal_uInt8 nArrowLength = ::extract_value< sal_uInt8 >( mnArrows, 8, 4 ); + double fArrowLength = 3.0; + switch( nArrowLength ) + { + case EXC_OBJ_ARROW_NARROW: fArrowLength = 2.5; break; + case EXC_OBJ_ARROW_MEDIUM: fArrowLength = 3.5; break; + case EXC_OBJ_ARROW_WIDE: fArrowLength = 6.0; break; + } + + ::basegfx::B2DPolygon aArrowPoly; +#define EXC_ARROW_POINT( x, y ) ::basegfx::B2DPoint( fArrowWidth * (x), fArrowLength * (y) ) + if( bFilled ) + { + aArrowPoly.append( EXC_ARROW_POINT( 0, 100 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 50, 0 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 100, 100 ) ); + } + else + { + sal_uInt8 nLineWidth = ::limit_cast< sal_uInt8 >( maLineData.mnWidth, EXC_OBJ_LINE_THIN, EXC_OBJ_LINE_THICK ); + aArrowPoly.append( EXC_ARROW_POINT( 50, 0 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 100, 100 - 3 * nLineWidth ) ); + aArrowPoly.append( EXC_ARROW_POINT( 100 - 5 * nLineWidth, 100 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 50, 12 * nLineWidth ) ); + aArrowPoly.append( EXC_ARROW_POINT( 5 * nLineWidth, 100 ) ); + aArrowPoly.append( EXC_ARROW_POINT( 0, 100 - 3 * nLineWidth ) ); + } +#undef EXC_ARROW_POINT + + ::basegfx::B2DPolyPolygon aArrowPolyPoly( aArrowPoly ); + tools::Long nWidth = static_cast< tools::Long >( 125 * fArrowWidth ); + if( bLineStart ) + { + xSdrObj->SetMergedItem( XLineStartItem( OUString(), aArrowPolyPoly ) ); + xSdrObj->SetMergedItem( XLineStartWidthItem( nWidth ) ); + xSdrObj->SetMergedItem( XLineStartCenterItem( false ) ); + } + if( bLineEnd ) + { + xSdrObj->SetMergedItem( XLineEndItem( OUString(), aArrowPolyPoly ) ); + xSdrObj->SetMergedItem( XLineEndWidthItem( nWidth ) ); + xSdrObj->SetMergedItem( XLineEndCenterItem( false ) ); + } + } + rDffConv.Progress(); + return xSdrObj; +} + +XclImpRectObj::XclImpRectObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ), + mnFrameFlags( 0 ) +{ + SetAreaObj( true ); +} + +void XclImpRectObj::ReadFrameData( XclImpStream& rStrm ) +{ + rStrm >> maFillData >> maLineData; + mnFrameFlags = rStrm.ReaduInt16(); +} + +void XclImpRectObj::ConvertRectStyle( SdrObject& rSdrObj ) const +{ + ConvertLineStyle( rSdrObj, maLineData ); + ConvertFillStyle( rSdrObj, maFillData ); + ConvertFrameStyle( rSdrObj, mnFrameFlags ); +} + +void XclImpRectObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + ReadMacro3( rStrm, nMacroSize ); +} + +void XclImpRectObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + ReadMacro4( rStrm, nMacroSize ); +} + +void XclImpRectObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); +} + +SdrObjectUniquePtr XclImpRectObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj( + new SdrRectObj( + *GetDoc().GetDrawLayer(), + rAnchorRect)); + ConvertRectStyle( *xSdrObj ); + rDffConv.Progress(); + return xSdrObj; +} + +XclImpOvalObj::XclImpOvalObj( const XclImpRoot& rRoot ) : + XclImpRectObj( rRoot ) +{ +} + +SdrObjectUniquePtr XclImpOvalObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj( + new SdrCircObj( + *GetDoc().GetDrawLayer(), + SdrCircKind::Full, + rAnchorRect)); + ConvertRectStyle( *xSdrObj ); + rDffConv.Progress(); + return xSdrObj; +} + +XclImpArcObj::XclImpArcObj( const XclImpRoot& rRoot ) : + XclImpDrawObjBase( rRoot ), + mnQuadrant( EXC_OBJ_ARC_TR ) +{ + SetAreaObj( false ); // arc may be 2-dimensional +} + +void XclImpArcObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm >> maFillData >> maLineData; + mnQuadrant = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadMacro3( rStrm, nMacroSize ); +} + +void XclImpArcObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + rStrm >> maFillData >> maLineData; + mnQuadrant = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadMacro4( rStrm, nMacroSize ); +} + +void XclImpArcObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + rStrm >> maFillData >> maLineData; + mnQuadrant = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); +} + +SdrObjectUniquePtr XclImpArcObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + tools::Rectangle aNewRect = rAnchorRect; + Degree100 nStartAngle; + Degree100 nEndAngle; + switch( mnQuadrant ) + { + default: + case EXC_OBJ_ARC_TR: + nStartAngle = 0_deg100; + nEndAngle = 9000_deg100; + aNewRect.AdjustLeft( -(rAnchorRect.GetWidth()) ); + aNewRect.AdjustBottom(rAnchorRect.GetHeight() ); + break; + case EXC_OBJ_ARC_TL: + nStartAngle = 9000_deg100; + nEndAngle = 18000_deg100; + aNewRect.AdjustRight(rAnchorRect.GetWidth() ); + aNewRect.AdjustBottom(rAnchorRect.GetHeight() ); + break; + case EXC_OBJ_ARC_BL: + nStartAngle = 18000_deg100; + nEndAngle = 27000_deg100; + aNewRect.AdjustRight(rAnchorRect.GetWidth() ); + aNewRect.AdjustTop( -(rAnchorRect.GetHeight()) ); + break; + case EXC_OBJ_ARC_BR: + nStartAngle = 27000_deg100; + nEndAngle = 0_deg100; + aNewRect.AdjustLeft( -(rAnchorRect.GetWidth()) ); + aNewRect.AdjustTop( -(rAnchorRect.GetHeight()) ); + break; + } + SdrCircKind eObjKind = maFillData.IsFilled() ? SdrCircKind::Section : SdrCircKind::Arc; + SdrObjectUniquePtr xSdrObj( + new SdrCircObj( + *GetDoc().GetDrawLayer(), + eObjKind, + aNewRect, + nStartAngle, + nEndAngle)); + ConvertFillStyle( *xSdrObj, maFillData ); + ConvertLineStyle( *xSdrObj, maLineData ); + rDffConv.Progress(); + return xSdrObj; +} + +XclImpPolygonObj::XclImpPolygonObj( const XclImpRoot& rRoot ) : + XclImpRectObj( rRoot ), + mnPolyFlags( 0 ), + mnPointCount( 0 ) +{ + SetAreaObj( false ); // polygon may be 2-dimensional +} + +void XclImpPolygonObj::ReadCoordList( XclImpStream& rStrm ) +{ + if( (rStrm.GetNextRecId() == EXC_ID_COORDLIST) && rStrm.StartNextRecord() ) + { + OSL_ENSURE( rStrm.GetRecLeft() / 4 == mnPointCount, "XclImpPolygonObj::ReadCoordList - wrong polygon point count" ); + while (true) + { + if (rStrm.GetRecLeft() < 4) + break; + sal_uInt16 nX = rStrm.ReaduInt16(); + sal_uInt16 nY = rStrm.ReaduInt16(); + maCoords.emplace_back( nX, nY ); + } + } +} + +void XclImpPolygonObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + mnPolyFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + mnPointCount = rStrm.ReaduInt16(); + rStrm.Ignore( 8 ); + ReadMacro4( rStrm, nMacroSize ); + ReadCoordList( rStrm ); +} + +void XclImpPolygonObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + mnPolyFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 10 ); + mnPointCount = rStrm.ReaduInt16(); + rStrm.Ignore( 8 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); + ReadCoordList( rStrm ); +} + +namespace { + +::basegfx::B2DPoint lclGetPolyPoint( const tools::Rectangle& rAnchorRect, const Point& rPoint ) +{ + return ::basegfx::B2DPoint( + rAnchorRect.Left() + static_cast< sal_Int32 >( ::std::min< double >( rPoint.X(), 16384.0 ) / 16384.0 * rAnchorRect.GetWidth() + 0.5 ), + rAnchorRect.Top() + static_cast< sal_Int32 >( ::std::min< double >( rPoint.Y(), 16384.0 ) / 16384.0 * rAnchorRect.GetHeight() + 0.5 ) ); +} + +} // namespace + +SdrObjectUniquePtr XclImpPolygonObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj; + if( maCoords.size() >= 2 ) + { + // create the polygon + ::basegfx::B2DPolygon aB2DPolygon; + for( const auto& rCoord : maCoords ) + aB2DPolygon.append( lclGetPolyPoint( rAnchorRect, rCoord ) ); + // close polygon if specified + if( ::get_flag( mnPolyFlags, EXC_OBJ_POLY_CLOSED ) && (maCoords.front() != maCoords.back()) ) + aB2DPolygon.append( lclGetPolyPoint( rAnchorRect, maCoords.front() ) ); + // create the SdrObject + SdrObjKind eObjKind = maFillData.IsFilled() ? SdrObjKind::PathPoly : SdrObjKind::PathPolyLine; + xSdrObj.reset( + new SdrPathObj( + *GetDoc().GetDrawLayer(), + eObjKind, + ::basegfx::B2DPolyPolygon(aB2DPolygon))); + ConvertRectStyle( *xSdrObj ); + } + rDffConv.Progress(); + return xSdrObj; +} + +void XclImpObjTextData::ReadByteString( XclImpStream& rStrm ) +{ + mxString.reset(); + if( maData.mnTextLen > 0 ) + { + mxString = std::make_shared( rStrm.ReadRawByteString( maData.mnTextLen ) ); + // skip padding byte for word boundaries + if( rStrm.GetRecPos() & 1 ) rStrm.Ignore( 1 ); + } +} + +void XclImpObjTextData::ReadFormats( XclImpStream& rStrm ) +{ + if( mxString ) + mxString->ReadObjFormats( rStrm, maData.mnFormatSize ); + else + rStrm.Ignore( maData.mnFormatSize ); +} + +XclImpTextObj::XclImpTextObj( const XclImpRoot& rRoot ) : + XclImpRectObj( rRoot ) +{ +} + +void XclImpTextObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + maTextData.maData.ReadObj3( rStrm ); + ReadMacro3( rStrm, nMacroSize ); + maTextData.ReadByteString( rStrm ); + maTextData.ReadFormats( rStrm ); +} + +void XclImpTextObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + maTextData.maData.ReadObj3( rStrm ); + ReadMacro4( rStrm, nMacroSize ); + maTextData.ReadByteString( rStrm ); + maTextData.ReadFormats( rStrm ); +} + +void XclImpTextObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + ReadFrameData( rStrm ); + maTextData.maData.ReadObj5( rStrm ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); + maTextData.ReadByteString( rStrm ); + rStrm.Ignore( maTextData.maData.mnLinkSize ); // ignore text link formula + maTextData.ReadFormats( rStrm ); +} + +SdrObjectUniquePtr XclImpTextObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + std::unique_ptr xSdrObj( + new SdrObjCustomShape( + *GetDoc().GetDrawLayer())); + xSdrObj->NbcSetSnapRect( rAnchorRect ); + OUString aRectType = "rectangle"; + xSdrObj->MergeDefaultAttributes( &aRectType ); + ConvertRectStyle( *xSdrObj ); + bool bAutoSize = ::get_flag( maTextData.maData.mnFlags, EXC_OBJ_TEXT_AUTOSIZE ); + xSdrObj->SetMergedItem( makeSdrTextAutoGrowWidthItem( bAutoSize ) ); + xSdrObj->SetMergedItem( makeSdrTextAutoGrowHeightItem( bAutoSize ) ); + xSdrObj->SetMergedItem( makeSdrTextWordWrapItem( true ) ); + rDffConv.Progress(); + return xSdrObj; +} + +void XclImpTextObj::DoPreProcessSdrObj( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + // set text data + if( SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >( &rSdrObj ) ) + { + if( maTextData.mxString ) + { + if( maTextData.mxString->IsRich() ) + { + if (maTextData.mxString->GetText().getLength() > 1024 && utl::ConfigManager::IsFuzzing()) + { + SAL_WARN("sc.filter", "truncating slow long rich text for fuzzing performance"); + maTextData.mxString->SetText(maTextData.mxString->GetText().copy(0, 1024)); + } + + // rich text + std::unique_ptr< EditTextObject > xEditObj( + XclImpStringHelper::CreateTextObject( GetRoot(), *maTextData.mxString ) ); + OutlinerParaObject aOutlineObj(std::move(xEditObj)); + aOutlineObj.SetOutlinerMode( OutlinerMode::TextObject ); + pTextObj->NbcSetOutlinerParaObject( std::move(aOutlineObj) ); + } + else + { + // plain text + pTextObj->NbcSetText( maTextData.mxString->GetText() ); + } + + /* #i96858# Do not apply any formatting if there is no text. + SdrObjCustomShape::SetVerticalWriting (initiated from + SetMergedItem) calls SdrTextObj::ForceOutlinerParaObject which + ensures that we can erroneously write a ClientTextbox record + (with no content) while exporting to XLS, which can cause a + corrupted exported document. */ + + SvxAdjust eHorAlign = SvxAdjust::Left; + SdrTextVertAdjust eVerAlign = SDRTEXTVERTADJUST_TOP; + + // orientation (this is only a fake, drawing does not support real text orientation) + namespace csst = ::com::sun::star::text; + csst::WritingMode eWriteMode = csst::WritingMode_LR_TB; + switch( maTextData.maData.mnOrient ) + { + default: + case EXC_OBJ_ORIENT_NONE: + { + eWriteMode = csst::WritingMode_LR_TB; + switch( maTextData.maData.GetHorAlign() ) + { + case EXC_OBJ_HOR_LEFT: eHorAlign = SvxAdjust::Left; break; + case EXC_OBJ_HOR_CENTER: eHorAlign = SvxAdjust::Center; break; + case EXC_OBJ_HOR_RIGHT: eHorAlign = SvxAdjust::Right; break; + case EXC_OBJ_HOR_JUSTIFY: eHorAlign = SvxAdjust::Block; break; + } + switch( maTextData.maData.GetVerAlign() ) + { + case EXC_OBJ_VER_TOP: eVerAlign = SDRTEXTVERTADJUST_TOP; break; + case EXC_OBJ_VER_CENTER: eVerAlign = SDRTEXTVERTADJUST_CENTER; break; + case EXC_OBJ_VER_BOTTOM: eVerAlign = SDRTEXTVERTADJUST_BOTTOM; break; + case EXC_OBJ_VER_JUSTIFY: eVerAlign = SDRTEXTVERTADJUST_BLOCK; break; + } + } + break; + + case EXC_OBJ_ORIENT_90CCW: + { + if( SdrObjCustomShape* pObjCustomShape = dynamic_cast< SdrObjCustomShape* >( &rSdrObj ) ) + { + css::beans::PropertyValue aTextRotateAngle; + aTextRotateAngle.Name = "TextRotateAngle"; + aTextRotateAngle.Value <<= 180.0; + SdrCustomShapeGeometryItem aGeometryItem(pObjCustomShape->GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY )); + aGeometryItem.SetPropertyValue( aTextRotateAngle ); + pObjCustomShape->SetMergedItem( aGeometryItem ); + } + eWriteMode = csst::WritingMode_TB_RL; + switch( maTextData.maData.GetHorAlign() ) + { + case EXC_OBJ_HOR_LEFT: eVerAlign = SDRTEXTVERTADJUST_TOP; break; + case EXC_OBJ_HOR_CENTER: eVerAlign = SDRTEXTVERTADJUST_CENTER; break; + case EXC_OBJ_HOR_RIGHT: eVerAlign = SDRTEXTVERTADJUST_BOTTOM; break; + case EXC_OBJ_HOR_JUSTIFY: eVerAlign = SDRTEXTVERTADJUST_BLOCK; break; + } + MSO_Anchor eTextAnchor = static_cast(rDffConv.GetPropertyValue( DFF_Prop_anchorText, mso_anchorTop )); + switch( eTextAnchor ) + { + case mso_anchorTopCentered : + case mso_anchorMiddleCentered : + case mso_anchorBottomCentered : + { + eHorAlign = SvxAdjust::Center; + } + break; + + default: + { + switch( maTextData.maData.GetVerAlign() ) + { + case EXC_OBJ_VER_TOP: eHorAlign = SvxAdjust::Right; break; + case EXC_OBJ_VER_CENTER: eHorAlign = SvxAdjust::Center; break; + case EXC_OBJ_VER_BOTTOM: eHorAlign = SvxAdjust::Left; break; + case EXC_OBJ_VER_JUSTIFY: eHorAlign = SvxAdjust::Block; break; + } + } + } + } + break; + + case EXC_OBJ_ORIENT_STACKED: + { + // sj: STACKED is not supported, maybe it can be optimized here a bit + [[fallthrough]]; + } + case EXC_OBJ_ORIENT_90CW: + { + eWriteMode = csst::WritingMode_TB_RL; + switch( maTextData.maData.GetHorAlign() ) + { + case EXC_OBJ_HOR_LEFT: eVerAlign = SDRTEXTVERTADJUST_BOTTOM; break; + case EXC_OBJ_HOR_CENTER: eVerAlign = SDRTEXTVERTADJUST_CENTER; break; + case EXC_OBJ_HOR_RIGHT: eVerAlign = SDRTEXTVERTADJUST_TOP; break; + case EXC_OBJ_HOR_JUSTIFY: eVerAlign = SDRTEXTVERTADJUST_BLOCK; break; + } + MSO_Anchor eTextAnchor = static_cast(rDffConv.GetPropertyValue( DFF_Prop_anchorText, mso_anchorTop )); + switch ( eTextAnchor ) + { + case mso_anchorTopCentered : + case mso_anchorMiddleCentered : + case mso_anchorBottomCentered : + { + eHorAlign = SvxAdjust::Center; + } + break; + + default: + { + switch( maTextData.maData.GetVerAlign() ) + { + case EXC_OBJ_VER_TOP: eHorAlign = SvxAdjust::Left; break; + case EXC_OBJ_VER_CENTER: eHorAlign = SvxAdjust::Center; break; + case EXC_OBJ_VER_BOTTOM: eHorAlign = SvxAdjust::Right; break; + case EXC_OBJ_VER_JUSTIFY: eHorAlign = SvxAdjust::Block; break; + } + } + } + } + break; + } + rSdrObj.SetMergedItem( SvxAdjustItem( eHorAlign, EE_PARA_JUST ) ); + rSdrObj.SetMergedItem( SdrTextVertAdjustItem( eVerAlign ) ); + rSdrObj.SetMergedItem( SvxWritingModeItem( eWriteMode, SDRATTR_TEXTDIRECTION ) ); + } + } + // base class processing + XclImpRectObj::DoPreProcessSdrObj( rDffConv, rSdrObj ); +} + +XclImpChartObj::XclImpChartObj( const XclImpRoot& rRoot, bool bOwnTab ) : + XclImpRectObj( rRoot ), + mbOwnTab( bOwnTab ) +{ + SetSimpleMacro( false ); + SetCustomDffObj( true ); +} + +void XclImpChartObj::ReadChartSubStream( XclImpStream& rStrm ) +{ + /* If chart is read from a chartsheet (mbOwnTab == true), the BOF record + has already been read. If chart is embedded as object, the next record + has to be the BOF record. */ + if( mbOwnTab ) + { + /* #i109800# The input stream may point somewhere inside the chart + substream and not exactly to the leading BOF record. To read this + record correctly in the following, the stream has to rewind it, so + that the next call to StartNextRecord() will find it correctly. */ + if( rStrm.GetRecId() != EXC_ID5_BOF ) + rStrm.RewindRecord(); + } + else + { + if( (rStrm.GetNextRecId() == EXC_ID5_BOF) && rStrm.StartNextRecord() ) + { + sal_uInt16 nBofType; + rStrm.Seek( 2 ); + nBofType = rStrm.ReaduInt16(); + SAL_WARN_IF( nBofType != EXC_BOF_CHART, "sc.filter", "XclImpChartObj::ReadChartSubStream - no chart BOF record" ); + } + else + { + SAL_INFO("sc.filter", "XclImpChartObj::ReadChartSubStream - missing chart substream"); + return; + } + } + + // read chart, even if BOF record contains wrong substream identifier + mxChart = std::make_shared( GetRoot(), mbOwnTab ); + mxChart->ReadChartSubStream( rStrm ); + if( mbOwnTab ) + FinalizeTabChart(); +} + +void XclImpChartObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + // read OBJ record and the following chart substream + ReadFrameData( rStrm ); + rStrm.Ignore( 18 ); + ReadMacro3( rStrm, nMacroSize ); + // set frame format from OBJ record, it is used if chart itself is transparent + if( mxChart ) + mxChart->UpdateObjFrame( maLineData, maFillData ); +} + +void XclImpChartObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + // read OBJ record and the following chart substream + ReadFrameData( rStrm ); + rStrm.Ignore( 18 ); + ReadMacro4( rStrm, nMacroSize ); + // set frame format from OBJ record, it is used if chart itself is transparent + if( mxChart ) + mxChart->UpdateObjFrame( maLineData, maFillData ); +} + +void XclImpChartObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + // read OBJ record and the following chart substream + ReadFrameData( rStrm ); + rStrm.Ignore( 18 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); + ReadChartSubStream( rStrm ); + // set frame format from OBJ record, it is used if chart itself is transparent + if( mxChart ) + mxChart->UpdateObjFrame( maLineData, maFillData ); +} + +void XclImpChartObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 /*nSubRecSize*/ ) +{ + // read the following chart substream + if( nSubRecId == EXC_ID_OBJEND ) + { + // enable CONTINUE handling for the entire chart substream + rStrm.ResetRecord( true ); + ReadChartSubStream( rStrm ); + /* disable CONTINUE handling again to be able to read + following CONTINUE records as MSODRAWING records. */ + rStrm.ResetRecord( false ); + } +} + +std::size_t XclImpChartObj::DoGetProgressSize() const +{ + return mxChart ? mxChart->GetProgressSize() : 1; +} + +SdrObjectUniquePtr XclImpChartObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj; + SfxObjectShell* pDocShell = GetDocShell(); + if( rDffConv.SupportsOleObjects() && SvtModuleOptions().IsChart() && pDocShell && mxChart && !mxChart->IsPivotChart() ) + { + // create embedded chart object + OUString aEmbObjName; + OUString sBaseURL(GetRoot().GetMedium().GetBaseURL()); + Reference< XEmbeddedObject > xEmbObj = pDocShell->GetEmbeddedObjectContainer(). + CreateEmbeddedObject( SvGlobalName( SO3_SCH_CLASSID ).GetByteSequence(), aEmbObjName, &sBaseURL ); + + if (!xEmbObj) + return xSdrObj; + + /* Set the size to the embedded object, this prevents that font sizes + of text objects are changed in the chart when the object is + inserted into the draw page. */ + sal_Int64 nAspect = css::embed::Aspects::MSOLE_CONTENT; + MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xEmbObj->getMapUnit( nAspect ) ); + Size aSize( OutputDevice::LogicToLogic( rAnchorRect.GetSize(), MapMode( MapUnit::Map100thMM ), MapMode( aUnit ) ) ); + css::awt::Size aAwtSize( aSize.Width(), aSize.Height() ); + xEmbObj->setVisualAreaSize( nAspect, aAwtSize ); + + // #i121334# This call will change the chart's default background fill from white to transparent. + // Add here again if this is wanted (see task description for details) + // ChartHelper::AdaptDefaultsForChart( xEmbObj ); + + // create the container OLE object + xSdrObj.reset( + new SdrOle2Obj( + *GetDoc().GetDrawLayer(), + svt::EmbeddedObjectRef(xEmbObj, nAspect), + aEmbObjName, + rAnchorRect)); + } + + return xSdrObj; +} + +void XclImpChartObj::DoPostProcessSdrObj( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + const SdrOle2Obj* pSdrOleObj = dynamic_cast< const SdrOle2Obj* >( &rSdrObj ); + if( !(mxChart && pSdrOleObj) ) + return; + + const Reference< XEmbeddedObject >& xEmbObj = pSdrOleObj->GetObjRef(); + if( xEmbObj.is() && ::svt::EmbeddedObjectRef::TryRunningState( xEmbObj ) ) try + { + Reference< XEmbedPersist > xPersist( xEmbObj, UNO_QUERY_THROW ); + Reference< XModel > xModel( xEmbObj->getComponent(), UNO_QUERY_THROW ); + mxChart->Convert( xModel, rDffConv, xPersist->getEntryName(), rSdrObj.GetLogicRect() ); + } + catch( const Exception& ) + { + } +} + +void XclImpChartObj::FinalizeTabChart() +{ + /* #i44077# Calculate and store DFF anchor for sheet charts. + Needed to get used area if this chart is inserted as OLE object. */ + OSL_ENSURE( mbOwnTab, "XclImpChartObj::FinalizeTabChart - not allowed for embedded chart objects" ); + + // set uninitialized page to landscape + if( !GetPageSettings().GetPageData().mbValid ) + GetPageSettings().SetPaperSize( EXC_PAPERSIZE_DEFAULT, false ); + + // calculate size of the chart object + const XclPageData& rPageData = GetPageSettings().GetPageData(); + Size aPaperSize = rPageData.GetScPaperSize(); + + tools::Long nWidth = XclTools::GetHmmFromTwips( aPaperSize.Width() ); + tools::Long nHeight = XclTools::GetHmmFromTwips( aPaperSize.Height() ); + + // subtract page margins, give some more extra space + nWidth -= o3tl::saturating_add(XclTools::GetHmmFromInch(rPageData.mfLeftMargin + rPageData.mfRightMargin), static_cast(2000)); + nHeight -= o3tl::saturating_add(XclTools::GetHmmFromInch(rPageData.mfTopMargin + rPageData.mfBottomMargin), static_cast(1000)); + + // print column/row headers? + if( rPageData.mbPrintHeadings ) + { + nWidth -= 2000; + nHeight -= 1000; + } + + // create the object anchor + XclObjAnchor aAnchor; + aAnchor.SetRect( GetRoot(), GetCurrScTab(), tools::Rectangle( 1000, 500, nWidth, nHeight ), MapUnit::Map100thMM ); + SetAnchor( aAnchor ); +} + +XclImpNoteObj::XclImpNoteObj( const XclImpRoot& rRoot ) : + XclImpTextObj( rRoot ), + maScPos( ScAddress::INITIALIZE_INVALID ), + mnNoteFlags( 0 ) +{ + SetSimpleMacro( false ); + // caption object will be created manually + SetInsertSdrObj( false ); +} + +void XclImpNoteObj::SetNoteData( const ScAddress& rScPos, sal_uInt16 nNoteFlags ) +{ + maScPos = rScPos; + mnNoteFlags = nNoteFlags; +} + +void XclImpNoteObj::DoPreProcessSdrObj( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + // create formatted text + XclImpTextObj::DoPreProcessSdrObj( rDffConv, rSdrObj ); + OutlinerParaObject* pOutlinerObj = rSdrObj.GetOutlinerParaObject(); + if( maScPos.IsValid() && pOutlinerObj ) + { + // create cell note with all data from drawing object + ScNoteUtil::CreateNoteFromObjectData( + GetDoc(), maScPos, + rSdrObj.GetMergedItemSet().CloneAsValue(), // new object on heap expected + *pOutlinerObj, + rSdrObj.GetLogicRect(), + ::get_flag( mnNoteFlags, EXC_NOTE_VISIBLE ) ); + } +} + +XclImpControlHelper::XclImpControlHelper( const XclImpRoot& rRoot, XclCtrlBindMode eBindMode ) : + mrRoot( rRoot ), + meBindMode( eBindMode ) +{ +} + +XclImpControlHelper::~XclImpControlHelper() +{ +} + +SdrObjectUniquePtr XclImpControlHelper::CreateSdrObjectFromShape( + const Reference< XShape >& rxShape, const tools::Rectangle& rAnchorRect ) const +{ + mxShape = rxShape; + SdrObjectUniquePtr xSdrObj( SdrObject::getSdrObjectFromXShape( rxShape ) ); + if( xSdrObj ) + { + xSdrObj->NbcSetSnapRect( rAnchorRect ); + // #i30543# insert into control layer + xSdrObj->NbcSetLayer( SC_LAYER_CONTROLS ); + } + return xSdrObj; +} + +void XclImpControlHelper::ApplySheetLinkProps() const +{ + + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( mxShape ); + if( !xCtrlModel.is() ) + return; + + // sheet links + SfxObjectShell* pDocShell = mrRoot.GetDocShell(); + if(!pDocShell) + return; + + Reference< XMultiServiceFactory > xFactory( pDocShell->GetModel(), UNO_QUERY ); + if( !xFactory.is() ) + return; + + // cell link + if( mxCellLink ) try + { + Reference< XBindableValue > xBindable( xCtrlModel, UNO_QUERY_THROW ); + + // create argument sequence for createInstanceWithArguments() + CellAddress aApiAddress; + ScUnoConversion::FillApiAddress( aApiAddress, *mxCellLink ); + + NamedValue aValue; + aValue.Name = SC_UNONAME_BOUNDCELL; + aValue.Value <<= aApiAddress; + + Sequence< Any > aArgs{ Any(aValue) }; + + // create the CellValueBinding instance and set at the control model + OUString aServiceName; + switch( meBindMode ) + { + case EXC_CTRL_BINDCONTENT: aServiceName = SC_SERVICENAME_VALBIND; break; + case EXC_CTRL_BINDPOSITION: aServiceName = SC_SERVICENAME_LISTCELLBIND; break; + } + Reference< XValueBinding > xBinding( + xFactory->createInstanceWithArguments( aServiceName, aArgs ), UNO_QUERY_THROW ); + xBindable->setValueBinding( xBinding ); + } + catch( const Exception& ) + { + } + + // source range + if( !mxSrcRange ) + return; + + try + { + Reference< XListEntrySink > xEntrySink( xCtrlModel, UNO_QUERY_THROW ); + + // create argument sequence for createInstanceWithArguments() + CellRangeAddress aApiRange; + ScUnoConversion::FillApiRange( aApiRange, *mxSrcRange ); + + NamedValue aValue; + aValue.Name = SC_UNONAME_CELLRANGE; + aValue.Value <<= aApiRange; + + Sequence< Any > aArgs{ Any(aValue) }; + + // create the EntrySource instance and set at the control model + Reference< XListEntrySource > xEntrySource( xFactory->createInstanceWithArguments( + SC_SERVICENAME_LISTSOURCE, aArgs ), UNO_QUERY_THROW ); + xEntrySink->setListEntrySource( xEntrySource ); + } + catch( const Exception& ) + { + } +} + +void XclImpControlHelper::ProcessControl( const XclImpDrawObjBase& rDrawObj ) const +{ + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( mxShape ); + if( !xCtrlModel.is() ) + return; + + ApplySheetLinkProps(); + + ScfPropertySet aPropSet( xCtrlModel ); + + // #i51348# set object name at control model + aPropSet.SetStringProperty( "Name", rDrawObj.GetObjName() ); + + // control visible and printable? + aPropSet.SetBoolProperty( "EnableVisible", rDrawObj.IsVisible() ); + aPropSet.SetBoolProperty( "Printable", rDrawObj.IsPrintable() ); + + // virtual call for type specific processing + DoProcessControl( aPropSet ); +} + +void XclImpControlHelper::ReadCellLinkFormula( XclImpStream& rStrm, bool bWithBoundSize ) +{ + ScRangeList aScRanges; + ReadRangeList( aScRanges, rStrm, bWithBoundSize ); + // Use first cell of first range + if ( !aScRanges.empty() ) + { + const ScRange & rScRange = aScRanges.front(); + mxCellLink = std::make_shared( rScRange.aStart ); + } +} + +void XclImpControlHelper::ReadSourceRangeFormula( XclImpStream& rStrm, bool bWithBoundSize ) +{ + ScRangeList aScRanges; + ReadRangeList( aScRanges, rStrm, bWithBoundSize ); + // Use first range + if ( !aScRanges.empty() ) + { + const ScRange & rScRange = aScRanges.front(); + mxSrcRange = std::make_shared( rScRange ); + } +} + +void XclImpControlHelper::DoProcessControl( ScfPropertySet& ) const +{ +} + +void XclImpControlHelper::ReadRangeList( ScRangeList& rScRanges, XclImpStream& rStrm ) +{ + XclTokenArray aXclTokArr; + sal_uInt16 nSize = XclTokenArray::ReadSize(rStrm); + rStrm.Ignore( 4 ); + aXclTokArr.ReadArray(nSize, rStrm); + mrRoot.GetFormulaCompiler().CreateRangeList( rScRanges, EXC_FMLATYPE_CONTROL, aXclTokArr, rStrm ); +} + +void XclImpControlHelper::ReadRangeList( ScRangeList& rScRanges, XclImpStream& rStrm, bool bWithBoundSize ) +{ + if( bWithBoundSize ) + { + sal_uInt16 nSize; + nSize = rStrm.ReaduInt16(); + if( nSize > 0 ) + { + rStrm.PushPosition(); + ReadRangeList( rScRanges, rStrm ); + rStrm.PopPosition(); + rStrm.Ignore( nSize ); + } + } + else + { + ReadRangeList( rScRanges, rStrm ); + } +} + +XclImpTbxObjBase::XclImpTbxObjBase( const XclImpRoot& rRoot ) : + XclImpTextObj( rRoot ), + XclImpControlHelper( rRoot, EXC_CTRL_BINDPOSITION ) +{ + SetSimpleMacro( false ); + SetCustomDffObj( true ); +} + +namespace { + +void lclExtractColor( sal_uInt8& rnColorIdx, const DffPropSet& rDffPropSet, sal_uInt32 nPropId ) +{ + if( rDffPropSet.IsProperty( nPropId ) ) + { + sal_uInt32 nColor = rDffPropSet.GetPropertyValue( nPropId, 0 ); + if( (nColor & 0xFF000000) == 0x08000000 ) + rnColorIdx = ::extract_value< sal_uInt8 >( nColor, 0, 8 ); + } +} + +} // namespace + +void XclImpTbxObjBase::SetDffProperties( const DffPropSet& rDffPropSet ) +{ + maFillData.mnPattern = rDffPropSet.GetPropertyBool( DFF_Prop_fFilled ) ? EXC_PATT_SOLID : EXC_PATT_NONE; + lclExtractColor( maFillData.mnBackColorIdx, rDffPropSet, DFF_Prop_fillBackColor ); + lclExtractColor( maFillData.mnPattColorIdx, rDffPropSet, DFF_Prop_fillColor ); + ::set_flag( maFillData.mnAuto, EXC_OBJ_LINE_AUTO, false ); + + maLineData.mnStyle = rDffPropSet.GetPropertyBool( DFF_Prop_fLine ) ? EXC_OBJ_LINE_SOLID : EXC_OBJ_LINE_NONE; + lclExtractColor( maLineData.mnColorIdx, rDffPropSet, DFF_Prop_lineColor ); + ::set_flag( maLineData.mnAuto, EXC_OBJ_FILL_AUTO, false ); +} + +bool XclImpTbxObjBase::FillMacroDescriptor( ScriptEventDescriptor& rDescriptor ) const +{ + return XclControlHelper::FillMacroDescriptor( rDescriptor, DoGetEventType(), GetMacroName(), GetDocShell() ); +} + +void XclImpTbxObjBase::ConvertFont( ScfPropertySet& rPropSet ) const +{ + if( maTextData.mxString ) + { + const XclFormatRunVec& rFormatRuns = maTextData.mxString->GetFormats(); + if( rFormatRuns.empty() ) + GetFontBuffer().WriteDefaultCtrlFontProperties( rPropSet ); + else + GetFontBuffer().WriteFontProperties( rPropSet, EXC_FONTPROPSET_CONTROL, rFormatRuns.front().mnFontIdx ); + } +} + +void XclImpTbxObjBase::ConvertLabel( ScfPropertySet& rPropSet ) const +{ + if( maTextData.mxString ) + { + OUString aLabel = maTextData.mxString->GetText(); + if( maTextData.maData.mnShortcut > 0 ) + { + sal_Int32 nPos = aLabel.indexOf( static_cast< sal_Unicode >( maTextData.maData.mnShortcut ) ); + if( nPos != -1 ) + aLabel = aLabel.replaceAt( nPos, 0, u"~" ); + } + rPropSet.SetStringProperty( "Label", aLabel ); + + //Excel Alt text <==> Aoo description + //For TBX control, if user does not operate alt text, alt text will be set label text as default value in Excel. + //In this case, DFF_Prop_wzDescription will not be set in excel file. + //So In the end of SvxMSDffManager::ImportShape, description will not be set. But actually in excel, + //the alt text is the label value. So here set description as label text first which is called before ImportShape. + Reference< css::beans::XPropertySet > xPropset( mxShape, UNO_QUERY ); + try{ + if(xPropset.is()) + xPropset->setPropertyValue( "Description", Any(aLabel) ); + }catch( ... ) + { + SAL_WARN("sc.filter", "Can't set a default text for TBX Control "); + } + } + ConvertFont( rPropSet ); +} + +SdrObjectUniquePtr XclImpTbxObjBase::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + SdrObjectUniquePtr xSdrObj( rDffConv.CreateSdrObject( *this, rAnchorRect ) ); + rDffConv.Progress(); + return xSdrObj; +} + +void XclImpTbxObjBase::DoPreProcessSdrObj( XclImpDffConverter& /*rDffConv*/, SdrObject& /*rSdrObj*/ ) const +{ + // do not call DoPreProcessSdrObj() from base class (to skip text processing) + ProcessControl( *this ); +} + +XclImpButtonObj::XclImpButtonObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ) +{ +} + +void XclImpButtonObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); + + /* Horizontal text alignment. For unknown reason, the property type is a + simple sal_Int16 and not a com.sun.star.style.HorizontalAlignment. */ + sal_Int16 nHorAlign = 1; + switch( maTextData.maData.GetHorAlign() ) + { + case EXC_OBJ_HOR_LEFT: nHorAlign = 0; break; + case EXC_OBJ_HOR_CENTER: nHorAlign = 1; break; + case EXC_OBJ_HOR_RIGHT: nHorAlign = 2; break; + } + rPropSet.SetProperty( "Align", nHorAlign ); + + // vertical text alignment + namespace csss = ::com::sun::star::style; + csss::VerticalAlignment eVerAlign = csss::VerticalAlignment_MIDDLE; + switch( maTextData.maData.GetVerAlign() ) + { + case EXC_OBJ_VER_TOP: eVerAlign = csss::VerticalAlignment_TOP; break; + case EXC_OBJ_VER_CENTER: eVerAlign = csss::VerticalAlignment_MIDDLE; break; + case EXC_OBJ_VER_BOTTOM: eVerAlign = csss::VerticalAlignment_BOTTOM; break; + } + rPropSet.SetProperty( "VerticalAlign", eVerAlign ); + + // always wrap text automatically + rPropSet.SetBoolProperty( "MultiLine", true ); + + // default button + bool bDefButton = ::get_flag( maTextData.maData.mnButtonFlags, EXC_OBJ_BUTTON_DEFAULT ); + rPropSet.SetBoolProperty( "DefaultButton", bDefButton ); + + // button type (flags cannot be combined in OOo) + namespace cssa = ::com::sun::star::awt; + cssa::PushButtonType eButtonType = cssa::PushButtonType_STANDARD; + if( ::get_flag( maTextData.maData.mnButtonFlags, EXC_OBJ_BUTTON_CLOSE ) ) + eButtonType = cssa::PushButtonType_OK; + else if( ::get_flag( maTextData.maData.mnButtonFlags, EXC_OBJ_BUTTON_CANCEL ) ) + eButtonType = cssa::PushButtonType_CANCEL; + else if( ::get_flag( maTextData.maData.mnButtonFlags, EXC_OBJ_BUTTON_HELP ) ) + eButtonType = cssa::PushButtonType_HELP; + // property type is short, not enum + rPropSet.SetProperty( "PushButtonType", sal_Int16( eButtonType ) ); +} + +OUString XclImpButtonObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.CommandButton"; +} + +XclTbxEventType XclImpButtonObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_ACTION; +} + +XclImpCheckBoxObj::XclImpCheckBoxObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ), + mnState( EXC_OBJ_CHECKBOX_UNCHECKED ), + mnCheckBoxFlags( 0 ) +{ +} + +void XclImpCheckBoxObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + rStrm.Ignore( 10 ); + maTextData.maData.mnFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 20 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + mnState = rStrm.ReaduInt16(); + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16(); + mnCheckBoxFlags = rStrm.ReaduInt16(); +} + +void XclImpCheckBoxObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJCBLS: + // do not read EXC_ID_OBJCBLSDATA, not written by OOo Excel export + mnState = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16(); + mnCheckBoxFlags = rStrm.ReaduInt16(); + break; + case EXC_ID_OBJCBLSFMLA: + ReadCellLinkFormula( rStrm, false ); + break; + default: + XclImpTbxObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpCheckBoxObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); + + // state + bool bSupportsTristate = GetObjType() == EXC_OBJTYPE_CHECKBOX; + sal_Int16 nApiState = 0; + switch( mnState ) + { + case EXC_OBJ_CHECKBOX_UNCHECKED: nApiState = 0; break; + case EXC_OBJ_CHECKBOX_CHECKED: nApiState = 1; break; + case EXC_OBJ_CHECKBOX_TRISTATE: nApiState = bSupportsTristate ? 2 : 1; break; + } + if( bSupportsTristate ) + rPropSet.SetBoolProperty( "TriState", nApiState == 2 ); + rPropSet.SetProperty( "DefaultState", nApiState ); + + // box style + namespace AwtVisualEffect = ::com::sun::star::awt::VisualEffect; + sal_Int16 nEffect = ::get_flagvalue( mnCheckBoxFlags, EXC_OBJ_CHECKBOX_FLAT, AwtVisualEffect::FLAT, AwtVisualEffect::LOOK3D ); + rPropSet.SetProperty( "VisualEffect", nEffect ); + + // do not wrap text automatically + rPropSet.SetBoolProperty( "MultiLine", false ); + + // #i40279# always centered vertically + namespace csss = ::com::sun::star::style; + rPropSet.SetProperty( "VerticalAlign", csss::VerticalAlignment_MIDDLE ); + + // background color + if( maFillData.IsFilled() ) + { + sal_Int32 nColor = static_cast< sal_Int32 >( GetSolidFillColor( maFillData ) ); + rPropSet.SetProperty( "BackgroundColor", nColor ); + } +} + +OUString XclImpCheckBoxObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.CheckBox"; +} + +XclTbxEventType XclImpCheckBoxObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_ACTION; +} + +XclImpOptionButtonObj::XclImpOptionButtonObj( const XclImpRoot& rRoot ) : + XclImpCheckBoxObj( rRoot ), + mnNextInGroup( 0 ), + mnFirstInGroup( 1 ) +{ +} + +void XclImpOptionButtonObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + rStrm.Ignore( 10 ); + maTextData.maData.mnFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 32 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + mnState = rStrm.ReaduInt16(); + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16(); + mnCheckBoxFlags = rStrm.ReaduInt16(); + mnNextInGroup = rStrm.ReaduInt16(); + mnFirstInGroup = rStrm.ReaduInt16(); +} + +void XclImpOptionButtonObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJRBODATA: + mnNextInGroup = rStrm.ReaduInt16(); + mnFirstInGroup = rStrm.ReaduInt16(); + break; + default: + XclImpCheckBoxObj::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpOptionButtonObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + XclImpCheckBoxObj::DoProcessControl( rPropSet ); + // TODO: grouping + XclImpOptionButtonObj* pTbxObj = dynamic_cast< XclImpOptionButtonObj* >( GetObjectManager().GetSheetDrawing( GetTab() ).FindDrawObj( mnNextInGroup ).get() ); + if ( pTbxObj && pTbxObj->mnFirstInGroup ) + { + // Group has terminated + // traverse each RadioButton in group and + // a) apply the groupname + // b) propagate the linked cell from the lead radiobutton + // c) apply the correct Ref value + XclImpOptionButtonObj* pLeader = pTbxObj; + + sal_Int32 nRefVal = 1; + do + { + + Reference< XControlModel > xCtrlModel = XclControlHelper::GetControlModel( pTbxObj->mxShape ); + if ( xCtrlModel.is() ) + { + ScfPropertySet aProps( xCtrlModel ); + OUString sGroupName = OUString::number( pLeader->GetDffShapeId() ); + + aProps.SetStringProperty( "GroupName", sGroupName ); + aProps.SetStringProperty( "RefValue", OUString::number( nRefVal++ ) ); + if ( pLeader->HasCellLink() && !pTbxObj->HasCellLink() ) + { + // propagate cell link info + pTbxObj->mxCellLink = std::make_shared( *pLeader->mxCellLink ); + pTbxObj->ApplySheetLinkProps(); + } + pTbxObj = dynamic_cast< XclImpOptionButtonObj* >( GetObjectManager().GetSheetDrawing( GetTab() ).FindDrawObj( pTbxObj->mnNextInGroup ).get() ); + } + else + pTbxObj = nullptr; + } while ( pTbxObj && ( pTbxObj->mnFirstInGroup != 1 ) ); + } + else + { + // not the leader? try and find it + } +} + +OUString XclImpOptionButtonObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.RadioButton"; +} + +XclTbxEventType XclImpOptionButtonObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_ACTION; +} + +XclImpLabelObj::XclImpLabelObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ) +{ +} + +void XclImpLabelObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); + + // text alignment (always top/left aligned) + rPropSet.SetProperty( "Align", sal_Int16( 0 ) ); + namespace csss = ::com::sun::star::style; + rPropSet.SetProperty( "VerticalAlign", csss::VerticalAlignment_TOP ); + + // always wrap text automatically + rPropSet.SetBoolProperty( "MultiLine", true ); +} + +OUString XclImpLabelObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.FixedText"; +} + +XclTbxEventType XclImpLabelObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_MOUSE; +} + +XclImpGroupBoxObj::XclImpGroupBoxObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ), + mnGroupBoxFlags( 0 ) +{ +} + +void XclImpGroupBoxObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + rStrm.Ignore( 10 ); + maTextData.maData.mnFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 26 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16( ); + mnGroupBoxFlags = rStrm.ReaduInt16(); +} + +void XclImpGroupBoxObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJGBODATA: + maTextData.maData.mnShortcut = rStrm.ReaduInt16(); + maTextData.maData.mnShortcutEA = rStrm.ReaduInt16(); + mnGroupBoxFlags = rStrm.ReaduInt16(); + break; + default: + XclImpTbxObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpGroupBoxObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); +} + +OUString XclImpGroupBoxObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.GroupBox"; +} + +XclTbxEventType XclImpGroupBoxObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_MOUSE; +} + +XclImpDialogObj::XclImpDialogObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ) +{ +} + +void XclImpDialogObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // label and text formatting + ConvertLabel( rPropSet ); +} + +OUString XclImpDialogObj::DoGetServiceName() const +{ + // dialog frame faked by a groupbox + return "com.sun.star.form.component.GroupBox"; +} + +XclTbxEventType XclImpDialogObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_MOUSE; +} + +XclImpEditObj::XclImpEditObj( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ), + mnContentType( EXC_OBJ_EDIT_TEXT ), + mnMultiLine( 0 ), + mnScrollBar( 0 ), + mnListBoxObjId( 0 ) +{ +} + +bool XclImpEditObj::IsNumeric() const +{ + return (mnContentType == EXC_OBJ_EDIT_INTEGER) || (mnContentType == EXC_OBJ_EDIT_DOUBLE); +} + +void XclImpEditObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + rStrm.Ignore( 10 ); + maTextData.maData.mnFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 14 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + mnContentType = rStrm.ReaduInt16(); + mnMultiLine = rStrm.ReaduInt16(); + mnScrollBar = rStrm.ReaduInt16(); + mnListBoxObjId = rStrm.ReaduInt16(); +} + +void XclImpEditObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJEDODATA: + mnContentType = rStrm.ReaduInt16(); + mnMultiLine = rStrm.ReaduInt16(); + mnScrollBar = rStrm.ReaduInt16(); + mnListBoxObjId = rStrm.ReaduInt16(); + break; + default: + XclImpTbxObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpEditObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + if( maTextData.mxString ) + { + OUString aText = maTextData.mxString->GetText(); + if( IsNumeric() ) + { + // TODO: OUString::toDouble() does not handle local decimal separator + rPropSet.SetProperty( "DefaultValue", aText.toDouble() ); + rPropSet.SetBoolProperty( "Spin", mnScrollBar != 0 ); + } + else + { + rPropSet.SetProperty( "DefaultText", aText ); + rPropSet.SetBoolProperty( "MultiLine", mnMultiLine != 0 ); + rPropSet.SetBoolProperty( "VScroll", mnScrollBar != 0 ); + } + } + ConvertFont( rPropSet ); +} + +OUString XclImpEditObj::DoGetServiceName() const +{ + return IsNumeric() ? + OUString( "com.sun.star.form.component.NumericField" ) : + OUString( "com.sun.star.form.component.TextField" ); +} + +XclTbxEventType XclImpEditObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_TEXT; +} + +XclImpTbxObjScrollableBase::XclImpTbxObjScrollableBase( const XclImpRoot& rRoot ) : + XclImpTbxObjBase( rRoot ), + mnValue( 0 ), + mnMin( 0 ), + mnMax( 100 ), + mnStep( 1 ), + mnPageStep( 10 ), + mnOrient( 0 ), + mnThumbWidth( 1 ), + mnScrollFlags( 0 ) +{ +} + +void XclImpTbxObjScrollableBase::ReadSbs( XclImpStream& rStrm ) +{ + rStrm.Ignore( 4 ); + mnValue = rStrm.ReaduInt16(); + mnMin = rStrm.ReaduInt16(); + mnMax = rStrm.ReaduInt16(); + mnStep = rStrm.ReaduInt16(); + mnPageStep = rStrm.ReaduInt16(); + mnOrient = rStrm.ReaduInt16(); + mnThumbWidth = rStrm.ReaduInt16(); + mnScrollFlags = rStrm.ReaduInt16(); +} + +void XclImpTbxObjScrollableBase::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJSBS: + ReadSbs( rStrm ); + break; + case EXC_ID_OBJSBSFMLA: + ReadCellLinkFormula( rStrm, false ); + break; + default: + XclImpTbxObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +XclImpSpinButtonObj::XclImpSpinButtonObj( const XclImpRoot& rRoot ) : + XclImpTbxObjScrollableBase( rRoot ) +{ +} + +void XclImpSpinButtonObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + ReadSbs( rStrm ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); +} + +void XclImpSpinButtonObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // Calc's "Border" property is not the 3D/flat style effect in Excel (#i34712#) + rPropSet.SetProperty( "Border", css::awt::VisualEffect::NONE ); + rPropSet.SetProperty< sal_Int32 >( "DefaultSpinValue", mnValue ); + rPropSet.SetProperty< sal_Int32 >( "SpinValueMin", mnMin ); + rPropSet.SetProperty< sal_Int32 >( "SpinValueMax", mnMax ); + rPropSet.SetProperty< sal_Int32 >( "SpinIncrement", mnStep ); + + // Excel spin buttons always vertical + rPropSet.SetProperty( "Orientation", css::awt::ScrollBarOrientation::VERTICAL ); +} + +OUString XclImpSpinButtonObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.SpinButton"; +} + +XclTbxEventType XclImpSpinButtonObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_VALUE; +} + +XclImpScrollBarObj::XclImpScrollBarObj( const XclImpRoot& rRoot ) : + XclImpTbxObjScrollableBase( rRoot ) +{ +} + +void XclImpScrollBarObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + ReadSbs( rStrm ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); +} + +void XclImpScrollBarObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // Calc's "Border" property is not the 3D/flat style effect in Excel (#i34712#) + rPropSet.SetProperty( "Border", css::awt::VisualEffect::NONE ); + rPropSet.SetProperty< sal_Int32 >( "DefaultScrollValue", mnValue ); + rPropSet.SetProperty< sal_Int32 >( "ScrollValueMin", mnMin ); + rPropSet.SetProperty< sal_Int32 >( "ScrollValueMax", mnMax ); + rPropSet.SetProperty< sal_Int32 >( "LineIncrement", mnStep ); + rPropSet.SetProperty< sal_Int32 >( "BlockIncrement", mnPageStep ); + rPropSet.SetProperty( "VisibleSize", ::std::min< sal_Int32 >( mnPageStep, 1 ) ); + + namespace AwtScrollOrient = ::com::sun::star::awt::ScrollBarOrientation; + sal_Int32 nApiOrient = ::get_flagvalue( mnOrient, EXC_OBJ_SCROLLBAR_HOR, AwtScrollOrient::HORIZONTAL, AwtScrollOrient::VERTICAL ); + rPropSet.SetProperty( "Orientation", nApiOrient ); +} + +OUString XclImpScrollBarObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.ScrollBar"; +} + +XclTbxEventType XclImpScrollBarObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_VALUE; +} + +XclImpTbxObjListBase::XclImpTbxObjListBase( const XclImpRoot& rRoot ) : + XclImpTbxObjScrollableBase( rRoot ), + mnEntryCount( 0 ), + mnSelEntry( 0 ), + mnListFlags( 0 ), + mnEditObjId( 0 ), + mbHasDefFontIdx( false ) +{ +} + +void XclImpTbxObjListBase::ReadLbsData( XclImpStream& rStrm ) +{ + ReadSourceRangeFormula( rStrm, true ); + mnEntryCount = rStrm.ReaduInt16(); + mnSelEntry = rStrm.ReaduInt16(); + mnListFlags = rStrm.ReaduInt16(); + mnEditObjId = rStrm.ReaduInt16(); +} + +void XclImpTbxObjListBase::SetBoxFormatting( ScfPropertySet& rPropSet ) const +{ + // border style + namespace AwtVisualEffect = ::com::sun::star::awt::VisualEffect; + sal_Int16 nApiBorder = ::get_flagvalue( mnListFlags, EXC_OBJ_LISTBOX_FLAT, AwtVisualEffect::FLAT, AwtVisualEffect::LOOK3D ); + rPropSet.SetProperty( "Border", nApiBorder ); + + // font formatting + if( mbHasDefFontIdx ) + GetFontBuffer().WriteFontProperties( rPropSet, EXC_FONTPROPSET_CONTROL, maTextData.maData.mnDefFontIdx ); + else + GetFontBuffer().WriteDefaultCtrlFontProperties( rPropSet ); +} + +XclImpListBoxObj::XclImpListBoxObj( const XclImpRoot& rRoot ) : + XclImpTbxObjListBase( rRoot ) +{ +} + +void XclImpListBoxObj::ReadFullLbsData( XclImpStream& rStrm, std::size_t nRecLeft ) +{ + std::size_t nRecEnd = rStrm.GetRecPos() + nRecLeft; + ReadLbsData( rStrm ); + OSL_ENSURE( (rStrm.GetRecPos() == nRecEnd) || (rStrm.GetRecPos() + mnEntryCount == nRecEnd), + "XclImpListBoxObj::ReadFullLbsData - invalid size of OBJLBSDATA record" ); + while (rStrm.IsValid()) + { + if (rStrm.GetRecPos() >= nRecEnd) + break; + maSelection.push_back( rStrm.ReaduInt8() ); + } +} + +void XclImpListBoxObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + ReadSbs( rStrm ); + rStrm.Ignore( 18 ); + maTextData.maData.mnDefFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); + ReadFullLbsData( rStrm, rStrm.GetRecLeft() ); + mbHasDefFontIdx = true; +} + +void XclImpListBoxObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJLBSDATA: + ReadFullLbsData( rStrm, nSubRecSize ); + break; + default: + XclImpTbxObjListBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpListBoxObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // listbox formatting + SetBoxFormatting( rPropSet ); + + // selection type + sal_uInt8 nSelType = ::extract_value< sal_uInt8 >( mnListFlags, 4, 2 ); + bool bMultiSel = nSelType != EXC_OBJ_LISTBOX_SINGLE; + rPropSet.SetBoolProperty( "MultiSelection", bMultiSel ); + + // selection (do not set, if listbox is linked to a cell) + if( HasCellLink() ) + return; + + ScfInt16Vec aSelVec; + + // multi selection: API expects sequence of list entry indexes + if( bMultiSel ) + { + sal_Int16 nIndex = 0; + for( const auto& rItem : maSelection ) + { + if( rItem != 0 ) + aSelVec.push_back( nIndex ); + ++nIndex; + } + } + // single selection: mnSelEntry is one-based, API expects zero-based + else if( mnSelEntry > 0 ) + aSelVec.push_back( static_cast< sal_Int16 >( mnSelEntry - 1 ) ); + + if( !aSelVec.empty() ) + { + Sequence aSelSeq(aSelVec.data(), static_cast(aSelVec.size())); + rPropSet.SetProperty( "DefaultSelection", aSelSeq ); + } +} + +OUString XclImpListBoxObj::DoGetServiceName() const +{ + return "com.sun.star.form.component.ListBox"; +} + +XclTbxEventType XclImpListBoxObj::DoGetEventType() const +{ + return EXC_TBX_EVENT_CHANGE; +} + +XclImpDropDownObj::XclImpDropDownObj( const XclImpRoot& rRoot ) : + XclImpTbxObjListBase( rRoot ), + mnLeft( 0 ), + mnTop( 0 ), + mnRight( 0 ), + mnBottom( 0 ), + mnDropDownFlags( 0 ), + mnLineCount( 0 ), + mnMinWidth( 0 ) +{ +} + +sal_uInt16 XclImpDropDownObj::GetDropDownType() const +{ + return ::extract_value< sal_uInt8 >( mnDropDownFlags, 0, 2 ); +} + +void XclImpDropDownObj::ReadFullLbsData( XclImpStream& rStrm ) +{ + ReadLbsData( rStrm ); + mnDropDownFlags = rStrm.ReaduInt16(); + mnLineCount = rStrm.ReaduInt16(); + mnMinWidth = rStrm.ReaduInt16(); + maTextData.maData.mnTextLen = rStrm.ReaduInt16(); + maTextData.ReadByteString( rStrm ); + // dropdowns of auto-filters have 'simple' style, they don't have a text area + if( GetDropDownType() == EXC_OBJ_DROPDOWN_SIMPLE ) + SetProcessSdrObj( false ); +} + +void XclImpDropDownObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 /*nMacroSize*/ ) +{ + ReadFrameData( rStrm ); + ReadSbs( rStrm ); + rStrm.Ignore( 18 ); + maTextData.maData.mnDefFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 14 ); + mnLeft = rStrm.ReaduInt16(); + mnTop = rStrm.ReaduInt16(); + mnRight = rStrm.ReaduInt16(); + mnBottom = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, rStrm.ReaduInt16() ); // first macro size invalid and unused + ReadCellLinkFormula( rStrm, true ); + ReadFullLbsData( rStrm ); + mbHasDefFontIdx = true; +} + +void XclImpDropDownObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJLBSDATA: + ReadFullLbsData( rStrm ); + break; + default: + XclImpTbxObjListBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +void XclImpDropDownObj::DoProcessControl( ScfPropertySet& rPropSet ) const +{ + // dropdown listbox formatting + SetBoxFormatting( rPropSet ); + // enable dropdown button + rPropSet.SetBoolProperty( "Dropdown", true ); + // dropdown line count + rPropSet.SetProperty( "LineCount", mnLineCount ); + + if( GetDropDownType() == EXC_OBJ_DROPDOWN_COMBOBOX ) + { + // text of editable combobox + if( maTextData.mxString ) + rPropSet.SetStringProperty( "DefaultText", maTextData.mxString->GetText() ); + } + else + { + // selection (do not set, if dropdown is linked to a cell) + if( !HasCellLink() && (mnSelEntry > 0) ) + { + Sequence< sal_Int16 > aSelSeq{ o3tl::narrowing(mnSelEntry - 1) }; + rPropSet.SetProperty( "DefaultSelection", aSelSeq ); + } + } +} + +OUString XclImpDropDownObj::DoGetServiceName() const +{ + return (GetDropDownType() == EXC_OBJ_DROPDOWN_COMBOBOX) ? + OUString( "com.sun.star.form.component.ComboBox" ) : + OUString( "com.sun.star.form.component.ListBox" ); +} + +XclTbxEventType XclImpDropDownObj::DoGetEventType() const +{ + return (GetDropDownType() == EXC_OBJ_DROPDOWN_COMBOBOX) ? EXC_TBX_EVENT_TEXT : EXC_TBX_EVENT_CHANGE; +} + +XclImpPictureObj::XclImpPictureObj( const XclImpRoot& rRoot ) : + XclImpRectObj( rRoot ), + XclImpControlHelper( rRoot, EXC_CTRL_BINDCONTENT ), + mnStorageId( 0 ), + mnCtlsStrmPos( 0 ), + mnCtlsStrmSize( 0 ), + mbEmbedded( false ), + mbLinked( false ), + mbSymbol( false ), + mbControl( false ), + mbUseCtlsStrm( false ) +{ + SetAreaObj( true ); + SetSimpleMacro( true ); + SetCustomDffObj( true ); +} + +OUString XclImpPictureObj::GetOleStorageName() const +{ + OUStringBuffer aStrgName; + if( (mbEmbedded || mbLinked) && !mbControl && (mnStorageId > 0) ) + { + aStrgName = mbEmbedded ? std::u16string_view(u"" EXC_STORAGE_OLE_EMBEDDED) : std::u16string_view(u"" EXC_STORAGE_OLE_LINKED); + static const char spcHexChars[] = "0123456789ABCDEF"; + for( sal_uInt8 nIndex = 32; nIndex > 0; nIndex -= 4 ) + aStrgName.append(OUStringChar( spcHexChars[ ::extract_value< sal_uInt8 >( mnStorageId, nIndex - 4, 4 ) ] )); + } + return aStrgName.makeStringAndClear(); +} + +void XclImpPictureObj::DoReadObj3( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + sal_uInt16 nLinkSize; + ReadFrameData( rStrm ); + rStrm.Ignore( 6 ); + nLinkSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + ReadFlags3( rStrm ); + ReadMacro3( rStrm, nMacroSize ); + ReadPictFmla( rStrm, nLinkSize ); + + if( (rStrm.GetNextRecId() == EXC_ID3_IMGDATA) && rStrm.StartNextRecord() ) + maGraphic = XclImpDrawing::ReadImgData( GetRoot(), rStrm ); +} + +void XclImpPictureObj::DoReadObj4( XclImpStream& rStrm, sal_uInt16 nMacroSize ) +{ + sal_uInt16 nLinkSize; + ReadFrameData( rStrm ); + rStrm.Ignore( 6 ); + nLinkSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + ReadFlags3( rStrm ); + ReadMacro4( rStrm, nMacroSize ); + ReadPictFmla( rStrm, nLinkSize ); + + if( (rStrm.GetNextRecId() == EXC_ID3_IMGDATA) && rStrm.StartNextRecord() ) + maGraphic = XclImpDrawing::ReadImgData( GetRoot(), rStrm ); +} + +void XclImpPictureObj::DoReadObj5( XclImpStream& rStrm, sal_uInt16 nNameLen, sal_uInt16 nMacroSize ) +{ + sal_uInt16 nLinkSize; + ReadFrameData( rStrm ); + rStrm.Ignore( 6 ); + nLinkSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + ReadFlags3( rStrm ); + rStrm.Ignore( 4 ); + ReadName5( rStrm, nNameLen ); + ReadMacro5( rStrm, nMacroSize ); + ReadPictFmla( rStrm, nLinkSize ); + + if( (rStrm.GetNextRecId() == EXC_ID3_IMGDATA) && rStrm.StartNextRecord() ) + { + // page background is stored as hidden picture with name "__BkgndObj" + if ( IsHidden() && (GetObjName() == "__BkgndObj") ) + GetPageSettings().ReadImgData( rStrm ); + else + maGraphic = XclImpDrawing::ReadImgData( GetRoot(), rStrm ); + } +} + +void XclImpPictureObj::DoReadObj8SubRec( XclImpStream& rStrm, sal_uInt16 nSubRecId, sal_uInt16 nSubRecSize ) +{ + switch( nSubRecId ) + { + case EXC_ID_OBJFLAGS: + ReadFlags8( rStrm ); + break; + case EXC_ID_OBJPICTFMLA: + ReadPictFmla( rStrm, rStrm.ReaduInt16() ); + break; + default: + XclImpDrawObjBase::DoReadObj8SubRec( rStrm, nSubRecId, nSubRecSize ); + } +} + +SdrObjectUniquePtr XclImpPictureObj::DoCreateSdrObj( XclImpDffConverter& rDffConv, const tools::Rectangle& rAnchorRect ) const +{ + // try to create an OLE object or form control + SdrObjectUniquePtr xSdrObj( rDffConv.CreateSdrObject( *this, rAnchorRect ) ); + + // insert a graphic replacement for unsupported ole object ( if none already + // exists ) Hmm ok, it's possibly that there has been some imported + // graphic at a base level but unlikely, normally controls have a valid + // preview in the IMGDATA record ( see below ) + // It might be possible to push such an imported graphic up to this + // XclImpPictureObj instance but there are so many layers of indirection I + // don't see an easy way. This way at least ensures that we can + // avoid a 'blank' shape that can result from a failed control import + if ( !xSdrObj && IsOcxControl() && maGraphic.GetType() == GraphicType::NONE ) + { + const_cast< XclImpPictureObj* >( this )->maGraphic = + SdrOle2Obj::GetEmptyOLEReplacementGraphic(); + } + // no OLE - create a plain picture from IMGDATA record data + if( !xSdrObj && (maGraphic.GetType() != GraphicType::NONE) ) + { + xSdrObj.reset( + new SdrGrafObj( + *GetDoc().GetDrawLayer(), + maGraphic, + rAnchorRect)); + ConvertRectStyle( *xSdrObj ); + } + + rDffConv.Progress(); + return xSdrObj; +} + +OUString XclImpPictureObj::GetObjName() const +{ + if( IsOcxControl() ) + { + OUString sName( GetObjectManager().GetOleNameOverride( GetTab(), GetObjId() ) ); + if (!sName.isEmpty()) + return sName; + } + return XclImpDrawObjBase::GetObjName(); +} + +void XclImpPictureObj::DoPreProcessSdrObj( XclImpDffConverter& rDffConv, SdrObject& rSdrObj ) const +{ + if( IsOcxControl() ) + { + // do not call XclImpRectObj::DoPreProcessSdrObj(), it would trace missing "printable" feature + ProcessControl( *this ); + } + else if( mbEmbedded || mbLinked ) + { + // trace missing "printable" feature + XclImpRectObj::DoPreProcessSdrObj( rDffConv, rSdrObj ); + + SfxObjectShell* pDocShell = GetDocShell(); + SdrOle2Obj* pOleSdrObj = dynamic_cast< SdrOle2Obj* >( &rSdrObj ); + if( pOleSdrObj && pDocShell ) + { + comphelper::EmbeddedObjectContainer& rEmbObjCont = pDocShell->GetEmbeddedObjectContainer(); + Reference< XEmbeddedObject > xEmbObj = pOleSdrObj->GetObjRef(); + OUString aOldName( pOleSdrObj->GetPersistName() ); + + /* The object persistence should be already in the storage, but + the object still might not be inserted into the container. */ + if( rEmbObjCont.HasEmbeddedObject( aOldName ) ) + { + if( !rEmbObjCont.HasEmbeddedObject( xEmbObj ) ) + // filter code is allowed to call the following method + rEmbObjCont.AddEmbeddedObject( xEmbObj, aOldName ); + } + else + { + /* If the object is still not in container it must be inserted + there, the name must be generated in this case. */ + OUString aNewName; + rEmbObjCont.InsertEmbeddedObject( xEmbObj, aNewName ); + if( aOldName != aNewName ) + // SetPersistName, not SetName + pOleSdrObj->SetPersistName( aNewName ); + } + } + } +} + +void XclImpPictureObj::ReadFlags3( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags; + nFlags = rStrm.ReaduInt16(); + mbSymbol = ::get_flag( nFlags, EXC_OBJ_PIC_SYMBOL ); +} + +void XclImpPictureObj::ReadFlags8( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags; + nFlags = rStrm.ReaduInt16(); + mbSymbol = ::get_flag( nFlags, EXC_OBJ_PIC_SYMBOL ); + mbControl = ::get_flag( nFlags, EXC_OBJ_PIC_CONTROL ); + mbUseCtlsStrm = ::get_flag( nFlags, EXC_OBJ_PIC_CTLSSTREAM ); + OSL_ENSURE( mbControl || !mbUseCtlsStrm, "XclImpPictureObj::ReadFlags8 - CTLS stream for controls only" ); + SetProcessSdrObj( mbControl || !mbUseCtlsStrm ); +} + +void XclImpPictureObj::ReadPictFmla( XclImpStream& rStrm, sal_uInt16 nLinkSize ) +{ + std::size_t nLinkEnd = rStrm.GetRecPos() + nLinkSize; + if( nLinkSize >= 6 ) + { + sal_uInt16 nFmlaSize; + nFmlaSize = rStrm.ReaduInt16(); + OSL_ENSURE( nFmlaSize > 0, "XclImpPictureObj::ReadPictFmla - missing link formula" ); + // BIFF3/BIFF4 do not support storages, nothing to do here + if( (nFmlaSize > 0) && (GetBiff() >= EXC_BIFF5) ) + { + rStrm.Ignore( 4 ); + sal_uInt8 nToken; + nToken = rStrm.ReaduInt8(); + + // different processing for linked vs. embedded OLE objects + if( nToken == XclTokenArrayHelper::GetTokenId( EXC_TOKID_NAMEX, EXC_TOKCLASS_REF ) ) + { + mbLinked = true; + switch( GetBiff() ) + { + case EXC_BIFF5: + { + sal_Int16 nRefIdx; + sal_uInt16 nNameIdx; + nRefIdx = rStrm.ReadInt16(); + rStrm.Ignore( 8 ); + nNameIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 12 ); + const ExtName* pExtName = GetOldRoot().pExtNameBuff->GetNameByIndex( nRefIdx, nNameIdx ); + if( pExtName && pExtName->IsOLE() ) + mnStorageId = pExtName->nStorageId; + } + break; + case EXC_BIFF8: + { + sal_uInt16 nXti, nExtName; + nXti = rStrm.ReaduInt16(); + nExtName = rStrm.ReaduInt16(); + const XclImpExtName* pExtName = GetLinkManager().GetExternName( nXti, nExtName ); + if( pExtName && (pExtName->GetType() == xlExtOLE) ) + mnStorageId = pExtName->GetStorageId(); + } + break; + default: + DBG_ERROR_BIFF(); + } + } + else if( nToken == XclTokenArrayHelper::GetTokenId( EXC_TOKID_TBL, EXC_TOKCLASS_NONE ) ) + { + mbEmbedded = true; + OSL_ENSURE( nFmlaSize == 5, "XclImpPictureObj::ReadPictFmla - unexpected formula size" ); + rStrm.Ignore( nFmlaSize - 1 ); // token ID already read + if( nFmlaSize & 1 ) + rStrm.Ignore( 1 ); // padding byte + + // a class name may follow inside the picture link + if( rStrm.GetRecPos() + 2 <= nLinkEnd ) + { + sal_uInt16 nLen = rStrm.ReaduInt16(); + if( nLen > 0 ) + maClassName = (GetBiff() == EXC_BIFF8) ? rStrm.ReadUniString( nLen ) : rStrm.ReadRawByteString( nLen ); + } + } + // else: ignore other formulas, e.g. pictures linked to cell ranges + } + } + + // seek behind picture link data + rStrm.Seek( nLinkEnd ); + + // read additional data for embedded OLE objects following the picture link + if( IsOcxControl() ) + { + // #i26521# form controls to be ignored + if( maClassName == "Forms.HTML:Hidden.1" ) + { + SetProcessSdrObj( false ); + return; + } + + if( rStrm.GetRecLeft() <= 8 ) return; + + // position and size of control data in 'Ctls' stream + mnCtlsStrmPos = static_cast< std::size_t >( rStrm.ReaduInt32() ); + mnCtlsStrmSize = static_cast< std::size_t >( rStrm.ReaduInt32() ); + + if( rStrm.GetRecLeft() <= 8 ) return; + + // additional string (16-bit characters), e.g. for progress bar control + sal_uInt32 nAddStrSize; + nAddStrSize = rStrm.ReaduInt32(); + OSL_ENSURE( rStrm.GetRecLeft() >= nAddStrSize + 4, "XclImpPictureObj::ReadPictFmla - missing data" ); + if( rStrm.GetRecLeft() >= nAddStrSize + 4 ) + { + rStrm.Ignore( nAddStrSize ); + // cell link and source range + ReadCellLinkFormula( rStrm, true ); + ReadSourceRangeFormula( rStrm, true ); + } + } + else if( mbEmbedded && (rStrm.GetRecLeft() >= 4) ) + { + mnStorageId = rStrm.ReaduInt32(); + } +} + +// DFF stream conversion ====================================================== + +void XclImpSolverContainer::InsertSdrObjectInfo( SdrObject& rSdrObj, sal_uInt32 nDffShapeId, ShapeFlag nDffFlags ) +{ + if( nDffShapeId > 0 ) + { + maSdrInfoMap[ nDffShapeId ].Set( &rSdrObj, nDffFlags ); + maSdrObjMap[ &rSdrObj ] = nDffShapeId; + } +} + +void XclImpSolverContainer::RemoveSdrObjectInfo( SdrObject& rSdrObj ) +{ + // remove info of passed object from the maps + XclImpSdrObjMap::iterator aIt = maSdrObjMap.find( &rSdrObj ); + if( aIt != maSdrObjMap.end() ) + { + maSdrInfoMap.erase( aIt->second ); + maSdrObjMap.erase( aIt ); + } + + // remove info of all child objects of a group object + if( SdrObjGroup* pGroupObj = dynamic_cast< SdrObjGroup* >( &rSdrObj ) ) + { + if( SdrObjList* pSubList = pGroupObj->GetSubList() ) + { + // iterate flat over the list because this function already works recursively + SdrObjListIter aObjIt( pSubList, SdrIterMode::Flat ); + for( SdrObject* pChildObj = aObjIt.Next(); pChildObj; pChildObj = aObjIt.Next() ) + RemoveSdrObjectInfo( *pChildObj ); + } + } +} + +void XclImpSolverContainer::UpdateConnectorRules() +{ + for (auto const & pRule : aCList) + { + UpdateConnection( pRule->nShapeA, pRule->pAObj, &pRule->nSpFlagsA ); + UpdateConnection( pRule->nShapeB, pRule->pBObj, &pRule->nSpFlagsB ); + UpdateConnection( pRule->nShapeC, pRule->pCObj ); + } +} + +void XclImpSolverContainer::RemoveConnectorRules() +{ + aCList.clear(); + maSdrInfoMap.clear(); + maSdrObjMap.clear(); +} + +void XclImpSolverContainer::UpdateConnection( sal_uInt32 nDffShapeId, SdrObject*& rpSdrObj, ShapeFlag* pnDffFlags ) +{ + XclImpSdrInfoMap::const_iterator aIt = maSdrInfoMap.find( nDffShapeId ); + if( aIt != maSdrInfoMap.end() ) + { + rpSdrObj = aIt->second.mpSdrObj; + if( pnDffFlags ) + *pnDffFlags = aIt->second.mnDffFlags; + } +} + +XclImpSimpleDffConverter::XclImpSimpleDffConverter( const XclImpRoot& rRoot, SvStream& rDffStrm ) : + SvxMSDffManager( rDffStrm, rRoot.GetBasePath(), 0, nullptr, rRoot.GetDoc().GetDrawLayer(), 1440, COL_DEFAULT, nullptr ), + XclImpRoot( rRoot ) +{ + SetSvxMSDffSettings( SVXMSDFF_SETTINGS_CROP_BITMAPS | SVXMSDFF_SETTINGS_IMPORT_EXCEL ); +} + +XclImpSimpleDffConverter::~XclImpSimpleDffConverter() +{ +} + +bool XclImpSimpleDffConverter::GetColorFromPalette( sal_uInt16 nIndex, Color& rColor ) const +{ + Color nColor = GetPalette().GetColor( nIndex ); + + if( nColor == COL_AUTO ) + return false; + + rColor = nColor; + return true; +} + +XclImpDffConverter::XclImpDffConvData::XclImpDffConvData( + XclImpDrawing& rDrawing, SdrModel& rSdrModel, SdrPage& rSdrPage ) : + mrDrawing( rDrawing ), + mrSdrModel( rSdrModel ), + mrSdrPage( rSdrPage ), + mnLastCtrlIndex( -1 ), + mbHasCtrlForm( false ) +{ +} + +constexpr OUStringLiteral gaStdFormName( u"Standard" ); /// Standard name of control forms. + +XclImpDffConverter::XclImpDffConverter( const XclImpRoot& rRoot, SvStream& rDffStrm ) : + XclImpSimpleDffConverter( rRoot, rDffStrm ), + oox::ole::MSConvertOCXControls( rRoot.GetDocShell()->GetModel() ), + mnOleImpFlags( 0 ), + mbNotifyMacroEventRead(false) +{ + const SvtFilterOptions& rFilterOpt = SvtFilterOptions::Get(); + if( rFilterOpt.IsMathType2Math() ) + mnOleImpFlags |= OLE_MATHTYPE_2_STARMATH; + if( rFilterOpt.IsWinWord2Writer() ) + mnOleImpFlags |= OLE_WINWORD_2_STARWRITER; + if( rFilterOpt.IsPowerPoint2Impress() ) + mnOleImpFlags |= OLE_POWERPOINT_2_STARIMPRESS; + + // try to open the 'Ctls' storage stream containing OCX control properties + mxCtlsStrm = OpenStream( EXC_STREAM_CTLS ); + + // default text margin (convert EMU to drawing layer units) + mnDefTextMargin = EXC_OBJ_TEXT_MARGIN; + ScaleEmu( mnDefTextMargin ); +} + +XclImpDffConverter::~XclImpDffConverter() +{ +} + +OUString XclImpObjectManager::GetOleNameOverride( SCTAB nTab, sal_uInt16 nObjId ) +{ + OUString sOleName; + OUString sCodeName = GetExtDocOptions().GetCodeName( nTab ); + + if (mxOleCtrlNameOverride.is() && mxOleCtrlNameOverride->hasByName(sCodeName)) + { + Reference< XIndexContainer > xIdToOleName; + mxOleCtrlNameOverride->getByName( sCodeName ) >>= xIdToOleName; + xIdToOleName->getByIndex( nObjId ) >>= sOleName; + } + + return sOleName; +} + +void XclImpDffConverter::StartProgressBar( std::size_t nProgressSize ) +{ + mxProgress = std::make_shared( GetDocShell(), STR_PROGRESS_CALCULATING ); + mxProgress->AddSegment( nProgressSize ); + mxProgress->Activate(); +} + +void XclImpDffConverter::Progress( std::size_t nDelta ) +{ + OSL_ENSURE( mxProgress, "XclImpDffConverter::Progress - invalid call, no progress bar" ); + mxProgress->Progress( nDelta ); +} + +void XclImpDffConverter::InitializeDrawing( XclImpDrawing& rDrawing, SdrModel& rSdrModel, SdrPage& rSdrPage ) +{ + XclImpDffConvDataRef xConvData = std::make_shared( rDrawing, rSdrModel, rSdrPage ); + maDataStack.push_back( xConvData ); + SetModel( &xConvData->mrSdrModel, 1440 ); +} + +void XclImpDffConverter::ProcessObject( SdrObjList& rObjList, XclImpDrawObjBase& rDrawObj ) +{ + if( !rDrawObj.IsProcessSdrObj() ) + return; + + const XclObjAnchor* pAnchor = rDrawObj.GetAnchor(); + if(!pAnchor) + return; + + tools::Rectangle aAnchorRect = GetConvData().mrDrawing.CalcAnchorRect( *pAnchor, false ); + if( rDrawObj.IsValidSize( aAnchorRect ) ) + { + // CreateSdrObject() recursively creates embedded child objects + SdrObjectUniquePtr xSdrObj( rDrawObj.CreateSdrObject( *this, aAnchorRect, false ) ); + if( xSdrObj ) + rDrawObj.PreProcessSdrObject( *this, *xSdrObj ); + // call InsertSdrObject() also, if SdrObject is missing + InsertSdrObject( rObjList, rDrawObj, xSdrObj.release() ); + } +} + +void XclImpDffConverter::ProcessDrawing( const XclImpDrawObjVector& rDrawObjs ) +{ + SdrPage& rSdrPage = GetConvData().mrSdrPage; + for( const auto& rxDrawObj : rDrawObjs ) + ProcessObject( rSdrPage, *rxDrawObj ); +} + +void XclImpDffConverter::ProcessDrawing( SvStream& rDffStrm ) +{ + if( rDffStrm.TellEnd() > 0 ) + { + rDffStrm.Seek( STREAM_SEEK_TO_BEGIN ); + DffRecordHeader aHeader; + ReadDffRecordHeader( rDffStrm, aHeader ); + OSL_ENSURE( aHeader.nRecType == DFF_msofbtDgContainer, "XclImpDffConverter::ProcessDrawing - unexpected record" ); + if( aHeader.nRecType == DFF_msofbtDgContainer ) + ProcessDgContainer( rDffStrm, aHeader ); + } +} + +void XclImpDffConverter::FinalizeDrawing() +{ + OSL_ENSURE( !maDataStack.empty(), "XclImpDffConverter::FinalizeDrawing - no drawing manager on stack" ); + maDataStack.pop_back(); + // restore previous model at core DFF converter + if( !maDataStack.empty() ) + SetModel( &maDataStack.back()->mrSdrModel, 1440 ); +} + +void XclImpDffConverter::NotifyMacroEventRead() +{ + if (mbNotifyMacroEventRead) + return; + comphelper::DocumentInfo::notifyMacroEventRead(mxModel); + mbNotifyMacroEventRead = true; +} + +SdrObjectUniquePtr XclImpDffConverter::CreateSdrObject( const XclImpTbxObjBase& rTbxObj, const tools::Rectangle& rAnchorRect ) +{ + SdrObjectUniquePtr xSdrObj; + + OUString aServiceName = rTbxObj.GetServiceName(); + if( SupportsOleObjects() && !aServiceName.isEmpty() ) try + { + // create the form control from scratch + Reference< XFormComponent > xFormComp( ScfApiHelper::CreateInstance( GetDocShell(), aServiceName ), UNO_QUERY_THROW ); + // set controls form, needed in virtual function InsertControl() + InitControlForm(); + // try to insert the control into the form + css::awt::Size aDummySize; + Reference< XShape > xShape; + XclImpDffConvData& rConvData = GetConvData(); + if( rConvData.mxCtrlForm.is() && InsertControl( xFormComp, aDummySize, &xShape, true ) ) + { + xSdrObj = rTbxObj.CreateSdrObjectFromShape( xShape, rAnchorRect ); + // try to attach a macro to the control + ScriptEventDescriptor aDescriptor; + if( (rConvData.mnLastCtrlIndex >= 0) && rTbxObj.FillMacroDescriptor( aDescriptor ) ) + { + NotifyMacroEventRead(); + Reference< XEventAttacherManager > xEventMgr( rConvData.mxCtrlForm, UNO_QUERY_THROW ); + xEventMgr->registerScriptEvent( rConvData.mnLastCtrlIndex, aDescriptor ); + } + } + } + catch( const Exception& ) + { + } + + return xSdrObj; +} + +SdrObjectUniquePtr XclImpDffConverter::CreateSdrObject( const XclImpPictureObj& rPicObj, const tools::Rectangle& rAnchorRect ) +{ + SdrObjectUniquePtr xSdrObj; + + if( SupportsOleObjects() ) + { + if( rPicObj.IsOcxControl() ) + { + if( mxCtlsStrm.is() ) try + { + /* set controls form, needed in virtual function InsertControl() + called from ReadOCXExcelKludgeStream() */ + InitControlForm(); + + // read from mxCtlsStrm into xShape, insert the control model into the form + Reference< XShape > xShape; + if( GetConvData().mxCtrlForm.is() ) + { + Reference< XFormComponent > xFComp; + ReadOCXCtlsStream( mxCtlsStrm, xFComp, rPicObj.GetCtlsStreamPos(), rPicObj.GetCtlsStreamSize() ); + // recreate the method formerly known as ReadOCXExcelKludgeStream() + if ( xFComp.is() ) + { + css::awt::Size aSz; // not used in import + ScfPropertySet aPropSet( xFComp ); + aPropSet.SetStringProperty( "Name", rPicObj.GetObjName() ); + InsertControl( xFComp, aSz,&xShape,true); + xSdrObj = rPicObj.CreateSdrObjectFromShape( xShape, rAnchorRect ); + } + } + } + catch( const Exception& ) + { + } + } + else + { + SfxObjectShell* pDocShell = GetDocShell(); + tools::SvRef xSrcStrg = GetRootStorage(); + OUString aStrgName = rPicObj.GetOleStorageName(); + if( pDocShell && xSrcStrg.is() && (!aStrgName.isEmpty()) ) + { + // first try to resolve graphic from DFF storage + Graphic aGraphic; + tools::Rectangle aVisArea; + if( !GetBLIP( GetPropertyValue( DFF_Prop_pib, 0 ), aGraphic, &aVisArea ) ) + { + // if not found, use graphic from object (imported from IMGDATA record) + aGraphic = rPicObj.GetGraphic(); + } + if( aGraphic.GetType() != GraphicType::NONE ) + { + ErrCode nError = ERRCODE_NONE; + namespace cssea = ::com::sun::star::embed::Aspects; + sal_Int64 nAspects = rPicObj.IsSymbol() ? cssea::MSOLE_ICON : cssea::MSOLE_CONTENT; + xSdrObj.reset( + CreateSdrOLEFromStorage( + GetConvData().mrSdrModel, + aStrgName, + xSrcStrg, + pDocShell->GetStorage(), + aGraphic, + rAnchorRect, + aVisArea, + nullptr, + nError, + mnOleImpFlags, + nAspects, + GetRoot().GetMedium().GetBaseURL())); + } + } + } + } + + return xSdrObj; +} + +bool XclImpDffConverter::SupportsOleObjects() const +{ + return GetConvData().mrDrawing.SupportsOleObjects(); +} + +// virtual functions ---------------------------------------------------------- + +void XclImpDffConverter::ProcessClientAnchor2( SvStream& rDffStrm, + DffRecordHeader& rHeader, DffObjData& rObjData ) +{ + // find the OBJ record data related to the processed shape + XclImpDffConvData& rConvData = GetConvData(); + XclImpDrawObjBase* pDrawObj = rConvData.mrDrawing.FindDrawObj( rObjData.rSpHd ).get(); + if(!pDrawObj) + return; + + OSL_ENSURE( rHeader.nRecType == DFF_msofbtClientAnchor, "XclImpDffConverter::ProcessClientAnchor2 - no client anchor record" ); + XclObjAnchor aAnchor; + rHeader.SeekToContent( rDffStrm ); + sal_uInt8 nFlags(0); + rDffStrm.ReadUChar( nFlags ); + rDffStrm.SeekRel( 1 ); // flags + rDffStrm >> aAnchor; // anchor format equal to BIFF5 OBJ records + + if (!rDffStrm.good()) + { + SAL_WARN("sc.filter", "ProcessClientAnchor2 short read"); + return; + } + + pDrawObj->SetAnchor( aAnchor ); + rObjData.aChildAnchor = rConvData.mrDrawing.CalcAnchorRect( aAnchor, true ); + rObjData.bChildAnchor = true; + // page anchoring is the best approximation we have if mbMove + // is set + rObjData.bPageAnchor = ( nFlags & 0x1 ); +} + +namespace { + +struct XclImpDrawObjClientData : public SvxMSDffClientData +{ + const XclImpDrawObjBase* m_pTopLevelObj; + + XclImpDrawObjClientData() + : m_pTopLevelObj(nullptr) + { + } + virtual void NotifyFreeObj(SdrObject*) override {} +}; + +} + +SdrObject* XclImpDffConverter::ProcessObj( SvStream& rDffStrm, DffObjData& rDffObjData, + SvxMSDffClientData& rClientData, tools::Rectangle& /*rTextRect*/, SdrObject* pOldSdrObj ) +{ + XclImpDffConvData& rConvData = GetConvData(); + + /* pOldSdrObj passes a generated SdrObject. This function owns this object + and can modify it. The function has either to return it back to caller + or to delete it by itself. */ + SdrObjectUniquePtr xSdrObj( pOldSdrObj ); + + // find the OBJ record data related to the processed shape + XclImpDrawObjRef xDrawObj = rConvData.mrDrawing.FindDrawObj( rDffObjData.rSpHd ); + const tools::Rectangle& rAnchorRect = rDffObjData.aChildAnchor; + + // Do not process the global page group shape + bool bGlobalPageGroup( rDffObjData.nSpFlags & ShapeFlag::Patriarch ); + if( !xDrawObj || !xDrawObj->IsProcessSdrObj() || bGlobalPageGroup ) + return nullptr; // simply return, xSdrObj will be destroyed + + /* Pass pointer to top-level object back to caller. If the processed + object is embedded in a group, the pointer is already set to the + top-level parent object. */ + XclImpDrawObjClientData& rDrawObjClientData = static_cast(rClientData); + const bool bIsTopLevel = !rDrawObjClientData.m_pTopLevelObj; + if (bIsTopLevel ) + rDrawObjClientData.m_pTopLevelObj = xDrawObj.get(); + + // connectors don't have to be area objects + if( dynamic_cast< SdrEdgeObj* >( xSdrObj.get() ) ) + xDrawObj->SetAreaObj( false ); + + /* Check for valid size for all objects. Needed to ignore lots of invisible + phantom objects from deleted rows or columns (for performance reasons). + #i30816# Include objects embedded in groups. + #i58780# Ignore group shapes, size is not initialized. */ + bool bEmbeddedGroup = !bIsTopLevel && dynamic_cast< SdrObjGroup* >( xSdrObj.get() ); + if( !bEmbeddedGroup && !xDrawObj->IsValidSize( rAnchorRect ) ) + return nullptr; // simply return, xSdrObj will be destroyed + + // set shape information from DFF stream + OUString aObjName = GetPropertyString( DFF_Prop_wzName, rDffStrm ); + OUString aHyperlink = ReadHlinkProperty( rDffStrm ); + bool bVisible = !GetPropertyBool( DFF_Prop_fHidden ); + bool bAutoMargin = GetPropertyBool( DFF_Prop_AutoTextMargin ); + xDrawObj->SetDffData( rDffObjData, aObjName, aHyperlink, bVisible, bAutoMargin ); + + /* Connect textbox data (string, alignment, text orientation) to object. + don't ask for a text-ID, DFF export doesn't set one. */ + if( XclImpTextObj* pTextObj = dynamic_cast< XclImpTextObj* >( xDrawObj.get() ) ) + if( const XclImpObjTextData* pTextData = rConvData.mrDrawing.FindTextData( rDffObjData.rSpHd ) ) + pTextObj->SetTextData( *pTextData ); + + // copy line and fill formatting of TBX form controls from DFF properties + if( XclImpTbxObjBase* pTbxObj = dynamic_cast< XclImpTbxObjBase* >( xDrawObj.get() ) ) + pTbxObj->SetDffProperties( *this ); + + // try to create a custom SdrObject that overwrites the passed object + SdrObjectUniquePtr xNewSdrObj( xDrawObj->CreateSdrObject( *this, rAnchorRect, true ) ); + if( xNewSdrObj ) + xSdrObj = std::move( xNewSdrObj ); + + // process the SdrObject + if( xSdrObj ) + { + // filled without color -> set system window color + if( GetPropertyBool( DFF_Prop_fFilled ) && !IsProperty( DFF_Prop_fillColor ) ) + xSdrObj->SetMergedItem( XFillColorItem( OUString(), GetPalette().GetColor( EXC_COLOR_WINDOWBACK ) ) ); + + // additional processing on the SdrObject + xDrawObj->PreProcessSdrObject( *this, *xSdrObj ); + + /* If the SdrObject will not be inserted into the draw page, delete it + here. Happens e.g. for notes: The PreProcessSdrObject() call above + has inserted the note into the document, and the SdrObject is not + needed anymore. */ + if( !xDrawObj->IsInsertSdrObj() ) + xSdrObj.reset(); + } + + if( xSdrObj ) + { + /* Store the relation between shape ID and SdrObject for connectors. + Must be done here (and not in InsertSdrObject() function), + otherwise all SdrObjects embedded in groups would be lost. */ + rConvData.maSolverCont.InsertSdrObjectInfo( *xSdrObj, xDrawObj->GetDffShapeId(), xDrawObj->GetDffFlags() ); + + /* If the drawing object is embedded in a group object, call + PostProcessSdrObject() here. For top-level objects this will be + done automatically in InsertSdrObject() but grouped shapes are + inserted into their groups somewhere in the SvxMSDffManager base + class without chance of notification. Unfortunately, now this is + called before the object is really inserted into its group object, + but that should not have any effect for grouped objects. */ + if( !bIsTopLevel ) + xDrawObj->PostProcessSdrObject( *this, *xSdrObj ); + } + + return xSdrObj.release(); +} + +SdrObject* XclImpDffConverter::FinalizeObj(DffObjData& rDffObjData, SdrObject* pOldSdrObj ) +{ + XclImpDffConvData& rConvData = GetConvData(); + + /* pOldSdrObj passes a generated SdrObject. This function owns this object + and can modify it. The function has either to return it back to caller + or to delete it by itself. */ + SdrObjectUniquePtr xSdrObj( pOldSdrObj ); + + // find the OBJ record data related to the processed shape + XclImpDrawObjRef xDrawObj = rConvData.mrDrawing.FindDrawObj( rDffObjData.rSpHd ); + + if( xSdrObj && xDrawObj ) + { + // cell anchoring + if ( !rDffObjData.bPageAnchor ) + ScDrawLayer::SetCellAnchoredFromPosition( *xSdrObj, GetDoc(), xDrawObj->GetTab(), false ); + } + + return xSdrObj.release(); +} + +bool XclImpDffConverter::InsertControl( const Reference< XFormComponent >& rxFormComp, + const css::awt::Size& /*rSize*/, Reference< XShape >* pxShape, + bool /*bFloatingCtrl*/ ) +{ + if( GetDocShell() ) try + { + XclImpDffConvData& rConvData = GetConvData(); + Reference< XIndexContainer > xFormIC( rConvData.mxCtrlForm, UNO_QUERY_THROW ); + Reference< XControlModel > xCtrlModel( rxFormComp, UNO_QUERY_THROW ); + + // create the control shape + Reference< XShape > xShape( ScfApiHelper::CreateInstance( GetDocShell(), "com.sun.star.drawing.ControlShape" ), UNO_QUERY_THROW ); + Reference< XControlShape > xCtrlShape( xShape, UNO_QUERY_THROW ); + + // insert the new control into the form + sal_Int32 nNewIndex = xFormIC->getCount(); + xFormIC->insertByIndex( nNewIndex, Any( rxFormComp ) ); + // on success: store new index of the control for later use (macro events) + rConvData.mnLastCtrlIndex = nNewIndex; + + // set control model at control shape and pass back shape to caller + xCtrlShape->setControl( xCtrlModel ); + if( pxShape ) *pxShape = xShape; + return true; + } + catch( const Exception& ) + { + OSL_FAIL( "XclImpDffConverter::InsertControl - cannot create form control" ); + } + + return false; +} + +// private -------------------------------------------------------------------- + +XclImpDffConverter::XclImpDffConvData& XclImpDffConverter::GetConvData() +{ + OSL_ENSURE( !maDataStack.empty(), "XclImpDffConverter::GetConvData - no drawing manager on stack" ); + return *maDataStack.back(); +} + +const XclImpDffConverter::XclImpDffConvData& XclImpDffConverter::GetConvData() const +{ + OSL_ENSURE( !maDataStack.empty(), "XclImpDffConverter::GetConvData - no drawing manager on stack" ); + return *maDataStack.back(); +} + +OUString XclImpDffConverter::ReadHlinkProperty( SvStream& rDffStrm ) const +{ + /* Reads hyperlink data from a complex DFF property. Contents of this + property are equal to the HLINK record, import of this record is + implemented in class XclImpHyperlink. This function has to create an + instance of the XclImpStream class to be able to reuse the + functionality of XclImpHyperlink. */ + OUString aString; + sal_uInt32 nBufferSize = GetPropertyValue( DFF_Prop_pihlShape, 0 ); + if( (0 < nBufferSize) && (nBufferSize <= 0xFFFF) && SeekToContent( DFF_Prop_pihlShape, rDffStrm ) ) + { + // create a faked BIFF record that can be read by XclImpStream class + SvMemoryStream aMemStream; + aMemStream.WriteUInt16( 0 ).WriteUInt16( nBufferSize ); + + // copy from DFF stream to memory stream + ::std::vector< sal_uInt8 > aBuffer( nBufferSize ); + sal_uInt8* pnData = aBuffer.data(); + if (rDffStrm.ReadBytes(pnData, nBufferSize) == nBufferSize) + { + aMemStream.WriteBytes(pnData, nBufferSize); + + // create BIFF import stream to be able to use XclImpHyperlink class + XclImpStream aXclStrm( aMemStream, GetRoot() ); + if( aXclStrm.StartNextRecord() ) + aString = XclImpHyperlink::ReadEmbeddedData( aXclStrm ); + } + } + return aString; +} + +bool XclImpDffConverter::ProcessDgContainer( SvStream& rDffStrm, const DffRecordHeader& rDgHeader ) +{ + std::size_t nEndPos = rDgHeader.GetRecEndFilePos(); + bool isBreak(false); + while (!isBreak && rDffStrm.good() && rDffStrm.Tell() < nEndPos) + { + DffRecordHeader aHeader; + ReadDffRecordHeader( rDffStrm, aHeader ); + switch( aHeader.nRecType ) + { + case DFF_msofbtSolverContainer: + isBreak = !ProcessSolverContainer( rDffStrm, aHeader ); + break; + case DFF_msofbtSpgrContainer: + isBreak = !ProcessShGrContainer( rDffStrm, aHeader ); + break; + default: + isBreak = !aHeader.SeekToEndOfRecord( rDffStrm ); + } + } + // seek to end of drawing page container + isBreak = !rDgHeader.SeekToEndOfRecord( rDffStrm ); + + // #i12638# #i37900# connector rules + XclImpSolverContainer& rSolverCont = GetConvData().maSolverCont; + rSolverCont.UpdateConnectorRules(); + SolveSolver( rSolverCont ); + rSolverCont.RemoveConnectorRules(); + return !isBreak; +} + +bool XclImpDffConverter::ProcessShGrContainer( SvStream& rDffStrm, const DffRecordHeader& rShGrHeader ) +{ + std::size_t nEndPos = rShGrHeader.GetRecEndFilePos(); + bool isBreak(false); + while (!isBreak && rDffStrm.good() && rDffStrm.Tell() < nEndPos) + { + DffRecordHeader aHeader; + ReadDffRecordHeader( rDffStrm, aHeader ); + switch( aHeader.nRecType ) + { + case DFF_msofbtSpgrContainer: + case DFF_msofbtSpContainer: + isBreak = !ProcessShContainer( rDffStrm, aHeader ); + break; + default: + isBreak = !aHeader.SeekToEndOfRecord( rDffStrm ); + } + } + // seek to end of shape group container + return rShGrHeader.SeekToEndOfRecord( rDffStrm ) && !isBreak; +} + +bool XclImpDffConverter::ProcessSolverContainer( SvStream& rDffStrm, const DffRecordHeader& rSolverHeader ) +{ + // solver container wants to read the solver container header again + rSolverHeader.SeekToBegOfRecord( rDffStrm ); + // read the entire solver container + ReadSvxMSDffSolverContainer( rDffStrm, GetConvData().maSolverCont ); + // seek to end of solver container + return rSolverHeader.SeekToEndOfRecord( rDffStrm ); +} + +bool XclImpDffConverter::ProcessShContainer( SvStream& rDffStrm, const DffRecordHeader& rShHeader ) +{ + rShHeader.SeekToBegOfRecord( rDffStrm ); + tools::Rectangle aDummy; + XclImpDrawObjClientData aDrawObjClientData; + /* The call to ImportObj() creates and returns a new SdrObject for the + processed shape. We take ownership of the returned object here. If the + shape is a group object, all embedded objects are created recursively, + and the returned group object contains them all. ImportObj() calls the + virtual functions ProcessClientAnchor2() and ProcessObj() and writes + the pointer to the related draw object data (OBJ record) into aDrawObjClientData. */ + SdrObjectUniquePtr xSdrObj( ImportObj( rDffStrm, aDrawObjClientData, aDummy, aDummy, /*nCalledByGroup*/0, /*pShapeId*/nullptr ) ); + if (aDrawObjClientData.m_pTopLevelObj && xSdrObj ) + InsertSdrObject( GetConvData().mrSdrPage, *aDrawObjClientData.m_pTopLevelObj, xSdrObj.release() ); + return rShHeader.SeekToEndOfRecord( rDffStrm ); +} + +void XclImpDffConverter::InsertSdrObject( SdrObjList& rObjList, const XclImpDrawObjBase& rDrawObj, SdrObject* pSdrObj ) +{ + XclImpDffConvData& rConvData = GetConvData(); + /* Take ownership of the passed object. If insertion fails (e.g. rDrawObj + states to skip insertion), the object is automatically deleted. */ + SdrObjectUniquePtr xSdrObj( pSdrObj ); + if( xSdrObj && rDrawObj.IsInsertSdrObj() ) + { + rObjList.NbcInsertObject( xSdrObj.release() ); + // callback to drawing manager for e.g. tracking of used sheet area + rConvData.mrDrawing.OnObjectInserted( rDrawObj ); + // callback to drawing object for post processing (use pSdrObj, xSdrObj already released) + rDrawObj.PostProcessSdrObject( *this, *pSdrObj ); + } + /* SdrObject still here? Insertion failed, remove data from shape ID map. + The SdrObject will be destructed then. */ + if( xSdrObj ) + rConvData.maSolverCont.RemoveSdrObjectInfo( *xSdrObj ); +} + +void XclImpDffConverter::InitControlForm() +{ + XclImpDffConvData& rConvData = GetConvData(); + if( rConvData.mbHasCtrlForm ) + return; + + rConvData.mbHasCtrlForm = true; + if( !SupportsOleObjects() ) + return; + + try + { + Reference< XFormsSupplier > xFormsSupplier( rConvData.mrSdrPage.getUnoPage(), UNO_QUERY_THROW ); + Reference< XNameContainer > xFormsNC( xFormsSupplier->getForms(), UNO_SET_THROW ); + // find or create the Standard form used to insert the imported controls + if( xFormsNC->hasByName( gaStdFormName ) ) + { + xFormsNC->getByName( gaStdFormName ) >>= rConvData.mxCtrlForm; + } + else if( SfxObjectShell* pDocShell = GetDocShell() ) + { + rConvData.mxCtrlForm.set( ScfApiHelper::CreateInstance( pDocShell, "com.sun.star.form.component.Form" ), UNO_QUERY_THROW ); + xFormsNC->insertByName( gaStdFormName, Any( rConvData.mxCtrlForm ) ); + } + } + catch( const Exception& ) + { + } +} + +// Drawing manager ============================================================ + +XclImpDrawing::XclImpDrawing( const XclImpRoot& rRoot, bool bOleObjects ) : + XclImpRoot( rRoot ), + mbOleObjs( bOleObjects ) +{ +} + +XclImpDrawing::~XclImpDrawing() +{ +} + +Graphic XclImpDrawing::ReadImgData( const XclImpRoot& rRoot, XclImpStream& rStrm ) +{ + Graphic aGraphic; + sal_uInt16 nFormat = rStrm.ReaduInt16(); + rStrm.Ignore( 2 );//nEnv + sal_uInt32 nDataSize = rStrm.ReaduInt32(); + if( nDataSize <= rStrm.GetRecLeft() ) + { + switch( nFormat ) + { + case EXC_IMGDATA_WMF: ReadWmf( aGraphic, rStrm ); break; + case EXC_IMGDATA_BMP: ReadBmp( aGraphic, rRoot, rStrm ); break; + default: OSL_FAIL( "XclImpDrawing::ReadImgData - unknown image format" ); + } + } + return aGraphic; +} + +void XclImpDrawing::ReadObj( XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj; + + /* #i61786# In BIFF8 streams, OBJ records may occur without MSODRAWING + records. In this case, the OBJ records are in BIFF5 format. Do a sanity + check here that there is no DFF data loaded before. */ + OSL_ENSURE( maDffStrm.Tell() == 0, "XclImpDrawing::ReadObj - unexpected DFF stream data, OBJ will be ignored" ); + if( maDffStrm.Tell() == 0 ) switch( GetBiff() ) + { + case EXC_BIFF3: + xDrawObj = XclImpDrawObjBase::ReadObj3( GetRoot(), rStrm ); + break; + case EXC_BIFF4: + xDrawObj = XclImpDrawObjBase::ReadObj4( GetRoot(), rStrm ); + break; + case EXC_BIFF5: + case EXC_BIFF8: + xDrawObj = XclImpDrawObjBase::ReadObj5( GetRoot(), rStrm ); + break; + default: + DBG_ERROR_BIFF(); + } + + if( xDrawObj ) + { + // insert into maRawObjs or into the last open group object + maRawObjs.InsertGrouped( xDrawObj ); + // to be able to find objects by ID + maObjMapId[ xDrawObj->GetObjId() ] = xDrawObj; + } +} + +void XclImpDrawing::ReadMsoDrawing( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF8 ); + // disable internal CONTINUE handling + rStrm.ResetRecord( false ); + // read leading MSODRAWING record + ReadDffRecord( rStrm ); + + // read following drawing records, but do not start following unrelated record + bool bLoop = true; + while( bLoop ) switch( rStrm.GetNextRecId() ) + { + case EXC_ID_MSODRAWING: + case EXC_ID_MSODRAWINGSEL: + case EXC_ID_CONT: + rStrm.StartNextRecord(); + ReadDffRecord( rStrm ); + break; + case EXC_ID_OBJ: + rStrm.StartNextRecord(); + ReadObj8( rStrm ); + break; + case EXC_ID_TXO: + rStrm.StartNextRecord(); + ReadTxo( rStrm ); + break; + default: + bLoop = false; + } + + // re-enable internal CONTINUE handling + rStrm.ResetRecord( true ); +} + +XclImpDrawObjRef XclImpDrawing::FindDrawObj( const DffRecordHeader& rHeader ) const +{ + /* maObjMap stores objects by position of the client data (OBJ record) in + the DFF stream, which is always behind shape start position of the + passed header. The function upper_bound() finds the first element in + the map whose key is greater than the start position of the header. Its + end position is used to test whether the found object is really related + to the shape. */ + XclImpDrawObjRef xDrawObj; + XclImpObjMap::const_iterator aIt = maObjMap.upper_bound( rHeader.GetRecBegFilePos() ); + if( (aIt != maObjMap.end()) && (aIt->first <= rHeader.GetRecEndFilePos()) ) + xDrawObj = aIt->second; + return xDrawObj; +} + +XclImpDrawObjRef XclImpDrawing::FindDrawObj( sal_uInt16 nObjId ) const +{ + XclImpDrawObjRef xDrawObj; + XclImpObjMapById::const_iterator aIt = maObjMapId.find( nObjId ); + if( aIt != maObjMapId.end() ) + xDrawObj = aIt->second; + return xDrawObj; +} + +const XclImpObjTextData* XclImpDrawing::FindTextData( const DffRecordHeader& rHeader ) const +{ + /* maTextMap stores textbox data by position of the client data (TXO + record) in the DFF stream, which is always behind shape start position + of the passed header. The function upper_bound() finds the first + element in the map whose key is greater than the start position of the + header. Its end position is used to test whether the found object is + really related to the shape. */ + XclImpObjTextMap::const_iterator aIt = maTextMap.upper_bound( rHeader.GetRecBegFilePos() ); + if( (aIt != maTextMap.end()) && (aIt->first <= rHeader.GetRecEndFilePos()) ) + return aIt->second.get(); + return nullptr; +} + +void XclImpDrawing::SetSkipObj( sal_uInt16 nObjId ) +{ + maSkipObjs.push_back( nObjId ); +} + +std::size_t XclImpDrawing::GetProgressSize() const +{ + return std::accumulate(maObjMap.begin(), maObjMap.end(), maRawObjs.GetProgressSize(), + [](const std::size_t& rSum, const XclImpObjMap::value_type& rEntry) { return rSum + rEntry.second->GetProgressSize(); }); +} + +void XclImpDrawing::ImplConvertObjects( XclImpDffConverter& rDffConv, SdrModel& rSdrModel, SdrPage& rSdrPage ) +{ + //rhbz#636521, disable undo during conversion. faster, smaller and stops + //temp objects being inserted into the undo list + bool bOrigUndoStatus = rSdrModel.IsUndoEnabled(); + rSdrModel.EnableUndo(false); + // register this drawing manager at the passed (global) DFF manager + rDffConv.InitializeDrawing( *this, rSdrModel, rSdrPage ); + // process list of objects to be skipped + for( const auto& rSkipObj : maSkipObjs ) + if( XclImpDrawObjBase* pDrawObj = FindDrawObj( rSkipObj ).get() ) + pDrawObj->SetProcessSdrObj( false ); + // process drawing objects without DFF data + rDffConv.ProcessDrawing( maRawObjs ); + // process all objects in the DFF stream + rDffConv.ProcessDrawing( maDffStrm ); + // unregister this drawing manager at the passed (global) DFF manager + rDffConv.FinalizeDrawing(); + rSdrModel.EnableUndo(bOrigUndoStatus); +} + +// protected ------------------------------------------------------------------ + +void XclImpDrawing::AppendRawObject( const XclImpDrawObjRef& rxDrawObj ) +{ + OSL_ENSURE( rxDrawObj, "XclImpDrawing::AppendRawObject - unexpected empty reference" ); + maRawObjs.push_back( rxDrawObj ); +} + +// private -------------------------------------------------------------------- + +void XclImpDrawing::ReadWmf( Graphic& rGraphic, XclImpStream& rStrm ) // static helper +{ + // extract graphic data from IMGDATA and following CONTINUE records + rStrm.Ignore( 8 ); + SvMemoryStream aMemStrm; + rStrm.CopyToStream( aMemStrm, rStrm.GetRecLeft() ); + aMemStrm.Seek( STREAM_SEEK_TO_BEGIN ); + // import the graphic from memory stream + GDIMetaFile aGDIMetaFile; + if( ::ReadWindowMetafile( aMemStrm, aGDIMetaFile ) ) + rGraphic = aGDIMetaFile; +} + +void XclImpDrawing::ReadBmp( Graphic& rGraphic, const XclImpRoot& rRoot, XclImpStream& rStrm ) // static helper +{ + // extract graphic data from IMGDATA and following CONTINUE records + SvMemoryStream aMemStrm; + + /* Excel 3 and 4 seem to write broken BMP data. Usually they write a + DIBCOREHEADER (12 bytes) containing width, height, planes = 1, and + pixel depth = 32 bit. After that, 3 unused bytes are added before the + actual pixel data. This does even confuse Excel 5 and later, which + cannot read the image data correctly. */ + if( rRoot.GetBiff() <= EXC_BIFF4 ) + { + rStrm.PushPosition(); + sal_uInt32 nHdrSize; + sal_uInt16 nWidth, nHeight, nPlanes, nDepth; + nHdrSize = rStrm.ReaduInt32(); + nWidth = rStrm.ReaduInt16(); + nHeight = rStrm.ReaduInt16(); + nPlanes = rStrm.ReaduInt16(); + nDepth = rStrm.ReaduInt16(); + if( (nHdrSize == 12) && (nPlanes == 1) && (nDepth == 32) ) + { + rStrm.Ignore( 3 ); + aMemStrm.SetEndian( SvStreamEndian::LITTLE ); + aMemStrm.WriteUInt32( nHdrSize ).WriteUInt16( nWidth ).WriteUInt16( nHeight ).WriteUInt16( nPlanes ).WriteUInt16( nDepth ); + rStrm.CopyToStream( aMemStrm, rStrm.GetRecLeft() ); + } + rStrm.PopPosition(); + } + + // no special handling above -> just copy the remaining record data + if( aMemStrm.Tell() == 0 ) + rStrm.CopyToStream( aMemStrm, rStrm.GetRecLeft() ); + + // import the graphic from memory stream + aMemStrm.Seek( STREAM_SEEK_TO_BEGIN ); + Bitmap aBitmap; + if( ReadDIB(aBitmap, aMemStrm, false) ) // read DIB without file header + rGraphic = BitmapEx(aBitmap); +} + +void XclImpDrawing::ReadDffRecord( XclImpStream& rStrm ) +{ + maDffStrm.Seek( STREAM_SEEK_TO_END ); + rStrm.CopyRecordToStream( maDffStrm ); +} + +void XclImpDrawing::ReadObj8( XclImpStream& rStrm ) +{ + XclImpDrawObjRef xDrawObj = XclImpDrawObjBase::ReadObj8( GetRoot(), rStrm ); + // store the new object in the internal containers + maObjMap[ maDffStrm.Tell() ] = xDrawObj; + maObjMapId[ xDrawObj->GetObjId() ] = xDrawObj; +} + +void XclImpDrawing::ReadTxo( XclImpStream& rStrm ) +{ + XclImpObjTextRef xTextData = std::make_shared(); + maTextMap[ maDffStrm.Tell() ] = xTextData; + + // 1) read the TXO record + xTextData->maData.ReadTxo8( rStrm ); + + // 2) first CONTINUE with string + xTextData->mxString.reset(); + bool bValid = true; + if( xTextData->maData.mnTextLen > 0 ) + { + bValid = (rStrm.GetNextRecId() == EXC_ID_CONT) && rStrm.StartNextRecord(); + OSL_ENSURE( bValid, "XclImpDrawing::ReadTxo - missing CONTINUE record" ); + if( bValid ) + xTextData->mxString = std::make_shared( rStrm.ReadUniString( xTextData->maData.mnTextLen ) ); + } + + // 3) second CONTINUE with formatting runs + if( xTextData->maData.mnFormatSize > 0 ) + { + bValid = (rStrm.GetNextRecId() == EXC_ID_CONT) && rStrm.StartNextRecord(); + OSL_ENSURE( bValid, "XclImpDrawing::ReadTxo - missing CONTINUE record" ); + if( bValid ) + xTextData->ReadFormats( rStrm ); + } +} + +XclImpSheetDrawing::XclImpSheetDrawing( const XclImpRoot& rRoot, SCTAB nScTab ) : + XclImpDrawing( rRoot, true ), + maScUsedArea( ScAddress::INITIALIZE_INVALID ) +{ + maScUsedArea.aStart.SetTab( nScTab ); + maScUsedArea.aEnd.SetTab( nScTab ); +} + +void XclImpSheetDrawing::ReadNote( XclImpStream& rStrm ) +{ + switch( GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + ReadNote3( rStrm ); + break; + case EXC_BIFF8: + ReadNote8( rStrm ); + break; + default: + DBG_ERROR_BIFF(); + } +} + +void XclImpSheetDrawing::ReadTabChart( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF5 ); + auto xChartObj = std::make_shared( GetRoot(), true ); + xChartObj->ReadChartSubStream( rStrm ); + // insert the chart as raw object without connected DFF data + AppendRawObject( xChartObj ); +} + +void XclImpSheetDrawing::ConvertObjects( XclImpDffConverter& rDffConv ) +{ + if( SdrModel* pSdrModel = GetDoc().GetDrawLayer() ) + if( SdrPage* pSdrPage = GetSdrPage( maScUsedArea.aStart.Tab() ) ) + ImplConvertObjects( rDffConv, *pSdrModel, *pSdrPage ); +} + +tools::Rectangle XclImpSheetDrawing::CalcAnchorRect( const XclObjAnchor& rAnchor, bool /*bDffAnchor*/ ) const +{ + return rAnchor.GetRect( GetRoot(), maScUsedArea.aStart.Tab(), MapUnit::Map100thMM ); +} + +void XclImpSheetDrawing::OnObjectInserted( const XclImpDrawObjBase& rDrawObj ) +{ + ScRange aScObjArea = rDrawObj.GetUsedArea( maScUsedArea.aStart.Tab() ); + if( aScObjArea.IsValid() ) + maScUsedArea.ExtendTo( aScObjArea ); +} + +// private -------------------------------------------------------------------- + +void XclImpSheetDrawing::ReadNote3( XclImpStream& rStrm ) +{ + XclAddress aXclPos; + rStrm >> aXclPos; + sal_uInt16 nTotalLen = rStrm.ReaduInt16(); + + ScAddress aScNotePos( ScAddress::UNINITIALIZED ); + if( !GetAddressConverter().ConvertAddress( aScNotePos, aXclPos, maScUsedArea.aStart.Tab(), true ) ) + return; + + sal_uInt16 nPartLen = ::std::min( nTotalLen, static_cast< sal_uInt16 >( rStrm.GetRecLeft() ) ); + OUStringBuffer aNoteText(rStrm.ReadRawByteString( nPartLen )); + nTotalLen = nTotalLen - nPartLen; + while (true) + { + if (!nTotalLen) + break; + if (rStrm.GetNextRecId() != EXC_ID_NOTE) + break; + if (!rStrm.StartNextRecord()) + break; + rStrm >> aXclPos; + nPartLen = rStrm.ReaduInt16(); + OSL_ENSURE( aXclPos.mnRow == 0xFFFF, "XclImpObjectManager::ReadNote3 - missing continuation NOTE record" ); + if( aXclPos.mnRow == 0xFFFF ) + { + OSL_ENSURE( nPartLen <= nTotalLen, "XclImpObjectManager::ReadNote3 - string too long" ); + aNoteText.append(rStrm.ReadRawByteString( nPartLen )); + nTotalLen = nTotalLen - ::std::min( nTotalLen, nPartLen ); + } + else + { + // seems to be a new note, record already started -> load the note + rStrm.Seek( EXC_REC_SEEK_TO_BEGIN ); + ReadNote( rStrm ); + nTotalLen = 0; + } + } + ScNoteUtil::CreateNoteFromString( GetDoc(), aScNotePos, aNoteText.makeStringAndClear(), false, false ); +} + +void XclImpSheetDrawing::ReadNote8( XclImpStream& rStrm ) +{ + XclAddress aXclPos; + sal_uInt16 nFlags, nObjId; + rStrm >> aXclPos; + nFlags = rStrm.ReaduInt16(); + nObjId = rStrm.ReaduInt16(); + + ScAddress aScNotePos( ScAddress::UNINITIALIZED ); + if( GetAddressConverter().ConvertAddress( aScNotePos, aXclPos, maScUsedArea.aStart.Tab(), true ) ) + if( nObjId != EXC_OBJ_INVALID_ID ) + if( XclImpNoteObj* pNoteObj = dynamic_cast< XclImpNoteObj* >( FindDrawObj( nObjId ).get() ) ) + pNoteObj->SetNoteData( aScNotePos, nFlags ); +} + +// The object manager ========================================================= + +XclImpObjectManager::XclImpObjectManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ + maDefObjNames[ EXC_OBJTYPE_GROUP ] = "Group"; + maDefObjNames[ EXC_OBJTYPE_LINE ] = ScResId( STR_SHAPE_LINE ); + maDefObjNames[ EXC_OBJTYPE_RECTANGLE ] = ScResId( STR_SHAPE_RECTANGLE ); + maDefObjNames[ EXC_OBJTYPE_OVAL ] = ScResId( STR_SHAPE_OVAL ); + maDefObjNames[ EXC_OBJTYPE_ARC ] = "Arc"; + maDefObjNames[ EXC_OBJTYPE_CHART ] = "Chart"; + maDefObjNames[ EXC_OBJTYPE_TEXT ] = "Text"; + maDefObjNames[ EXC_OBJTYPE_BUTTON ] = ScResId( STR_FORM_BUTTON ); + maDefObjNames[ EXC_OBJTYPE_PICTURE ] = "Picture"; + maDefObjNames[ EXC_OBJTYPE_POLYGON ] = "Freeform"; + maDefObjNames[ EXC_OBJTYPE_CHECKBOX ] = ScResId( STR_FORM_CHECKBOX ); + maDefObjNames[ EXC_OBJTYPE_OPTIONBUTTON ] = ScResId( STR_FORM_OPTIONBUTTON ); + maDefObjNames[ EXC_OBJTYPE_EDIT ] = "Edit Box"; + maDefObjNames[ EXC_OBJTYPE_LABEL ] = ScResId( STR_FORM_LABEL ); + maDefObjNames[ EXC_OBJTYPE_DIALOG ] = "Dialog Frame"; + maDefObjNames[ EXC_OBJTYPE_SPIN ] = ScResId( STR_FORM_SPINNER ); + maDefObjNames[ EXC_OBJTYPE_SCROLLBAR ] = ScResId( STR_FORM_SCROLLBAR ); + maDefObjNames[ EXC_OBJTYPE_LISTBOX ] = ScResId( STR_FORM_LISTBOX ); + maDefObjNames[ EXC_OBJTYPE_GROUPBOX ] = ScResId( STR_FORM_GROUPBOX ); + maDefObjNames[ EXC_OBJTYPE_DROPDOWN ] = ScResId( STR_FORM_DROPDOWN ); + maDefObjNames[ EXC_OBJTYPE_NOTE ] = "Comment"; + maDefObjNames[ EXC_OBJTYPE_DRAWING ] = ScResId( STR_SHAPE_AUTOSHAPE ); +} + +XclImpObjectManager::~XclImpObjectManager() +{ +} + +void XclImpObjectManager::ReadMsoDrawingGroup( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF8 ); + // Excel continues this record with MSODRAWINGGROUP and CONTINUE records, hmm. + rStrm.ResetRecord( true, EXC_ID_MSODRAWINGGROUP ); + maDggStrm.Seek( STREAM_SEEK_TO_END ); + rStrm.CopyRecordToStream( maDggStrm ); +} + +XclImpSheetDrawing& XclImpObjectManager::GetSheetDrawing( SCTAB nScTab ) +{ + XclImpSheetDrawingRef& rxDrawing = maSheetDrawings[ nScTab ]; + if( !rxDrawing ) + rxDrawing = std::make_shared( GetRoot(), nScTab ); + return *rxDrawing; +} + +void XclImpObjectManager::ConvertObjects() +{ + // do nothing if the document does not contain a drawing layer + if( !GetDoc().GetDrawLayer() ) + return; + + // get total progress bar size for all sheet drawing managers + std::size_t nProgressSize = std::accumulate(maSheetDrawings.begin(), maSheetDrawings.end(), std::size_t(0), + [](const std::size_t& rSum, const XclImpSheetDrawingMap::value_type& rEntry) { return rSum + rEntry.second->GetProgressSize(); }); + // nothing to do if progress bar is zero (no objects present) + if( nProgressSize == 0 ) + return; + + XclImpDffConverter aDffConv( GetRoot(), maDggStrm ); + aDffConv.StartProgressBar( nProgressSize ); + for( auto& rEntry : maSheetDrawings ) + rEntry.second->ConvertObjects( aDffConv ); + + // #i112436# don't call ScChartListenerCollection::SetDirty here, + // instead use InterpretDirtyCells in ScDocument::CalcAfterLoad. +} + +OUString XclImpObjectManager::GetDefaultObjName( const XclImpDrawObjBase& rDrawObj ) const +{ + OUStringBuffer aDefName; + DefObjNameMap::const_iterator aIt = maDefObjNames.find( rDrawObj.GetObjType() ); + if( aIt != maDefObjNames.end() ) + aDefName.append(aIt->second); + return aDefName.append(' ').append(static_cast(rDrawObj.GetObjId())).makeStringAndClear(); +} + +ScRange XclImpObjectManager::GetUsedArea( SCTAB nScTab ) const +{ + XclImpSheetDrawingMap::const_iterator aIt = maSheetDrawings.find( nScTab ); + if( aIt != maSheetDrawings.end() ) + return aIt->second->GetUsedArea(); + return ScRange( ScAddress::INITIALIZE_INVALID ); +} + +// DFF property set helper ==================================================== + +XclImpDffPropSet::XclImpDffPropSet( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maDffConv( rRoot, maDummyStrm ) +{ +} + +void XclImpDffPropSet::Read( XclImpStream& rStrm ) +{ + sal_uInt32 nPropSetSize; + + rStrm.PushPosition(); + rStrm.Ignore( 4 ); + nPropSetSize = rStrm.ReaduInt32(); + rStrm.PopPosition(); + + mxMemStrm.reset( new SvMemoryStream ); + rStrm.CopyToStream( *mxMemStrm, 8 + nPropSetSize ); + mxMemStrm->Seek( STREAM_SEEK_TO_BEGIN ); + maDffConv.ReadPropSet( *mxMemStrm, nullptr ); +} + +sal_uInt32 XclImpDffPropSet::GetPropertyValue( sal_uInt16 nPropId ) const +{ + return maDffConv.GetPropertyValue( nPropId, 0 ); +} + +void XclImpDffPropSet::FillToItemSet( SfxItemSet& rItemSet ) const +{ + if( mxMemStrm ) + maDffConv.ApplyAttributes( *mxMemStrm, rItemSet ); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclImpDffPropSet& rPropSet ) +{ + rPropSet.Read( rStrm ); + return rStrm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiformula.cxx b/sc/source/filter/excel/xiformula.cxx new file mode 100644 index 000000000..a5f4d7864 --- /dev/null +++ b/sc/source/filter/excel/xiformula.cxx @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include + +// Formula compiler =========================================================== + +/** Implementation class of the export formula compiler. */ +class XclImpFmlaCompImpl : protected XclImpRoot, protected XclTokenArrayHelper +{ +public: + explicit XclImpFmlaCompImpl( const XclImpRoot& rRoot ); + + /** Creates a range list from the passed Excel token array. */ + void CreateRangeList( + ScRangeList& rScRanges, XclFormulaType eType, + const XclTokenArray& rXclTokArr, XclImpStream& rStrm ); + + std::unique_ptr CreateFormula( XclFormulaType eType, const XclTokenArray& rXclTokArr ); + +}; + +XclImpFmlaCompImpl::XclImpFmlaCompImpl( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpFmlaCompImpl::CreateRangeList( + ScRangeList& rScRanges, XclFormulaType /*eType*/, + const XclTokenArray& rXclTokArr, XclImpStream& /*rStrm*/ ) +{ + rScRanges.RemoveAll(); + + //FIXME: evil hack, using old formula import :-) + if( !rXclTokArr.Empty() ) + { + SvMemoryStream aMemStrm; + aMemStrm.WriteUInt16( EXC_ID_EOF ).WriteUInt16( rXclTokArr.GetSize() ); + aMemStrm.WriteBytes(rXclTokArr.GetData(), rXclTokArr.GetSize()); + XclImpStream aFmlaStrm( aMemStrm, GetRoot() ); + aFmlaStrm.StartNextRecord(); + GetOldFmlaConverter().GetAbsRefs( rScRanges, aFmlaStrm, aFmlaStrm.GetRecSize() ); + } +} + +std::unique_ptr XclImpFmlaCompImpl::CreateFormula( + XclFormulaType /*eType*/, const XclTokenArray& rXclTokArr ) +{ + if (rXclTokArr.Empty()) + return nullptr; + + // evil hack! are we trying to phase out the old style formula converter ? + SvMemoryStream aMemStrm; + aMemStrm.WriteUInt16( EXC_ID_EOF ).WriteUInt16( rXclTokArr.GetSize() ); + aMemStrm.WriteBytes(rXclTokArr.GetData(), rXclTokArr.GetSize()); + XclImpStream aFmlaStrm( aMemStrm, GetRoot() ); + aFmlaStrm.StartNextRecord(); + std::unique_ptr pArray; + GetOldFmlaConverter().Reset(); + GetOldFmlaConverter().Convert(pArray, aFmlaStrm, aFmlaStrm.GetRecSize(), true); + return pArray; +} + +XclImpFormulaCompiler::XclImpFormulaCompiler( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mxImpl( std::make_shared( rRoot ) ) +{ +} + +XclImpFormulaCompiler::~XclImpFormulaCompiler() +{ +} + +void XclImpFormulaCompiler::CreateRangeList( + ScRangeList& rScRanges, XclFormulaType eType, + const XclTokenArray& rXclTokArr, XclImpStream& rStrm ) +{ + mxImpl->CreateRangeList( rScRanges, eType, rXclTokArr, rStrm ); +} + +std::unique_ptr XclImpFormulaCompiler::CreateFormula( + XclFormulaType eType, const XclTokenArray& rXclTokArr ) +{ + return mxImpl->CreateFormula(eType, rXclTokArr); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xihelper.cxx b/sc/source/filter/excel/xihelper.cxx new file mode 100644 index 000000000..ef38c5b65 --- /dev/null +++ b/sc/source/filter/excel/xihelper.cxx @@ -0,0 +1,896 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Excel->Calc cell address/range conversion ================================== + +namespace { + +/** Fills the passed Calc address with the passed Excel cell coordinates without checking any limits. */ +void lclFillAddress( ScAddress& rScPos, sal_uInt16 nXclCol, sal_uInt32 nXclRow, SCTAB nScTab ) +{ + rScPos.SetCol( static_cast< SCCOL >( nXclCol ) ); + rScPos.SetRow( static_cast< SCROW >( nXclRow ) ); + rScPos.SetTab( nScTab ); +} + +} // namespace + +XclImpAddressConverter::XclImpAddressConverter( const XclImpRoot& rRoot ) : + XclAddressConverterBase( rRoot.GetTracer(), rRoot.GetScMaxPos() ) +{ +} + +// cell address --------------------------------------------------------------- + +bool XclImpAddressConverter::CheckAddress( const XclAddress& rXclPos, bool bWarn ) +{ + bool bValidCol = rXclPos.mnCol <= mnMaxCol; + bool bValidRow = rXclPos.mnRow <= mnMaxRow; + bool bValid = bValidCol && bValidRow; + if( !bValid && bWarn ) + { + mbColTrunc |= !bValidCol; + mbRowTrunc |= !bValidRow; + mrTracer.TraceInvalidAddress( ScAddress( + static_cast< SCCOL >( rXclPos.mnCol ), static_cast< SCROW >( rXclPos.mnRow ), 0 ), maMaxPos ); + } + return bValid; +} + +bool XclImpAddressConverter::ConvertAddress( ScAddress& rScPos, + const XclAddress& rXclPos, SCTAB nScTab, bool bWarn ) +{ + bool bValid = CheckAddress( rXclPos, bWarn ); + if( bValid ) + lclFillAddress( rScPos, rXclPos.mnCol, rXclPos.mnRow, nScTab ); + return bValid; +} + +ScAddress XclImpAddressConverter::CreateValidAddress( + const XclAddress& rXclPos, SCTAB nScTab, bool bWarn ) +{ + ScAddress aScPos( ScAddress::UNINITIALIZED ); + if( !ConvertAddress( aScPos, rXclPos, nScTab, bWarn ) ) + { + aScPos.SetCol( static_cast< SCCOL >( ::std::min( rXclPos.mnCol, mnMaxCol ) ) ); + aScPos.SetRow( static_cast< SCROW >( ::std::min( rXclPos.mnRow, mnMaxRow ) ) ); + aScPos.SetTab( limit_cast< SCTAB >( nScTab, 0, maMaxPos.Tab() ) ); + } + return aScPos; +} + +// cell range ----------------------------------------------------------------- + +bool XclImpAddressConverter::ConvertRange( ScRange& rScRange, + const XclRange& rXclRange, SCTAB nScTab1, SCTAB nScTab2, bool bWarn ) +{ + // check start position + bool bValidStart = CheckAddress( rXclRange.maFirst, bWarn ); + if( bValidStart ) + { + lclFillAddress( rScRange.aStart, rXclRange.maFirst.mnCol, rXclRange.maFirst.mnRow, nScTab1 ); + + // check & correct end position + sal_uInt16 nXclCol2 = rXclRange.maLast.mnCol; + sal_uInt32 nXclRow2 = rXclRange.maLast.mnRow; + if( !CheckAddress( rXclRange.maLast, bWarn ) ) + { + nXclCol2 = ::std::min( nXclCol2, mnMaxCol ); + nXclRow2 = ::std::min( nXclRow2, mnMaxRow ); + } + lclFillAddress( rScRange.aEnd, nXclCol2, nXclRow2, nScTab2 ); + } + return bValidStart; +} + +// cell range list ------------------------------------------------------------ + +void XclImpAddressConverter::ConvertRangeList( ScRangeList& rScRanges, + const XclRangeList& rXclRanges, SCTAB nScTab, bool bWarn ) +{ + rScRanges.RemoveAll(); + for( const auto& rXclRange : rXclRanges ) + { + ScRange aScRange( ScAddress::UNINITIALIZED ); + if( ConvertRange( aScRange, rXclRange, nScTab, nScTab, bWarn ) ) + rScRanges.push_back( aScRange ); + } +} + +// String->EditEngine conversion ============================================== + +namespace { + +std::unique_ptr lclCreateTextObject( const XclImpRoot& rRoot, + const XclImpString& rString, XclFontItemType eType, sal_uInt16 nXFIndex ) +{ + std::unique_ptr pTextObj; + + const XclImpXFBuffer& rXFBuffer = rRoot.GetXFBuffer(); + const XclImpFont* pFirstFont = rXFBuffer.GetFont( nXFIndex ); + bool bFirstEscaped = pFirstFont && pFirstFont->HasEscapement(); + + if( rString.IsRich() || bFirstEscaped ) + { + const XclImpFontBuffer& rFontBuffer = rRoot.GetFontBuffer(); + const XclFormatRunVec& rFormats = rString.GetFormats(); + + ScEditEngineDefaulter& rEE = rRoot.GetEditEngine(); + rEE.SetTextCurrentDefaults( rString.GetText() ); + + SfxItemSet aItemSet( rEE.GetEmptyItemSet() ); + if( bFirstEscaped ) + rFontBuffer.FillToItemSet( aItemSet, eType, rXFBuffer.GetFontIndex( nXFIndex ) ); + ESelection aSelection; + + XclFormatRun aNextRun; + XclFormatRunVec::const_iterator aIt = rFormats.begin(); + XclFormatRunVec::const_iterator aEnd = rFormats.end(); + + if( aIt != aEnd ) + aNextRun = *aIt++; + else + aNextRun.mnChar = 0xFFFF; + + sal_Int32 nLen = rString.GetText().getLength(); + for( sal_Int32 nChar = 0; nChar < nLen; ++nChar ) + { + // reached new different formatted text portion + if( nChar >= aNextRun.mnChar ) + { + // send items to edit engine + rEE.QuickSetAttribs( aItemSet, aSelection ); + + // start new item set + aItemSet.ClearItem(); + rFontBuffer.FillToItemSet( aItemSet, eType, aNextRun.mnFontIdx ); + + // read new formatting information + if( aIt != aEnd ) + aNextRun = *aIt++; + else + aNextRun.mnChar = 0xFFFF; + + // reset selection start to current position + aSelection.nStartPara = aSelection.nEndPara; + aSelection.nStartPos = aSelection.nEndPos; + } + + // set end of selection to current position + if( rString.GetText()[ nChar ] == '\n' ) + { + ++aSelection.nEndPara; + aSelection.nEndPos = 0; + } + else + ++aSelection.nEndPos; + } + + // send items of last text portion to edit engine + rEE.QuickSetAttribs( aItemSet, aSelection ); + + pTextObj = rEE.CreateTextObject(); + } + + return pTextObj; +} + +} // namespace + +std::unique_ptr XclImpStringHelper::CreateTextObject( + const XclImpRoot& rRoot, const XclImpString& rString ) +{ + return lclCreateTextObject( rRoot, rString, XclFontItemType::Editeng, 0 ); +} + +void XclImpStringHelper::SetToDocument( + ScDocumentImport& rDoc, const ScAddress& rPos, const XclImpRoot& rRoot, + const XclImpString& rString, sal_uInt16 nXFIndex ) +{ + if (rString.GetText().isEmpty()) + return; + + ::std::unique_ptr< EditTextObject > pTextObj( lclCreateTextObject( rRoot, rString, XclFontItemType::Editeng, nXFIndex ) ); + + if (pTextObj) + { + rDoc.setEditCell(rPos, std::move(pTextObj)); + } + else + { + const OUString& aStr = rString.GetText(); + if (aStr.indexOf('\n') != -1 || aStr.indexOf('\r') != -1) + { + // Multiline content. + ScFieldEditEngine& rEngine = rDoc.getDoc().GetEditEngine(); + rEngine.SetTextCurrentDefaults(aStr); + rDoc.setEditCell(rPos, rEngine.CreateTextObject()); + } + else + { + // Normal text cell. + rDoc.setStringCell(rPos, aStr); + } + } +} + +// Header/footer conversion =================================================== + +XclImpHFConverter::XclImpHFPortionInfo::XclImpHFPortionInfo() : + mnHeight( 0 ), + mnMaxLineHt( 0 ) +{ + maSel.nStartPara = maSel.nEndPara = 0; + maSel.nStartPos = maSel.nEndPos = 0; +} + +XclImpHFConverter::XclImpHFConverter( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mrEE( rRoot.GetHFEditEngine() ), + mxFontData( new XclFontData ), + meCurrObj( EXC_HF_CENTER ) +{ +} + +XclImpHFConverter::~XclImpHFConverter() +{ +} + +void XclImpHFConverter::ParseString( const OUString& rHFString ) +{ + // edit engine objects + mrEE.SetText( OUString() ); + maInfos.clear(); + maInfos.resize( EXC_HF_PORTION_COUNT ); + meCurrObj = EXC_HF_CENTER; + + // parser temporaries + maCurrText.truncate(); + OUStringBuffer aReadFont; // current font name + OUStringBuffer aReadStyle; // current font style + sal_uInt16 nReadHeight = 0; // current font height + ResetFontData(); + + /** State of the parser. */ + enum XclHFParserState + { + xlPSText, /// Read text, search for functions. + xlPSFunc, /// Read function (token following a '&'). + xlPSFont, /// Read font name ('&' is followed by '"', reads until next '"' or ','). + xlPSFontStyle, /// Read font style name (font part after ',', reads until next '"'). + xlPSHeight /// Read font height ('&' is followed by num. digits, reads until non-digit). + } eState = xlPSText; + + const sal_Unicode* pChar = rHFString.getStr(); + const sal_Unicode* pNull = pChar + rHFString.getLength(); // pointer to terminating null char + while( *pChar ) + { + switch( eState ) + { + +// --- read text character --- + + case xlPSText: + { + switch( *pChar ) + { + case '&': // new command + InsertText(); + eState = xlPSFunc; + break; + case '\n': // line break + InsertText(); + InsertLineBreak(); + break; + default: + maCurrText.append(OUStringChar(*pChar)); + } + } + break; + +// --- read control sequence --- + + case xlPSFunc: + { + eState = xlPSText; + switch( *pChar ) + { + case '&': maCurrText.append("&"); break; // the '&' character + + case 'L': SetNewPortion( EXC_HF_LEFT ); break; // Left portion + case 'C': SetNewPortion( EXC_HF_CENTER ); break; // Center portion + case 'R': SetNewPortion( EXC_HF_RIGHT ); break; // Right portion + + case 'P': InsertField( SvxFieldItem( SvxPageField(), EE_FEATURE_FIELD ) ); break; // page + case 'N': InsertField( SvxFieldItem( SvxPagesField(), EE_FEATURE_FIELD ) ); break; // page count + case 'D': InsertField( SvxFieldItem( SvxDateField(), EE_FEATURE_FIELD ) ); break; // date + case 'T': InsertField( SvxFieldItem( SvxTimeField(), EE_FEATURE_FIELD ) ); break; // time + case 'A': InsertField( SvxFieldItem( SvxTableField(), EE_FEATURE_FIELD ) ); break; // table name + + case 'Z': // file path + InsertField( SvxFieldItem( SvxExtFileField(), EE_FEATURE_FIELD ) ); // convert to full name + if( (pNull - pChar >= 2) && (*(pChar + 1) == '&') && (*(pChar + 2) == 'F') ) + { + // &Z&F found - ignore the &F part + pChar += 2; + } + break; + case 'F': // file name + InsertField( SvxFieldItem( SvxExtFileField( OUString(), SvxFileType::Var, SvxFileFormat::NameAndExt ), EE_FEATURE_FIELD ) ); + break; + + case 'U': // underline + SetAttribs(); + mxFontData->mnUnderline = (mxFontData->mnUnderline == EXC_FONTUNDERL_SINGLE) ? + EXC_FONTUNDERL_NONE : EXC_FONTUNDERL_SINGLE; + break; + case 'E': // double underline + SetAttribs(); + mxFontData->mnUnderline = (mxFontData->mnUnderline == EXC_FONTUNDERL_DOUBLE) ? + EXC_FONTUNDERL_NONE : EXC_FONTUNDERL_DOUBLE; + break; + case 'S': // strikeout + SetAttribs(); + mxFontData->mbStrikeout = !mxFontData->mbStrikeout; + break; + case 'X': // superscript + SetAttribs(); + mxFontData->mnEscapem = (mxFontData->mnEscapem == EXC_FONTESC_SUPER) ? + EXC_FONTESC_NONE : EXC_FONTESC_SUPER; + break; + case 'Y': // subscript + SetAttribs(); + mxFontData->mnEscapem = (mxFontData->mnEscapem == EXC_FONTESC_SUB) ? + EXC_FONTESC_NONE : EXC_FONTESC_SUB; + break; + + case '\"': // font name + aReadFont.setLength(0); + aReadStyle.setLength(0); + eState = xlPSFont; + break; + default: + if( ('0' <= *pChar) && (*pChar <= '9') ) // font size + { + nReadHeight = *pChar - '0'; + eState = xlPSHeight; + } + } + } + break; + +// --- read font name --- + + case xlPSFont: + { + switch( *pChar ) + { + case '\"': + --pChar; + [[fallthrough]]; + case ',': + eState = xlPSFontStyle; + break; + default: + aReadFont.append(*pChar); + } + } + break; + +// --- read font style --- + + case xlPSFontStyle: + { + switch( *pChar ) + { + case '\"': + SetAttribs(); + if( !aReadFont.isEmpty() ) + mxFontData->maName = aReadFont.toString(); + mxFontData->maStyle = aReadStyle.toString(); + eState = xlPSText; + break; + default: + aReadStyle.append(*pChar); + } + } + break; + +// --- read font height --- + + case xlPSHeight: + { + if( ('0' <= *pChar) && (*pChar <= '9') ) + { + if( nReadHeight != 0xFFFF ) + { + nReadHeight *= 10; + nReadHeight += (*pChar - '0'); + if( nReadHeight > 1600 ) // max 1600pt = 32000twips + nReadHeight = 0xFFFF; + } + } + else + { + if( (nReadHeight != 0) && (nReadHeight != 0xFFFF) ) + { + SetAttribs(); + mxFontData->mnHeight = nReadHeight * 20; + } + --pChar; + eState = xlPSText; + } + } + break; + } + ++pChar; + } + + // finalize + CreateCurrObject(); + maInfos[ EXC_HF_LEFT ].mnHeight += GetMaxLineHeight( EXC_HF_LEFT ); + maInfos[ EXC_HF_CENTER ].mnHeight += GetMaxLineHeight( EXC_HF_CENTER ); + maInfos[ EXC_HF_RIGHT ].mnHeight += GetMaxLineHeight( EXC_HF_RIGHT ); +} + +void XclImpHFConverter::FillToItemSet( SfxItemSet& rItemSet, sal_uInt16 nWhichId ) const +{ + ScPageHFItem aHFItem( nWhichId ); + if( maInfos[ EXC_HF_LEFT ].mxObj ) + aHFItem.SetLeftArea( *maInfos[ EXC_HF_LEFT ].mxObj ); + if( maInfos[ EXC_HF_CENTER ].mxObj ) + aHFItem.SetCenterArea( *maInfos[ EXC_HF_CENTER ].mxObj ); + if( maInfos[ EXC_HF_RIGHT ].mxObj ) + aHFItem.SetRightArea( *maInfos[ EXC_HF_RIGHT ].mxObj ); + rItemSet.Put( aHFItem ); +} + +sal_Int32 XclImpHFConverter::GetTotalHeight() const +{ + return ::std::max( maInfos[ EXC_HF_LEFT ].mnHeight, + ::std::max( maInfos[ EXC_HF_CENTER ].mnHeight, maInfos[ EXC_HF_RIGHT ].mnHeight ) ); +} + +// private -------------------------------------------------------------------- + +sal_uInt16 XclImpHFConverter::GetMaxLineHeight( XclImpHFPortion ePortion ) const +{ + sal_uInt16 nMaxHt = maInfos[ ePortion ].mnMaxLineHt; + return (nMaxHt == 0) ? mxFontData->mnHeight : nMaxHt; +} + +void XclImpHFConverter::UpdateMaxLineHeight( XclImpHFPortion ePortion ) +{ + sal_uInt16& rnMaxHt = maInfos[ ePortion ].mnMaxLineHt; + rnMaxHt = ::std::max( rnMaxHt, mxFontData->mnHeight ); +} + +void XclImpHFConverter::UpdateCurrMaxLineHeight() +{ + UpdateMaxLineHeight( meCurrObj ); +} + +void XclImpHFConverter::SetAttribs() +{ + ESelection& rSel = GetCurrSel(); + if( (rSel.nStartPara != rSel.nEndPara) || (rSel.nStartPos != rSel.nEndPos) ) + { + SfxItemSet aItemSet( mrEE.GetEmptyItemSet() ); + XclImpFont aFont( GetRoot(), *mxFontData ); + aFont.FillToItemSet( aItemSet, XclFontItemType::HeaderFooter ); + mrEE.QuickSetAttribs( aItemSet, rSel ); + rSel.nStartPara = rSel.nEndPara; + rSel.nStartPos = rSel.nEndPos; + } +} + +void XclImpHFConverter::ResetFontData() +{ + if( const XclImpFont* pFirstFont = GetFontBuffer().GetFont( EXC_FONT_APP ) ) + *mxFontData = pFirstFont->GetFontData(); + else + { + mxFontData->Clear(); + mxFontData->mnHeight = 200; + } +} + +void XclImpHFConverter::InsertText() +{ + if( !maCurrText.isEmpty() ) + { + ESelection& rSel = GetCurrSel(); + OUString sString(maCurrText.makeStringAndClear()); + mrEE.QuickInsertText( sString, ESelection( rSel.nEndPara, rSel.nEndPos, rSel.nEndPara, rSel.nEndPos ) ); + rSel.nEndPos = rSel.nEndPos + sString.getLength(); + UpdateCurrMaxLineHeight(); + } +} + +void XclImpHFConverter::InsertField( const SvxFieldItem& rFieldItem ) +{ + ESelection& rSel = GetCurrSel(); + mrEE.QuickInsertField( rFieldItem, ESelection( rSel.nEndPara, rSel.nEndPos, rSel.nEndPara, rSel.nEndPos ) ); + ++rSel.nEndPos; + UpdateCurrMaxLineHeight(); +} + +void XclImpHFConverter::InsertLineBreak() +{ + ESelection& rSel = GetCurrSel(); + mrEE.QuickInsertText( OUString('\n'), ESelection( rSel.nEndPara, rSel.nEndPos, rSel.nEndPara, rSel.nEndPos ) ); + ++rSel.nEndPara; + rSel.nEndPos = 0; + GetCurrInfo().mnHeight += GetMaxLineHeight( meCurrObj ); + GetCurrInfo().mnMaxLineHt = 0; +} + +void XclImpHFConverter::CreateCurrObject() +{ + InsertText(); + SetAttribs(); + GetCurrObj() = mrEE.CreateTextObject(); +} + +void XclImpHFConverter::SetNewPortion( XclImpHFPortion eNew ) +{ + if( eNew != meCurrObj ) + { + CreateCurrObject(); + meCurrObj = eNew; + if( GetCurrObj() ) + mrEE.SetText( *GetCurrObj() ); + else + mrEE.SetText( OUString() ); + ResetFontData(); + } +} + +// URL conversion ============================================================= + +namespace { + +void lclAppendUrlChar( OUString& rUrl, sal_Unicode cChar ) +{ + // encode special characters + switch( cChar ) + { + case '#': rUrl += "%23"; break; + case '%': rUrl += "%25"; break; + default: rUrl += OUStringChar( cChar ); + } +} + +} // namespace + +void XclImpUrlHelper::DecodeUrl( + OUString& rUrl, OUString& rTabName, bool& rbSameWb, + const XclImpRoot& rRoot, const OUString& rEncodedUrl ) +{ + enum + { + xlUrlInit, /// Initial state, read string mode character. + xlUrlPath, /// Read URL path. + xlUrlFileName, /// Read file name. + xlUrlSheetName, /// Read sheet name. + xlUrlRaw /// Raw mode. No control characters will occur. + } eState = xlUrlInit; + + bool bEncoded = true; + rbSameWb = false; + + sal_Unicode cCurrDrive = 0; + OUString aDosBase( INetURLObject( rRoot.GetBasePath() ).getFSysPath( FSysStyle::Dos ) ); + if (!aDosBase.isEmpty() && aDosBase.match(":\\", 1)) + cCurrDrive = aDosBase[0]; + + const sal_Unicode* pChar = rEncodedUrl.getStr(); + while( *pChar ) + { + switch( eState ) + { + +// --- first character --- + + case xlUrlInit: + { + switch( *pChar ) + { + case EXC_URLSTART_ENCODED: + eState = xlUrlPath; + break; + case EXC_URLSTART_SELF: + case EXC_URLSTART_SELFENCODED: + rbSameWb = true; + eState = xlUrlSheetName; + break; + case '[': + bEncoded = false; + eState = xlUrlFileName; + break; + default: + bEncoded = false; + lclAppendUrlChar( rUrl, *pChar ); + eState = xlUrlPath; + } + } + break; + +// --- URL path --- + + case xlUrlPath: + { + switch( *pChar ) + { + case EXC_URL_DOSDRIVE: + { + if( *(pChar + 1) ) + { + ++pChar; + if( *pChar == '@' ) + rUrl += "\\\\"; + else + { + lclAppendUrlChar( rUrl, *pChar ); + rUrl += ":\\"; + } + } + else + rUrl += ""; + } + break; + case EXC_URL_DRIVEROOT: + if( cCurrDrive ) + { + lclAppendUrlChar( rUrl, cCurrDrive ); + rUrl += ":"; + } + [[fallthrough]]; + case EXC_URL_SUBDIR: + if( bEncoded ) + rUrl += "\\"; + else // control character in raw name -> DDE link + { + rUrl += OUStringChar(EXC_DDE_DELIM); + eState = xlUrlRaw; + } + break; + case EXC_URL_PARENTDIR: + rUrl += "..\\"; + break; + case EXC_URL_RAW: + { + if( *(pChar + 1) ) + { + sal_Int32 nLen = *++pChar; + for( sal_Int32 nChar = 0; (nChar < nLen) && *(pChar + 1); ++nChar ) + lclAppendUrlChar( rUrl, *++pChar ); +// rUrl.Append( ':' ); + } + } + break; + case '[': + eState = xlUrlFileName; + break; + default: + lclAppendUrlChar( rUrl, *pChar ); + } + } + break; + +// --- file name --- + + case xlUrlFileName: + { + switch( *pChar ) + { + case ']': eState = xlUrlSheetName; break; + default: lclAppendUrlChar( rUrl, *pChar ); + } + } + break; + +// --- sheet name --- + + case xlUrlSheetName: + rTabName += OUStringChar( *pChar ); + break; + +// --- raw read mode --- + + case xlUrlRaw: + lclAppendUrlChar( rUrl, *pChar ); + break; + } + + ++pChar; + } +} + +void XclImpUrlHelper::DecodeUrl( + OUString& rUrl, bool& rbSameWb, const XclImpRoot& rRoot, const OUString& rEncodedUrl ) +{ + OUString aTabName; + OUString aUrl; + DecodeUrl( aUrl, aTabName, rbSameWb, rRoot, rEncodedUrl ); + rUrl = aUrl; + OSL_ENSURE( aTabName.isEmpty(), "XclImpUrlHelper::DecodeUrl - sheet name ignored" ); +} + +bool XclImpUrlHelper::DecodeLink( OUString& rApplic, OUString& rTopic, const OUString& rEncUrl ) +{ + sal_Int32 nPos = rEncUrl.indexOf( EXC_DDE_DELIM ); + if( (nPos > 0) && (nPos + 1 < rEncUrl.getLength()) ) + { + rApplic = rEncUrl.copy( 0, nPos ); + rTopic = rEncUrl.copy( nPos + 1 ); + return true; + } + return false; +} + +// Cached Values ============================================================== + +XclImpCachedValue::XclImpCachedValue( XclImpStream& rStrm ) : + mfValue( 0.0 ), + mnBoolErr( 0 ) +{ + mnType = rStrm.ReaduInt8(); + switch( mnType ) + { + case EXC_CACHEDVAL_EMPTY: + rStrm.Ignore( 8 ); + break; + case EXC_CACHEDVAL_DOUBLE: + mfValue = rStrm.ReadDouble(); + break; + case EXC_CACHEDVAL_STRING: + maStr = rStrm.ReadUniString(); + break; + case EXC_CACHEDVAL_BOOL: + case EXC_CACHEDVAL_ERROR: + { + double fVal; + mnBoolErr = rStrm.ReaduInt8(); + rStrm.Ignore( 7 ); + + std::unique_ptr pScTokArr = rStrm.GetRoot().GetOldFmlaConverter().GetBoolErr( + XclTools::ErrorToEnum( fVal, mnType == EXC_CACHEDVAL_ERROR, mnBoolErr ) ); + if( pScTokArr ) + mxTokArr = std::move( pScTokArr ); + } + break; + default: + OSL_FAIL( "XclImpCachedValue::XclImpCachedValue - unknown data type" ); + } +} + +XclImpCachedValue::~XclImpCachedValue() +{ +} + +FormulaError XclImpCachedValue::GetScError() const +{ + return (mnType == EXC_CACHEDVAL_ERROR) ? XclTools::GetScErrorCode( mnBoolErr ) : FormulaError::NONE; +} + +// Matrix Cached Values ============================================================== + +XclImpCachedMatrix::XclImpCachedMatrix( XclImpStream& rStrm ) : + mnScCols( 0 ), + mnScRows( 0 ) +{ + mnScCols = rStrm.ReaduInt8(); + mnScRows = rStrm.ReaduInt16(); + + if( rStrm.GetRoot().GetBiff() <= EXC_BIFF5 ) + { + // in BIFF2-BIFF7: 256 columns represented by 0 columns + if( mnScCols == 0 ) + mnScCols = 256; + } + else + { + // in BIFF8: columns and rows decreased by 1 + ++mnScCols; + ++mnScRows; + } + + //assuming worst case scenario of unknown types + const size_t nMinRecordSize = 1; + const size_t nMaxRows = rStrm.GetRecLeft() / (nMinRecordSize * mnScCols); + if (mnScRows > nMaxRows) + { + SAL_WARN("sc", "Parsing error: " << nMaxRows << + " max possible rows, but " << mnScRows << " claimed, truncating"); + mnScRows = nMaxRows; + } + + for( SCSIZE nScRow = 0; nScRow < mnScRows; ++nScRow ) + for( SCSIZE nScCol = 0; nScCol < mnScCols; ++nScCol ) + maValueList.push_back( std::make_unique( rStrm ) ); +} + +XclImpCachedMatrix::~XclImpCachedMatrix() +{ +} + +ScMatrixRef XclImpCachedMatrix::CreateScMatrix( svl::SharedStringPool& rPool ) const +{ + ScMatrixRef xScMatrix; + OSL_ENSURE( mnScCols * mnScRows == maValueList.size(), "XclImpCachedMatrix::CreateScMatrix - element count mismatch" ); + if( mnScCols && mnScRows && static_cast< sal_uLong >( mnScCols * mnScRows ) <= maValueList.size() ) + { + xScMatrix = new ScMatrix(mnScCols, mnScRows, 0.0); + XclImpValueList::const_iterator itValue = maValueList.begin(); + for( SCSIZE nScRow = 0; nScRow < mnScRows; ++nScRow ) + { + for( SCSIZE nScCol = 0; nScCol < mnScCols; ++nScCol ) + { + switch( (*itValue)->GetType() ) + { + case EXC_CACHEDVAL_EMPTY: + // Excel shows 0.0 here, not an empty cell + xScMatrix->PutEmpty( nScCol, nScRow ); + break; + case EXC_CACHEDVAL_DOUBLE: + xScMatrix->PutDouble( (*itValue)->GetValue(), nScCol, nScRow ); + break; + case EXC_CACHEDVAL_STRING: + xScMatrix->PutString(rPool.intern((*itValue)->GetString()), nScCol, nScRow); + break; + case EXC_CACHEDVAL_BOOL: + xScMatrix->PutBoolean( (*itValue)->GetBool(), nScCol, nScRow ); + break; + case EXC_CACHEDVAL_ERROR: + xScMatrix->PutError( (*itValue)->GetScError(), nScCol, nScRow ); + break; + default: + OSL_FAIL( "XclImpCachedMatrix::CreateScMatrix - unknown value type" ); + xScMatrix->PutEmpty( nScCol, nScRow ); + } + ++itValue; + } + } + } + return xScMatrix; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xilink.cxx b/sc/source/filter/excel/xilink.cxx new file mode 100644 index 000000000..e9fea951e --- /dev/null +++ b/sc/source/filter/excel/xilink.cxx @@ -0,0 +1,962 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// *** Helper classes *** + +// Cached external cells ====================================================== + +namespace { + +/** + * Contains the address and value of an external referenced cell. + * Note that this is non-copyable, so cannot be used in most stl/boost containers. + */ +class XclImpCrn : public XclImpCachedValue +{ +public: + /** Reads a cached value and stores it with its cell address. */ + explicit XclImpCrn( XclImpStream& rStrm, const XclAddress& rXclPos ); + + const XclAddress& GetAddress() const { return maXclPos;} + +private: + XclAddress maXclPos; /// Excel position of the cached cell. +}; + +// Sheet in an external document ============================================== + +/** Contains the name and sheet index of one sheet in an external document. */ +class XclImpSupbookTab +{ +public: + /** Stores the sheet name and marks the sheet index as invalid. + The sheet index is set while creating the Calc sheet with CreateTable(). */ + explicit XclImpSupbookTab( const OUString& rTabName ); + + const OUString& GetTabName() const { return maTabName; } + + /** Reads a CRN record (external referenced cell) at the specified address. */ + void ReadCrn( XclImpStream& rStrm, const XclAddress& rXclPos ); + + void LoadCachedValues( const ScExternalRefCache::TableTypeRef& pCacheTable, + svl::SharedStringPool& rPool ); + +private: + typedef std::shared_ptr< XclImpCrn > XclImpCrnRef; + + std::vector< XclImpCrnRef > maCrnList; /// List of CRN records (cached cell values). + OUString maTabName; /// Name of the external sheet. +}; + +} + +// External document (SUPBOOK) ================================================ + +/** This class represents an external linked document (record SUPBOOK). + @descr Contains a list of all referenced sheets in the document. */ +class XclImpSupbook : protected XclImpRoot +{ +public: + /** Reads the SUPBOOK record from stream. */ + explicit XclImpSupbook( XclImpStream& rStrm ); + + /** Reads an XCT record (count of following CRNs and current sheet). */ + void ReadXct( XclImpStream& rStrm ); + /** Reads a CRN record (external referenced cell). */ + void ReadCrn( XclImpStream& rStrm ); + /** Reads an EXTERNNAME record. */ + void ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ); + + /** Returns the SUPBOOK record type. */ + XclSupbookType GetType() const { return meType; } + + /** Returns the URL of the external document. */ + const OUString& GetXclUrl() const { return maXclUrl; } + + /** Returns the external name specified by an index from the Excel document (one-based). */ + const XclImpExtName* GetExternName( sal_uInt16 nXclIndex ) const; + /** Tries to decode the URL to OLE or DDE link components. + @descr For DDE links: Decodes to application name and topic. + For OLE object links: Decodes to class name and document URL. + @return true = decoding was successful, returned strings are valid (not empty). */ + bool GetLinkData( OUString& rApplic, OUString& rDoc ) const; + /** Returns the specified macro name (1-based) or an empty string on error. */ + OUString GetMacroName( sal_uInt16 nXclNameIdx ) const; + + OUString GetTabName( sal_uInt16 nXtiTab ) const; + + sal_uInt16 GetTabCount() const; + + void LoadCachedValues(); + + svl::SharedStringPool& GetSharedStringPool(); + +private: + + std::vector< std::unique_ptr > + maSupbTabList; /// All sheet names of the document. + std::vector< std::unique_ptr > + maExtNameList; /// All external names of the document. + OUString maXclUrl; /// URL of the external document (Excel mode). + XclSupbookType meType; /// Type of the supbook record. + sal_uInt16 mnSBTab; /// Current Excel sheet index from SUPBOOK for XCT/CRN records. +}; + +// Import link manager ======================================================== + +namespace { + +/** Contains the SUPBOOK index and sheet indexes of an external link. + @descr It is possible to enter a formula like =SUM(Sheet1:Sheet3!A1), + therefore here occurs a sheet range. */ +struct XclImpXti +{ + sal_uInt16 mnSupbook; /// Index to SUPBOOK record. + sal_uInt16 mnSBTabFirst; /// Index to the first sheet of the range in the SUPBOOK. + sal_uInt16 mnSBTabLast; /// Index to the last sheet of the range in the SUPBOOK. + explicit XclImpXti() : mnSupbook( SAL_MAX_UINT16 ), mnSBTabFirst( SAL_MAX_UINT16 ), mnSBTabLast( SAL_MAX_UINT16 ) {} +}; + +XclImpStream& operator>>( XclImpStream& rStrm, XclImpXti& rXti ) +{ + rXti.mnSupbook = rStrm.ReaduInt16(); + rXti.mnSBTabFirst = rStrm.ReaduInt16(); + rXti.mnSBTabLast = rStrm.ReaduInt16(); + return rStrm; +} + +} + +/** Implementation of the link manager. */ +class XclImpLinkManagerImpl : protected XclImpRoot +{ +public: + explicit XclImpLinkManagerImpl( const XclImpRoot& rRoot ); + + /** Reads the EXTERNSHEET record. */ + void ReadExternsheet( XclImpStream& rStrm ); + /** Reads a SUPBOOK record. */ + void ReadSupbook( XclImpStream& rStrm ); + /** Reads an XCT record and appends it to the current SUPBOOK. */ + void ReadXct( XclImpStream& rStrm ); + /** Reads a CRN record and appends it to the current SUPBOOK. */ + void ReadCrn( XclImpStream& rStrm ); + /** Reads an EXTERNNAME record and appends it to the current SUPBOOK. */ + void ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ); + + /** Returns true, if the specified XTI entry contains an internal reference. */ + bool IsSelfRef( sal_uInt16 nXtiIndex ) const; + /** Returns the Calc sheet index range of the specified XTI entry. + @return true = XTI data found, returned sheet index range is valid. */ + bool GetScTabRange( + SCTAB& rnFirstScTab, SCTAB& rnLastScTab, + sal_uInt16 nXtiIndex ) const; + /** Returns the specified external name or 0 on error. */ + const XclImpExtName* GetExternName( sal_uInt16 nXtiIndex, sal_uInt16 nExtName ) const; + + /** Returns the absolute file URL of a supporting workbook specified by + the index. */ + const OUString* GetSupbookUrl( sal_uInt16 nXtiIndex ) const; + + OUString GetSupbookTabName( sal_uInt16 nXti, sal_uInt16 nXtiTab ) const; + + /** Tries to decode the URL of the specified XTI entry to OLE or DDE link components. + @descr For DDE links: Decodes to application name and topic. + For OLE object links: Decodes to class name and document URL. + @return true = decoding was successful, returned strings are valid (not empty). */ + bool GetLinkData( OUString& rApplic, OUString& rTopic, sal_uInt16 nXtiIndex ) const; + /** Returns the specified macro name or an empty string on error. */ + OUString GetMacroName( sal_uInt16 nExtSheet, sal_uInt16 nExtName ) const; + +private: + /** Returns the specified XTI (link entry from BIFF8 EXTERNSHEET record). */ + const XclImpXti* GetXti( sal_uInt16 nXtiIndex ) const; + /** Returns the specified SUPBOOK (external document). */ + const XclImpSupbook* GetSupbook( sal_uInt16 nXtiIndex ) const; + + void LoadCachedValues(); + +private: + typedef std::vector< XclImpXti > XclImpXtiVector; + + XclImpXtiVector maXtiList; /// List of all XTI structures. + std::vector< std::unique_ptr > + maSupbookList; /// List of external documents. +}; + +// *** Implementation *** + +// Excel sheet indexes ======================================================== + +// original Excel sheet names ------------------------------------------------- + +void XclImpTabInfo::AppendXclTabName( const OUString& rXclTabName, SCTAB nScTab ) +{ + maTabNames[ rXclTabName ] = nScTab; +} + +void XclImpTabInfo::InsertScTab( SCTAB nScTab ) +{ + for( auto& rEntry : maTabNames ) + if( rEntry.second >= nScTab ) + ++rEntry.second; +} + +SCTAB XclImpTabInfo::GetScTabFromXclName( const OUString& rXclTabName ) const +{ + XclTabNameMap::const_iterator aIt = maTabNames.find( rXclTabName ); + return (aIt != maTabNames.end()) ? aIt->second : SCTAB_INVALID; +} + +// record creation order - TABID record --------------------------------------- + +void XclImpTabInfo::ReadTabid( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ); + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + { + rStrm.EnableDecryption(); + std::size_t nReadCount = rStrm.GetRecLeft() / 2; + OSL_ENSURE( nReadCount <= 0xFFFF, "XclImpTabInfo::ReadTabid - record too long" ); + maTabIdVec.clear(); + maTabIdVec.reserve( nReadCount ); + for( std::size_t nIndex = 0; rStrm.IsValid() && (nIndex < nReadCount); ++nIndex ) + // zero index is not allowed in BIFF8, but it seems that it occurs in real life + maTabIdVec.push_back( rStrm.ReaduInt16() ); + } +} + +sal_uInt16 XclImpTabInfo::GetCurrentIndex( sal_uInt16 nCreatedId, sal_uInt16 nMaxTabId ) const +{ + sal_uInt16 nReturn = 0; + for( sal_uInt16 nValue : maTabIdVec ) + { + if( nValue == nCreatedId ) + return nReturn; + if( nValue <= nMaxTabId ) + ++nReturn; + } + return 0; +} + +// External names ============================================================= + +XclImpExtName::MOper::MOper(svl::SharedStringPool& rPool, XclImpStream& rStrm) : + mxCached(new ScMatrix(0,0)) +{ + SCSIZE nLastCol = rStrm.ReaduInt8(); + SCSIZE nLastRow = rStrm.ReaduInt16(); + + //assuming worst case scenario of nOp + one byte unistring len + const size_t nMinRecordSize = 2; + const size_t nMaxRows = rStrm.GetRecLeft() / (nMinRecordSize * (nLastCol+1)); + if (nLastRow >= nMaxRows) + { + SAL_WARN("sc", "Parsing error: " << nMaxRows << + " max possible rows, but " << nLastRow << " index claimed, truncating"); + if (nMaxRows > 0) + nLastRow = nMaxRows-1; + else + return; + } + + mxCached->Resize(nLastCol+1, nLastRow+1); + for (SCSIZE nRow = 0; nRow <= nLastRow; ++nRow) + { + for (SCSIZE nCol = 0; nCol <= nLastCol; ++nCol) + { + sal_uInt8 nOp; + nOp = rStrm.ReaduInt8(); + switch (nOp) + { + case 0x01: + { + double fVal = rStrm.ReadDouble(); + mxCached->PutDouble(fVal, nCol, nRow); + } + break; + case 0x02: + { + OUString aStr = rStrm.ReadUniString(); + mxCached->PutString(rPool.intern(aStr), nCol, nRow); + } + break; + case 0x04: + { + bool bVal = rStrm.ReaduInt8(); + mxCached->PutBoolean(bVal, nCol, nRow); + rStrm.Ignore(7); + } + break; + case 0x10: + { + sal_uInt8 nErr = rStrm.ReaduInt8(); + // Map the error code from xls to calc. + mxCached->PutError(XclTools::GetScErrorCode(nErr), nCol, nRow); + rStrm.Ignore(7); + } + break; + default: + rStrm.Ignore(8); + } + } + } +} + +const ScMatrix& XclImpExtName::MOper::GetCache() const +{ + return *mxCached; +} + +XclImpExtName::XclImpExtName( XclImpSupbook& rSupbook, XclImpStream& rStrm, XclSupbookType eSubType, ExcelToSc* pFormulaConv ) + : mnStorageId(0) +{ + sal_uInt16 nFlags(0); + sal_uInt8 nLen(0); + + nFlags = rStrm.ReaduInt16(); + mnStorageId = rStrm.ReaduInt32(); + nLen = rStrm.ReaduInt8(); + maName = rStrm.ReadUniString( nLen ); + if( ::get_flag( nFlags, EXC_EXTN_BUILTIN ) || !::get_flag( nFlags, EXC_EXTN_OLE_OR_DDE ) ) + { + if( eSubType == XclSupbookType::Addin ) + { + meType = xlExtAddIn; + maName = XclImpRoot::GetScAddInName( maName ); + } + else if ( (eSubType == XclSupbookType::Eurotool) && + maName.equalsIgnoreAsciiCase( "EUROCONVERT" ) ) + meType = xlExtEuroConvert; + else + { + meType = xlExtName; + maName = ScfTools::ConvertToScDefinedName( maName ); + } + } + else + { + meType = ::get_flagvalue( nFlags, EXC_EXTN_OLE, xlExtOLE, xlExtDDE ); + } + + switch (meType) + { + case xlExtDDE: + if (rStrm.GetRecLeft() > 1) + mxDdeMatrix.reset(new XclImpCachedMatrix(rStrm)); + break; + case xlExtName: + // TODO: For now, only global external names are supported. In future + // we should extend this to supporting per-sheet external names. + if (mnStorageId == 0 && pFormulaConv) + { + std::unique_ptr pArray; + sal_uInt16 nFmlaLen; + nFmlaLen = rStrm.ReaduInt16(); + std::vector aTabNames; + sal_uInt16 nCount = rSupbook.GetTabCount(); + aTabNames.reserve(nCount); + for (sal_uInt16 i = 0; i < nCount; ++i) + aTabNames.push_back(rSupbook.GetTabName(i)); + + pFormulaConv->ConvertExternName(pArray, rStrm, nFmlaLen, rSupbook.GetXclUrl(), aTabNames); + if (pArray) + mxArray = std::move( pArray ); + } + break; + case xlExtOLE: + mpMOper.reset( new MOper(rSupbook.GetSharedStringPool(), rStrm) ); + break; + default: + ; + } +} + +XclImpExtName::~XclImpExtName() +{ +} + +void XclImpExtName::CreateDdeData( ScDocument& rDoc, const OUString& rApplic, const OUString& rTopic ) const +{ + ScMatrixRef xResults; + if( mxDdeMatrix ) + xResults = mxDdeMatrix->CreateScMatrix(rDoc.GetSharedStringPool()); + rDoc.CreateDdeLink( rApplic, rTopic, maName, SC_DDE_DEFAULT, xResults ); +} + +void XclImpExtName::CreateExtNameData( const ScDocument& rDoc, sal_uInt16 nFileId ) const +{ + if (!mxArray) + return; + + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + pRefMgr->storeRangeNameTokens(nFileId, maName, *mxArray); +} + +namespace { + +/** + * Decompose the name into sheet name and range name. An OLE link name is + * always formatted like this [ !Sheet1!R1C1:R5C2 ] and it always uses R1C1 + * notation. + */ +bool extractSheetAndRange(const OUString& rName, OUString& rSheet, OUString& rRange) +{ + sal_Int32 n = rName.getLength(); + const sal_Unicode* p = rName.getStr(); + OUStringBuffer aBuf; + bool bInSheet = true; + for (sal_Int32 i = 0; i < n; ++i, ++p) + { + if (i == 0) + { + // first character must be '!'. + if (*p != '!') + return false; + continue; + } + + if (*p == '!') + { + // sheet name to range separator. + if (!bInSheet) + return false; + rSheet = aBuf.makeStringAndClear(); + bInSheet = false; + continue; + } + + aBuf.append(*p); + } + + rRange = aBuf.makeStringAndClear(); + return true; +} + +} + +bool XclImpExtName::CreateOleData(const ScDocument& rDoc, const OUString& rUrl, + sal_uInt16& rFileId, OUString& rTabName, ScRange& rRange) const +{ + if (!mpMOper) + return false; + + OUString aSheet, aRangeStr; + if (!extractSheetAndRange(maName, aSheet, aRangeStr)) + return false; + + ScRange aRange; + ScRefFlags nRes = aRange.ParseAny(aRangeStr, rDoc, formula::FormulaGrammar::CONV_XL_R1C1); + if ((nRes & ScRefFlags::VALID) == ScRefFlags::ZERO) + return false; + + if (aRange.aStart.Tab() != aRange.aEnd.Tab()) + // We don't support multi-sheet range for this. + return false; + + const ScMatrix& rCache = mpMOper->GetCache(); + SCSIZE nC, nR; + rCache.GetDimensions(nC, nR); + if (!nC || !nR) + // cache matrix is empty. + return false; + + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(rUrl); + ScExternalRefCache::TableTypeRef xTab = pRefMgr->getCacheTable(nFileId, aSheet, true); + if (!xTab) + // cache table creation failed. + return false; + + xTab->setWholeTableCached(); + for (SCSIZE i = 0; i < nR; ++i) + { + for (SCSIZE j = 0; j < nC; ++j) + { + SCCOL nCol = aRange.aStart.Col() + j; + SCROW nRow = aRange.aStart.Row() + i; + + ScMatrixValue aVal = rCache.Get(j, i); + switch (aVal.nType) + { + case ScMatValType::Boolean: + { + bool b = aVal.GetBoolean(); + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(b ? 1.0 : 0.0)); + xTab->setCell(nCol, nRow, pToken, 0, false); + } + break; + case ScMatValType::Value: + { + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(aVal.fVal)); + xTab->setCell(nCol, nRow, pToken, 0, false); + } + break; + case ScMatValType::String: + { + ScExternalRefCache::TokenRef pToken(new formula::FormulaStringToken(aVal.GetString())); + xTab->setCell(nCol, nRow, pToken, 0, false); + } + break; + default: + ; + } + } + } + + rFileId = nFileId; + rTabName = aSheet; + rRange = aRange; + return true; +} + +bool XclImpExtName::HasFormulaTokens() const +{ + return bool(mxArray); +} + +// Cached external cells ====================================================== + +XclImpCrn::XclImpCrn( XclImpStream& rStrm, const XclAddress& rXclPos ) : + XclImpCachedValue( rStrm ), + maXclPos( rXclPos ) +{ +} + +// Sheet in an external document ============================================== + +XclImpSupbookTab::XclImpSupbookTab( const OUString& rTabName ) : + maTabName( rTabName ) +{ +} + +void XclImpSupbookTab::ReadCrn( XclImpStream& rStrm, const XclAddress& rXclPos ) +{ + XclImpCrnRef crnRef = std::make_shared(rStrm, rXclPos); + maCrnList.push_back( crnRef ); +} + +void XclImpSupbookTab::LoadCachedValues( const ScExternalRefCache::TableTypeRef& pCacheTable, + svl::SharedStringPool& rPool ) +{ + if (maCrnList.empty()) + return; + + for (const auto& rxCrn : maCrnList) + { + const XclImpCrn* const pCrn = rxCrn.get(); + const XclAddress& rAddr = pCrn->GetAddress(); + switch (pCrn->GetType()) + { + case EXC_CACHEDVAL_BOOL: + { + bool b = pCrn->GetBool(); + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(b ? 1.0 : 0.0)); + pCacheTable->setCell(rAddr.mnCol, rAddr.mnRow, pToken, 0, false); + } + break; + case EXC_CACHEDVAL_DOUBLE: + { + double f = pCrn->GetValue(); + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(f)); + pCacheTable->setCell(rAddr.mnCol, rAddr.mnRow, pToken, 0, false); + } + break; + case EXC_CACHEDVAL_ERROR: + { + double fError = XclTools::ErrorToDouble( pCrn->GetXclError() ); + ScExternalRefCache::TokenRef pToken(new formula::FormulaDoubleToken(fError)); + pCacheTable->setCell(rAddr.mnCol, rAddr.mnRow, pToken, 0, false); + } + break; + case EXC_CACHEDVAL_STRING: + { + svl::SharedString aSS( rPool.intern( pCrn->GetString())); + ScExternalRefCache::TokenRef pToken(new formula::FormulaStringToken( aSS)); + pCacheTable->setCell(rAddr.mnCol, rAddr.mnRow, pToken, 0, false); + } + break; + default: + ; + } + } +} + +// External document (SUPBOOK) ================================================ + +XclImpSupbook::XclImpSupbook( XclImpStream& rStrm ) : + XclImpRoot( rStrm.GetRoot() ), + meType( XclSupbookType::Unknown ), + mnSBTab( EXC_TAB_DELETED ) +{ + sal_uInt16 nSBTabCnt; + nSBTabCnt = rStrm.ReaduInt16(); + + if( rStrm.GetRecLeft() == 2 ) + { + switch( rStrm.ReaduInt16() ) + { + case EXC_SUPB_SELF: meType = XclSupbookType::Self; break; + case EXC_SUPB_ADDIN: meType = XclSupbookType::Addin; break; + default: OSL_FAIL( "XclImpSupbook::XclImpSupbook - unknown special SUPBOOK type" ); + } + return; + } + + OUString aEncUrl( rStrm.ReadUniString() ); + bool bSelf = false; + XclImpUrlHelper::DecodeUrl( maXclUrl, bSelf, GetRoot(), aEncUrl ); + + if( maXclUrl.equalsIgnoreAsciiCase( "\010EUROTOOL.XLA" ) ) + { + meType = XclSupbookType::Eurotool; + maSupbTabList.push_back( std::make_unique( maXclUrl ) ); + } + else if( nSBTabCnt ) + { + meType = XclSupbookType::Extern; + + //assuming all empty strings with just len header of 0 + const size_t nMinRecordSize = sizeof(sal_Int16); + const size_t nMaxRecords = rStrm.GetRecLeft() / nMinRecordSize; + if (nSBTabCnt > nMaxRecords) + { + SAL_WARN("sc", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nSBTabCnt << " claimed, truncating"); + nSBTabCnt = nMaxRecords; + } + + for( sal_uInt16 nSBTab = 0; nSBTab < nSBTabCnt; ++nSBTab ) + { + OUString aTabName( rStrm.ReadUniString() ); + maSupbTabList.push_back( std::make_unique( aTabName ) ); + } + } + else + { + meType = XclSupbookType::Special; + // create dummy list entry + maSupbTabList.push_back( std::make_unique( maXclUrl ) ); + } +} + +void XclImpSupbook::ReadXct( XclImpStream& rStrm ) +{ + rStrm.Ignore( 2 ); + mnSBTab = rStrm.ReaduInt16(); +} + +void XclImpSupbook::ReadCrn( XclImpStream& rStrm ) +{ + if (mnSBTab >= maSupbTabList.size()) + return; + XclImpSupbookTab& rSbTab = *maSupbTabList[mnSBTab]; + sal_uInt8 nXclColLast, nXclColFirst; + sal_uInt16 nXclRow; + nXclColLast = rStrm.ReaduInt8(); + nXclColFirst = rStrm.ReaduInt8(); + nXclRow = rStrm.ReaduInt16(); + + for( sal_uInt8 nXclCol = nXclColFirst; (nXclCol <= nXclColLast) && (rStrm.GetRecLeft() > 1); ++nXclCol ) + rSbTab.ReadCrn( rStrm, XclAddress( nXclCol, nXclRow ) ); +} + +void XclImpSupbook::ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ) +{ + maExtNameList.push_back( std::make_unique( *this, rStrm, meType, pFormulaConv ) ); +} + +const XclImpExtName* XclImpSupbook::GetExternName( sal_uInt16 nXclIndex ) const +{ + if (nXclIndex == 0) + { + SAL_WARN("sc", "XclImpSupbook::GetExternName - index must be >0"); + return nullptr; + } + if (meType == XclSupbookType::Self || nXclIndex > maExtNameList.size()) + return nullptr; + return maExtNameList[nXclIndex-1].get(); +} + +bool XclImpSupbook::GetLinkData( OUString& rApplic, OUString& rTopic ) const +{ + return (meType == XclSupbookType::Special) && XclImpUrlHelper::DecodeLink( rApplic, rTopic, maXclUrl ); +} + +OUString XclImpSupbook::GetMacroName( sal_uInt16 nXclNameIdx ) const +{ + OSL_ENSURE( nXclNameIdx > 0, "XclImpSupbook::GetMacroName - index must be >0" ); + const XclImpName* pName = (meType == XclSupbookType::Self) ? GetNameManager().GetName( nXclNameIdx ) : nullptr; + return (pName && pName->IsVBName()) ? pName->GetScName() : OUString(); +} + +OUString XclImpSupbook::GetTabName( sal_uInt16 nXtiTab ) const +{ + if (nXtiTab >= maSupbTabList.size()) + return OUString(); + return maSupbTabList[nXtiTab]->GetTabName(); +} + +sal_uInt16 XclImpSupbook::GetTabCount() const +{ + return ulimit_cast(maSupbTabList.size()); +} + +void XclImpSupbook::LoadCachedValues() +{ + if (meType != XclSupbookType::Extern || GetExtDocOptions().GetDocSettings().mnLinkCnt > 0 || !GetDocShell()) + return; + + OUString aAbsUrl( ScGlobal::GetAbsDocName(maXclUrl, GetDocShell()) ); + + ScExternalRefManager* pRefMgr = GetRoot().GetDoc().GetExternalRefManager(); + sal_uInt16 nFileId = pRefMgr->getExternalFileId(aAbsUrl); + + for (auto& rxTab : maSupbTabList) + { + const OUString& rTabName = rxTab->GetTabName(); + ScExternalRefCache::TableTypeRef pCacheTable = pRefMgr->getCacheTable(nFileId, rTabName, true); + rxTab->LoadCachedValues( pCacheTable, GetSharedStringPool()); + pCacheTable->setWholeTableCached(); + } +} + +svl::SharedStringPool& XclImpSupbook::GetSharedStringPool() +{ + return GetDoc().GetSharedStringPool(); +} + +// Import link manager ======================================================== + +XclImpLinkManagerImpl::XclImpLinkManagerImpl( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpLinkManagerImpl::ReadExternsheet( XclImpStream& rStrm ) +{ + sal_uInt16 nXtiCount; + nXtiCount = rStrm.ReaduInt16(); + OSL_ENSURE( static_cast< std::size_t >( nXtiCount * 6 ) == rStrm.GetRecLeft(), "XclImpLinkManagerImpl::ReadExternsheet - invalid count" ); + nXtiCount = static_cast< sal_uInt16 >( ::std::min< std::size_t >( nXtiCount, rStrm.GetRecLeft() / 6 ) ); + + /* #i104057# A weird external XLS generator writes multiple EXTERNSHEET + records instead of only one as expected. Surprisingly, Excel seems to + insert the entries of the second record before the entries of the first + record. */ + XclImpXtiVector aNewEntries( nXtiCount ); + for( auto& rNewEntry : aNewEntries ) + { + if (!rStrm.IsValid()) + break; + rStrm >> rNewEntry; + } + maXtiList.insert( maXtiList.begin(), aNewEntries.begin(), aNewEntries.end() ); + + LoadCachedValues(); +} + +void XclImpLinkManagerImpl::ReadSupbook( XclImpStream& rStrm ) +{ + maSupbookList.push_back( std::make_unique( rStrm ) ); +} + +void XclImpLinkManagerImpl::ReadXct( XclImpStream& rStrm ) +{ + if( !maSupbookList.empty() ) + maSupbookList.back()->ReadXct( rStrm ); +} + +void XclImpLinkManagerImpl::ReadCrn( XclImpStream& rStrm ) +{ + if( !maSupbookList.empty() ) + maSupbookList.back()->ReadCrn( rStrm ); +} + +void XclImpLinkManagerImpl::ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ) +{ + if( !maSupbookList.empty() ) + maSupbookList.back()->ReadExternname( rStrm, pFormulaConv ); +} + +bool XclImpLinkManagerImpl::IsSelfRef( sal_uInt16 nXtiIndex ) const +{ + const XclImpSupbook* pSupbook = GetSupbook( nXtiIndex ); + return pSupbook && (pSupbook->GetType() == XclSupbookType::Self); +} + +bool XclImpLinkManagerImpl::GetScTabRange( + SCTAB& rnFirstScTab, SCTAB& rnLastScTab, sal_uInt16 nXtiIndex ) const +{ + if( const XclImpXti* pXti = GetXti( nXtiIndex ) ) + { + if (!maSupbookList.empty() && (pXti->mnSupbook < maSupbookList.size()) ) + { + rnFirstScTab = pXti->mnSBTabFirst; + rnLastScTab = pXti->mnSBTabLast; + return true; + } + } + return false; +} + +const XclImpExtName* XclImpLinkManagerImpl::GetExternName( sal_uInt16 nXtiIndex, sal_uInt16 nExtName ) const +{ + const XclImpSupbook* pSupbook = GetSupbook( nXtiIndex ); + return pSupbook ? pSupbook->GetExternName( nExtName ) : nullptr; +} + +const OUString* XclImpLinkManagerImpl::GetSupbookUrl( sal_uInt16 nXtiIndex ) const +{ + const XclImpSupbook* p = GetSupbook( nXtiIndex ); + if (!p) + return nullptr; + return &p->GetXclUrl(); +} + +OUString XclImpLinkManagerImpl::GetSupbookTabName( sal_uInt16 nXti, sal_uInt16 nXtiTab ) const +{ + const XclImpSupbook* p = GetSupbook(nXti); + return p ? p->GetTabName(nXtiTab) : OUString(); +} + +bool XclImpLinkManagerImpl::GetLinkData( OUString& rApplic, OUString& rTopic, sal_uInt16 nXtiIndex ) const +{ + const XclImpSupbook* pSupbook = GetSupbook( nXtiIndex ); + return pSupbook && pSupbook->GetLinkData( rApplic, rTopic ); +} + +OUString XclImpLinkManagerImpl::GetMacroName( sal_uInt16 nExtSheet, sal_uInt16 nExtName ) const +{ + const XclImpSupbook* pSupbook = GetSupbook( nExtSheet ); + return pSupbook ? pSupbook->GetMacroName( nExtName ) : OUString(); +} + +const XclImpXti* XclImpLinkManagerImpl::GetXti( sal_uInt16 nXtiIndex ) const +{ + return (nXtiIndex < maXtiList.size()) ? &maXtiList[ nXtiIndex ] : nullptr; +} + +const XclImpSupbook* XclImpLinkManagerImpl::GetSupbook( sal_uInt16 nXtiIndex ) const +{ + if ( maSupbookList.empty() ) + return nullptr; + const XclImpXti* pXti = GetXti( nXtiIndex ); + if (!pXti || pXti->mnSupbook >= maSupbookList.size()) + return nullptr; + return maSupbookList.at( pXti->mnSupbook ).get(); +} + +void XclImpLinkManagerImpl::LoadCachedValues() +{ + // Read all CRN records which can be accessed via XclImpSupbook, and store + // the cached values to the external reference manager. + for (auto& rxSupbook : maSupbookList) + rxSupbook->LoadCachedValues(); +} + +XclImpLinkManager::XclImpLinkManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mxImpl( new XclImpLinkManagerImpl( rRoot ) ) +{ +} + +XclImpLinkManager::~XclImpLinkManager() +{ +} + +void XclImpLinkManager::ReadExternsheet( XclImpStream& rStrm ) +{ + mxImpl->ReadExternsheet( rStrm ); +} + +void XclImpLinkManager::ReadSupbook( XclImpStream& rStrm ) +{ + mxImpl->ReadSupbook( rStrm ); +} + +void XclImpLinkManager::ReadXct( XclImpStream& rStrm ) +{ + mxImpl->ReadXct( rStrm ); +} + +void XclImpLinkManager::ReadCrn( XclImpStream& rStrm ) +{ + mxImpl->ReadCrn( rStrm ); +} + +void XclImpLinkManager::ReadExternname( XclImpStream& rStrm, ExcelToSc* pFormulaConv ) +{ + mxImpl->ReadExternname( rStrm, pFormulaConv ); +} + +bool XclImpLinkManager::IsSelfRef( sal_uInt16 nXtiIndex ) const +{ + return mxImpl->IsSelfRef( nXtiIndex ); +} + +bool XclImpLinkManager::GetScTabRange( + SCTAB& rnFirstScTab, SCTAB& rnLastScTab, sal_uInt16 nXtiIndex ) const +{ + return mxImpl->GetScTabRange( rnFirstScTab, rnLastScTab, nXtiIndex ); +} + +const XclImpExtName* XclImpLinkManager::GetExternName( sal_uInt16 nXtiIndex, sal_uInt16 nExtName ) const +{ + return mxImpl->GetExternName( nXtiIndex, nExtName ); +} + +const OUString* XclImpLinkManager::GetSupbookUrl( sal_uInt16 nXtiIndex ) const +{ + return mxImpl->GetSupbookUrl(nXtiIndex); +} + +OUString XclImpLinkManager::GetSupbookTabName( sal_uInt16 nXti, sal_uInt16 nXtiTab ) const +{ + return mxImpl->GetSupbookTabName(nXti, nXtiTab); +} + +bool XclImpLinkManager::GetLinkData( OUString& rApplic, OUString& rTopic, sal_uInt16 nXtiIndex ) const +{ + return mxImpl->GetLinkData( rApplic, rTopic, nXtiIndex ); +} + +OUString XclImpLinkManager::GetMacroName( sal_uInt16 nExtSheet, sal_uInt16 nExtName ) const +{ + return mxImpl->GetMacroName( nExtSheet, nExtName ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiname.cxx b/sc/source/filter/excel/xiname.cxx new file mode 100644 index 000000000..d498dfba4 --- /dev/null +++ b/sc/source/filter/excel/xiname.cxx @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +// *** Implementation *** + +XclImpName::TokenStrmData::TokenStrmData( XclImpStream& rStrm ) : + mrStrm(rStrm), mnStrmPos(0), mnStrmSize(0) {} + +XclImpName::XclImpName( XclImpStream& rStrm, sal_uInt16 nXclNameIdx ) : + XclImpRoot( rStrm.GetRoot() ), + mpScData( nullptr ), + mnScTab( SCTAB_MAX ), + meNameType( ScRangeData::Type::Name ), + mnXclTab( EXC_NAME_GLOBAL ), + mnNameIndex( nXclNameIdx ), + mbVBName( false ), + mbMacro( false ) +{ + ExcelToSc& rFmlaConv = GetOldFmlaConverter(); + + // 1) *** read data from stream *** --------------------------------------- + + sal_uInt16 nFlags = 0, nFmlaSize = 0, nExtSheet = EXC_NAME_GLOBAL; + sal_uInt8 nNameLen = 0; + sal_Unicode cBuiltIn(EXC_BUILTIN_UNKNOWN); /// Excel built-in name index. + + switch( GetBiff() ) + { + case EXC_BIFF2: + { + sal_uInt8 nFlagsBiff2; + nFlagsBiff2 = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + rStrm.Ignore( 1 ); //nShortCut + nNameLen = rStrm.ReaduInt8(); + nFmlaSize = rStrm.ReaduInt8(); + ::set_flag( nFlags, EXC_NAME_FUNC, ::get_flag( nFlagsBiff2, EXC_NAME2_FUNC ) ); + } + break; + + case EXC_BIFF3: + case EXC_BIFF4: + { + nFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 1 ); //nShortCut + nNameLen = rStrm.ReaduInt8(); + nFmlaSize = rStrm.ReaduInt16(); + } + break; + + case EXC_BIFF5: + case EXC_BIFF8: + { + nFlags = rStrm.ReaduInt16(); + rStrm.Ignore( 1 ); //nShortCut + nNameLen = rStrm.ReaduInt8(); + nFmlaSize = rStrm.ReaduInt16(); + nExtSheet = rStrm.ReaduInt16(); + mnXclTab = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + } + break; + + default: DBG_ERROR_BIFF(); + } + + if( GetBiff() <= EXC_BIFF5 ) + maXclName = rStrm.ReadRawByteString( nNameLen ); + else + maXclName = rStrm.ReadUniString( nNameLen ); + + // 2) *** convert sheet index and name *** -------------------------------- + + // functions and VBA + bool bFunction = ::get_flag( nFlags, EXC_NAME_FUNC ); + mbVBName = ::get_flag( nFlags, EXC_NAME_VB ); + mbMacro = ::get_flag( nFlags, EXC_NAME_PROC ); + + // get built-in name, or convert characters invalid in Calc + bool bBuiltIn = ::get_flag( nFlags, EXC_NAME_BUILTIN ); + + // special case for BIFF5 filter range - name appears as plain text without built-in flag + if( (GetBiff() == EXC_BIFF5) && (maXclName == XclTools::GetXclBuiltInDefName(EXC_BUILTIN_FILTERDATABASE)) ) + { + bBuiltIn = true; + maXclName = OUStringChar(EXC_BUILTIN_FILTERDATABASE); + } + + // convert Excel name to Calc name + if( mbVBName ) + { + // VB macro name + maScName = maXclName; + } + else if( bBuiltIn ) + { + // built-in name + if( !maXclName.isEmpty() ) + cBuiltIn = maXclName[0]; + if( cBuiltIn == '?' ) // NUL character is imported as '?' + cBuiltIn = '\0'; + maScName = XclTools::GetBuiltInDefName( cBuiltIn ); + } + else + { + // any other name + maScName = ScfTools::ConvertToScDefinedName( maXclName ); + } + + // add index for local names + if( mnXclTab != EXC_NAME_GLOBAL ) + { + sal_uInt16 nUsedTab = (GetBiff() == EXC_BIFF8) ? mnXclTab : nExtSheet; + // TODO: may not work for BIFF5, handle skipped sheets (all BIFF) + mnScTab = static_cast< SCTAB >( nUsedTab - 1 ); + } + + // 3) *** convert the name definition formula *** ------------------------- + + rFmlaConv.Reset(); + std::unique_ptr pTokArr; + + if( ::get_flag( nFlags, EXC_NAME_BIG ) ) + { + // special, unsupported name + pTokArr = rFmlaConv.GetDummy(); + } + else if( bBuiltIn ) + { + SCTAB const nLocalTab = (mnXclTab == EXC_NAME_GLOBAL) ? SCTAB_MAX : (mnXclTab - 1); + + // --- print ranges or title ranges --- + rStrm.PushPosition(); + switch( cBuiltIn ) + { + case EXC_BUILTIN_PRINTAREA: + if( rFmlaConv.Convert( GetPrintAreaBuffer(), rStrm, nFmlaSize, nLocalTab, FT_RangeName ) == ConvErr::OK ) + meNameType |= ScRangeData::Type::PrintArea; + break; + case EXC_BUILTIN_PRINTTITLES: + if( rFmlaConv.Convert( GetTitleAreaBuffer(), rStrm, nFmlaSize, nLocalTab, FT_RangeName ) == ConvErr::OK ) + meNameType |= ScRangeData::Type::ColHeader | ScRangeData::Type::RowHeader; + break; + } + rStrm.PopPosition(); + + // --- name formula --- + // JEG : double check this. It is clearly false for normal names + // but some of the builtins (sheettitle?) might be able to handle arrays + rFmlaConv.Convert( pTokArr, rStrm, nFmlaSize, false, FT_RangeName ); + + // --- auto or advanced filter --- + if ((GetBiff() == EXC_BIFF8) && pTokArr) + { + ScRange aRange; + if (pTokArr->IsReference(aRange, ScAddress())) + { + switch( cBuiltIn ) + { + case EXC_BUILTIN_FILTERDATABASE: + GetFilterManager().Insert( &GetOldRoot(), aRange); + break; + case EXC_BUILTIN_CRITERIA: + GetFilterManager().AddAdvancedRange( aRange ); + meNameType |= ScRangeData::Type::Criteria; + break; + case EXC_BUILTIN_EXTRACT: + if (pTokArr->IsValidReference(aRange, ScAddress())) + GetFilterManager().AddExtractPos( aRange ); + break; + } + } + } + } + else if( nFmlaSize > 0 ) + { + // Regular defined name. We need to convert the tokens after all the + // names have been registered (for cross-referenced names). + mpTokensData.reset(new TokenStrmData(rStrm)); + mpTokensData->mnStrmPos = rStrm.GetSvStreamPos(); + rStrm.StorePosition(mpTokensData->maStrmPos); + mpTokensData->mnStrmSize = nFmlaSize; + } + + if (pTokArr && !bFunction && !mbVBName) + InsertName(pTokArr.get()); +} + +void XclImpName::ConvertTokens() +{ + if (!mpTokensData) + return; + + ExcelToSc& rFmlaConv = GetOldFmlaConverter(); + rFmlaConv.Reset(); + std::unique_ptr pArray; + + XclImpStreamPos aOldPos; + XclImpStream& rStrm = mpTokensData->mrStrm; + rStrm.StorePosition(aOldPos); + rStrm.RestorePosition(mpTokensData->maStrmPos); + rFmlaConv.Convert(pArray, rStrm, mpTokensData->mnStrmSize, true, FT_RangeName); + rStrm.RestorePosition(aOldPos); + + if (pArray) + InsertName(pArray.get()); + + mpTokensData.reset(); +} + +void XclImpName::InsertName(const ScTokenArray* pArray) +{ + // create the Calc name data + ScRangeData* pData = new ScRangeData(GetDoc(), maScName, *pArray, ScAddress(), meNameType); + pData->GuessPosition(); // calculate base position for relative refs + pData->SetIndex( mnNameIndex ); // used as unique identifier in formulas + if (mnXclTab == EXC_NAME_GLOBAL) + { + if (!GetDoc().GetRangeName()->insert(pData)) + pData = nullptr; + } + else + { + ScRangeName* pLocalNames = GetDoc().GetRangeName(mnScTab); + if (pLocalNames) + { + if (!pLocalNames->insert(pData)) + pData = nullptr; + } + else + { + delete pData; + pData = nullptr; + } + + if (GetBiff() == EXC_BIFF8 && pData) + { + ScRange aRange; + // discard deleted ranges ( for the moment at least ) + if ( pData->IsValidReference( aRange ) ) + { + GetExtDocOptions().GetOrCreateTabSettings( mnXclTab ); + } + } + } + if (pData) + { + GetDoc().CheckLinkFormulaNeedingCheck( *pData->GetCode()); + mpScData = pData; // cache for later use + } +} + +XclImpNameManager::XclImpNameManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpNameManager::ReadName( XclImpStream& rStrm ) +{ + size_t nCount = maNameList.size(); + if( nCount < 0xFFFF ) + maNameList.push_back( std::make_unique( rStrm, static_cast< sal_uInt16 >( nCount + 1 ) ) ); +} + +const XclImpName* XclImpNameManager::FindName( std::u16string_view rXclName, SCTAB nScTab ) const +{ + const XclImpName* pGlobalName = nullptr; // a found global name + const XclImpName* pLocalName = nullptr; // a found local name + for( const auto& rxName : maNameList ) + { + if( rxName->GetXclName() == rXclName ) + { + if( rxName->GetScTab() == nScTab ) + pLocalName = rxName.get(); + else if( rxName->IsGlobal() ) + pGlobalName = rxName.get(); + } + + if (pLocalName) + break; + } + return pLocalName ? pLocalName : pGlobalName; +} + +const XclImpName* XclImpNameManager::GetName( sal_uInt16 nXclNameIdx ) const +{ + OSL_ENSURE( nXclNameIdx > 0, "XclImpNameManager::GetName - index must be >0" ); + return ( nXclNameIdx <= 0 || nXclNameIdx > maNameList.size() ) ? nullptr : maNameList.at( nXclNameIdx - 1 ).get(); +} + +void XclImpNameManager::ConvertAllTokens() +{ + for (auto& rxName : maNameList) + rxName->ConvertTokens(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xipage.cxx b/sc/source/filter/excel/xipage.cxx new file mode 100644 index 000000000..c06308ba7 --- /dev/null +++ b/sc/source/filter/excel/xipage.cxx @@ -0,0 +1,401 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Page settings ============================================================== + +XclImpPageSettings::XclImpPageSettings( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ + Initialize(); +} + +void XclImpPageSettings::Initialize() +{ + maData.SetDefaults(); + mbValidPaper = false; +} + +void XclImpPageSettings::ReadSetup( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF4 ); + if( GetBiff() < EXC_BIFF4 ) + return; + + // BIFF4 - BIFF8 + sal_uInt16 nFlags; + maData.mnPaperSize = rStrm.ReaduInt16(); + maData.mnScaling = rStrm.ReaduInt16(); + maData.mnStartPage = rStrm.ReaduInt16(); + maData.mnFitToWidth = rStrm.ReaduInt16(); + maData.mnFitToHeight = rStrm.ReaduInt16(); + nFlags = rStrm.ReaduInt16(); + + mbValidPaper = maData.mbValid = !::get_flag( nFlags, EXC_SETUP_INVALID ); + maData.mbPrintInRows = ::get_flag( nFlags, EXC_SETUP_INROWS ); + maData.mbPortrait = ::get_flag( nFlags, EXC_SETUP_PORTRAIT ); + maData.mbBlackWhite = ::get_flag( nFlags, EXC_SETUP_BLACKWHITE ); + maData.mbManualStart = true; + + // new in BIFF5 - BIFF8 + if( GetBiff() >= EXC_BIFF5 ) + { + maData.mnHorPrintRes = rStrm.ReaduInt16(); + maData.mnVerPrintRes = rStrm.ReaduInt16(); + maData.mfHeaderMargin = rStrm.ReadDouble(); + maData.mfFooterMargin = rStrm.ReadDouble(); + maData.mnCopies = rStrm.ReaduInt16(); + + maData.mbDraftQuality = ::get_flag( nFlags, EXC_SETUP_DRAFT ); + maData.mbPrintNotes = ::get_flag( nFlags, EXC_SETUP_PRINTNOTES ); + maData.mbManualStart = ::get_flag( nFlags, EXC_SETUP_STARTPAGE ); + } +} + +void XclImpPageSettings::ReadMargin( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_LEFTMARGIN: maData.mfLeftMargin = rStrm.ReadDouble(); break; + case EXC_ID_RIGHTMARGIN: maData.mfRightMargin = rStrm.ReadDouble(); break; + case EXC_ID_TOPMARGIN: maData.mfTopMargin = rStrm.ReadDouble(); break; + case EXC_ID_BOTTOMMARGIN: maData.mfBottomMargin = rStrm.ReadDouble(); break; + default: OSL_FAIL( "XclImpPageSettings::ReadMargin - unknown record" ); + } +} + +void XclImpPageSettings::ReadCenter( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF3 ); // read it anyway + bool bCenter = (rStrm.ReaduInt16() != 0); + switch( rStrm.GetRecId() ) + { + case EXC_ID_HCENTER: maData.mbHorCenter = bCenter; break; + case EXC_ID_VCENTER: maData.mbVerCenter = bCenter; break; + default: OSL_FAIL( "XclImpPageSettings::ReadCenter - unknown record" ); + } +} + +void XclImpPageSettings::ReadHeaderFooter( XclImpStream& rStrm ) +{ + OUString aString; + if( rStrm.GetRecLeft() ) + aString = (GetBiff() <= EXC_BIFF5) ? rStrm.ReadByteString( false ) : rStrm.ReadUniString(); + + switch( rStrm.GetRecId() ) + { + case EXC_ID_HEADER: maData.maHeader = aString; break; + case EXC_ID_FOOTER: maData.maFooter = aString; break; + case EXC_ID_HEADER_EVEN: maData.maHeaderEven = aString; break; + case EXC_ID_FOOTER_EVEN: maData.maFooterEven = aString; break; + default: OSL_FAIL( "XclImpPageSettings::ReadHeaderFooter - unknown record" ); + } + + if (utl::ConfigManager::IsFuzzing()) + { + if (maData.maHeader.getLength() > 10) + maData.maHeader = maData.maHeader.copy(0, 10); + if (maData.maFooter.getLength() > 10) + maData.maFooter = maData.maFooter.copy(0, 10); + if (maData.maHeaderEven.getLength() > 10) + maData.maHeaderEven = maData.maHeaderEven.copy(0, 10); + if (maData.maFooterEven.getLength() > 10) + maData.maFooterEven = maData.maFooterEven.copy(0, 10); + } +} + +void XclImpPageSettings::ReadPageBreaks( XclImpStream& rStrm ) +{ + ScfUInt16Vec* pVec = nullptr; + switch( rStrm.GetRecId() ) + { + case EXC_ID_HORPAGEBREAKS: pVec = &maData.maHorPageBreaks; break; + case EXC_ID_VERPAGEBREAKS: pVec = &maData.maVerPageBreaks; break; + default: OSL_FAIL( "XclImpPageSettings::ReadPageBreaks - unknown record" ); + } + + if( !pVec ) + return; + + bool bIgnore = GetBiff() == EXC_BIFF8; // ignore start/end columns or rows in BIFF8 + + sal_uInt16 nCount, nBreak; + nCount = rStrm.ReaduInt16(); + pVec->clear(); + pVec->reserve( nCount ); + + while( nCount-- ) + { + nBreak = rStrm.ReaduInt16(); + if( nBreak ) + pVec->push_back( nBreak ); + if( bIgnore ) + rStrm.Ignore( 4 ); + } +} + +void XclImpPageSettings::ReadPrintHeaders( XclImpStream& rStrm ) +{ + maData.mbPrintHeadings = (rStrm.ReaduInt16() != 0); +} + +void XclImpPageSettings::ReadPrintGridLines( XclImpStream& rStrm ) +{ + maData.mbPrintGrid = (rStrm.ReaduInt16() != 0); +} + +void XclImpPageSettings::ReadImgData( XclImpStream& rStrm ) +{ + Graphic aGraphic = XclImpDrawing::ReadImgData( GetRoot(), rStrm ); + if( aGraphic.GetType() != GraphicType::NONE ) + maData.mxBrushItem.reset( new SvxBrushItem( aGraphic, GPOS_TILED, ATTR_BACKGROUND ) ); +} + +void XclImpPageSettings::SetPaperSize( sal_uInt16 nXclPaperSize, bool bPortrait ) +{ + maData.mnPaperSize = nXclPaperSize; + maData.mbPortrait = bPortrait; + mbValidPaper = true; +} + +namespace { + +void lclPutMarginItem( SfxItemSet& rItemSet, sal_uInt16 nRecId, double fMarginInch ) +{ + sal_uInt16 nMarginTwips = XclTools::GetTwipsFromInch( fMarginInch ); + switch( nRecId ) + { + case EXC_ID_TOPMARGIN: + case EXC_ID_BOTTOMMARGIN: + { + SvxULSpaceItem aItem( rItemSet.Get( ATTR_ULSPACE ) ); + if( nRecId == EXC_ID_TOPMARGIN ) + aItem.SetUpperValue( nMarginTwips ); + else + aItem.SetLowerValue( nMarginTwips ); + rItemSet.Put( aItem ); + } + break; + case EXC_ID_LEFTMARGIN: + case EXC_ID_RIGHTMARGIN: + { + SvxLRSpaceItem aItem( rItemSet.Get( ATTR_LRSPACE ) ); + if( nRecId == EXC_ID_LEFTMARGIN ) + aItem.SetLeftValue( nMarginTwips ); + else + aItem.SetRightValue( nMarginTwips ); + rItemSet.Put( aItem ); + } + break; + default: + OSL_FAIL( "XclImpPageSettings::SetMarginItem - unknown record id" ); + } +} + +} // namespace + +void XclImpPageSettings::Finalize() +{ + ScDocument& rDoc = GetDoc(); + SCTAB nScTab = GetCurrScTab(); + + // *** create page style sheet *** + + OUString aStyleName; + OUString aTableName; + if( GetDoc().GetName( nScTab, aTableName ) ) + aStyleName = "PageStyle_" + aTableName; + else + aStyleName = "PageStyle_" + OUString::number(static_cast(nScTab+1)); + + ScStyleSheet& rStyleSheet = ScfTools::MakePageStyleSheet( + GetStyleSheetPool(), aStyleName, false); + + SfxItemSet& rItemSet = rStyleSheet.GetItemSet(); + + // *** page settings *** + + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_TOPDOWN, !maData.mbPrintInRows ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_HORCENTER, maData.mbHorCenter ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_VERCENTER, maData.mbVerCenter ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_HEADERS, maData.mbPrintHeadings ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_GRID, maData.mbPrintGrid ), true ); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_PAGE_NOTES, maData.mbPrintNotes ), true ); + + sal_uInt16 nStartPage = maData.mbManualStart ? maData.mnStartPage : 0; + ScfTools::PutItem( rItemSet, SfxUInt16Item( ATTR_PAGE_FIRSTPAGENO, nStartPage ), true ); + + if( maData.mxBrushItem ) + rItemSet.Put( *maData.mxBrushItem ); + + if( mbValidPaper ) + { + SvxPageItem aPageItem( rItemSet.Get( ATTR_PAGE ) ); + aPageItem.SetLandscape( !maData.mbPortrait ); + rItemSet.Put( aPageItem ); + ScfTools::PutItem( rItemSet, SvxSizeItem( ATTR_PAGE_SIZE, maData.GetScPaperSize() ), true ); + } + + if( maData.mbFitToPages ) + rItemSet.Put( ScPageScaleToItem( maData.mnFitToWidth, maData.mnFitToHeight ) ); + else if( maData.mbValid ) + rItemSet.Put( SfxUInt16Item( ATTR_PAGE_SCALE, maData.mnScaling ) ); + + // *** margin preparations *** + + double fLeftMargin = maData.mfLeftMargin; + double fRightMargin = maData.mfRightMargin; + double fTopMargin = maData.mfTopMargin; + double fBottomMargin = maData.mfBottomMargin; + // distances between header/footer and page area + double fHeaderHeight = 0.0; + double fHeaderDist = 0.0; + double fFooterHeight = 0.0; + double fFooterDist = 0.0; + // in Calc, "header/footer left/right margin" is X distance between header/footer and page margin + double fHdrLeftMargin = maData.mfHdrLeftMargin - maData.mfLeftMargin; + double fHdrRightMargin = maData.mfHdrRightMargin - maData.mfRightMargin; + double fFtrLeftMargin = maData.mfFtrLeftMargin - maData.mfLeftMargin; + double fFtrRightMargin = maData.mfFtrRightMargin - maData.mfRightMargin; + + // *** header and footer *** + + XclImpHFConverter aHFConv( GetRoot() ); + + // header + bool bHasHeader = !maData.maHeader.isEmpty(); + SvxSetItem aHdrSetItem( rItemSet.Get( ATTR_PAGE_HEADERSET ) ); + SfxItemSet& rHdrItemSet = aHdrSetItem.GetItemSet(); + rHdrItemSet.Put( SfxBoolItem( ATTR_PAGE_ON, bHasHeader ) ); + if( bHasHeader ) + { + aHFConv.ParseString( maData.maHeader ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_HEADERLEFT ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_HEADERRIGHT ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_HEADERFIRST ); + // #i23296# In Calc, "top margin" is distance to header + fTopMargin = maData.mfHeaderMargin; + // Calc uses distance between header and sheet data area + fHeaderHeight = XclTools::GetInchFromTwips( aHFConv.GetTotalHeight() ); + fHeaderDist = maData.mfTopMargin - maData.mfHeaderMargin - fHeaderHeight; + } + if( fHeaderDist < 0.0 ) + { + /* #i23296# Header overlays sheet data: + -> set fixed header height to get correct sheet data position. */ + ScfTools::PutItem( rHdrItemSet, SfxBoolItem( ATTR_PAGE_DYNAMIC, false ), true ); + // shrink header height + tools::Long nHdrHeight = XclTools::GetTwipsFromInch( fHeaderHeight + fHeaderDist ); + ScfTools::PutItem( rHdrItemSet, SvxSizeItem( ATTR_PAGE_SIZE, Size( 0, nHdrHeight ) ), true ); + lclPutMarginItem( rHdrItemSet, EXC_ID_BOTTOMMARGIN, 0.0 ); + } + else + { + // use dynamic header height + ScfTools::PutItem( rHdrItemSet, SfxBoolItem( ATTR_PAGE_DYNAMIC, true ), true ); + lclPutMarginItem( rHdrItemSet, EXC_ID_BOTTOMMARGIN, fHeaderDist ); + } + lclPutMarginItem( rHdrItemSet, EXC_ID_LEFTMARGIN, fHdrLeftMargin ); + lclPutMarginItem( rHdrItemSet, EXC_ID_RIGHTMARGIN, fHdrRightMargin ); + rItemSet.Put( aHdrSetItem ); + + // footer + bool bHasFooter = !maData.maFooter.isEmpty(); + SvxSetItem aFtrSetItem( rItemSet.Get( ATTR_PAGE_FOOTERSET ) ); + SfxItemSet& rFtrItemSet = aFtrSetItem.GetItemSet(); + rFtrItemSet.Put( SfxBoolItem( ATTR_PAGE_ON, bHasFooter ) ); + if( bHasFooter ) + { + aHFConv.ParseString( maData.maFooter ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_FOOTERLEFT ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_FOOTERRIGHT ); + aHFConv.FillToItemSet( rItemSet, ATTR_PAGE_FOOTERFIRST ); + // #i23296# In Calc, "bottom margin" is distance to footer + fBottomMargin = maData.mfFooterMargin; + // Calc uses distance between footer and sheet data area + fFooterHeight = XclTools::GetInchFromTwips( aHFConv.GetTotalHeight() ); + fFooterDist = maData.mfBottomMargin - maData.mfFooterMargin - fFooterHeight; + } + if( fFooterDist < 0.0 ) + { + /* #i23296# Footer overlays sheet data: + -> set fixed footer height to get correct sheet data end position. */ + ScfTools::PutItem( rFtrItemSet, SfxBoolItem( ATTR_PAGE_DYNAMIC, false ), true ); + // shrink footer height + tools::Long nFtrHeight = XclTools::GetTwipsFromInch( fFooterHeight + fFooterDist ); + ScfTools::PutItem( rFtrItemSet, SvxSizeItem( ATTR_PAGE_SIZE, Size( 0, nFtrHeight ) ), true ); + lclPutMarginItem( rFtrItemSet, EXC_ID_TOPMARGIN, 0.0 ); + } + else + { + // use dynamic footer height + ScfTools::PutItem( rFtrItemSet, SfxBoolItem( ATTR_PAGE_DYNAMIC, true ), true ); + lclPutMarginItem( rFtrItemSet, EXC_ID_TOPMARGIN, fFooterDist ); + } + lclPutMarginItem( rFtrItemSet, EXC_ID_LEFTMARGIN, fFtrLeftMargin ); + lclPutMarginItem( rFtrItemSet, EXC_ID_RIGHTMARGIN, fFtrRightMargin ); + rItemSet.Put( aFtrSetItem ); + + // *** set final margins *** + + lclPutMarginItem( rItemSet, EXC_ID_LEFTMARGIN, fLeftMargin ); + lclPutMarginItem( rItemSet, EXC_ID_RIGHTMARGIN, fRightMargin ); + lclPutMarginItem( rItemSet, EXC_ID_TOPMARGIN, fTopMargin ); + lclPutMarginItem( rItemSet, EXC_ID_BOTTOMMARGIN, fBottomMargin ); + + // *** put style sheet into document *** + + rDoc.SetPageStyle( nScTab, rStyleSheet.GetName() ); + + // *** page breaks *** + + for( const auto& rHorPageBreak : maData.maHorPageBreaks ) + { + SCROW nScRow = static_cast< SCROW >( rHorPageBreak ); + if( nScRow <= rDoc.MaxRow() ) + rDoc.SetRowBreak(nScRow, nScTab, false, true); + } + + for( const auto& rVerPageBreak : maData.maVerPageBreaks ) + { + SCCOL nScCol = static_cast< SCCOL >( rVerPageBreak ); + if( nScCol <= rDoc.MaxCol() ) + rDoc.SetColBreak(nScCol, nScTab, false, true); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xipivot.cxx b/sc/source/filter/excel/xipivot.cxx new file mode 100644 index 000000000..d8d4eaa63 --- /dev/null +++ b/sc/source/filter/excel/xipivot.cxx @@ -0,0 +1,1737 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//TODO ExcelToSc usage +#include +#include + +#include + +using namespace com::sun::star; + +using ::com::sun::star::sheet::DataPilotFieldOrientation_DATA; +using ::com::sun::star::sheet::DataPilotFieldSortInfo; +using ::com::sun::star::sheet::DataPilotFieldAutoShowInfo; +using ::com::sun::star::sheet::DataPilotFieldLayoutInfo; +using ::com::sun::star::sheet::DataPilotFieldReference; +using ::std::vector; + +// Pivot cache + +XclImpPCItem::XclImpPCItem( XclImpStream& rStrm ) +{ + switch( rStrm.GetRecId() ) + { + case EXC_ID_SXDOUBLE: ReadSxdouble( rStrm ); break; + case EXC_ID_SXBOOLEAN: ReadSxboolean( rStrm ); break; + case EXC_ID_SXERROR: ReadSxerror( rStrm ); break; + case EXC_ID_SXINTEGER: ReadSxinteger( rStrm ); break; + case EXC_ID_SXSTRING: ReadSxstring( rStrm ); break; + case EXC_ID_SXDATETIME: ReadSxdatetime( rStrm ); break; + case EXC_ID_SXEMPTY: ReadSxempty( rStrm ); break; + default: OSL_FAIL( "XclImpPCItem::XclImpPCItem - unknown record id" ); + } +} + +namespace { + +void lclSetValue( XclImpRoot& rRoot, const ScAddress& rScPos, double fValue, SvNumFormatType nFormatType ) +{ + ScDocumentImport& rDoc = rRoot.GetDocImport(); + rDoc.setNumericCell(rScPos, fValue); + sal_uInt32 nScNumFmt = rRoot.GetFormatter().GetStandardFormat( nFormatType, rRoot.GetDocLanguage() ); + rDoc.getDoc().ApplyAttr( + rScPos.Col(), rScPos.Row(), rScPos.Tab(), SfxUInt32Item(ATTR_VALUE_FORMAT, nScNumFmt)); +} + +} // namespace + +void XclImpPCItem::WriteToSource( XclImpRoot& rRoot, const ScAddress& rScPos ) const +{ + ScDocumentImport& rDoc = rRoot.GetDocImport(); + if( const OUString* pText = GetText() ) + rDoc.setStringCell(rScPos, *pText); + else if( const double* pfValue = GetDouble() ) + rDoc.setNumericCell(rScPos, *pfValue); + else if( const sal_Int16* pnValue = GetInteger() ) + rDoc.setNumericCell(rScPos, *pnValue); + else if( const bool* pbValue = GetBool() ) + lclSetValue( rRoot, rScPos, *pbValue ? 1.0 : 0.0, SvNumFormatType::LOGICAL ); + else if( const DateTime* pDateTime = GetDateTime() ) + { + // set number format date, time, or date/time, depending on the value + double fValue = rRoot.GetDoubleFromDateTime( *pDateTime ); + double fInt = 0.0; + double fFrac = modf( fValue, &fInt ); + SvNumFormatType nFormatType = ((fFrac == 0.0) && (fInt != 0.0)) ? SvNumFormatType::DATE : + ((fInt == 0.0) ? SvNumFormatType::TIME : SvNumFormatType::DATETIME); + lclSetValue( rRoot, rScPos, fValue, nFormatType ); + } + else if( const sal_uInt16* pnError = GetError() ) + { + double fValue; + sal_uInt8 nErrCode = static_cast< sal_uInt8 >( *pnError ); + std::unique_ptr pScTokArr = rRoot.GetOldFmlaConverter().GetBoolErr( + XclTools::ErrorToEnum( fValue, true, nErrCode ) ); + ScFormulaCell* pCell = pScTokArr + ? new ScFormulaCell(rDoc.getDoc(), rScPos, std::move(pScTokArr)) + : new ScFormulaCell(rDoc.getDoc(), rScPos); + pCell->SetHybridDouble( fValue ); + rDoc.setFormulaCell(rScPos, pCell); + } +} + +void XclImpPCItem::ReadSxdouble( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 8, "XclImpPCItem::ReadSxdouble - wrong record size" ); + SetDouble( rStrm.ReadDouble() ); +} + +void XclImpPCItem::ReadSxboolean( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxboolean - wrong record size" ); + SetBool( rStrm.ReaduInt16() != 0 ); +} + +void XclImpPCItem::ReadSxerror( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxerror - wrong record size" ); + SetError( rStrm.ReaduInt16() ); +} + +void XclImpPCItem::ReadSxinteger( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 2, "XclImpPCItem::ReadSxinteger - wrong record size" ); + SetInteger( rStrm.ReadInt16() ); +} + +void XclImpPCItem::ReadSxstring( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() >= 3, "XclImpPCItem::ReadSxstring - wrong record size" ); + SetText( rStrm.ReadUniString() ); +} + +void XclImpPCItem::ReadSxdatetime( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 8, "XclImpPCItem::ReadSxdatetime - wrong record size" ); + sal_uInt16 nYear, nMonth; + sal_uInt8 nDay, nHour, nMin, nSec; + nYear = rStrm.ReaduInt16(); + nMonth = rStrm.ReaduInt16(); + nDay = rStrm.ReaduInt8(); + nHour = rStrm.ReaduInt8(); + nMin = rStrm.ReaduInt8(); + nSec = rStrm.ReaduInt8(); + SetDateTime( DateTime( Date( nDay, nMonth, nYear ), tools::Time( nHour, nMin, nSec ) ) ); +} + +void XclImpPCItem::ReadSxempty( XclImpStream& rStrm ) +{ + OSL_ENSURE( rStrm.GetRecSize() == 0, "XclImpPCItem::ReadSxempty - wrong record size" ); + SetEmpty(); +} + +XclImpPCField::XclImpPCField( const XclImpRoot& rRoot, XclImpPivotCache& rPCache, sal_uInt16 nFieldIdx ) : + XclPCField( EXC_PCFIELD_UNKNOWN, nFieldIdx ), + XclImpRoot( rRoot ), + mrPCache( rPCache ), + mnSourceScCol( -1 ), + mbNumGroupInfoRead( false ) +{ +} + +XclImpPCField::~XclImpPCField() +{ +} + +// general field/item access -------------------------------------------------- + +const OUString& XclImpPCField::GetFieldName( const ScfStringVec& rVisNames ) const +{ + if( IsGroupChildField() && (mnFieldIdx < rVisNames.size()) ) + { + const OUString& rVisName = rVisNames[ mnFieldIdx ]; + if (!rVisName.isEmpty()) + return rVisName; + } + return maFieldInfo.maName; +} + +const XclImpPCField* XclImpPCField::GetGroupBaseField() const +{ + OSL_ENSURE( IsGroupChildField(), "XclImpPCField::GetGroupBaseField - this field type does not have a base field" ); + return IsGroupChildField() ? mrPCache.GetField( maFieldInfo.mnGroupBase ) : nullptr; +} + +const XclImpPCItem* XclImpPCField::GetItem( sal_uInt16 nItemIdx ) const +{ + return (nItemIdx < maItems.size()) ? maItems[ nItemIdx ].get() : nullptr; +} + +const XclImpPCItem* XclImpPCField::GetLimitItem( sal_uInt16 nItemIdx ) const +{ + OSL_ENSURE( nItemIdx < 3, "XclImpPCField::GetLimitItem - invalid item index" ); + OSL_ENSURE( nItemIdx < maNumGroupItems.size(), "XclImpPCField::GetLimitItem - no item found" ); + return (nItemIdx < maNumGroupItems.size()) ? maNumGroupItems[ nItemIdx ].get() : nullptr; +} + +void XclImpPCField::WriteFieldNameToSource( SCCOL nScCol, SCTAB nScTab ) +{ + OSL_ENSURE( HasOrigItems(), "XclImpPCField::WriteFieldNameToSource - only for standard fields" ); + GetDocImport().setStringCell(ScAddress(nScCol, 0, nScTab), maFieldInfo.maName); + mnSourceScCol = nScCol; +} + +void XclImpPCField::WriteOrigItemToSource( SCROW nScRow, SCTAB nScTab, sal_uInt16 nItemIdx ) +{ + if( nItemIdx < maOrigItems.size() ) + maOrigItems[ nItemIdx ]->WriteToSource( GetRoot(), ScAddress( mnSourceScCol, nScRow, nScTab ) ); +} + +void XclImpPCField::WriteLastOrigItemToSource( SCROW nScRow, SCTAB nScTab ) +{ + if( !maOrigItems.empty() ) + maOrigItems.back()->WriteToSource( GetRoot(), ScAddress( mnSourceScCol, nScRow, nScTab ) ); +} + +// records -------------------------------------------------------------------- + +void XclImpPCField::ReadSxfield( XclImpStream& rStrm ) +{ + rStrm >> maFieldInfo; + + /* Detect the type of this field. This is done very restrictive to detect + any unexpected state. */ + meFieldType = EXC_PCFIELD_UNKNOWN; + + bool bItems = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASITEMS ); + bool bPostp = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_POSTPONE ); + bool bCalced = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_CALCED ); + bool bChild = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ); + bool bNum = ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_NUMGROUP ); + + sal_uInt16 nVisC = maFieldInfo.mnVisItems; + sal_uInt16 nGroupC = maFieldInfo.mnGroupItems; + sal_uInt16 nBaseC = maFieldInfo.mnBaseItems; + sal_uInt16 nOrigC = maFieldInfo.mnOrigItems; + OSL_ENSURE( nVisC > 0, "XclImpPCField::ReadSxfield - field without visible items" ); + + sal_uInt16 nType = maFieldInfo.mnFlags & EXC_SXFIELD_DATA_MASK; + bool bType = + (nType == EXC_SXFIELD_DATA_STR) || + (nType == EXC_SXFIELD_DATA_INT) || + (nType == EXC_SXFIELD_DATA_DBL) || + (nType == EXC_SXFIELD_DATA_STR_INT) || + (nType == EXC_SXFIELD_DATA_STR_DBL) || + (nType == EXC_SXFIELD_DATA_DATE) || + (nType == EXC_SXFIELD_DATA_DATE_EMP) || + (nType == EXC_SXFIELD_DATA_DATE_NUM) || + (nType == EXC_SXFIELD_DATA_DATE_STR); + bool bTypeNone = + (nType == EXC_SXFIELD_DATA_NONE); + // for now, ignore data type of calculated fields + OSL_ENSURE( bCalced || bType || bTypeNone, "XclImpPCField::ReadSxfield - unknown item data type" ); + + if( !(nVisC > 0 || bPostp) ) + return; + + if( bItems && !bPostp ) + { + if( !bCalced ) + { + // 1) standard fields and standard grouping fields + if( !bNum ) + { + // 1a) standard field without grouping + if( bType && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == nVisC) ) + meFieldType = EXC_PCFIELD_STANDARD; + + // 1b) standard grouping field + else if( bTypeNone && (nGroupC == nVisC) && (nBaseC > 0) && (nOrigC == 0) ) + meFieldType = EXC_PCFIELD_STDGROUP; + } + // 2) numerical grouping fields + else if( (nGroupC == nVisC) && (nBaseC == 0) ) + { + // 2a) single num/date grouping field without child grouping field + if( !bChild && bType && (nOrigC > 0) ) + { + switch( nType ) + { + case EXC_SXFIELD_DATA_INT: + case EXC_SXFIELD_DATA_DBL: meFieldType = EXC_PCFIELD_NUMGROUP; break; + case EXC_SXFIELD_DATA_DATE: meFieldType = EXC_PCFIELD_DATEGROUP; break; + default: OSL_FAIL( "XclImpPCField::ReadSxfield - numeric group with wrong data type" ); + } + } + + // 2b) first date grouping field with child grouping field + else if( bChild && (nType == EXC_SXFIELD_DATA_DATE) && (nOrigC > 0) ) + meFieldType = EXC_PCFIELD_DATEGROUP; + + // 2c) additional date grouping field + else if( bTypeNone && (nOrigC == 0) ) + meFieldType = EXC_PCFIELD_DATECHILD; + } + OSL_ENSURE( meFieldType != EXC_PCFIELD_UNKNOWN, "XclImpPCField::ReadSxfield - invalid standard or grouped field" ); + } + + // 3) calculated field + else + { + if( !bChild && !bNum && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == 0) ) + meFieldType = EXC_PCFIELD_CALCED; + OSL_ENSURE( meFieldType == EXC_PCFIELD_CALCED, "XclImpPCField::ReadSxfield - invalid calculated field" ); + } + } + + else if( !bItems && bPostp ) + { + // 4) standard field with postponed items + if( !bCalced && !bChild && !bNum && bType && (nGroupC == 0) && (nBaseC == 0) && (nOrigC == 0) ) + meFieldType = EXC_PCFIELD_STANDARD; + OSL_ENSURE( meFieldType == EXC_PCFIELD_STANDARD, "XclImpPCField::ReadSxfield - invalid postponed field" ); + } +} + +void XclImpPCField::ReadItem( XclImpStream& rStrm ) +{ + OSL_ENSURE( HasInlineItems() || HasPostponedItems(), "XclImpPCField::ReadItem - field does not expect items" ); + + // read the item + XclImpPCItemRef xItem = std::make_shared( rStrm ); + + // try to insert into an item list + if( mbNumGroupInfoRead ) + { + // there are 3 items after SXNUMGROUP that contain grouping limits and step count + if( maNumGroupItems.size() < 3 ) + maNumGroupItems.push_back( xItem ); + else + maOrigItems.push_back( xItem ); + } + else if( HasInlineItems() || HasPostponedItems() ) + { + maItems.push_back( xItem ); + // visible item is original item in standard fields + if( IsStandardField() ) + maOrigItems.push_back( xItem ); + } +} + +void XclImpPCField::ReadSxnumgroup( XclImpStream& rStrm ) +{ + OSL_ENSURE( IsNumGroupField() || IsDateGroupField(), "XclImpPCField::ReadSxnumgroup - SXNUMGROUP outside numeric grouping field" ); + OSL_ENSURE( !mbNumGroupInfoRead, "XclImpPCField::ReadSxnumgroup - multiple SXNUMGROUP records" ); + OSL_ENSURE( maItems.size() == maFieldInfo.mnGroupItems, "XclImpPCField::ReadSxnumgroup - SXNUMGROUP out of record order" ); + rStrm >> maNumGroupInfo; + mbNumGroupInfoRead = IsNumGroupField() || IsDateGroupField(); +} + +void XclImpPCField::ReadSxgroupinfo( XclImpStream& rStrm ) +{ + OSL_ENSURE( IsStdGroupField(), "XclImpPCField::ReadSxgroupinfo - SXGROUPINFO outside grouping field" ); + OSL_ENSURE( maGroupOrder.empty(), "XclImpPCField::ReadSxgroupinfo - multiple SXGROUPINFO records" ); + OSL_ENSURE( maItems.size() == maFieldInfo.mnGroupItems, "XclImpPCField::ReadSxgroupinfo - SXGROUPINFO out of record order" ); + OSL_ENSURE( (rStrm.GetRecLeft() / 2) == maFieldInfo.mnBaseItems, "XclImpPCField::ReadSxgroupinfo - wrong SXGROUPINFO size" ); + maGroupOrder.clear(); + size_t nSize = rStrm.GetRecLeft() / 2; + maGroupOrder.resize( nSize, 0 ); + for( size_t nIdx = 0; nIdx < nSize; ++nIdx ) + maGroupOrder[ nIdx ] = rStrm.ReaduInt16(); +} + +// grouping ------------------------------------------------------------------- + +void XclImpPCField::ConvertGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const +{ + if (!GetFieldName(rVisNames).isEmpty()) + { + if( IsStdGroupField() ) + ConvertStdGroupField( rSaveData, rVisNames ); + else if( IsNumGroupField() ) + ConvertNumGroupField( rSaveData, rVisNames ); + else if( IsDateGroupField() ) + ConvertDateGroupField( rSaveData, rVisNames ); + } +} + +// private -------------------------------------------------------------------- + +void XclImpPCField::ConvertStdGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const +{ + const XclImpPCField* pBaseField = GetGroupBaseField(); + if(!pBaseField) + return; + + const OUString& rBaseFieldName = pBaseField->GetFieldName( rVisNames ); + if( rBaseFieldName.isEmpty() ) + return; + + // *** create a ScDPSaveGroupItem for each own item, they collect base item names *** + ScDPSaveGroupItemVec aGroupItems; + aGroupItems.reserve( maItems.size() ); + // initialize with own item names + for( const auto& rxItem : maItems ) + aGroupItems.emplace_back( rxItem->ConvertToText() ); + + // *** iterate over all base items, set their names at corresponding own items *** + for( sal_uInt16 nItemIdx = 0, nItemCount = static_cast< sal_uInt16 >( maGroupOrder.size() ); nItemIdx < nItemCount; ++nItemIdx ) + if( maGroupOrder[ nItemIdx ] < aGroupItems.size() ) + if( const XclImpPCItem* pBaseItem = pBaseField->GetItem( nItemIdx ) ) + if( const XclImpPCItem* pGroupItem = GetItem( maGroupOrder[ nItemIdx ] ) ) + if( *pBaseItem != *pGroupItem ) + aGroupItems[ maGroupOrder[ nItemIdx ] ].AddElement( pBaseItem->ConvertToText() ); + + // *** create the ScDPSaveGroupDimension object, fill with grouping info *** + ScDPSaveGroupDimension aGroupDim( rBaseFieldName, GetFieldName( rVisNames ) ); + for( const auto& rGroupItem : aGroupItems ) + if( !rGroupItem.IsEmpty() ) + aGroupDim.AddGroupItem( rGroupItem ); + rSaveData.GetDimensionData()->AddGroupDimension( aGroupDim ); +} + +void XclImpPCField::ConvertNumGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const +{ + ScDPNumGroupInfo aNumInfo( GetScNumGroupInfo() ); + ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), aNumInfo ); + rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim ); +} + +void XclImpPCField::ConvertDateGroupField( ScDPSaveData& rSaveData, const ScfStringVec& rVisNames ) const +{ + ScDPNumGroupInfo aDateInfo( GetScDateGroupInfo() ); + sal_Int32 nScDateType = maNumGroupInfo.GetScDateType(); + + switch( meFieldType ) + { + case EXC_PCFIELD_DATEGROUP: + { + if( aDateInfo.mbDateValues ) + { + // special case for days only with step value - create numeric grouping + ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), aDateInfo ); + rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim ); + } + else + { + ScDPSaveNumGroupDimension aNumGroupDim( GetFieldName( rVisNames ), ScDPNumGroupInfo() ); + aNumGroupDim.SetDateInfo( aDateInfo, nScDateType ); + rSaveData.GetDimensionData()->AddNumGroupDimension( aNumGroupDim ); + } + } + break; + + case EXC_PCFIELD_DATECHILD: + { + if( const XclImpPCField* pBaseField = GetGroupBaseField() ) + { + const OUString& rBaseFieldName = pBaseField->GetFieldName( rVisNames ); + if( !rBaseFieldName.isEmpty() ) + { + ScDPSaveGroupDimension aGroupDim( rBaseFieldName, GetFieldName( rVisNames ) ); + aGroupDim.SetDateInfo( aDateInfo, nScDateType ); + rSaveData.GetDimensionData()->AddGroupDimension( aGroupDim ); + } + } + } + break; + + default: + OSL_FAIL( "XclImpPCField::ConvertDateGroupField - unknown date field type" ); + } +} + +ScDPNumGroupInfo XclImpPCField::GetScNumGroupInfo() const +{ + ScDPNumGroupInfo aNumInfo; + aNumInfo.mbEnable = true; + aNumInfo.mbDateValues = false; + aNumInfo.mbAutoStart = true; + aNumInfo.mbAutoEnd = true; + + if( const double* pfMinValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_MIN ) ) + { + aNumInfo.mfStart = *pfMinValue; + aNumInfo.mbAutoStart = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN ); + } + if( const double* pfMaxValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_MAX ) ) + { + aNumInfo.mfEnd = *pfMaxValue; + aNumInfo.mbAutoEnd = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX ); + } + if( const double* pfStepValue = GetNumGroupLimit( EXC_SXFIELD_INDEX_STEP ) ) + aNumInfo.mfStep = *pfStepValue; + + return aNumInfo; +} + +ScDPNumGroupInfo XclImpPCField::GetScDateGroupInfo() const +{ + ScDPNumGroupInfo aDateInfo; + aDateInfo.mbEnable = true; + aDateInfo.mbDateValues = false; + aDateInfo.mbAutoStart = true; + aDateInfo.mbAutoEnd = true; + + if( const DateTime* pMinDate = GetDateGroupLimit( EXC_SXFIELD_INDEX_MIN ) ) + { + aDateInfo.mfStart = GetDoubleFromDateTime( *pMinDate ); + aDateInfo.mbAutoStart = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMIN ); + } + if( const DateTime* pMaxDate = GetDateGroupLimit( EXC_SXFIELD_INDEX_MAX ) ) + { + aDateInfo.mfEnd = GetDoubleFromDateTime( *pMaxDate ); + aDateInfo.mbAutoEnd = ::get_flag( maNumGroupInfo.mnFlags, EXC_SXNUMGROUP_AUTOMAX ); + } + // GetDateGroupStep() returns a value for date type "day" in single date groups only + if( const sal_Int16* pnStepValue = GetDateGroupStep() ) + { + aDateInfo.mfStep = *pnStepValue; + aDateInfo.mbDateValues = true; + } + + return aDateInfo; +} + +const double* XclImpPCField::GetNumGroupLimit( sal_uInt16 nLimitIdx ) const +{ + OSL_ENSURE( IsNumGroupField(), "XclImpPCField::GetNumGroupLimit - only for numeric grouping fields" ); + if( const XclImpPCItem* pItem = GetLimitItem( nLimitIdx ) ) + { + OSL_ENSURE( pItem->GetDouble(), "XclImpPCField::GetNumGroupLimit - SXDOUBLE item expected" ); + return pItem->GetDouble(); + } + return nullptr; +} + +const DateTime* XclImpPCField::GetDateGroupLimit( sal_uInt16 nLimitIdx ) const +{ + OSL_ENSURE( IsDateGroupField(), "XclImpPCField::GetDateGroupLimit - only for date grouping fields" ); + if( const XclImpPCItem* pItem = GetLimitItem( nLimitIdx ) ) + { + OSL_ENSURE( pItem->GetDateTime(), "XclImpPCField::GetDateGroupLimit - SXDATETIME item expected" ); + return pItem->GetDateTime(); + } + return nullptr; +} + +const sal_Int16* XclImpPCField::GetDateGroupStep() const +{ + // only for single date grouping fields, not for grouping chains + if( !IsGroupBaseField() && !IsGroupChildField() ) + { + // only days may have a step value, return 0 for all other date types + if( maNumGroupInfo.GetXclDataType() == EXC_SXNUMGROUP_TYPE_DAY ) + { + if( const XclImpPCItem* pItem = GetLimitItem( EXC_SXFIELD_INDEX_STEP ) ) + { + OSL_ENSURE( pItem->GetInteger(), "XclImpPCField::GetDateGroupStep - SXINTEGER item expected" ); + if( const sal_Int16* pnStep = pItem->GetInteger() ) + { + OSL_ENSURE( *pnStep > 0, "XclImpPCField::GetDateGroupStep - invalid step count" ); + // return nothing for step count 1 - this is also a standard date group in Excel + return (*pnStep > 1) ? pnStep : nullptr; + } + } + } + } + return nullptr; +} + +XclImpPivotCache::XclImpPivotCache( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maSrcRange( ScAddress::INITIALIZE_INVALID ), + mnStrmId( 0 ), + mnSrcType( EXC_SXVS_UNKNOWN ), + mbSelfRef( false ) +{ +} + +XclImpPivotCache::~XclImpPivotCache() +{ +} + +// data access ---------------------------------------------------------------- + +const XclImpPCField* XclImpPivotCache::GetField( sal_uInt16 nFieldIdx ) const +{ + return (nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr; +} + +// records -------------------------------------------------------------------- + +void XclImpPivotCache::ReadSxidstm( XclImpStream& rStrm ) +{ + mnStrmId = rStrm.ReaduInt16(); +} + +void XclImpPivotCache::ReadSxvs( XclImpStream& rStrm ) +{ + mnSrcType = rStrm.ReaduInt16(); + GetTracer().TracePivotDataSource( mnSrcType != EXC_SXVS_SHEET ); +} + +void XclImpPivotCache::ReadDconref( XclImpStream& rStrm ) +{ + /* Read DCONREF only once (by checking maTabName), there may be other + DCONREF records in another context. Read reference only if a leading + SXVS record is present (by checking mnSrcType). */ + if( !maTabName.isEmpty() || (mnSrcType != EXC_SXVS_SHEET) ) + return; + + XclRange aXclRange( ScAddress::UNINITIALIZED ); + aXclRange.Read( rStrm, false ); + OUString aEncUrl = rStrm.ReadUniString(); + + XclImpUrlHelper::DecodeUrl( maUrl, maTabName, mbSelfRef, GetRoot(), aEncUrl ); + + /* Do not convert maTabName to Calc sheet name -> original name is used to + find the sheet in the document. Sheet index of source range will be + found later in XclImpPivotCache::ReadPivotCacheStream(), because sheet + may not exist yet. */ + if( mbSelfRef ) + GetAddressConverter().ConvertRange( maSrcRange, aXclRange, 0, 0, true ); +} + +void XclImpPivotCache::ReadDConName( XclImpStream& rStrm ) +{ + maSrcRangeName = rStrm.ReadUniString(); + + // This 2-byte value equals the length of string that follows, or if 0 it + // indicates that the name has a workbook scope. For now, we only support + // internal defined name with a workbook scope. + sal_uInt16 nFlag; + nFlag = rStrm.ReaduInt16(); + mbSelfRef = (nFlag == 0); + + if (!mbSelfRef) + // External name is not supported yet. + maSrcRangeName.clear(); +} + +void XclImpPivotCache::ReadPivotCacheStream( const XclImpStream& rStrm ) +{ + if( (mnSrcType != EXC_SXVS_SHEET) && (mnSrcType != EXC_SXVS_EXTERN) ) + return; + + ScDocument& rDoc = GetDoc(); + SCCOL nFieldScCol = 0; // column index of source data for next field + SCROW nItemScRow = 0; // row index of source data for current items + SCTAB nScTab = 0; // sheet index of source data + bool bGenerateSource = false; // true = write source data from cache to dummy table + + if( mbSelfRef ) + { + if (maSrcRangeName.isEmpty()) + { + // try to find internal sheet containing the source data + nScTab = GetTabInfo().GetScTabFromXclName( maTabName ); + if( rDoc.HasTable( nScTab ) ) + { + // set sheet index to source range + maSrcRange.aStart.SetTab( nScTab ); + maSrcRange.aEnd.SetTab( nScTab ); + } + else + { + // create dummy sheet for deleted internal sheet + bGenerateSource = true; + } + } + } + else + { + // create dummy sheet for external sheet + bGenerateSource = true; + } + + // create dummy sheet for source data from external or deleted sheet + if( bGenerateSource ) + { + if( rDoc.GetTableCount() >= MAXTABCOUNT ) + // cannot create more sheets -> exit + return; + + nScTab = rDoc.GetTableCount(); + rDoc.MakeTable( nScTab ); + OUStringBuffer aDummyName("DPCache"); + if( maTabName.getLength() > 0 ) + aDummyName.append( '_' ).append( maTabName ); + OUString aName = aDummyName.makeStringAndClear(); + rDoc.CreateValidTabName( aName ); + rDoc.RenameTab( nScTab, aName ); + // set sheet index to source range + maSrcRange.aStart.SetTab( nScTab ); + maSrcRange.aEnd.SetTab( nScTab ); + } + + // open pivot cache storage stream + tools::SvRef xSvStrg = OpenStorage( EXC_STORAGE_PTCACHE ); + tools::SvRef xSvStrm = OpenStream( xSvStrg, ScfTools::GetHexStr( mnStrmId ) ); + if( !xSvStrm.is() ) + return; + + // create Excel record stream object + XclImpStream aPCStrm( *xSvStrm, GetRoot() ); + aPCStrm.CopyDecrypterFrom( rStrm ); // pivot cache streams are encrypted + + XclImpPCFieldRef xCurrField; // current field for new items + XclImpPCFieldVec aOrigFields; // all standard fields with inline original items + XclImpPCFieldVec aPostpFields; // all standard fields with postponed original items + size_t nPostpIdx = 0; // index to current field with postponed items + bool bLoop = true; // true = continue loop + + while( bLoop && aPCStrm.StartNextRecord() ) + { + switch( aPCStrm.GetRecId() ) + { + case EXC_ID_EOF: + bLoop = false; + break; + + case EXC_ID_SXDB: + aPCStrm >> maPCInfo; + break; + + case EXC_ID_SXFIELD: + { + xCurrField.reset(); + sal_uInt16 nNewFieldIdx = static_cast< sal_uInt16 >( maFields.size() ); + if( nNewFieldIdx < EXC_PC_MAXFIELDCOUNT ) + { + xCurrField = std::make_shared( GetRoot(), *this, nNewFieldIdx ); + maFields.push_back( xCurrField ); + xCurrField->ReadSxfield( aPCStrm ); + if( xCurrField->HasOrigItems() ) + { + if( xCurrField->HasPostponedItems() ) + aPostpFields.push_back( xCurrField ); + else + aOrigFields.push_back( xCurrField ); + // insert field name into generated source data, field remembers its column index + if( bGenerateSource && (nFieldScCol <= rDoc.MaxCol()) ) + xCurrField->WriteFieldNameToSource( nFieldScCol++, nScTab ); + } + // do not read items into invalid/postponed fields + if( !xCurrField->HasInlineItems() ) + xCurrField.reset(); + } + } + break; + + case EXC_ID_SXINDEXLIST: + // read index list and insert all items into generated source data + if( bGenerateSource && (nItemScRow <= rDoc.MaxRow()) && (++nItemScRow <= rDoc.MaxRow()) ) + { + for( const auto& rxOrigField : aOrigFields ) + { + sal_uInt16 nItemIdx = rxOrigField->Has16BitIndexes() ? aPCStrm.ReaduInt16() : aPCStrm.ReaduInt8(); + rxOrigField->WriteOrigItemToSource( nItemScRow, nScTab, nItemIdx ); + } + } + xCurrField.reset(); + break; + + case EXC_ID_SXDOUBLE: + case EXC_ID_SXBOOLEAN: + case EXC_ID_SXERROR: + case EXC_ID_SXINTEGER: + case EXC_ID_SXSTRING: + case EXC_ID_SXDATETIME: + case EXC_ID_SXEMPTY: + if( xCurrField ) // inline items + { + xCurrField->ReadItem( aPCStrm ); + } + else if( !aPostpFields.empty() ) // postponed items + { + // read postponed item + aPostpFields[ nPostpIdx ]->ReadItem( aPCStrm ); + // write item to source + if( bGenerateSource && (nItemScRow <= rDoc.MaxRow()) ) + { + // start new row, if there are only postponed fields + if( aOrigFields.empty() && (nPostpIdx == 0) ) + ++nItemScRow; + if( nItemScRow <= rDoc.MaxRow() ) + aPostpFields[ nPostpIdx ]->WriteLastOrigItemToSource( nItemScRow, nScTab ); + } + // get index of next postponed field + ++nPostpIdx; + if( nPostpIdx >= aPostpFields.size() ) + nPostpIdx = 0; + } + break; + + case EXC_ID_SXNUMGROUP: + if( xCurrField ) + xCurrField->ReadSxnumgroup( aPCStrm ); + break; + + case EXC_ID_SXGROUPINFO: + if( xCurrField ) + xCurrField->ReadSxgroupinfo( aPCStrm ); + break; + + // known but ignored records + case EXC_ID_SXRULE: + case EXC_ID_SXFILT: + case EXC_ID_00F5: + case EXC_ID_SXNAME: + case EXC_ID_SXPAIR: + case EXC_ID_SXFMLA: + case EXC_ID_SXFORMULA: + case EXC_ID_SXDBEX: + case EXC_ID_SXFDBTYPE: + break; + + default: + SAL_WARN("sc.filter", "XclImpPivotCache::ReadPivotCacheStream - unknown record 0x" << std::hex << aPCStrm.GetRecId() ); + } + } + + OSL_ENSURE( maPCInfo.mnTotalFields == maFields.size(), + "XclImpPivotCache::ReadPivotCacheStream - field count mismatch" ); + + if (static_cast(maPCInfo.mnFlags & EXC_SXDB_SAVEDATA)) + { + SCROW nNewEnd = maSrcRange.aStart.Row() + maPCInfo.mnSrcRecs; + maSrcRange.aEnd.SetRow(nNewEnd); + } + + // set source range for external source data + if( bGenerateSource && (nFieldScCol > 0) ) + { + maSrcRange.aStart.SetCol( 0 ); + maSrcRange.aStart.SetRow( 0 ); + // nFieldScCol points to first unused column + maSrcRange.aEnd.SetCol( nFieldScCol - 1 ); + // nItemScRow points to last used row + maSrcRange.aEnd.SetRow( nItemScRow ); + } +} + +bool XclImpPivotCache::IsRefreshOnLoad() const +{ + return static_cast(maPCInfo.mnFlags & EXC_SXDB_REFRESH_LOAD); +} + +bool XclImpPivotCache::IsValid() const +{ + if (!maSrcRangeName.isEmpty()) + return true; + + return maSrcRange.IsValid(); +} + +// Pivot table + +XclImpPTItem::XclImpPTItem( const XclImpPCField* pCacheField ) : + mpCacheField( pCacheField ) +{ +} + +const OUString* XclImpPTItem::GetItemName() const +{ + if( mpCacheField ) + if( const XclImpPCItem* pCacheItem = mpCacheField->GetItem( maItemInfo.mnCacheIdx ) ) + //TODO: use XclImpPCItem::ConvertToText(), if all conversions are available + return pCacheItem->IsEmpty() ? nullptr : pCacheItem->GetText(); + return nullptr; +} + +std::pair XclImpPTItem::GetItemName(const ScDPSaveDimension& rSaveDim, ScDPObject* pObj, const XclImpRoot& rRoot) const +{ + if(!mpCacheField) + return std::pair(false, OUString()); + + const XclImpPCItem* pCacheItem = mpCacheField->GetItem( maItemInfo.mnCacheIdx ); + if(!pCacheItem) + return std::pair(false, OUString()); + + OUString sItemName; + if(pCacheItem->GetType() == EXC_PCITEM_TEXT || pCacheItem->GetType() == EXC_PCITEM_ERROR) + { + const OUString* pItemName = pCacheItem->GetText(); + if(!pItemName) + return std::pair(false, OUString()); + sItemName = *pItemName; + } + else if (pCacheItem->GetType() == EXC_PCITEM_DOUBLE) + { + sItemName = pObj->GetFormattedString(rSaveDim.GetName(), *pCacheItem->GetDouble()); + } + else if (pCacheItem->GetType() == EXC_PCITEM_INTEGER) + { + sItemName = pObj->GetFormattedString(rSaveDim.GetName(), static_cast(*pCacheItem->GetInteger())); + } + else if (pCacheItem->GetType() == EXC_PCITEM_BOOL) + { + sItemName = pObj->GetFormattedString(rSaveDim.GetName(), static_cast(*pCacheItem->GetBool())); + } + else if (pCacheItem->GetType() == EXC_PCITEM_DATETIME) + { + sItemName = pObj->GetFormattedString(rSaveDim.GetName(), rRoot.GetDoubleFromDateTime(*pCacheItem->GetDateTime())); + } + else if (pCacheItem->GetType() == EXC_PCITEM_EMPTY) + { + // sItemName is an empty string + } + else // EXC_PCITEM_INVALID + return std::pair(false, OUString()); + + return std::pair(true, sItemName); +} + +void XclImpPTItem::ReadSxvi( XclImpStream& rStrm ) +{ + rStrm >> maItemInfo; +} + +void XclImpPTItem::ConvertItem( ScDPSaveDimension& rSaveDim, ScDPObject* pObj, const XclImpRoot& rRoot ) const +{ + // Find member and set properties + std::pair aReturnedName = GetItemName(rSaveDim, pObj, rRoot); + if(aReturnedName.first) + { + ScDPSaveMember* pMember = rSaveDim.GetExistingMemberByName(aReturnedName.second); + if(pMember) + { + pMember->SetIsVisible( !::get_flag( maItemInfo.mnFlags, EXC_SXVI_HIDDEN ) ); + pMember->SetShowDetails( !::get_flag( maItemInfo.mnFlags, EXC_SXVI_HIDEDETAIL ) ); + if (maItemInfo.HasVisName()) + pMember->SetLayoutName(*maItemInfo.GetVisName()); + } + } +} + +XclImpPTField::XclImpPTField( const XclImpPivotTable& rPTable, sal_uInt16 nCacheIdx ) : + mrPTable( rPTable ) +{ + maFieldInfo.mnCacheIdx = nCacheIdx; +} + +// general field/item access -------------------------------------------------- + +const XclImpPCField* XclImpPTField::GetCacheField() const +{ + XclImpPivotCacheRef xPCache = mrPTable.GetPivotCache(); + return xPCache ? xPCache->GetField( maFieldInfo.mnCacheIdx ) : nullptr; +} + +OUString XclImpPTField::GetFieldName() const +{ + const XclImpPCField* pField = GetCacheField(); + return pField ? pField->GetFieldName( mrPTable.GetVisFieldNames() ) : OUString(); +} + +OUString XclImpPTField::GetVisFieldName() const +{ + const OUString* pVisName = maFieldInfo.GetVisName(); + return pVisName ? *pVisName : OUString(); +} + +const XclImpPTItem* XclImpPTField::GetItem( sal_uInt16 nItemIdx ) const +{ + return (nItemIdx < maItems.size()) ? maItems[ nItemIdx ].get() : nullptr; +} + +const OUString* XclImpPTField::GetItemName( sal_uInt16 nItemIdx ) const +{ + const XclImpPTItem* pItem = GetItem( nItemIdx ); + return pItem ? pItem->GetItemName() : nullptr; +} + +// records -------------------------------------------------------------------- + +void XclImpPTField::ReadSxvd( XclImpStream& rStrm ) +{ + rStrm >> maFieldInfo; +} + +void XclImpPTField::ReadSxvdex( XclImpStream& rStrm ) +{ + rStrm >> maFieldExtInfo; +} + +void XclImpPTField::ReadSxvi( XclImpStream& rStrm ) +{ + XclImpPTItemRef xItem = std::make_shared( GetCacheField() ); + maItems.push_back( xItem ); + xItem->ReadSxvi( rStrm ); +} + +// row/column fields ---------------------------------------------------------- + +void XclImpPTField::ConvertRowColField( ScDPSaveData& rSaveData ) const +{ + OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_ROWCOL, "XclImpPTField::ConvertRowColField - no row/column field" ); + // special data orientation field? + if( maFieldInfo.mnCacheIdx == EXC_SXIVD_DATA ) + rSaveData.GetDataLayoutDimension()->SetOrientation( maFieldInfo.GetApiOrient( EXC_SXVD_AXIS_ROWCOL ) ); + else + ConvertRCPField( rSaveData ); +} + +// page fields ---------------------------------------------------------------- + +void XclImpPTField::SetPageFieldInfo( const XclPTPageFieldInfo& rPageInfo ) +{ + maPageInfo = rPageInfo; +} + +void XclImpPTField::ConvertPageField( ScDPSaveData& rSaveData ) const +{ + OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_PAGE, "XclImpPTField::ConvertPageField - no page field" ); + ConvertRCPField( rSaveData ); +} + +// hidden fields -------------------------------------------------------------- + +void XclImpPTField::ConvertHiddenField( ScDPSaveData& rSaveData ) const +{ + OSL_ENSURE( (maFieldInfo.mnAxes & EXC_SXVD_AXIS_ROWCOLPAGE) == 0, "XclImpPTField::ConvertHiddenField - field not hidden" ); + ConvertRCPField( rSaveData ); +} + +// data fields ---------------------------------------------------------------- + +bool XclImpPTField::HasDataFieldInfo() const +{ + return !maDataInfoVector.empty(); +} + +void XclImpPTField::AddDataFieldInfo( const XclPTDataFieldInfo& rDataInfo ) +{ + OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_DATA, "XclImpPTField::AddDataFieldInfo - no data field" ); + maDataInfoVector.push_back( rDataInfo ); +} + +void XclImpPTField::ConvertDataField( ScDPSaveData& rSaveData ) const +{ + OSL_ENSURE( maFieldInfo.mnAxes & EXC_SXVD_AXIS_DATA, "XclImpPTField::ConvertDataField - no data field" ); + OSL_ENSURE( !maDataInfoVector.empty(), "XclImpPTField::ConvertDataField - no data field info" ); + if (maDataInfoVector.empty()) + return; + + OUString aFieldName = GetFieldName(); + if (aFieldName.isEmpty()) + return; + + ScDPSaveDimension* pSaveDim = rSaveData.GetNewDimensionByName(aFieldName); + if (!pSaveDim) + { + SAL_WARN("sc.filter","XclImpPTField::ConvertDataField - field name not found: " << aFieldName); + return; + } + + auto aIt = maDataInfoVector.begin(), aEnd = maDataInfoVector.end(); + + ConvertDataField( *pSaveDim, *aIt ); + + // multiple data fields -> clone dimension + for( ++aIt; aIt != aEnd; ++aIt ) + { + ScDPSaveDimension& rDupDim = rSaveData.DuplicateDimension( *pSaveDim ); + ConvertDataFieldInfo( rDupDim, *aIt ); + } +} + +// private -------------------------------------------------------------------- + +/** + * Convert Excel-encoded subtotal name to a Calc-encoded one. + */ +static OUString lcl_convertExcelSubtotalName(const OUString& rName) +{ + OUStringBuffer aBuf; + const sal_Unicode* p = rName.getStr(); + sal_Int32 n = rName.getLength(); + for (sal_Int32 i = 0; i < n; ++i) + { + const sal_Unicode c = p[i]; + if (c == '\\') + { + aBuf.append(c); + aBuf.append(c); + } + else + aBuf.append(c); + } + return aBuf.makeStringAndClear(); +} + +void XclImpPTField::ConvertRCPField( ScDPSaveData& rSaveData ) const +{ + const OUString& rFieldName = GetFieldName(); + if( rFieldName.isEmpty() ) + return; + + const XclImpPCField* pCacheField = GetCacheField(); + if( !pCacheField || !pCacheField->IsSupportedField() ) + return; + + ScDPSaveDimension* pTest = rSaveData.GetNewDimensionByName(rFieldName); + if (!pTest) + return; + + ScDPSaveDimension& rSaveDim = *pTest; + + // orientation + rSaveDim.SetOrientation( maFieldInfo.GetApiOrient( EXC_SXVD_AXIS_ROWCOLPAGE ) ); + + // visible name + if (const OUString* pVisName = maFieldInfo.GetVisName()) + if (!pVisName->isEmpty()) + rSaveDim.SetLayoutName( *pVisName ); + + // subtotal function(s) + XclPTSubtotalVec aSubtotalVec; + maFieldInfo.GetSubtotals( aSubtotalVec ); + if( !aSubtotalVec.empty() ) + rSaveDim.SetSubTotals( std::move(aSubtotalVec) ); + + // sorting + DataPilotFieldSortInfo aSortInfo; + aSortInfo.Field = mrPTable.GetDataFieldName( maFieldExtInfo.mnSortField ); + aSortInfo.IsAscending = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SORT_ASC ); + aSortInfo.Mode = maFieldExtInfo.GetApiSortMode(); + rSaveDim.SetSortInfo( &aSortInfo ); + + // auto show + DataPilotFieldAutoShowInfo aShowInfo; + aShowInfo.IsEnabled = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_AUTOSHOW ); + aShowInfo.ShowItemsMode = maFieldExtInfo.GetApiAutoShowMode(); + aShowInfo.ItemCount = maFieldExtInfo.GetApiAutoShowCount(); + aShowInfo.DataField = mrPTable.GetDataFieldName( maFieldExtInfo.mnShowField ); + rSaveDim.SetAutoShowInfo( &aShowInfo ); + + // layout + DataPilotFieldLayoutInfo aLayoutInfo; + aLayoutInfo.LayoutMode = maFieldExtInfo.GetApiLayoutMode(); + aLayoutInfo.AddEmptyLines = ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_LAYOUT_BLANK ); + rSaveDim.SetLayoutInfo( &aLayoutInfo ); + + // grouping info + pCacheField->ConvertGroupField( rSaveData, mrPTable.GetVisFieldNames() ); + + // custom subtotal name + if (maFieldExtInfo.mpFieldTotalName) + { + OUString aSubName = lcl_convertExcelSubtotalName(*maFieldExtInfo.mpFieldTotalName); + rSaveDim.SetSubtotalName(aSubName); + } +} + +void XclImpPTField::ConvertFieldInfo( const ScDPSaveData& rSaveData, ScDPObject* pObj, const XclImpRoot& rRoot, bool bPageField ) const +{ + const OUString& rFieldName = GetFieldName(); + if( rFieldName.isEmpty() ) + return; + + const XclImpPCField* pCacheField = GetCacheField(); + if( !pCacheField || !pCacheField->IsSupportedField() ) + return; + + ScDPSaveDimension* pSaveDim = rSaveData.GetExistingDimensionByName(rFieldName); + if (!pSaveDim) + return; + + pSaveDim->SetShowEmpty( ::get_flag( maFieldExtInfo.mnFlags, EXC_SXVDEX_SHOWALL ) ); + for( const auto& rxItem : maItems ) + rxItem->ConvertItem( *pSaveDim, pObj, rRoot ); + + if(bPageField && maPageInfo.mnSelItem != EXC_SXPI_ALLITEMS) + { + const XclImpPTItem* pItem = GetItem( maPageInfo.mnSelItem ); + if(pItem) + { + std::pair aReturnedName = pItem->GetItemName(*pSaveDim, pObj, rRoot); + if(aReturnedName.first) + pSaveDim->SetCurrentPage(&aReturnedName.second); + } + } +} + +void XclImpPTField::ConvertDataField( ScDPSaveDimension& rSaveDim, const XclPTDataFieldInfo& rDataInfo ) const +{ + // orientation + rSaveDim.SetOrientation( DataPilotFieldOrientation_DATA ); + // extended data field info + ConvertDataFieldInfo( rSaveDim, rDataInfo ); +} + +void XclImpPTField::ConvertDataFieldInfo( ScDPSaveDimension& rSaveDim, const XclPTDataFieldInfo& rDataInfo ) const +{ + // visible name + const OUString* pVisName = rDataInfo.GetVisName(); + if (pVisName && !pVisName->isEmpty()) + rSaveDim.SetLayoutName(*pVisName); + + // aggregation function + rSaveDim.SetFunction( rDataInfo.GetApiAggFunc() ); + + // result field reference + sal_Int32 nRefType = rDataInfo.GetApiRefType(); + DataPilotFieldReference aFieldRef; + aFieldRef.ReferenceType = nRefType; + const XclImpPTField* pRefField = mrPTable.GetField(rDataInfo.mnRefField); + if (pRefField) + { + aFieldRef.ReferenceField = pRefField->GetFieldName(); + aFieldRef.ReferenceItemType = rDataInfo.GetApiRefItemType(); + if (aFieldRef.ReferenceItemType == sheet::DataPilotFieldReferenceItemType::NAMED) + { + const OUString* pRefItemName = pRefField->GetItemName(rDataInfo.mnRefItem); + if (pRefItemName) + aFieldRef.ReferenceItemName = *pRefItemName; + } + } + + rSaveDim.SetReferenceValue(&aFieldRef); +} + +XclImpPivotTable::XclImpPivotTable( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maDataOrientField( *this, EXC_SXIVD_DATA ), + mpDPObj(nullptr) +{ +} + +XclImpPivotTable::~XclImpPivotTable() +{ +} + +// cache/field access, misc. -------------------------------------------------- + +sal_uInt16 XclImpPivotTable::GetFieldCount() const +{ + return static_cast< sal_uInt16 >( maFields.size() ); +} + +const XclImpPTField* XclImpPivotTable::GetField( sal_uInt16 nFieldIdx ) const +{ + return (nFieldIdx == EXC_SXIVD_DATA) ? &maDataOrientField : + ((nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr); +} + +XclImpPTField* XclImpPivotTable::GetFieldAcc( sal_uInt16 nFieldIdx ) +{ + // do not return maDataOrientField + return (nFieldIdx < maFields.size()) ? maFields[ nFieldIdx ].get() : nullptr; +} + +const XclImpPTField* XclImpPivotTable::GetDataField( sal_uInt16 nDataFieldIdx ) const +{ + if( nDataFieldIdx < maOrigDataFields.size() ) + return GetField( maOrigDataFields[ nDataFieldIdx ] ); + return nullptr; +} + +OUString XclImpPivotTable::GetDataFieldName( sal_uInt16 nDataFieldIdx ) const +{ + if( const XclImpPTField* pField = GetDataField( nDataFieldIdx ) ) + return pField->GetFieldName(); + return OUString(); +} + +// records -------------------------------------------------------------------- + +void XclImpPivotTable::ReadSxview( XclImpStream& rStrm ) +{ + rStrm >> maPTInfo; + + GetAddressConverter().ConvertRange( + maOutScRange, maPTInfo.maOutXclRange, GetCurrScTab(), GetCurrScTab(), true ); + + mxPCache = GetPivotTableManager().GetPivotCache( maPTInfo.mnCacheIdx ); + mxCurrField.reset(); +} + +void XclImpPivotTable::ReadSxvd( XclImpStream& rStrm ) +{ + sal_uInt16 nFieldCount = GetFieldCount(); + if( nFieldCount < EXC_PT_MAXFIELDCOUNT ) + { + // cache index for the field is equal to the SXVD record index + mxCurrField = std::make_shared( *this, nFieldCount ); + maFields.push_back( mxCurrField ); + mxCurrField->ReadSxvd( rStrm ); + // add visible name of new field to list of visible names + maVisFieldNames.push_back( mxCurrField->GetVisFieldName() ); + OSL_ENSURE( maFields.size() == maVisFieldNames.size(), + "XclImpPivotTable::ReadSxvd - wrong size of visible name array" ); + } + else + mxCurrField.reset(); +} + +void XclImpPivotTable::ReadSxvi( XclImpStream& rStrm ) +{ + if( mxCurrField ) + mxCurrField->ReadSxvi( rStrm ); +} + +void XclImpPivotTable::ReadSxvdex( XclImpStream& rStrm ) +{ + if( mxCurrField ) + mxCurrField->ReadSxvdex( rStrm ); +} + +void XclImpPivotTable::ReadSxivd( XclImpStream& rStrm ) +{ + mxCurrField.reset(); + + // find the index vector to fill (row SXIVD doesn't exist without row fields) + ScfUInt16Vec* pFieldVec = nullptr; + if( maRowFields.empty() && (maPTInfo.mnRowFields > 0) ) + pFieldVec = &maRowFields; + else if( maColFields.empty() && (maPTInfo.mnColFields > 0) ) + pFieldVec = &maColFields; + + // fill the vector from record data + if( !pFieldVec ) + return; + + sal_uInt16 nSize = ulimit_cast< sal_uInt16 >( rStrm.GetRecSize() / 2, EXC_PT_MAXROWCOLCOUNT ); + pFieldVec->reserve( nSize ); + for( sal_uInt16 nIdx = 0; nIdx < nSize; ++nIdx ) + { + sal_uInt16 nFieldIdx; + nFieldIdx = rStrm.ReaduInt16(); + pFieldVec->push_back( nFieldIdx ); + + // set orientation at special data orientation field + if( nFieldIdx == EXC_SXIVD_DATA ) + { + sal_uInt16 nAxis = (pFieldVec == &maRowFields) ? EXC_SXVD_AXIS_ROW : EXC_SXVD_AXIS_COL; + maDataOrientField.SetAxes( nAxis ); + } + } +} + +void XclImpPivotTable::ReadSxpi( XclImpStream& rStrm ) +{ + mxCurrField.reset(); + + sal_uInt16 nSize = ulimit_cast< sal_uInt16 >( rStrm.GetRecSize() / 6 ); + for( sal_uInt16 nEntry = 0; nEntry < nSize; ++nEntry ) + { + XclPTPageFieldInfo aPageInfo; + rStrm >> aPageInfo; + if( XclImpPTField* pField = GetFieldAcc( aPageInfo.mnField ) ) + { + maPageFields.push_back( aPageInfo.mnField ); + pField->SetPageFieldInfo( aPageInfo ); + } + GetCurrSheetDrawing().SetSkipObj( aPageInfo.mnObjId ); + } +} + +void XclImpPivotTable::ReadSxdi( XclImpStream& rStrm ) +{ + mxCurrField.reset(); + + XclPTDataFieldInfo aDataInfo; + rStrm >> aDataInfo; + if( XclImpPTField* pField = GetFieldAcc( aDataInfo.mnField ) ) + { + maOrigDataFields.push_back( aDataInfo.mnField ); + // DataPilot does not support double data fields -> add first appearance to index list only + if( !pField->HasDataFieldInfo() ) + maFiltDataFields.push_back( aDataInfo.mnField ); + pField->AddDataFieldInfo( aDataInfo ); + } +} + +void XclImpPivotTable::ReadSxex( XclImpStream& rStrm ) +{ + rStrm >> maPTExtInfo; +} + +void XclImpPivotTable::ReadSxViewEx9( XclImpStream& rStrm ) +{ + rStrm >> maPTViewEx9Info; +} + +void XclImpPivotTable::ReadSxAddl( XclImpStream& rStrm ) +{ + rStrm >> maPTAddlInfo; +} + +void XclImpPivotTable::Convert() +{ + if( !mxPCache || !mxPCache->IsValid() ) + return; + + if (utl::ConfigManager::IsFuzzing()) //just too slow + return; + + ScDPSaveData aSaveData; + + // *** global settings *** + + aSaveData.SetRowGrand( ::get_flag( maPTInfo.mnFlags, EXC_SXVIEW_ROWGRAND ) ); + aSaveData.SetColumnGrand( ::get_flag( maPTInfo.mnFlags, EXC_SXVIEW_COLGRAND ) ); + aSaveData.SetFilterButton( false ); + aSaveData.SetDrillDown( ::get_flag( maPTExtInfo.mnFlags, EXC_SXEX_DRILLDOWN ) ); + aSaveData.SetIgnoreEmptyRows( false ); + aSaveData.SetRepeatIfEmpty( false ); + + // *** fields *** + + // row fields + for( const auto& rRowField : maRowFields ) + if( const XclImpPTField* pField = GetField( rRowField ) ) + pField->ConvertRowColField( aSaveData ); + + // column fields + for( const auto& rColField : maColFields ) + if( const XclImpPTField* pField = GetField( rColField ) ) + pField->ConvertRowColField( aSaveData ); + + // page fields + for( const auto& rPageField : maPageFields ) + if( const XclImpPTField* pField = GetField( rPageField ) ) + pField->ConvertPageField( aSaveData ); + + // We need to import hidden fields because hidden fields may contain + // special settings for subtotals (aggregation function, filters, custom + // name etc.) and members (hidden, custom name etc.). + + // hidden fields + for( sal_uInt16 nField = 0, nCount = GetFieldCount(); nField < nCount; ++nField ) + if( const XclImpPTField* pField = GetField( nField ) ) + if (!pField->GetAxes()) + pField->ConvertHiddenField( aSaveData ); + + // data fields + for( const auto& rFiltDataField : maFiltDataFields ) + if( const XclImpPTField* pField = GetField( rFiltDataField ) ) + pField->ConvertDataField( aSaveData ); + + // *** insert into Calc document *** + + // create source descriptor + ScSheetSourceDesc aDesc(&GetDoc()); + const OUString& rSrcName = mxPCache->GetSourceRangeName(); + if (!rSrcName.isEmpty()) + // Range name is the data source. + aDesc.SetRangeName(rSrcName); + else + // Normal cell range. + aDesc.SetSourceRange(mxPCache->GetSourceRange()); + + // adjust output range to include the page fields + ScRange aOutRange( maOutScRange ); + if( !maPageFields.empty() ) + { + SCROW nDecRows = ::std::min< SCROW >( aOutRange.aStart.Row(), maPageFields.size() + 1 ); + aOutRange.aStart.IncRow( -nDecRows ); + } + + // create the DataPilot + std::unique_ptr pDPObj(new ScDPObject( &GetDoc() )); + pDPObj->SetName( maPTInfo.maTableName ); + if (!maPTInfo.maDataName.isEmpty()) + aSaveData.GetDataLayoutDimension()->SetLayoutName(maPTInfo.maDataName); + + if (!maPTViewEx9Info.maGrandTotalName.isEmpty()) + aSaveData.SetGrandTotalName(maPTViewEx9Info.maGrandTotalName); + + pDPObj->SetSaveData( aSaveData ); + pDPObj->SetSheetDesc( aDesc ); + pDPObj->SetOutRange( aOutRange ); + pDPObj->SetHeaderLayout( maPTViewEx9Info.mnGridLayout == 0 ); + + mpDPObj = GetDoc().GetDPCollection()->InsertNewTable(std::move(pDPObj)); + + ApplyFieldInfo(); + ApplyMergeFlags(aOutRange, aSaveData); +} + +void XclImpPivotTable::MaybeRefresh() +{ + if (mpDPObj && mxPCache->IsRefreshOnLoad()) + { + // 'refresh table on load' flag is set. Refresh the table now. Some + // Excel files contain partial table output when this flag is set. + ScRange aOutRange = mpDPObj->GetOutRange(); + mpDPObj->Output(aOutRange.aStart); + } +} + +void XclImpPivotTable::ApplyMergeFlags(const ScRange& rOutRange, const ScDPSaveData& rSaveData) +{ + // Apply merge flags for various datapilot controls. + + ScDPOutputGeometry aGeometry(rOutRange, false); + aGeometry.setColumnFieldCount(maPTInfo.mnColFields); + aGeometry.setPageFieldCount(maPTInfo.mnPageFields); + aGeometry.setDataFieldCount(maPTInfo.mnDataFields); + aGeometry.setRowFieldCount(maPTInfo.mnRowFields); + + // Make sure we set headerlayout when input file has additional raw header + if(maPTInfo.mnColFields == 0) + { + mpDPObj->SetHeaderLayout( maPTInfo.mnFirstHeadRow - 2 == static_cast(aGeometry.getRowFieldHeaderRow()) ); + } + aGeometry.setHeaderLayout(mpDPObj->GetHeaderLayout()); + aGeometry.setCompactMode(maPTAddlInfo.mbCompactMode); + + ScDocument& rDoc = GetDoc(); + + vector aFieldDims; + vector aFieldBtns; + + aGeometry.getPageFieldPositions(aFieldBtns); + for (const auto& rFieldBtn : aFieldBtns) + { + rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), ScMF::Button); + + ScMF nMFlag = ScMF::ButtonPopup; + OUString aName = rDoc.GetString(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab()); + if (rSaveData.HasInvisibleMember(aName)) + nMFlag |= ScMF::HiddenMember; + + rDoc.ApplyFlagsTab(rFieldBtn.Col()+1, rFieldBtn.Row(), rFieldBtn.Col()+1, rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag); + } + + aGeometry.getColumnFieldPositions(aFieldBtns); + rSaveData.GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_COLUMN, aFieldDims); + if (aFieldBtns.size() == aFieldDims.size()) + { + vector::const_iterator itDim = aFieldDims.begin(); + for (const auto& rFieldBtn : aFieldBtns) + { + ScMF nMFlag = ScMF::Button; + const ScDPSaveDimension* pDim = *itDim; + if (pDim->HasInvisibleMember()) + nMFlag |= ScMF::HiddenMember; + if (!pDim->IsDataLayout()) + nMFlag |= ScMF::ButtonPopup; + rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag); + ++itDim; + } + } + + aGeometry.getRowFieldPositions(aFieldBtns); + rSaveData.GetAllDimensionsByOrientation(sheet::DataPilotFieldOrientation_ROW, aFieldDims); + if (!((aFieldBtns.size() == aFieldDims.size()) || (maPTAddlInfo.mbCompactMode && aFieldBtns.size() == 1))) + return; + + vector::const_iterator itDim = aFieldDims.begin(); + for (const auto& rFieldBtn : aFieldBtns) + { + ScMF nMFlag = ScMF::Button; + const ScDPSaveDimension* pDim = itDim != aFieldDims.end() ? *itDim++ : nullptr; + if (pDim && pDim->HasInvisibleMember()) + nMFlag |= ScMF::HiddenMember; + if (!pDim || !pDim->IsDataLayout()) + nMFlag |= ScMF::ButtonPopup; + rDoc.ApplyFlagsTab(rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Col(), rFieldBtn.Row(), rFieldBtn.Tab(), nMFlag); + } +} + + +void XclImpPivotTable::ApplyFieldInfo() +{ + mpDPObj->BuildAllDimensionMembers(); + ScDPSaveData& rSaveData = *mpDPObj->GetSaveData(); + + // row fields + for( const auto& rRowField : maRowFields ) + if( const XclImpPTField* pField = GetField( rRowField ) ) + pField->ConvertFieldInfo( rSaveData, mpDPObj, *this ); + + // column fields + for( const auto& rColField : maColFields ) + if( const XclImpPTField* pField = GetField( rColField ) ) + pField->ConvertFieldInfo( rSaveData, mpDPObj, *this ); + + // page fields + for( const auto& rPageField : maPageFields ) + if( const XclImpPTField* pField = GetField( rPageField ) ) + pField->ConvertFieldInfo( rSaveData, mpDPObj, *this, true ); + + // hidden fields + for( sal_uInt16 nField = 0, nCount = GetFieldCount(); nField < nCount; ++nField ) + if( const XclImpPTField* pField = GetField( nField ) ) + if (!pField->GetAxes()) + pField->ConvertFieldInfo( rSaveData, mpDPObj, *this ); +} + +XclImpPivotTableManager::XclImpPivotTableManager( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +XclImpPivotTableManager::~XclImpPivotTableManager() +{ +} + +// pivot cache records -------------------------------------------------------- + +XclImpPivotCacheRef XclImpPivotTableManager::GetPivotCache( sal_uInt16 nCacheIdx ) +{ + XclImpPivotCacheRef xPCache; + if( nCacheIdx < maPCaches.size() ) + xPCache = maPCaches[ nCacheIdx ]; + return xPCache; +} + +void XclImpPivotTableManager::ReadSxidstm( XclImpStream& rStrm ) +{ + XclImpPivotCacheRef xPCache = std::make_shared( GetRoot() ); + maPCaches.push_back( xPCache ); + xPCache->ReadSxidstm( rStrm ); +} + +void XclImpPivotTableManager::ReadSxvs( XclImpStream& rStrm ) +{ + if( !maPCaches.empty() ) + maPCaches.back()->ReadSxvs( rStrm ); +} + +void XclImpPivotTableManager::ReadDconref( XclImpStream& rStrm ) +{ + if( !maPCaches.empty() ) + maPCaches.back()->ReadDconref( rStrm ); +} + +void XclImpPivotTableManager::ReadDConName( XclImpStream& rStrm ) +{ + if( !maPCaches.empty() ) + maPCaches.back()->ReadDConName( rStrm ); +} + +// pivot table records -------------------------------------------------------- + +void XclImpPivotTableManager::ReadSxview( XclImpStream& rStrm ) +{ + XclImpPivotTableRef xPTable = std::make_shared( GetRoot() ); + maPTables.push_back( xPTable ); + xPTable->ReadSxview( rStrm ); +} + +void XclImpPivotTableManager::ReadSxvd( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxvd( rStrm ); +} + +void XclImpPivotTableManager::ReadSxvdex( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxvdex( rStrm ); +} + +void XclImpPivotTableManager::ReadSxivd( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxivd( rStrm ); +} + +void XclImpPivotTableManager::ReadSxpi( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxpi( rStrm ); +} + +void XclImpPivotTableManager::ReadSxdi( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxdi( rStrm ); +} + +void XclImpPivotTableManager::ReadSxvi( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxvi( rStrm ); +} + +void XclImpPivotTableManager::ReadSxex( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxex( rStrm ); +} + +void XclImpPivotTableManager::ReadSxViewEx9( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxViewEx9( rStrm ); +} + +void XclImpPivotTableManager::ReadSxAddl( XclImpStream& rStrm ) +{ + if( !maPTables.empty() ) + maPTables.back()->ReadSxAddl( rStrm ); +} + +void XclImpPivotTableManager::ReadPivotCaches( const XclImpStream& rStrm ) +{ + for( auto& rxPCache : maPCaches ) + rxPCache->ReadPivotCacheStream( rStrm ); +} + +void XclImpPivotTableManager::ConvertPivotTables() +{ + for( auto& rxPTable : maPTables ) + rxPTable->Convert(); +} + +void XclImpPivotTableManager::MaybeRefreshPivotTables() +{ + for( auto& rxPTable : maPTables ) + rxPTable->MaybeRefresh(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiroot.cxx b/sc/source/filter/excel/xiroot.cxx new file mode 100644 index 000000000..af04654de --- /dev/null +++ b/sc/source/filter/excel/xiroot.cxx @@ -0,0 +1,298 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// Global data ================================================================ + +XclImpRootData::XclImpRootData( XclBiff eBiff, SfxMedium& rMedium, + const tools::SvRef& xRootStrg, ScDocument& rDoc, rtl_TextEncoding eTextEnc ) : + XclRootData( eBiff, rMedium, xRootStrg, rDoc, eTextEnc, false ), + mxDocImport(std::make_shared(rDoc)), + mbHasCodePage( false ), + mbHasBasic( false ) +{ +} + +XclImpRootData::~XclImpRootData() +{ +} + +XclImpRoot::XclImpRoot( XclImpRootData& rImpRootData ) : + XclRoot( rImpRootData ), + mrImpData( rImpRootData ) +{ + mrImpData.mxAddrConv = std::make_shared( GetRoot() ); + mrImpData.mxFmlaComp = std::make_shared( GetRoot() ); + mrImpData.mxPalette = std::make_shared( GetRoot() ); + mrImpData.mxFontBfr = std::make_shared( GetRoot() ); + mrImpData.mxNumFmtBfr = std::make_shared( GetRoot() ); + mrImpData.mpXFBfr = std::make_shared( GetRoot() ); + mrImpData.mxXFRangeBfr = std::make_shared( GetRoot() ); + mrImpData.mxTabInfo = std::make_shared(); + mrImpData.mxNameMgr = std::make_shared( GetRoot() ); + mrImpData.mxObjMgr = std::make_shared( GetRoot() ); + + if( GetBiff() == EXC_BIFF8 ) + { + mrImpData.mxLinkMgr = std::make_shared( GetRoot() ); + mrImpData.mxSst = std::make_shared( GetRoot() ); + mrImpData.mxCondFmtMgr = std::make_shared( GetRoot() ); + mrImpData.mxValidMgr = std::make_shared( GetRoot() ); + // TODO still in old RootData (deleted by RootData) + GetOldRoot().pAutoFilterBuffer.reset( new XclImpAutoFilterBuffer ); + mrImpData.mxWebQueryBfr = std::make_shared( GetRoot() ); + mrImpData.mxPTableMgr = std::make_shared( GetRoot() ); + mrImpData.mxTabProtect = std::make_shared( GetRoot() ); + mrImpData.mxDocProtect = std::make_shared( GetRoot() ); + } + + mrImpData.mxPageSett = std::make_shared( GetRoot() ); + mrImpData.mxDocViewSett = std::make_shared( GetRoot() ); + mrImpData.mxTabViewSett = std::make_shared( GetRoot() ); + mrImpData.mpPrintRanges = std::make_unique( GetRoot() ); + mrImpData.mpPrintTitles = std::make_unique( GetRoot() ); +} + +void XclImpRoot::SetCodePage( sal_uInt16 nCodePage ) +{ + SetTextEncoding( XclTools::GetTextEncoding( nCodePage ) ); + mrImpData.mbHasCodePage = true; +} + +void XclImpRoot::InitializeTable( SCTAB nScTab ) +{ + if( GetBiff() <= EXC_BIFF4 ) + { + GetPalette().Initialize(); + GetFontBuffer().Initialize(); + GetNumFmtBuffer().Initialize(); + GetXFBuffer().Initialize(); + } + GetXFRangeBuffer().Initialize(); + GetPageSettings().Initialize(); + GetTabViewSettings().Initialize(); + // delete the automatically generated codename + GetDoc().SetCodeName( nScTab, OUString() ); +} + +void XclImpRoot::FinalizeTable() +{ + GetXFRangeBuffer().Finalize(); + GetOldRoot().pColRowBuff->Convert( GetCurrScTab() ); + GetPageSettings().Finalize(); + GetTabViewSettings().Finalize(); +} + +XclImpAddressConverter& XclImpRoot::GetAddressConverter() const +{ + return *mrImpData.mxAddrConv; +} + +XclImpFormulaCompiler& XclImpRoot::GetFormulaCompiler() const +{ + return *mrImpData.mxFmlaComp; +} + +ExcelToSc& XclImpRoot::GetOldFmlaConverter() const +{ + // TODO still in old RootData + return *GetOldRoot().pFmlaConverter; +} + +XclImpSst& XclImpRoot::GetSst() const +{ + assert(mrImpData.mxSst && "XclImpRoot::GetSst - invalid call, wrong BIFF"); + return *mrImpData.mxSst; +} + +XclImpPalette& XclImpRoot::GetPalette() const +{ + return *mrImpData.mxPalette; +} + +XclImpFontBuffer& XclImpRoot::GetFontBuffer() const +{ + return *mrImpData.mxFontBfr; +} + +XclImpNumFmtBuffer& XclImpRoot::GetNumFmtBuffer() const +{ + return *mrImpData.mxNumFmtBfr; +} + +XclImpXFBuffer& XclImpRoot::GetXFBuffer() const +{ + return *mrImpData.mpXFBfr; +} + +XclImpXFRangeBuffer& XclImpRoot::GetXFRangeBuffer() const +{ + return *mrImpData.mxXFRangeBfr; +} + +ScRangeListTabs& XclImpRoot::GetPrintAreaBuffer() const +{ + return *mrImpData.mpPrintRanges; +} + +ScRangeListTabs& XclImpRoot::GetTitleAreaBuffer() const +{ + return *mrImpData.mpPrintTitles; +} + +XclImpTabInfo& XclImpRoot::GetTabInfo() const +{ + return *mrImpData.mxTabInfo; +} + +XclImpNameManager& XclImpRoot::GetNameManager() const +{ + return *mrImpData.mxNameMgr; +} + +XclImpLinkManager& XclImpRoot::GetLinkManager() const +{ + assert(mrImpData.mxLinkMgr && "XclImpRoot::GetLinkManager - invalid call, wrong BIFF"); + return *mrImpData.mxLinkMgr; +} + +XclImpObjectManager& XclImpRoot::GetObjectManager() const +{ + return *mrImpData.mxObjMgr; +} + +XclImpSheetDrawing& XclImpRoot::GetCurrSheetDrawing() const +{ + OSL_ENSURE( !IsInGlobals(), "XclImpRoot::GetCurrSheetDrawing - must not be called from workbook globals" ); + return mrImpData.mxObjMgr->GetSheetDrawing( GetCurrScTab() ); +} + +XclImpCondFormatManager& XclImpRoot::GetCondFormatManager() const +{ + assert(mrImpData.mxCondFmtMgr && "XclImpRoot::GetCondFormatManager - invalid call, wrong BIFF"); + return *mrImpData.mxCondFmtMgr; +} + +XclImpValidationManager& XclImpRoot::GetValidationManager() const +{ + assert(mrImpData.mxValidMgr && "XclImpRoot::GetValidationManager - invalid call, wrong BIFF"); + return *mrImpData.mxValidMgr; +} + +XclImpAutoFilterBuffer& XclImpRoot::GetFilterManager() const +{ + // TODO still in old RootData + assert(GetOldRoot().pAutoFilterBuffer && "XclImpRoot::GetFilterManager - invalid call, wrong BIFF"); + return *GetOldRoot().pAutoFilterBuffer; +} + +XclImpWebQueryBuffer& XclImpRoot::GetWebQueryBuffer() const +{ + assert(mrImpData.mxWebQueryBfr && "XclImpRoot::GetWebQueryBuffer - invalid call, wrong BIFF"); + return *mrImpData.mxWebQueryBfr; +} + +XclImpPivotTableManager& XclImpRoot::GetPivotTableManager() const +{ + assert(mrImpData.mxPTableMgr && "XclImpRoot::GetPivotTableManager - invalid call, wrong BIFF"); + return *mrImpData.mxPTableMgr; +} + +XclImpSheetProtectBuffer& XclImpRoot::GetSheetProtectBuffer() const +{ + assert(mrImpData.mxTabProtect && "XclImpRoot::GetSheetProtectBuffer - invalid call, wrong BIFF"); + return *mrImpData.mxTabProtect; +} + +XclImpDocProtectBuffer& XclImpRoot::GetDocProtectBuffer() const +{ + assert(mrImpData.mxDocProtect && "XclImpRoot::GetDocProtectBuffer - invalid call, wrong BIFF"); + return *mrImpData.mxDocProtect; +} + +XclImpPageSettings& XclImpRoot::GetPageSettings() const +{ + return *mrImpData.mxPageSett; +} + +XclImpDocViewSettings& XclImpRoot::GetDocViewSettings() const +{ + return *mrImpData.mxDocViewSett; +} + +XclImpTabViewSettings& XclImpRoot::GetTabViewSettings() const +{ + return *mrImpData.mxTabViewSett; +} + +OUString XclImpRoot::GetScAddInName( const OUString& rXclName ) +{ + OUString aScName; + if( ScGlobal::GetAddInCollection()->GetCalcName( rXclName, aScName ) ) + return aScName; + return rXclName; +} + +void XclImpRoot::ReadCodeName( XclImpStream& rStrm, bool bGlobals ) +{ + if( !(mrImpData.mbHasBasic && (GetBiff() == EXC_BIFF8)) ) + return; + + OUString aName = rStrm.ReadUniString(); + if( aName.isEmpty() ) + return; + + if( bGlobals ) + { + GetExtDocOptions().GetDocSettings().maGlobCodeName = aName; + GetDoc().SetCodeName( aName ); + } + else + { + GetExtDocOptions().SetCodeName( GetCurrScTab(), aName ); + GetDoc().SetCodeName( GetCurrScTab(), aName ); + } +} + +ScDocumentImport& XclImpRoot::GetDocImport() +{ + return *mrImpData.mxDocImport; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xistream.cxx b/sc/source/filter/excel/xistream.cxx new file mode 100644 index 000000000..0a6c24aca --- /dev/null +++ b/sc/source/filter/excel/xistream.cxx @@ -0,0 +1,1084 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ::com::sun::star; + +// Decryption +XclImpDecrypter::XclImpDecrypter() : + mnError( EXC_ENCR_ERROR_UNSUPP_CRYPT ), + mnOldPos( STREAM_SEEK_TO_END ), + mnRecSize( 0 ) +{ +} + +XclImpDecrypter::XclImpDecrypter( const XclImpDecrypter& rSrc ) : + ::comphelper::IDocPasswordVerifier(), + mnError( rSrc.mnError ), + mnOldPos( STREAM_SEEK_TO_END ), + mnRecSize( 0 ) +{ +} + +XclImpDecrypter::~XclImpDecrypter() +{ +} + +XclImpDecrypterRef XclImpDecrypter::Clone() const +{ + XclImpDecrypterRef xNewDecr; + if( IsValid() ) + xNewDecr.reset( OnClone() ); + return xNewDecr; +} + +::comphelper::DocPasswordVerifierResult XclImpDecrypter::verifyPassword( const OUString& rPassword, uno::Sequence< beans::NamedValue >& o_rEncryptionData ) +{ + o_rEncryptionData = OnVerifyPassword( rPassword ); + mnError = o_rEncryptionData.hasElements() ? ERRCODE_NONE : ERRCODE_ABORT; + return o_rEncryptionData.hasElements() ? ::comphelper::DocPasswordVerifierResult::OK : ::comphelper::DocPasswordVerifierResult::WrongPassword; +} + +::comphelper::DocPasswordVerifierResult XclImpDecrypter::verifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + bool bValid = OnVerifyEncryptionData( rEncryptionData ); + mnError = bValid ? ERRCODE_NONE : ERRCODE_ABORT; + return bValid ? ::comphelper::DocPasswordVerifierResult::OK : ::comphelper::DocPasswordVerifierResult::WrongPassword; +} + +void XclImpDecrypter::Update( const SvStream& rStrm, sal_uInt16 nRecSize ) +{ + if( IsValid() ) + { + sal_uInt64 const nNewPos = rStrm.Tell(); + if( (mnOldPos != nNewPos) || (mnRecSize != nRecSize) ) + { + OnUpdate( mnOldPos, nNewPos, nRecSize ); + mnOldPos = nNewPos; + mnRecSize = nRecSize; + } + } +} + +sal_uInt16 XclImpDecrypter::Read( SvStream& rStrm, void* pData, sal_uInt16 nBytes ) +{ + sal_uInt16 nRet = 0; + if( pData && nBytes ) + { + if( IsValid() ) + { + Update( rStrm, mnRecSize ); + nRet = OnRead( rStrm, static_cast< sal_uInt8* >( pData ), nBytes ); + mnOldPos = rStrm.Tell(); + } + else + nRet = static_cast(rStrm.ReadBytes(pData, nBytes)); + } + return nRet; +} + +XclImpBiff5Decrypter::XclImpBiff5Decrypter( sal_uInt16 nKey, sal_uInt16 nHash ) : + mnKey( nKey ), + mnHash( nHash ) +{ +} + +XclImpBiff5Decrypter::XclImpBiff5Decrypter( const XclImpBiff5Decrypter& rSrc ) : + XclImpDecrypter( rSrc ), + maEncryptionData( rSrc.maEncryptionData ), + mnKey( rSrc.mnKey ), + mnHash( rSrc.mnHash ) +{ + if( IsValid() ) + maCodec.InitCodec( maEncryptionData ); +} + +XclImpBiff5Decrypter* XclImpBiff5Decrypter::OnClone() const +{ + return new XclImpBiff5Decrypter( *this ); +} + +uno::Sequence< beans::NamedValue > XclImpBiff5Decrypter::OnVerifyPassword( const OUString& rPassword ) +{ + maEncryptionData.realloc( 0 ); + + /* Convert password to a byte string. TODO: this needs some fine tuning + according to the spec... */ + OString aBytePassword = OUStringToOString( rPassword, osl_getThreadTextEncoding() ); + sal_Int32 nLen = aBytePassword.getLength(); + if( (0 < nLen) && (nLen < 16) ) + { + // init codec + maCodec.InitKey( reinterpret_cast(aBytePassword.getStr()) ); + + if ( maCodec.VerifyKey( mnKey, mnHash ) ) + { + maEncryptionData = maCodec.GetEncryptionData(); + + // since the export uses Std97 encryption always we have to request it here + ::std::vector< sal_uInt16 > aPassVect( 16 ); + sal_Int32 nInd = 0; + std::for_each(aPassVect.begin(), aPassVect.begin() + nLen, + [&rPassword, &nInd](sal_uInt16& rPass) { + rPass = static_cast< sal_uInt16 >( rPassword[nInd] ); + ++nInd; + }); + + uno::Sequence< sal_Int8 > aDocId = ::comphelper::DocPasswordHelper::GenerateRandomByteSequence( 16 ); + OSL_ENSURE( aDocId.getLength() == 16, "Unexpected length of the sequence!" ); + + ::msfilter::MSCodec_Std97 aCodec97; + aCodec97.InitKey(aPassVect.data(), reinterpret_cast(aDocId.getConstArray())); + + // merge the EncryptionData, there should be no conflicts + ::comphelper::SequenceAsHashMap aEncryptionHash( maEncryptionData ); + aEncryptionHash.update( ::comphelper::SequenceAsHashMap( aCodec97.GetEncryptionData() ) ); + aEncryptionHash >> maEncryptionData; + } + } + + return maEncryptionData; +} + +bool XclImpBiff5Decrypter::OnVerifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + maEncryptionData.realloc( 0 ); + + if( rEncryptionData.hasElements() ) + { + // init codec + maCodec.InitCodec( rEncryptionData ); + + if ( maCodec.VerifyKey( mnKey, mnHash ) ) + maEncryptionData = rEncryptionData; + } + + return maEncryptionData.hasElements(); +} + +void XclImpBiff5Decrypter::OnUpdate( std::size_t /*nOldStrmPos*/, std::size_t nNewStrmPos, sal_uInt16 nRecSize ) +{ + maCodec.InitCipher(); + maCodec.Skip( (nNewStrmPos + nRecSize) & 0x0F ); +} + +sal_uInt16 XclImpBiff5Decrypter::OnRead( SvStream& rStrm, sal_uInt8* pnData, sal_uInt16 nBytes ) +{ + sal_uInt16 nRet = static_cast(rStrm.ReadBytes(pnData, nBytes)); + maCodec.Decode( pnData, nRet ); + return nRet; +} + +XclImpBiff8Decrypter::XclImpBiff8Decrypter( std::vector&& rSalt, + std::vector&& rVerifier, + std::vector&& rVerifierHash) + : maSalt(std::move(rSalt)) + , maVerifier(std::move(rVerifier)) + , maVerifierHash(std::move(rVerifierHash)) + , mpCodec(nullptr) +{ +} + +XclImpBiff8Decrypter::XclImpBiff8Decrypter(const XclImpBiff8Decrypter& rSrc) + : XclImpDecrypter(rSrc) + , maEncryptionData(rSrc.maEncryptionData) + , maSalt(rSrc.maSalt) + , maVerifier(rSrc.maVerifier) + , maVerifierHash(rSrc.maVerifierHash) + , mpCodec(nullptr) +{ +} + +XclImpBiff8StdDecrypter::XclImpBiff8StdDecrypter(const XclImpBiff8StdDecrypter& rSrc) + : XclImpBiff8Decrypter(rSrc) +{ + mpCodec = &maCodec; + if (IsValid()) + maCodec.InitCodec(maEncryptionData); +} + +XclImpBiff8StdDecrypter* XclImpBiff8StdDecrypter::OnClone() const +{ + return new XclImpBiff8StdDecrypter(*this); +} + +XclImpBiff8CryptoAPIDecrypter::XclImpBiff8CryptoAPIDecrypter(const XclImpBiff8CryptoAPIDecrypter& rSrc) + : XclImpBiff8Decrypter(rSrc) +{ + mpCodec = &maCodec; + if (IsValid()) + maCodec.InitCodec(maEncryptionData); +} + +XclImpBiff8CryptoAPIDecrypter* XclImpBiff8CryptoAPIDecrypter::OnClone() const +{ + return new XclImpBiff8CryptoAPIDecrypter(*this); +} + +uno::Sequence< beans::NamedValue > XclImpBiff8Decrypter::OnVerifyPassword( const OUString& rPassword ) +{ + maEncryptionData.realloc( 0 ); + + sal_Int32 nLen = rPassword.getLength(); + if( (0 < nLen) && (nLen < 16) ) + { + // copy string to sal_uInt16 array + ::std::vector< sal_uInt16 > aPassVect( 16 ); + const sal_Unicode* pcChar = rPassword.getStr(); + std::for_each(aPassVect.begin(), aPassVect.begin() + nLen, + [&pcChar](sal_uInt16& rPass) { + rPass = static_cast< sal_uInt16 >( *pcChar ); + ++pcChar; + }); + + // init codec + mpCodec->InitKey(aPassVect.data(), maSalt.data()); + if (mpCodec->VerifyKey(maVerifier.data(), maVerifierHash.data())) + maEncryptionData = mpCodec->GetEncryptionData(); + } + + return maEncryptionData; +} + +bool XclImpBiff8Decrypter::OnVerifyEncryptionData( const uno::Sequence< beans::NamedValue >& rEncryptionData ) +{ + maEncryptionData.realloc( 0 ); + + if( rEncryptionData.hasElements() ) + { + // init codec + mpCodec->InitCodec( rEncryptionData ); + + if (mpCodec->VerifyKey(maVerifier.data(), maVerifierHash.data())) + maEncryptionData = rEncryptionData; + } + + return maEncryptionData.hasElements(); +} + +void XclImpBiff8Decrypter::OnUpdate( std::size_t nOldStrmPos, std::size_t nNewStrmPos, sal_uInt16 /*nRecSize*/ ) +{ + if( nNewStrmPos == nOldStrmPos ) + return; + + sal_uInt32 nOldBlock = GetBlock( nOldStrmPos ); + sal_uInt16 nOldOffset = GetOffset( nOldStrmPos ); + + sal_uInt32 nNewBlock = GetBlock( nNewStrmPos ); + sal_uInt16 nNewOffset = GetOffset( nNewStrmPos ); + + /* Rekey cipher, if block changed or if previous offset in same block. */ + if( (nNewBlock != nOldBlock) || (nNewOffset < nOldOffset) ) + { + mpCodec->InitCipher( nNewBlock ); + nOldOffset = 0; // reset nOldOffset for next if() statement + } + + /* Seek to correct offset. */ + if( nNewOffset > nOldOffset ) + mpCodec->Skip( nNewOffset - nOldOffset ); +} + +sal_uInt16 XclImpBiff8Decrypter::OnRead( SvStream& rStrm, sal_uInt8* pnData, sal_uInt16 nBytes ) +{ + sal_uInt16 nRet = 0; + + sal_uInt8* pnCurrData = pnData; + sal_uInt16 nBytesLeft = nBytes; + while( nBytesLeft ) + { + sal_uInt16 nBlockLeft = EXC_ENCR_BLOCKSIZE - GetOffset( rStrm.Tell() ); + sal_uInt16 nDecBytes = ::std::min< sal_uInt16 >( nBytesLeft, nBlockLeft ); + + // read the block from stream + nRet = nRet + static_cast(rStrm.ReadBytes(pnCurrData, nDecBytes)); + // decode the block inplace + mpCodec->Decode( pnCurrData, nDecBytes, pnCurrData, nDecBytes ); + if( GetOffset( rStrm.Tell() ) == 0 ) + mpCodec->InitCipher( GetBlock( rStrm.Tell() ) ); + + pnCurrData += nDecBytes; + nBytesLeft = nBytesLeft - nDecBytes; + } + + return nRet; +} + +sal_uInt32 XclImpBiff8Decrypter::GetBlock( std::size_t nStrmPos ) +{ + return static_cast< sal_uInt32 >( nStrmPos / EXC_ENCR_BLOCKSIZE ); +} + +sal_uInt16 XclImpBiff8Decrypter::GetOffset( std::size_t nStrmPos ) +{ + return static_cast< sal_uInt16 >( nStrmPos % EXC_ENCR_BLOCKSIZE ); +} + +// Stream +XclImpStreamPos::XclImpStreamPos() : + mnPos( STREAM_SEEK_TO_BEGIN ), + mnNextPos( STREAM_SEEK_TO_BEGIN ), + mnCurrSize( 0 ), + mnRawRecId( EXC_ID_UNKNOWN ), + mnRawRecSize( 0 ), + mnRawRecLeft( 0 ), + mbValid( false ) +{ +} + +void XclImpStreamPos::Set( + const SvStream& rStrm, std::size_t nNextPos, std::size_t nCurrSize, + sal_uInt16 nRawRecId, sal_uInt16 nRawRecSize, sal_uInt16 nRawRecLeft, + bool bValid ) +{ + mnPos = rStrm.Tell(); + mnNextPos = nNextPos; + mnCurrSize = nCurrSize; + mnRawRecId = nRawRecId; + mnRawRecSize = nRawRecSize; + mnRawRecLeft = nRawRecLeft; + mbValid = bValid; +} + +void XclImpStreamPos::Get( + SvStream& rStrm, std::size_t& rnNextPos, std::size_t& rnCurrSize, + sal_uInt16& rnRawRecId, sal_uInt16& rnRawRecSize, sal_uInt16& rnRawRecLeft, + bool& rbValid ) const +{ + rStrm.Seek( mnPos ); + rnNextPos = mnNextPos; + rnCurrSize = mnCurrSize; + rnRawRecId = mnRawRecId; + rnRawRecSize = mnRawRecSize; + rnRawRecLeft = mnRawRecLeft; + rbValid = mbValid; +} + +XclBiff XclImpStream::DetectBiffVersion( SvStream& rStrm ) +{ + XclBiff eBiff = EXC_BIFF_UNKNOWN; + + rStrm.Seek( STREAM_SEEK_TO_BEGIN ); + sal_uInt16 nBofId(0), nBofSize(0); + rStrm.ReadUInt16( nBofId ).ReadUInt16( nBofSize ); + + if (rStrm.good() && 4 <= nBofSize && nBofSize <= 16) switch( nBofId ) + { + case EXC_ID2_BOF: + eBiff = EXC_BIFF2; + break; + case EXC_ID3_BOF: + eBiff = EXC_BIFF3; + break; + case EXC_ID4_BOF: + eBiff = EXC_BIFF4; + break; + case EXC_ID5_BOF: + { + sal_uInt16 nVersion(0); + rStrm.ReadUInt16( nVersion ); + // #i23425# #i44031# #i62752# there are some *really* broken documents out there... + switch( nVersion & 0xFF00 ) + { + case 0: eBiff = EXC_BIFF5; break; // #i44031# #i62752# + case EXC_BOF_BIFF2: eBiff = EXC_BIFF2; break; + case EXC_BOF_BIFF3: eBiff = EXC_BIFF3; break; + case EXC_BOF_BIFF4: eBiff = EXC_BIFF4; break; + case EXC_BOF_BIFF5: eBiff = EXC_BIFF5; break; + case EXC_BOF_BIFF8: eBiff = EXC_BIFF8; break; + default: SAL_WARN("sc", "XclImpStream::DetectBiffVersion - unknown BIFF version: 0x" << std::hex << nVersion ); + } + } + break; + } + return eBiff; +} + +XclImpStream::XclImpStream( SvStream& rInStrm, const XclImpRoot& rRoot ) : + mrStrm( rInStrm ), + mrRoot( rRoot ), + mnGlobRecId( EXC_ID_UNKNOWN ), + mbGlobValidRec( false ), + mbHasGlobPos( false ), + mnNextRecPos( STREAM_SEEK_TO_BEGIN ), + mnCurrRecSize( 0 ), + mnComplRecSize( 0 ), + mbHasComplRec( false ), + mnRecId( EXC_ID_UNKNOWN ), + mnAltContId( EXC_ID_UNKNOWN ), + mnRawRecId( EXC_ID_UNKNOWN ), + mnRawRecSize( 0 ), + mnRawRecLeft( 0 ), + mcNulSubst( '?' ), + mbCont( true ), + mbUseDecr( false ), + mbValidRec( false ), + mbValid( false ) +{ + mnStreamSize = mrStrm.TellEnd(); + mrStrm.Seek( STREAM_SEEK_TO_BEGIN ); +} + +XclImpStream::~XclImpStream() +{ +} + +bool XclImpStream::StartNextRecord() +{ + maPosStack.clear(); + + /* #i4266# Counter to ignore zero records (id==len==0) (i.e. the application + "Crystal Report" writes zero records between other records) */ + std::size_t nZeroRecCount = 5; + bool bIsZeroRec = false; + + do + { + mbValidRec = ReadNextRawRecHeader(); + bIsZeroRec = (mnRawRecId == 0) && (mnRawRecSize == 0); + if( bIsZeroRec ) --nZeroRecCount; + mnNextRecPos = mrStrm.Tell() + mnRawRecSize; + } + while( mbValidRec && ((mbCont && IsContinueId( mnRawRecId )) || (bIsZeroRec && nZeroRecCount)) ); + + mbValidRec = mbValidRec && !bIsZeroRec; + mbValid = mbValidRec; + SetupRecord(); + + return mbValidRec; +} + +bool XclImpStream::StartNextRecord( std::size_t nNextRecPos ) +{ + mnNextRecPos = nNextRecPos; + return StartNextRecord(); +} + +void XclImpStream::ResetRecord( bool bContLookup, sal_uInt16 nAltContId ) +{ + if( mbValidRec ) + { + maPosStack.clear(); + RestorePosition( maFirstRec ); + mnCurrRecSize = mnComplRecSize = mnRawRecSize; + mbHasComplRec = !bContLookup; + mbCont = bContLookup; + mnAltContId = nAltContId; + EnableDecryption(); + } +} + +void XclImpStream::RewindRecord() +{ + mnNextRecPos = maFirstRec.GetPos(); + mbValid = mbValidRec = false; +} + +void XclImpStream::SetDecrypter( XclImpDecrypterRef const & xDecrypter ) +{ + mxDecrypter = xDecrypter; + EnableDecryption(); + SetupDecrypter(); +} + +void XclImpStream::CopyDecrypterFrom( const XclImpStream& rStrm ) +{ + XclImpDecrypterRef xNewDecr; + if( rStrm.mxDecrypter ) + xNewDecr = rStrm.mxDecrypter->Clone(); + SetDecrypter( xNewDecr ); +} + +void XclImpStream::EnableDecryption( bool bEnable ) +{ + mbUseDecr = bEnable && mxDecrypter && mxDecrypter->IsValid(); +} + +void XclImpStream::PushPosition() +{ + maPosStack.emplace_back( ); + StorePosition( maPosStack.back() ); +} + +void XclImpStream::PopPosition() +{ + OSL_ENSURE( !maPosStack.empty(), "XclImpStream::PopPosition - stack empty" ); + if( !maPosStack.empty() ) + { + RestorePosition( maPosStack.back() ); + maPosStack.pop_back(); + } +} + +void XclImpStream::StoreGlobalPosition() +{ + StorePosition( maGlobPos ); + mnGlobRecId = mnRecId; + mbGlobValidRec = mbValidRec; + mbHasGlobPos = true; +} + +void XclImpStream::SeekGlobalPosition() +{ + OSL_ENSURE( mbHasGlobPos, "XclImpStream::SeekGlobalPosition - no position stored" ); + if( mbHasGlobPos ) + { + RestorePosition( maGlobPos ); + mnRecId = mnGlobRecId; + mnComplRecSize = mnCurrRecSize; + mbHasComplRec = !mbCont; + mbValidRec = mbGlobValidRec; + } +} + +std::size_t XclImpStream::GetRecPos() const +{ + return mbValid ? (mnCurrRecSize - mnRawRecLeft) : EXC_REC_SEEK_TO_END; +} + +std::size_t XclImpStream::GetRecSize() +{ + if( !mbHasComplRec ) + { + PushPosition(); + while( JumpToNextContinue() ) ; // JumpToNextContinue() adds up mnCurrRecSize + mnComplRecSize = mnCurrRecSize; + mbHasComplRec = true; + PopPosition(); + } + return mnComplRecSize; +} + +std::size_t XclImpStream::GetRecLeft() +{ + return mbValid ? (GetRecSize() - GetRecPos()) : 0; +} + +sal_uInt16 XclImpStream::GetNextRecId() +{ + sal_uInt16 nRecId = EXC_ID_UNKNOWN; + if( mbValidRec ) + { + PushPosition(); + while( JumpToNextContinue() ) ; // skip following CONTINUE records + if( mnNextRecPos < mnStreamSize ) + { + mrStrm.Seek( mnNextRecPos ); + mrStrm.ReadUInt16( nRecId ); + } + PopPosition(); + } + return nRecId; +} + +sal_uInt16 XclImpStream::PeekRecId( std::size_t nPos ) +{ + sal_uInt16 nRecId = EXC_ID_UNKNOWN; + if (mbValidRec && nPos < mnStreamSize) + { + sal_uInt64 const nCurPos = mrStrm.Tell(); + mrStrm.Seek(nPos); + mrStrm.ReadUInt16( nRecId ); + mrStrm.Seek(nCurPos); + } + return nRecId; +} + +sal_uInt8 XclImpStream::ReaduInt8() +{ + sal_uInt8 nValue = 0; + if( EnsureRawReadSize( 1 ) ) + { + if( mbUseDecr ) + mxDecrypter->Read( mrStrm, &nValue, 1 ); + else + mrStrm.ReadUChar( nValue ); + --mnRawRecLeft; + } + return nValue; +} + +sal_Int16 XclImpStream::ReadInt16() +{ + sal_Int16 nValue = 0; + if( EnsureRawReadSize( 2 ) ) + { + if( mbUseDecr ) + { + SVBT16 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 2 ); + nValue = static_cast< sal_Int16 >( SVBT16ToUInt16( pnBuffer ) ); + } + else + mrStrm.ReadInt16( nValue ); + mnRawRecLeft -= 2; + } + return nValue; +} + +sal_uInt16 XclImpStream::ReaduInt16() +{ + sal_uInt16 nValue = 0; + if( EnsureRawReadSize( 2 ) ) + { + if( mbUseDecr ) + { + SVBT16 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 2 ); + nValue = SVBT16ToUInt16( pnBuffer ); + } + else + mrStrm.ReadUInt16( nValue ); + mnRawRecLeft -= 2; + } + return nValue; +} + +sal_Int32 XclImpStream::ReadInt32() +{ + sal_Int32 nValue = 0; + if( EnsureRawReadSize( 4 ) ) + { + if( mbUseDecr ) + { + SVBT32 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 4 ); + nValue = static_cast< sal_Int32 >( SVBT32ToUInt32( pnBuffer ) ); + } + else + mrStrm.ReadInt32( nValue ); + mnRawRecLeft -= 4; + } + return nValue; +} + +sal_uInt32 XclImpStream::ReaduInt32() +{ + sal_uInt32 nValue = 0; + if( EnsureRawReadSize( 4 ) ) + { + if( mbUseDecr ) + { + SVBT32 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 4 ); + nValue = SVBT32ToUInt32( pnBuffer ); + } + else + mrStrm.ReadUInt32( nValue ); + mnRawRecLeft -= 4; + } + return nValue; +} + +double XclImpStream::ReadDouble() +{ + double nValue = 0; + if( EnsureRawReadSize( 8 ) ) + { + if( mbUseDecr ) + { + SVBT64 pnBuffer{0}; + mxDecrypter->Read( mrStrm, pnBuffer, 8 ); + nValue = SVBT64ToDouble( pnBuffer ); + } + else + mrStrm.ReadDouble( nValue ); + mnRawRecLeft -= 8; + } + return nValue; +} + +std::size_t XclImpStream::Read( void* pData, std::size_t nBytes ) +{ + std::size_t nRet = 0; + if( mbValid && pData && (nBytes > 0) ) + { + sal_uInt8* pnBuffer = static_cast< sal_uInt8* >( pData ); + std::size_t nBytesLeft = nBytes; + + while( mbValid && (nBytesLeft > 0) ) + { + sal_uInt16 nReadSize = GetMaxRawReadSize( nBytesLeft ); + sal_uInt16 nReadRet = ReadRawData( pnBuffer, nReadSize ); + nRet += nReadRet; + mbValid = (nReadSize == nReadRet); + OSL_ENSURE( mbValid, "XclImpStream::Read - stream read error" ); + pnBuffer += nReadRet; + nBytesLeft -= nReadRet; + if( mbValid && (nBytesLeft > 0) ) + JumpToNextContinue(); + OSL_ENSURE( mbValid, "XclImpStream::Read - record overread" ); + } + } + return nRet; +} + +std::size_t XclImpStream::CopyToStream( SvStream& rOutStrm, std::size_t nBytes ) +{ + std::size_t nRet = 0; + if (mbValid && nBytes) + { + const std::size_t nMaxBuffer = 4096; + std::vector aBuffer(o3tl::sanitizing_min(nBytes, nMaxBuffer)); + std::size_t nBytesLeft = nBytes; + + while (mbValid) + { + if (!nBytesLeft) + break; + std::size_t nReadSize = o3tl::sanitizing_min(nBytesLeft, nMaxBuffer); + nRet += Read(aBuffer.data(), nReadSize); + // writing more bytes than read results in invalid memory access + SAL_WARN_IF(nRet != nReadSize, "sc", "read less bytes than requested"); + rOutStrm.WriteBytes(aBuffer.data(), nReadSize); + nBytesLeft -= nReadSize; + } + } + return nRet; +} + +void XclImpStream::CopyRecordToStream( SvStream& rOutStrm ) +{ + if( mbValidRec ) + { + PushPosition(); + RestorePosition( maFirstRec ); + CopyToStream( rOutStrm, GetRecSize() ); + PopPosition(); + } +} + +void XclImpStream::Seek( std::size_t nPos ) +{ + if( !mbValidRec ) + return; + + std::size_t nCurrPos = GetRecPos(); + if( !mbValid || (nPos < nCurrPos) ) // from invalid state or backward + { + RestorePosition( maFirstRec ); + Ignore( nPos ); + } + else if( nPos > nCurrPos ) // forward + { + Ignore( nPos - nCurrPos ); + } +} + +void XclImpStream::Ignore( std::size_t nBytes ) +{ + // implementation similar to Read(), but without really reading anything + std::size_t nBytesLeft = nBytes; + while (mbValid) + { + if (!nBytesLeft) + break; + sal_uInt16 nReadSize = GetMaxRawReadSize( nBytesLeft ); + mbValid = checkSeek(mrStrm, mrStrm.Tell() + nReadSize); + mnRawRecLeft = mnRawRecLeft - nReadSize; + nBytesLeft -= nReadSize; + if (mbValid && nBytesLeft > 0) + JumpToNextContinue(); + OSL_ENSURE( mbValid, "XclImpStream::Ignore - record overread" ); + } +} + +std::size_t XclImpStream::ReadUniStringExtHeader( + bool& rb16Bit, bool& rbRich, bool& rbFareast, + sal_uInt16& rnFormatRuns, sal_uInt32& rnExtInf, sal_uInt8 nFlags ) +{ + OSL_ENSURE( !::get_flag( nFlags, EXC_STRF_UNKNOWN ), "XclImpStream::ReadUniStringExt - unknown flags" ); + rb16Bit = ::get_flag( nFlags, EXC_STRF_16BIT ); + rbRich = ::get_flag( nFlags, EXC_STRF_RICH ); + rbFareast = ::get_flag( nFlags, EXC_STRF_FAREAST ); + rnFormatRuns = rbRich ? ReaduInt16() : 0; + rnExtInf = rbFareast ? ReaduInt32() : 0; + return rnExtInf + 4 * rnFormatRuns; +} + +std::size_t XclImpStream::ReadUniStringExtHeader( bool& rb16Bit, sal_uInt8 nFlags ) +{ + bool bRich, bFareast; + sal_uInt16 nCrun; + sal_uInt32 nExtInf; + return ReadUniStringExtHeader( rb16Bit, bRich, bFareast, nCrun, nExtInf, nFlags ); +} + +OUString XclImpStream::ReadRawUniString( sal_uInt16 nChars, bool b16Bit ) +{ + OUStringBuffer aRet(o3tl::sanitizing_min(nChars, mnRawRecLeft / (b16Bit ? 2 : 1))); + sal_uInt16 nCharsLeft = nChars; + sal_uInt16 nReadSize; + + while (IsValid() && nCharsLeft) + { + if( b16Bit ) + { + nReadSize = o3tl::sanitizing_min(nCharsLeft, mnRawRecLeft / 2); + OSL_ENSURE( (nReadSize <= nCharsLeft) || !(mnRawRecLeft & 0x1), + "XclImpStream::ReadRawUniString - missing a byte" ); + } + else + nReadSize = GetMaxRawReadSize( nCharsLeft ); + + std::unique_ptr pcBuffer(new sal_Unicode[nReadSize + 1]); + + sal_Unicode* pcUniChar = pcBuffer.get(); + sal_Unicode* pcEndChar = pcBuffer.get() + nReadSize; + + if( b16Bit ) + { + sal_uInt16 nReadChar; + for( ; IsValid() && (pcUniChar < pcEndChar); ++pcUniChar ) + { + nReadChar = ReaduInt16(); + (*pcUniChar) = (nReadChar == EXC_NUL) ? mcNulSubst : static_cast< sal_Unicode >( nReadChar ); + } + } + else + { + sal_uInt8 nReadChar; + for( ; IsValid() && (pcUniChar < pcEndChar); ++pcUniChar ) + { + nReadChar = ReaduInt8(); + (*pcUniChar) = (nReadChar == EXC_NUL_C) ? mcNulSubst : static_cast< sal_Unicode >( nReadChar ); + } + } + + *pcEndChar = '\0'; + // this has the side-effect of only copying as far as the first null, which appears to be intentional. e.g. + // see tdf#124318 + aRet.append( pcBuffer.get() ); + + nCharsLeft = nCharsLeft - nReadSize; + if( nCharsLeft > 0 ) + JumpToNextStringContinue( b16Bit ); + } + + return aRet.makeStringAndClear(); +} + +OUString XclImpStream::ReadUniString( sal_uInt16 nChars, sal_uInt8 nFlags ) +{ + bool b16Bit; + std::size_t nExtSize = ReadUniStringExtHeader( b16Bit, nFlags ); + OUString aRet( ReadRawUniString( nChars, b16Bit ) ); + Ignore( nExtSize ); + return aRet; +} + +OUString XclImpStream::ReadUniString( sal_uInt16 nChars ) +{ + return ReadUniString( nChars, ReaduInt8() ); +} + +OUString XclImpStream::ReadUniString() +{ + return ReadUniString( ReaduInt16() ); +} + +void XclImpStream::IgnoreRawUniString( sal_uInt16 nChars, bool b16Bit ) +{ + sal_uInt16 nCharsLeft = nChars; + sal_uInt16 nReadSize; + + while( IsValid() && (nCharsLeft > 0) ) + { + if( b16Bit ) + { + nReadSize = o3tl::sanitizing_min(nCharsLeft, mnRawRecLeft / 2); + OSL_ENSURE( (nReadSize <= nCharsLeft) || !(mnRawRecLeft & 0x1), + "XclImpStream::IgnoreRawUniString - missing a byte" ); + Ignore( nReadSize * 2 ); + } + else + { + nReadSize = GetMaxRawReadSize( nCharsLeft ); + Ignore( nReadSize ); + } + + nCharsLeft = nCharsLeft - nReadSize; + if( nCharsLeft > 0 ) + JumpToNextStringContinue( b16Bit ); + } +} + +void XclImpStream::IgnoreUniString( sal_uInt16 nChars, sal_uInt8 nFlags ) +{ + bool b16Bit; + std::size_t nExtSize = ReadUniStringExtHeader( b16Bit, nFlags ); + IgnoreRawUniString( nChars, b16Bit ); + Ignore( nExtSize ); +} + +void XclImpStream::IgnoreUniString( sal_uInt16 nChars ) +{ + IgnoreUniString( nChars, ReaduInt8() ); +} + +OUString XclImpStream::ReadRawByteString( sal_uInt16 nChars ) +{ + nChars = GetMaxRawReadSize(nChars); + std::unique_ptr pcBuffer(new char[ nChars + 1 ]); + sal_uInt16 nCharsRead = ReadRawData( pcBuffer.get(), nChars ); + pcBuffer[ nCharsRead ] = '\0'; + OUString aRet( pcBuffer.get(), strlen(pcBuffer.get()), mrRoot.GetTextEncoding() ); + return aRet; +} + +OUString XclImpStream::ReadByteString( bool b16BitLen ) +{ + return ReadRawByteString( b16BitLen ? ReaduInt16() : ReaduInt8() ); +} + +// private -------------------------------------------------------------------- + +void XclImpStream::StorePosition( XclImpStreamPos& rPos ) +{ + rPos.Set( mrStrm, mnNextRecPos, mnCurrRecSize, mnRawRecId, mnRawRecSize, mnRawRecLeft, mbValid ); +} + +void XclImpStream::RestorePosition( const XclImpStreamPos& rPos ) +{ + rPos.Get( mrStrm, mnNextRecPos, mnCurrRecSize, mnRawRecId, mnRawRecSize, mnRawRecLeft, mbValid ); + SetupDecrypter(); +} + +bool XclImpStream::ReadNextRawRecHeader() +{ + bool bRet = checkSeek(mrStrm, mnNextRecPos) && (mnNextRecPos + 4 <= mnStreamSize); + if (bRet) + { + mrStrm.ReadUInt16( mnRawRecId ).ReadUInt16( mnRawRecSize ); + bRet = mrStrm.good(); + } + return bRet; +} + +void XclImpStream::SetupDecrypter() +{ + if( mxDecrypter ) + mxDecrypter->Update( mrStrm, mnRawRecSize ); +} + +void XclImpStream::SetupRawRecord() +{ + // pre: mnRawRecSize contains current raw record size + // pre: mrStrm points to start of raw record data + mnNextRecPos = mrStrm.Tell() + mnRawRecSize; + mnRawRecLeft = mnRawRecSize; + mnCurrRecSize += mnRawRecSize; + SetupDecrypter(); // decrypter works on raw record level +} + +void XclImpStream::SetupRecord() +{ + mnRecId = mnRawRecId; + mnAltContId = EXC_ID_UNKNOWN; + mnCurrRecSize = 0; + mnComplRecSize = mnRawRecSize; + mbHasComplRec = !mbCont; + SetupRawRecord(); + SetNulSubstChar(); + EnableDecryption(); + StorePosition( maFirstRec ); +} + +bool XclImpStream::IsContinueId( sal_uInt16 nRecId ) const +{ + return (nRecId == EXC_ID_CONT) || (nRecId == mnAltContId); +} + +bool XclImpStream::JumpToNextContinue() +{ + mbValid = mbValid && mbCont && ReadNextRawRecHeader() && IsContinueId( mnRawRecId ); + if( mbValid ) // do not setup a following non-CONTINUE record + SetupRawRecord(); + return mbValid; +} + +bool XclImpStream::JumpToNextStringContinue( bool& rb16Bit ) +{ + OSL_ENSURE( mnRawRecLeft == 0, "XclImpStream::JumpToNextStringContinue - unexpected garbage" ); + + if( mbCont && (GetRecLeft() > 0) ) + { + JumpToNextContinue(); + } + else if( mnRecId == EXC_ID_CONT ) + { + // CONTINUE handling is off, but we have started reading in a CONTINUE record + // -> start next CONTINUE for TXO import + mbValidRec = ReadNextRawRecHeader() && ((mnRawRecId != 0) || (mnRawRecSize > 0)); + mbValid = mbValidRec && (mnRawRecId == EXC_ID_CONT); + // we really start a new record here - no chance to return to string origin + if( mbValid ) + SetupRecord(); + } + else + mbValid = false; + + if( mbValid ) + rb16Bit = ::get_flag( ReaduInt8(), EXC_STRF_16BIT ); + return mbValid; +} + +bool XclImpStream::EnsureRawReadSize( sal_uInt16 nBytes ) +{ + if( mbValid && nBytes ) + { + while( mbValid && !mnRawRecLeft ) JumpToNextContinue(); + mbValid = mbValid && (nBytes <= mnRawRecLeft); + OSL_ENSURE( mbValid, "XclImpStream::EnsureRawReadSize - record overread" ); + } + return mbValid; +} + +sal_uInt16 XclImpStream::GetMaxRawReadSize( std::size_t nBytes ) const +{ + return static_cast(o3tl::sanitizing_min(nBytes, mnRawRecLeft)); +} + +sal_uInt16 XclImpStream::ReadRawData( void* pData, sal_uInt16 nBytes ) +{ + OSL_ENSURE( (nBytes <= mnRawRecLeft), "XclImpStream::ReadRawData - record overread" ); + sal_uInt16 nRet = 0; + if( mbUseDecr ) + nRet = mxDecrypter->Read( mrStrm, pData, nBytes ); + else + nRet = static_cast(mrStrm.ReadBytes(pData, nBytes)); + mnRawRecLeft = mnRawRecLeft - nRet; + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xistring.cxx b/sc/source/filter/excel/xistring.cxx new file mode 100644 index 000000000..f0878a617 --- /dev/null +++ b/sc/source/filter/excel/xistring.cxx @@ -0,0 +1,212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +// Byte/Unicode strings ======================================================= + +/** All allowed flags for import. */ +const XclStrFlags nAllowedFlags = XclStrFlags::EightBitLength | XclStrFlags::SmartFlags | XclStrFlags::SeparateFormats; + +XclImpString::XclImpString() +{ +} + +XclImpString::XclImpString( const OUString& rString ) : + maString( rString ) +{ +} + +void XclImpString::Read( XclImpStream& rStrm, XclStrFlags nFlags ) +{ + if( !( nFlags & XclStrFlags::SeparateFormats ) ) + maFormats.clear(); + + SAL_WARN_IF( + nFlags & ~nAllowedFlags, "sc.filter", + "XclImpString::Read - unknown flag"); + bool b16BitLen = !( nFlags & XclStrFlags::EightBitLength ); + + switch( rStrm.GetRoot().GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + // no integrated formatting in BIFF2-BIFF7 + maString = rStrm.ReadByteString( b16BitLen ); + break; + + case EXC_BIFF8: + { + // --- string header --- + sal_uInt16 nChars = b16BitLen ? rStrm.ReaduInt16() : rStrm.ReaduInt8(); + sal_uInt8 nFlagField = 0; + if( nChars || !( nFlags & XclStrFlags::SmartFlags ) ) + nFlagField = rStrm.ReaduInt8(); + + bool b16Bit, bRich, bFarEast; + sal_uInt16 nRunCount; + sal_uInt32 nExtInf; + rStrm.ReadUniStringExtHeader( b16Bit, bRich, bFarEast, nRunCount, nExtInf, nFlagField ); + // ignore the flags, they may be wrong + + // --- character array --- + maString = rStrm.ReadRawUniString( nChars, b16Bit ); + + // --- formatting --- + if (nRunCount) + ReadFormats( rStrm, maFormats, nRunCount ); + + // --- extended (FarEast) information --- + rStrm.Ignore( nExtInf ); + } + break; + + default: + DBG_ERROR_BIFF(); + } +} + +void XclImpString::AppendFormat( XclFormatRunVec& rFormats, sal_uInt16 nChar, sal_uInt16 nFontIdx ) +{ + // #i33341# real life -- same character index may occur several times + OSL_ENSURE( rFormats.empty() || (rFormats.back().mnChar <= nChar), "XclImpString::AppendFormat - wrong char order" ); + if( rFormats.empty() || (rFormats.back().mnChar < nChar) ) + rFormats.emplace_back( nChar, nFontIdx ); + else + rFormats.back().mnFontIdx = nFontIdx; +} + +void XclImpString::ReadFormats( XclImpStream& rStrm, XclFormatRunVec& rFormats ) +{ + bool bBiff8 = rStrm.GetRoot().GetBiff() == EXC_BIFF8; + sal_uInt16 nRunCount = bBiff8 ? rStrm.ReaduInt16() : rStrm.ReaduInt8(); + ReadFormats( rStrm, rFormats, nRunCount ); +} + +void XclImpString::ReadFormats( XclImpStream& rStrm, XclFormatRunVec& rFormats, sal_uInt16 nRunCount ) +{ + rFormats.clear(); + + size_t nElementSize = rStrm.GetRoot().GetBiff() == EXC_BIFF8 ? 4 : 2; + size_t nAvailableBytes = rStrm.GetRecLeft(); + size_t nMaxElements = nAvailableBytes / nElementSize; + if (nRunCount > nMaxElements) + { + SAL_WARN("sc.filter", "XclImpString::ReadFormats - more formats claimed than stream could contain"); + rStrm.SetSvStreamError(SVSTREAM_FILEFORMAT_ERROR); + return; + } + + rFormats.reserve( nRunCount ); + /* #i33341# real life -- same character index may occur several times + -> use AppendFormat() to validate formats */ + if( rStrm.GetRoot().GetBiff() == EXC_BIFF8 ) + { + for( sal_uInt16 nIdx = 0; nIdx < nRunCount; ++nIdx ) + { + sal_uInt16 nChar = rStrm.ReaduInt16(); + sal_uInt16 nFontIdx = rStrm.ReaduInt16(); + AppendFormat( rFormats, nChar, nFontIdx ); + } + } + else + { + for( sal_uInt16 nIdx = 0; nIdx < nRunCount; ++nIdx ) + { + sal_uInt8 nChar = rStrm.ReaduInt8(); + sal_uInt8 nFontIdx = rStrm.ReaduInt8(); + AppendFormat( rFormats, nChar, nFontIdx ); + } + } +} + +void XclImpString::ReadObjFormats( XclImpStream& rStrm, XclFormatRunVec& rFormats, sal_uInt16 nFormatSize ) +{ + // number of formatting runs, each takes 8 bytes + sal_uInt16 nRunCount = nFormatSize / 8; + rFormats.clear(); + rFormats.reserve( nRunCount ); + for( sal_uInt16 nIdx = 0; nIdx < nRunCount; ++nIdx ) + { + sal_uInt16 nChar = rStrm.ReaduInt16(); + sal_uInt16 nFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 4 ); + AppendFormat( rFormats, nChar, nFontIdx ); + } +} + +// String iterator ============================================================ + +XclImpStringIterator::XclImpStringIterator( const XclImpString& rString ) : + mrText( rString.GetText() ), + mrFormats( rString.GetFormats() ), + mnPortion( 0 ), + mnTextBeg( 0 ), + mnTextEnd( 0 ), + mnFormatsBeg( 0 ), + mnFormatsEnd( 0 ) +{ + // first portion is formatted, adjust vector index to next portion + if( !mrFormats.empty() && (mrFormats.front().mnChar == 0) ) + ++mnFormatsEnd; + // find end position of the first portion + mnTextEnd = (mnFormatsEnd < mrFormats.size() ? + mrFormats[ mnFormatsEnd ].mnChar : mrText.getLength() ); +} + +OUString XclImpStringIterator::GetPortionText() const +{ + return mrText.copy( mnTextBeg, mnTextEnd - mnTextBeg ); +} + +sal_uInt16 XclImpStringIterator::GetPortionFont() const +{ + return (mnFormatsBeg < mnFormatsEnd) ? mrFormats[ mnFormatsBeg ].mnFontIdx : EXC_FONT_NOTFOUND; +} + +XclImpStringIterator& XclImpStringIterator::operator++() +{ + if( Is() ) + { + ++mnPortion; + do + { + // indexes into vector of formatting runs + if( mnFormatsBeg < mnFormatsEnd ) + ++mnFormatsBeg; + if( mnFormatsEnd < mrFormats.size() ) + ++mnFormatsEnd; + // character positions of next portion + mnTextBeg = mnTextEnd; + mnTextEnd = (mnFormatsEnd < mrFormats.size()) ? + mrFormats[ mnFormatsEnd ].mnChar : mrText.getLength(); + } + while( Is() && (mnTextBeg == mnTextEnd) ); + } + return *this; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xistyle.cxx b/sc/source/filter/excel/xistyle.cxx new file mode 100644 index 000000000..7361c7ee6 --- /dev/null +++ b/sc/source/filter/excel/xistyle.cxx @@ -0,0 +1,2084 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using ::std::vector; +using namespace ::com::sun::star; + +typedef ::cppu::WeakImplHelper< container::XIndexAccess > XIndexAccess_BASE; +typedef ::std::vector< Color > ColorVec; + +namespace { + +class PaletteIndex : public XIndexAccess_BASE +{ +public: + explicit PaletteIndex( ColorVec&& rColorTable ) : maColor( std::move(rColorTable) ) {} + + // Methods XIndexAccess + virtual ::sal_Int32 SAL_CALL getCount() override + { + return maColor.size(); + } + + virtual uno::Any SAL_CALL getByIndex( ::sal_Int32 Index ) override + { + //--Index; // apparently the palette is already 1 based + return uno::Any( sal_Int32( maColor[ Index ] ) ); + } + + // Methods XElementAccess + virtual uno::Type SAL_CALL getElementType() override + { + return ::cppu::UnoType::get(); + } + virtual sal_Bool SAL_CALL hasElements() override + { + return (!maColor.empty()); + } + +private: + ColorVec maColor; +}; + +} + +void +XclImpPalette::ExportPalette() +{ + SfxObjectShell* pDocShell = mrRoot.GetDocShell(); + if(!pDocShell) + return; + + // copy values in color palette + sal_Int16 nColors = maColorTable.size(); + ColorVec aColors; + aColors.resize( nColors ); + for( sal_uInt16 nIndex = 0; nIndex < nColors; ++nIndex ) + aColors[ nIndex ] = GetColor( nIndex ); + + uno::Reference< beans::XPropertySet > xProps( pDocShell->GetModel(), uno::UNO_QUERY ); + if ( xProps.is() ) + { + uno::Reference< container::XIndexAccess > xIndex( new PaletteIndex( std::move(aColors) ) ); + xProps->setPropertyValue( "ColorPalette", uno::Any( xIndex ) ); + } + +} +// PALETTE record - color information ========================================= + +XclImpPalette::XclImpPalette( const XclImpRoot& rRoot ) : + XclDefaultPalette( rRoot ), mrRoot( rRoot ) +{ +} + +void XclImpPalette::Initialize() +{ + maColorTable.clear(); +} + +Color XclImpPalette::GetColor( sal_uInt16 nXclIndex ) const +{ + if( nXclIndex >= EXC_COLOR_USEROFFSET ) + { + sal_uInt32 nIx = nXclIndex - EXC_COLOR_USEROFFSET; + if( nIx < maColorTable.size() ) + return maColorTable[ nIx ]; + } + return GetDefColor( nXclIndex ); +} + +void XclImpPalette::ReadPalette( XclImpStream& rStrm ) +{ + sal_uInt16 nCount; + nCount = rStrm.ReaduInt16(); + + const size_t nMinRecordSize = 4; + const size_t nMaxRecords = rStrm.GetRecLeft() / nMinRecordSize; + if (nCount > nMaxRecords) + { + SAL_WARN("sc", "Parsing error: " << nMaxRecords << + " max possible entries, but " << nCount << " claimed, truncating"); + nCount = nMaxRecords; + } + + maColorTable.resize( nCount ); + Color aColor; + for( sal_uInt16 nIndex = 0; nIndex < nCount; ++nIndex ) + { + rStrm >> aColor; + maColorTable[ nIndex ] = aColor; + } + ExportPalette(); +} + +// FONT record - font information ============================================= +XclImpFont::XclImpFont( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mbHasCharSet( false ), + mbHasWstrn( true ), + mbHasAsian( false ), + mbHasCmplx( false ) +{ + SetAllUsedFlags( false ); +} + +XclImpFont::XclImpFont( const XclImpRoot& rRoot, const XclFontData& rFontData ) : + XclImpRoot( rRoot ) +{ + SetFontData( rFontData, false ); +} + +void XclImpFont::SetAllUsedFlags( bool bUsed ) +{ + mbFontNameUsed = mbHeightUsed = mbColorUsed = mbWeightUsed = mbEscapemUsed = + mbUnderlUsed = mbItalicUsed = mbStrikeUsed = mbOutlineUsed = mbShadowUsed = bUsed; +} + +void XclImpFont::SetFontData( const XclFontData& rFontData, bool bHasCharSet ) +{ + maData = rFontData; + mbHasCharSet = bHasCharSet; + if( !maData.maStyle.isEmpty() ) + { + if( SfxObjectShell* pDocShell = GetDocShell() ) + { + if( const SvxFontListItem* pInfoItem = static_cast< const SvxFontListItem* >( + pDocShell->GetItem( SID_ATTR_CHAR_FONTLIST ) ) ) + { + if( const FontList* pFontList = pInfoItem->GetFontList() ) + { + FontMetric aFontMetric( pFontList->Get( maData.maName, maData.maStyle ) ); + maData.SetScWeight( aFontMetric.GetWeight() ); + maData.SetScPosture( aFontMetric.GetItalic() ); + } + } + } + maData.maStyle.clear(); + } + GuessScriptType(); + SetAllUsedFlags( true ); +} + +rtl_TextEncoding XclImpFont::GetFontEncoding() const +{ + // #i63105# use text encoding from FONT record + // #i67768# BIFF2-BIFF4 FONT records do not contain character set + rtl_TextEncoding eFontEnc = mbHasCharSet ? maData.GetFontEncoding() : GetTextEncoding(); + return (eFontEnc == RTL_TEXTENCODING_DONTKNOW) ? GetTextEncoding() : eFontEnc; +} + +void XclImpFont::ReadFont( XclImpStream& rStrm ) +{ + switch( GetBiff() ) + { + case EXC_BIFF2: + ReadFontData2( rStrm ); + ReadFontName2( rStrm ); + break; + case EXC_BIFF3: + case EXC_BIFF4: + ReadFontData2( rStrm ); + ReadFontColor( rStrm ); + ReadFontName2( rStrm ); + break; + case EXC_BIFF5: + ReadFontData5( rStrm ); + ReadFontName2( rStrm ); + break; + case EXC_BIFF8: + ReadFontData5( rStrm ); + ReadFontName8( rStrm ); + break; + default: + DBG_ERROR_BIFF(); + return; + } + GuessScriptType(); + SetAllUsedFlags( true ); +} + +void XclImpFont::ReadEfont( XclImpStream& rStrm ) +{ + ReadFontColor( rStrm ); +} + +void XclImpFont::ReadCFFontBlock( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() == EXC_BIFF8 ); + if( GetBiff() != EXC_BIFF8 ) + return; + + rStrm.Ignore( 64 ); + sal_uInt32 nHeight = rStrm.ReaduInt32(); + sal_uInt32 nStyle = rStrm.ReaduInt32(); + sal_uInt16 nWeight = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); //nEscapem + sal_uInt8 nUnderl = rStrm.ReaduInt8(); + rStrm.Ignore( 3 ); + sal_uInt32 nColor = rStrm.ReaduInt32(); + rStrm.Ignore( 4 ); + sal_uInt32 nFontFlags1 = rStrm.ReaduInt32(); + rStrm.Ignore( 4 ); //nFontFlags2 + sal_uInt32 nFontFlags3 = rStrm.ReaduInt32(); + rStrm.Ignore( 18 ); + + if( (mbHeightUsed = (nHeight <= 0x7FFF)) ) + maData.mnHeight = static_cast< sal_uInt16 >( nHeight ); + if( (mbWeightUsed = !::get_flag( nFontFlags1, EXC_CF_FONT_STYLE ) && (nWeight < 0x7FFF)) ) + maData.mnWeight = nWeight; + if( (mbItalicUsed = !::get_flag( nFontFlags1, EXC_CF_FONT_STYLE )) ) + maData.mbItalic = ::get_flag( nStyle, EXC_CF_FONT_STYLE ); + if( (mbUnderlUsed = !::get_flag( nFontFlags3, EXC_CF_FONT_UNDERL ) && (nUnderl <= 0x7F)) ) + maData.mnUnderline = nUnderl; + if( (mbColorUsed = (nColor <= 0x7FFF)) ) + maData.maColor = GetPalette().GetColor( static_cast< sal_uInt16 >( nColor ) ); + if( (mbStrikeUsed = !::get_flag( nFontFlags1, EXC_CF_FONT_STRIKEOUT )) ) + maData.mbStrikeout = ::get_flag( nStyle, EXC_CF_FONT_STRIKEOUT ); +} + +void XclImpFont::FillToItemSet( SfxItemSet& rItemSet, XclFontItemType eType, bool bSkipPoolDefs ) const +{ + // true = edit engine Which-IDs (EE_CHAR_*); false = Calc Which-IDs (ATTR_*) + bool bEE = eType != XclFontItemType::Cell; + +// item = the item to put into the item set +// sc_which = the Calc Which-ID of the item +// ee_which = the edit engine Which-ID of the item +#define PUTITEM( item, sc_which, ee_which ) \ + ScfTools::PutItem( rItemSet, item, (bEE ? (static_cast(ee_which)) : (sc_which)), bSkipPoolDefs ) + +// Font item + if( mbFontNameUsed ) + { + rtl_TextEncoding eFontEnc = maData.GetFontEncoding(); + rtl_TextEncoding eTempTextEnc = (bEE && (eFontEnc == GetTextEncoding())) ? + ScfTools::GetSystemTextEncoding() : eFontEnc; + + //add corresponding pitch for FontFamily + FontPitch ePitch = PITCH_DONTKNOW; + FontFamily eFtFamily = maData.GetScFamily( GetTextEncoding() ); + switch( eFtFamily ) //refer http://msdn.microsoft.com/en-us/library/aa246306(v=VS.60).aspx + { + case FAMILY_ROMAN: ePitch = PITCH_VARIABLE; break; + case FAMILY_SWISS: ePitch = PITCH_VARIABLE; break; + case FAMILY_MODERN: ePitch = PITCH_FIXED; break; + default: break; + } + SvxFontItem aFontItem( eFtFamily , maData.maName, OUString(), ePitch, eTempTextEnc, ATTR_FONT ); + + // set only for valid script types + if( mbHasWstrn ) + PUTITEM( aFontItem, ATTR_FONT, EE_CHAR_FONTINFO ); + if( mbHasAsian ) + PUTITEM( aFontItem, ATTR_CJK_FONT, EE_CHAR_FONTINFO_CJK ); + if( mbHasCmplx ) + PUTITEM( aFontItem, ATTR_CTL_FONT, EE_CHAR_FONTINFO_CTL ); + } + +// Font height (for all script types) + if( mbHeightUsed ) + { + sal_Int32 nHeight = maData.mnHeight; + if( bEE && (eType != XclFontItemType::HeaderFooter) ) // do not convert header/footer height + nHeight = convertTwipToMm100(nHeight); + + SvxFontHeightItem aHeightItem( nHeight, 100, ATTR_FONT_HEIGHT ); + PUTITEM( aHeightItem, ATTR_FONT_HEIGHT, EE_CHAR_FONTHEIGHT ); + PUTITEM( aHeightItem, ATTR_CJK_FONT_HEIGHT, EE_CHAR_FONTHEIGHT_CJK ); + PUTITEM( aHeightItem, ATTR_CTL_FONT_HEIGHT, EE_CHAR_FONTHEIGHT_CTL ); + } + +// Font color - pass AUTO_COL to item + if( mbColorUsed ) + PUTITEM( SvxColorItem( maData.maColor, ATTR_FONT_COLOR ), ATTR_FONT_COLOR, EE_CHAR_COLOR ); + +// Font weight (for all script types) + if( mbWeightUsed ) + { + SvxWeightItem aWeightItem( maData.GetScWeight(), ATTR_FONT_WEIGHT ); + PUTITEM( aWeightItem, ATTR_FONT_WEIGHT, EE_CHAR_WEIGHT ); + PUTITEM( aWeightItem, ATTR_CJK_FONT_WEIGHT, EE_CHAR_WEIGHT_CJK ); + PUTITEM( aWeightItem, ATTR_CTL_FONT_WEIGHT, EE_CHAR_WEIGHT_CTL ); + } + +// Font underline + if( mbUnderlUsed ) + { + SvxUnderlineItem aUnderlItem( maData.GetScUnderline(), ATTR_FONT_UNDERLINE ); + PUTITEM( aUnderlItem, ATTR_FONT_UNDERLINE, EE_CHAR_UNDERLINE ); + } + +// Font posture (for all script types) + if( mbItalicUsed ) + { + SvxPostureItem aPostItem( maData.GetScPosture(), ATTR_FONT_POSTURE ); + PUTITEM( aPostItem, ATTR_FONT_POSTURE, EE_CHAR_ITALIC ); + PUTITEM( aPostItem, ATTR_CJK_FONT_POSTURE, EE_CHAR_ITALIC_CJK ); + PUTITEM( aPostItem, ATTR_CTL_FONT_POSTURE, EE_CHAR_ITALIC_CTL ); + } + +// Boolean attributes crossed out, contoured, shadowed + if( mbStrikeUsed ) + PUTITEM( SvxCrossedOutItem( maData.GetScStrikeout(), ATTR_FONT_CROSSEDOUT ), ATTR_FONT_CROSSEDOUT, EE_CHAR_STRIKEOUT ); + if( mbOutlineUsed ) + PUTITEM( SvxContourItem( maData.mbOutline, ATTR_FONT_CONTOUR ), ATTR_FONT_CONTOUR, EE_CHAR_OUTLINE ); + if( mbShadowUsed ) + PUTITEM( SvxShadowedItem( maData.mbShadow, ATTR_FONT_SHADOWED ), ATTR_FONT_SHADOWED, EE_CHAR_SHADOW ); + +// Super-/subscript: only on edit engine objects + if( mbEscapemUsed && bEE ) + rItemSet.Put( SvxEscapementItem( maData.GetScEscapement(), EE_CHAR_ESCAPEMENT ) ); + +#undef PUTITEM +} + +void XclImpFont::WriteFontProperties( ScfPropertySet& rPropSet, + XclFontPropSetType eType, const Color* pFontColor ) const +{ + GetFontPropSetHelper().WriteFontProperties( + rPropSet, eType, maData, mbHasWstrn, mbHasAsian, mbHasCmplx, pFontColor ); +} + +void XclImpFont::ReadFontData2( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags; + maData.mnHeight = rStrm.ReaduInt16(); + nFlags = rStrm.ReaduInt16(); + + maData.mnWeight = ::get_flagvalue( nFlags, EXC_FONTATTR_BOLD, EXC_FONTWGHT_BOLD, EXC_FONTWGHT_NORMAL ); + maData.mnUnderline = ::get_flagvalue( nFlags, EXC_FONTATTR_UNDERLINE, EXC_FONTUNDERL_SINGLE, EXC_FONTUNDERL_NONE ); + maData.mbItalic = ::get_flag( nFlags, EXC_FONTATTR_ITALIC ); + maData.mbStrikeout = ::get_flag( nFlags, EXC_FONTATTR_STRIKEOUT ); + maData.mbOutline = ::get_flag( nFlags, EXC_FONTATTR_OUTLINE ); + maData.mbShadow = ::get_flag( nFlags, EXC_FONTATTR_SHADOW ); + mbHasCharSet = false; +} + +void XclImpFont::ReadFontData5( XclImpStream& rStrm ) +{ + sal_uInt16 nFlags; + + maData.mnHeight = rStrm.ReaduInt16(); + nFlags = rStrm.ReaduInt16(); + ReadFontColor( rStrm ); + maData.mnWeight = rStrm.ReaduInt16(); + maData.mnEscapem = rStrm.ReaduInt16(); + maData.mnUnderline = rStrm.ReaduInt8(); + maData.mnFamily = rStrm.ReaduInt8(); + maData.mnCharSet = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + + maData.mbItalic = ::get_flag( nFlags, EXC_FONTATTR_ITALIC ); + maData.mbStrikeout = ::get_flag( nFlags, EXC_FONTATTR_STRIKEOUT ); + maData.mbOutline = ::get_flag( nFlags, EXC_FONTATTR_OUTLINE ); + maData.mbShadow = ::get_flag( nFlags, EXC_FONTATTR_SHADOW ); + mbHasCharSet = maData.mnCharSet != 0; +} + +void XclImpFont::ReadFontColor( XclImpStream& rStrm ) +{ + maData.maColor = GetPalette().GetColor( rStrm.ReaduInt16() ); +} + +void XclImpFont::ReadFontName2( XclImpStream& rStrm ) +{ + maData.maName = rStrm.ReadByteString( false ); +} + +void XclImpFont::ReadFontName8( XclImpStream& rStrm ) +{ + maData.maName = rStrm.ReadUniString( rStrm.ReaduInt8() ); +} + +void XclImpFont::GuessScriptType() +{ + mbHasWstrn = true; + mbHasAsian = mbHasCmplx = false; + + // find the script types for which the font contains characters + OutputDevice* pPrinter = GetPrinter(); + if(!pPrinter) + return; + + vcl::Font aFont( maData.maName, Size( 0, 10 ) ); + FontCharMapRef xFontCharMap; + + pPrinter->SetFont( aFont ); + if( !pPrinter->GetFontCharMap( xFontCharMap ) ) + return; + + // CJK fonts + mbHasAsian = + xFontCharMap->HasChar( 0x3041 ) || // 3040-309F: Hiragana + xFontCharMap->HasChar( 0x30A1 ) || // 30A0-30FF: Katakana + xFontCharMap->HasChar( 0x3111 ) || // 3100-312F: Bopomofo + xFontCharMap->HasChar( 0x3131 ) || // 3130-318F: Hangul Compatibility Jamo + xFontCharMap->HasChar( 0x3301 ) || // 3300-33FF: CJK Compatibility + xFontCharMap->HasChar( 0x3401 ) || // 3400-4DBF: CJK Unified Ideographs Extension A + xFontCharMap->HasChar( 0x4E01 ) || // 4E00-9FFF: CJK Unified Ideographs + xFontCharMap->HasChar( 0x7E01 ) || // 4E00-9FFF: CJK Unified Ideographs + xFontCharMap->HasChar( 0xA001 ) || // A001-A48F: Yi Syllables + xFontCharMap->HasChar( 0xAC01 ) || // AC00-D7AF: Hangul Syllables + xFontCharMap->HasChar( 0xCC01 ) || // AC00-D7AF: Hangul Syllables + xFontCharMap->HasChar( 0xF901 ) || // F900-FAFF: CJK Compatibility Ideographs + xFontCharMap->HasChar( 0xFF71 ); // FF00-FFEF: Halfwidth/Fullwidth Forms + // CTL fonts + mbHasCmplx = + xFontCharMap->HasChar( 0x05D1 ) || // 0590-05FF: Hebrew + xFontCharMap->HasChar( 0x0631 ) || // 0600-06FF: Arabic + xFontCharMap->HasChar( 0x0721 ) || // 0700-074F: Syriac + xFontCharMap->HasChar( 0x0911 ) || // 0900-0DFF: Indic scripts + xFontCharMap->HasChar( 0x0E01 ) || // 0E00-0E7F: Thai + xFontCharMap->HasChar( 0xFB21 ) || // FB1D-FB4F: Hebrew Presentation Forms + xFontCharMap->HasChar( 0xFB51 ) || // FB50-FDFF: Arabic Presentation Forms-A + xFontCharMap->HasChar( 0xFE71 ); // FE70-FEFF: Arabic Presentation Forms-B + // Western fonts + mbHasWstrn = (!mbHasAsian && !mbHasCmplx) || xFontCharMap->HasChar( 'A' ); +} + +XclImpFontBuffer::XclImpFontBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + maFont4( rRoot ), + maCtrlFont( rRoot ) +{ + Initialize(); + + // default font for form controls without own font information + XclFontData aCtrlFontData; + switch( GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + aCtrlFontData.maName = "Helv"; + aCtrlFontData.mnHeight = 160; + aCtrlFontData.mnWeight = EXC_FONTWGHT_BOLD; + break; + case EXC_BIFF8: + aCtrlFontData.maName = "Tahoma"; + aCtrlFontData.mnHeight = 160; + aCtrlFontData.mnWeight = EXC_FONTWGHT_NORMAL; + break; + default: + DBG_ERROR_BIFF(); + } + maCtrlFont.SetFontData( aCtrlFontData, false ); +} + +void XclImpFontBuffer::Initialize() +{ + maFontList.clear(); + + // application font for column width calculation, later filled with first font from font list + XclFontData aAppFontData; + aAppFontData.maName = "Arial"; + aAppFontData.mnHeight = 200; + aAppFontData.mnWeight = EXC_FONTWGHT_NORMAL; + UpdateAppFont( aAppFontData, false ); +} + +const XclImpFont* XclImpFontBuffer::GetFont( sal_uInt16 nFontIndex ) const +{ + /* Font with index 4 is not stored in an Excel file, but used e.g. by + BIFF5 form pushbutton objects. It is the bold default font. + This also means that entries above 4 are out by one in the list. */ + + if (nFontIndex == 4) + return &maFont4; + + if (nFontIndex < 4) + { + // Font ID is zero-based when it's less than 4. + return nFontIndex >= maFontList.size() ? nullptr : &maFontList[nFontIndex]; + } + + // Font ID is greater than 4. It is now 1-based. + return nFontIndex > maFontList.size() ? nullptr : &maFontList[nFontIndex-1]; +} + +void XclImpFontBuffer::ReadFont( XclImpStream& rStrm ) +{ + maFontList.emplace_back( GetRoot() ); + XclImpFont& rFont = maFontList.back(); + rFont.ReadFont( rStrm ); + + if( maFontList.size() == 1 ) + { + UpdateAppFont( rFont.GetFontData(), rFont.HasCharSet() ); + } +} + +void XclImpFontBuffer::ReadEfont( XclImpStream& rStrm ) +{ + if( !maFontList.empty() ) + maFontList.back().ReadEfont( rStrm ); +} + +void XclImpFontBuffer::FillToItemSet( + SfxItemSet& rItemSet, XclFontItemType eType, + sal_uInt16 nFontIdx, bool bSkipPoolDefs ) const +{ + if( const XclImpFont* pFont = GetFont( nFontIdx ) ) + pFont->FillToItemSet( rItemSet, eType, bSkipPoolDefs ); +} + +void XclImpFontBuffer::WriteFontProperties( ScfPropertySet& rPropSet, + XclFontPropSetType eType, sal_uInt16 nFontIdx, const Color* pFontColor ) const +{ + if( const XclImpFont* pFont = GetFont( nFontIdx ) ) + pFont->WriteFontProperties( rPropSet, eType, pFontColor ); +} + +void XclImpFontBuffer::WriteDefaultCtrlFontProperties( ScfPropertySet& rPropSet ) const +{ + maCtrlFont.WriteFontProperties( rPropSet, EXC_FONTPROPSET_CONTROL ); +} + +void XclImpFontBuffer::UpdateAppFont( const XclFontData& rFontData, bool bHasCharSet ) +{ + maAppFont = rFontData; + // #i3006# Calculate the width of '0' from first font and current printer. + SetCharWidth( maAppFont ); + + // font 4 is bold font 0 + XclFontData aFont4Data( maAppFont ); + aFont4Data.mnWeight = EXC_FONTWGHT_BOLD; + maFont4.SetFontData( aFont4Data, bHasCharSet ); +} + +// FORMAT record - number formats ============================================= + +XclImpNumFmtBuffer::XclImpNumFmtBuffer( const XclImpRoot& rRoot ) : + XclNumFmtBuffer( rRoot ), + XclImpRoot( rRoot ), + mnNextXclIdx( 0 ) +{ +} + +void XclImpNumFmtBuffer::Initialize() +{ + maIndexMap.clear(); + mnNextXclIdx = 0; + InitializeImport(); // base class +} + +void XclImpNumFmtBuffer::ReadFormat( XclImpStream& rStrm ) +{ + OUString aFormat; + switch( GetBiff() ) + { + case EXC_BIFF2: + case EXC_BIFF3: + aFormat = rStrm.ReadByteString( false ); + break; + + case EXC_BIFF4: + rStrm.Ignore( 2 ); // in BIFF4 the index field exists, but is undefined + aFormat = rStrm.ReadByteString( false ); + break; + + case EXC_BIFF5: + mnNextXclIdx = rStrm.ReaduInt16(); + aFormat = rStrm.ReadByteString( false ); + break; + + case EXC_BIFF8: + mnNextXclIdx = rStrm.ReaduInt16(); + aFormat = rStrm.ReadUniString(); + break; + + default: + DBG_ERROR_BIFF(); + return; + } + + if( mnNextXclIdx < 0xFFFF ) + { + InsertFormat( mnNextXclIdx, aFormat ); + ++mnNextXclIdx; + } +} + +sal_uInt16 XclImpNumFmtBuffer::ReadCFFormat( XclImpStream& rStrm, bool bIFmt ) +{ + // internal number format ? + if(bIFmt) + { + rStrm.Ignore(1); + sal_uInt8 nIndex; + nIndex = rStrm.ReaduInt8(); + return nIndex; + } + else + { + OUString aFormat = rStrm.ReadUniString(); + InsertFormat( mnNextXclIdx, aFormat ); + ++mnNextXclIdx; + return mnNextXclIdx - 1; + } +} + +void XclImpNumFmtBuffer::CreateScFormats() +{ + OSL_ENSURE( maIndexMap.empty(), "XclImpNumFmtBuffer::CreateScFormats - already created" ); + + SvNumberFormatter& rFormatter = GetFormatter(); + for( const auto& [rXclNumFmt, rNumFmt] : GetFormatMap() ) + { + // insert/convert the Excel number format + sal_uInt32 nKey; + if( !rNumFmt.maFormat.isEmpty() ) + { + OUString aFormat( rNumFmt.maFormat ); + sal_Int32 nCheckPos; + SvNumFormatType nType = SvNumFormatType::DEFINED; + rFormatter.PutandConvertEntry( aFormat, nCheckPos, + nType, nKey, LANGUAGE_ENGLISH_US, rNumFmt.meLanguage, false); + } + else + nKey = rFormatter.GetFormatIndex( rNumFmt.meOffset, rNumFmt.meLanguage ); + + // insert the resulting format key into the Excel->Calc index map + maIndexMap[ rXclNumFmt ] = nKey; + } +} + +sal_uInt32 XclImpNumFmtBuffer::GetScFormat( sal_uInt16 nXclNumFmt ) const +{ + XclImpIndexMap::const_iterator aIt = maIndexMap.find( nXclNumFmt ); + return (aIt != maIndexMap.end()) ? aIt->second : NUMBERFORMAT_ENTRY_NOT_FOUND; +} + +void XclImpNumFmtBuffer::FillToItemSet( SfxItemSet& rItemSet, sal_uInt16 nXclNumFmt, bool bSkipPoolDefs ) const +{ + sal_uInt32 nScNumFmt = GetScFormat( nXclNumFmt ); + if( nScNumFmt == NUMBERFORMAT_ENTRY_NOT_FOUND ) + nScNumFmt = GetStdScNumFmt(); + FillScFmtToItemSet( rItemSet, nScNumFmt, bSkipPoolDefs ); +} + +void XclImpNumFmtBuffer::FillScFmtToItemSet( SfxItemSet& rItemSet, sal_uInt32 nScNumFmt, bool bSkipPoolDefs ) const +{ + OSL_ENSURE( nScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND, "XclImpNumFmtBuffer::FillScFmtToItemSet - invalid number format" ); + ScfTools::PutItem( rItemSet, SfxUInt32Item( ATTR_VALUE_FORMAT, nScNumFmt ), bSkipPoolDefs ); + if( rItemSet.GetItemState( ATTR_VALUE_FORMAT, false ) == SfxItemState::SET ) + ScGlobal::AddLanguage( rItemSet, GetFormatter() ); +} + +// XF, STYLE record - Cell formatting ========================================= + +void XclImpCellProt::FillFromXF2( sal_uInt8 nNumFmt ) +{ + mbLocked = ::get_flag( nNumFmt, EXC_XF2_LOCKED ); + mbHidden = ::get_flag( nNumFmt, EXC_XF2_HIDDEN ); +} + +void XclImpCellProt::FillFromXF3( sal_uInt16 nProt ) +{ + mbLocked = ::get_flag( nProt, EXC_XF_LOCKED ); + mbHidden = ::get_flag( nProt, EXC_XF_HIDDEN ); +} + +void XclImpCellProt::FillToItemSet( SfxItemSet& rItemSet, bool bSkipPoolDefs ) const +{ + ScfTools::PutItem( rItemSet, ScProtectionAttr( mbLocked, mbHidden ), bSkipPoolDefs ); +} + +void XclImpCellAlign::FillFromXF2( sal_uInt8 nFlags ) +{ + mnHorAlign = ::extract_value< sal_uInt8 >( nFlags, 0, 3 ); +} + +void XclImpCellAlign::FillFromXF3( sal_uInt16 nAlign ) +{ + mnHorAlign = ::extract_value< sal_uInt8 >( nAlign, 0, 3 ); + mbLineBreak = ::get_flag( nAlign, EXC_XF_LINEBREAK ); // new in BIFF3 +} + +void XclImpCellAlign::FillFromXF4( sal_uInt16 nAlign ) +{ + FillFromXF3( nAlign ); + mnVerAlign = ::extract_value< sal_uInt8 >( nAlign, 4, 2 ); // new in BIFF4 + mnOrient = ::extract_value< sal_uInt8 >( nAlign, 6, 2 ); // new in BIFF4 +} + +void XclImpCellAlign::FillFromXF5( sal_uInt16 nAlign ) +{ + mnHorAlign = ::extract_value< sal_uInt8 >( nAlign, 0, 3 ); + mnVerAlign = ::extract_value< sal_uInt8 >( nAlign, 4, 3 ); + mbLineBreak = ::get_flag( nAlign, EXC_XF_LINEBREAK ); + mnOrient = ::extract_value< sal_uInt8 >( nAlign, 8, 2 ); +} + +void XclImpCellAlign::FillFromXF8( sal_uInt16 nAlign, sal_uInt16 nMiscAttrib ) +{ + mnHorAlign = ::extract_value< sal_uInt8 >( nAlign, 0, 3 ); + mnVerAlign = ::extract_value< sal_uInt8 >( nAlign, 4, 3 ); + mbLineBreak = ::get_flag( nAlign, EXC_XF_LINEBREAK ); + mnRotation = ::extract_value< sal_uInt8 >( nAlign, 8, 8 ); // new in BIFF8 + mnIndent = ::extract_value< sal_uInt8 >( nMiscAttrib, 0, 4 ); // new in BIFF8 + mbShrink = ::get_flag( nMiscAttrib, EXC_XF8_SHRINK ); // new in BIFF8 + mnTextDir = ::extract_value< sal_uInt8 >( nMiscAttrib, 6, 2 ); // new in BIFF8 +} + +void XclImpCellAlign::FillFromCF( sal_uInt16 nAlign, sal_uInt16 nMiscAttrib ) +{ + mnHorAlign = extract_value< sal_uInt8 >( nAlign, 0, 3 ); + mbLineBreak = get_flag< sal_uInt8 >( nAlign, EXC_XF_LINEBREAK ); + mnVerAlign = ::extract_value< sal_uInt8 >( nAlign, 4, 3 ); + mnRotation = ::extract_value< sal_uInt8 >( nAlign, 8, 8 ); + mnIndent = ::extract_value< sal_uInt8 >( nMiscAttrib, 0, 4 ); + mbShrink = ::get_flag( nMiscAttrib, EXC_XF8_SHRINK ); + mnTextDir = ::extract_value< sal_uInt8 >( nMiscAttrib, 6, 2 ); +} + +void XclImpCellAlign::FillToItemSet( SfxItemSet& rItemSet, const XclImpFont* pFont, bool bSkipPoolDefs ) const +{ + // horizontal alignment + ScfTools::PutItem( rItemSet, SvxHorJustifyItem( GetScHorAlign(), ATTR_HOR_JUSTIFY ), bSkipPoolDefs ); + ScfTools::PutItem( rItemSet, SvxJustifyMethodItem( GetScHorJustifyMethod(), ATTR_HOR_JUSTIFY_METHOD ), bSkipPoolDefs ); + + // text wrap (#i74508# always if vertical alignment is justified or distributed) + bool bLineBreak = mbLineBreak || (mnVerAlign == EXC_XF_VER_JUSTIFY) || (mnVerAlign == EXC_XF_VER_DISTRIB); + ScfTools::PutItem( rItemSet, ScLineBreakCell( bLineBreak ), bSkipPoolDefs ); + + // vertical alignment + ScfTools::PutItem( rItemSet, SvxVerJustifyItem( GetScVerAlign(), ATTR_VER_JUSTIFY ), bSkipPoolDefs ); + ScfTools::PutItem( rItemSet, SvxJustifyMethodItem( GetScVerJustifyMethod(), ATTR_VER_JUSTIFY_METHOD ), bSkipPoolDefs ); + + // indent + sal_uInt16 nScIndent = mnIndent * 200; // 1 Excel unit == 10 pt == 200 twips + ScfTools::PutItem( rItemSet, ScIndentItem( nScIndent ), bSkipPoolDefs ); + + // shrink to fit + ScfTools::PutItem( rItemSet, ScShrinkToFitCell( mbShrink ), bSkipPoolDefs ); + + // text orientation/rotation (BIFF2-BIFF7 sets mnOrient) + sal_uInt8 nXclRot = (mnOrient == EXC_ORIENT_NONE) ? mnRotation : XclTools::GetXclRotFromOrient( mnOrient ); + bool bStacked = (nXclRot == EXC_ROT_STACKED); + ScfTools::PutItem( rItemSet, ScVerticalStackCell( bStacked ), bSkipPoolDefs ); + // set an angle in the range from -90 to 90 degrees + Degree100 nAngle = XclTools::GetScRotation( nXclRot, 0_deg100 ); + ScfTools::PutItem( rItemSet, ScRotateValueItem( nAngle ), bSkipPoolDefs ); + // set "Use asian vertical layout", if cell is stacked and font contains CKJ characters + bool bAsianVert = bStacked && pFont && pFont->HasAsianChars(); + ScfTools::PutItem( rItemSet, SfxBoolItem( ATTR_VERTICAL_ASIAN, bAsianVert ), bSkipPoolDefs ); + + // CTL text direction + ScfTools::PutItem( rItemSet, SvxFrameDirectionItem( GetScFrameDir(), ATTR_WRITINGDIR ), bSkipPoolDefs ); +} + +XclImpCellBorder::XclImpCellBorder() +{ + SetUsedFlags( false, false ); +} + +void XclImpCellBorder::SetUsedFlags( bool bOuterUsed, bool bDiagUsed ) +{ + mbLeftUsed = mbRightUsed = mbTopUsed = mbBottomUsed = bOuterUsed; + mbDiagUsed = bDiagUsed; +} + +void XclImpCellBorder::FillFromXF2( sal_uInt8 nFlags ) +{ + mnLeftLine = ::get_flagvalue( nFlags, EXC_XF2_LEFTLINE, EXC_LINE_THIN, EXC_LINE_NONE ); + mnRightLine = ::get_flagvalue( nFlags, EXC_XF2_RIGHTLINE, EXC_LINE_THIN, EXC_LINE_NONE ); + mnTopLine = ::get_flagvalue( nFlags, EXC_XF2_TOPLINE, EXC_LINE_THIN, EXC_LINE_NONE ); + mnBottomLine = ::get_flagvalue( nFlags, EXC_XF2_BOTTOMLINE, EXC_LINE_THIN, EXC_LINE_NONE ); + mnLeftColor = mnRightColor = mnTopColor = mnBottomColor = EXC_COLOR_BIFF2_BLACK; + SetUsedFlags( true, false ); +} + +void XclImpCellBorder::FillFromXF3( sal_uInt32 nBorder ) +{ + mnTopLine = ::extract_value< sal_uInt8 >( nBorder, 0, 3 ); + mnLeftLine = ::extract_value< sal_uInt8 >( nBorder, 8, 3 ); + mnBottomLine = ::extract_value< sal_uInt8 >( nBorder, 16, 3 ); + mnRightLine = ::extract_value< sal_uInt8 >( nBorder, 24, 3 ); + mnTopColor = ::extract_value< sal_uInt16 >( nBorder, 3, 5 ); + mnLeftColor = ::extract_value< sal_uInt16 >( nBorder, 11, 5 ); + mnBottomColor = ::extract_value< sal_uInt16 >( nBorder, 19, 5 ); + mnRightColor = ::extract_value< sal_uInt16 >( nBorder, 27, 5 ); + SetUsedFlags( true, false ); +} + +void XclImpCellBorder::FillFromXF5( sal_uInt32 nBorder, sal_uInt32 nArea ) +{ + mnTopLine = ::extract_value< sal_uInt8 >( nBorder, 0, 3 ); + mnLeftLine = ::extract_value< sal_uInt8 >( nBorder, 3, 3 ); + mnBottomLine = ::extract_value< sal_uInt8 >( nArea, 22, 3 ); + mnRightLine = ::extract_value< sal_uInt8 >( nBorder, 6, 3 ); + mnTopColor = ::extract_value< sal_uInt16 >( nBorder, 9, 7 ); + mnLeftColor = ::extract_value< sal_uInt16 >( nBorder, 16, 7 ); + mnBottomColor = ::extract_value< sal_uInt16 >( nArea, 25, 7 ); + mnRightColor = ::extract_value< sal_uInt16 >( nBorder, 23, 7 ); + SetUsedFlags( true, false ); +} + +void XclImpCellBorder::FillFromXF8( sal_uInt32 nBorder1, sal_uInt32 nBorder2 ) +{ + mnLeftLine = ::extract_value< sal_uInt8 >( nBorder1, 0, 4 ); + mnRightLine = ::extract_value< sal_uInt8 >( nBorder1, 4, 4 ); + mnTopLine = ::extract_value< sal_uInt8 >( nBorder1, 8, 4 ); + mnBottomLine = ::extract_value< sal_uInt8 >( nBorder1, 12, 4 ); + mnLeftColor = ::extract_value< sal_uInt16 >( nBorder1, 16, 7 ); + mnRightColor = ::extract_value< sal_uInt16 >( nBorder1, 23, 7 ); + mnTopColor = ::extract_value< sal_uInt16 >( nBorder2, 0, 7 ); + mnBottomColor = ::extract_value< sal_uInt16 >( nBorder2, 7, 7 ); + mbDiagTLtoBR = ::get_flag( nBorder1, EXC_XF_DIAGONAL_TL_TO_BR ); + mbDiagBLtoTR = ::get_flag( nBorder1, EXC_XF_DIAGONAL_BL_TO_TR ); + if( mbDiagTLtoBR || mbDiagBLtoTR ) + { + mnDiagLine = ::extract_value< sal_uInt8 >( nBorder2, 21, 4 ); + mnDiagColor = ::extract_value< sal_uInt16 >( nBorder2, 14, 7 ); + } + SetUsedFlags( true, true ); +} + +void XclImpCellBorder::FillFromCF8( sal_uInt16 nLineStyle, sal_uInt32 nLineColor, sal_uInt32 nFlags ) +{ + mnLeftLine = ::extract_value< sal_uInt8 >( nLineStyle, 0, 4 ); + mnRightLine = ::extract_value< sal_uInt8 >( nLineStyle, 4, 4 ); + mnTopLine = ::extract_value< sal_uInt8 >( nLineStyle, 8, 4 ); + mnBottomLine = ::extract_value< sal_uInt8 >( nLineStyle, 12, 4 ); + mnLeftColor = ::extract_value< sal_uInt16 >( nLineColor, 0, 7 ); + mnRightColor = ::extract_value< sal_uInt16 >( nLineColor, 7, 7 ); + mnTopColor = ::extract_value< sal_uInt16 >( nLineColor, 16, 7 ); + mnBottomColor = ::extract_value< sal_uInt16 >( nLineColor, 23, 7 ); + mbLeftUsed = !::get_flag( nFlags, EXC_CF_BORDER_LEFT ); + mbRightUsed = !::get_flag( nFlags, EXC_CF_BORDER_RIGHT ); + mbTopUsed = !::get_flag( nFlags, EXC_CF_BORDER_TOP ); + mbBottomUsed = !::get_flag( nFlags, EXC_CF_BORDER_BOTTOM ); + mbDiagUsed = false; +} + +bool XclImpCellBorder::HasAnyOuterBorder() const +{ + return + (mbLeftUsed && (mnLeftLine != EXC_LINE_NONE)) || + (mbRightUsed && (mnRightLine != EXC_LINE_NONE)) || + (mbTopUsed && (mnTopLine != EXC_LINE_NONE)) || + (mbBottomUsed && (mnBottomLine != EXC_LINE_NONE)); +} + +namespace { + +/** Converts the passed line style to a ::editeng::SvxBorderLine, or returns false, if style is "no line". */ +bool lclConvertBorderLine( ::editeng::SvxBorderLine& rLine, const XclImpPalette& rPalette, sal_uInt8 nXclLine, sal_uInt16 nXclColor ) +{ + static const sal_uInt16 ppnLineParam[][ 4 ] = + { + // outer width, type + { 0, table::BorderLineStyle::SOLID }, // 0 = none + { EXC_BORDER_THIN, table::BorderLineStyle::SOLID }, // 1 = thin + { EXC_BORDER_MEDIUM, table::BorderLineStyle::SOLID }, // 2 = medium + { EXC_BORDER_THIN, table::BorderLineStyle::FINE_DASHED }, // 3 = dashed + { EXC_BORDER_THIN, table::BorderLineStyle::DOTTED }, // 4 = dotted + { EXC_BORDER_THICK, table::BorderLineStyle::SOLID }, // 5 = thick + { EXC_BORDER_THICK, table::BorderLineStyle::DOUBLE_THIN }, // 6 = double + { EXC_BORDER_HAIR, table::BorderLineStyle::SOLID }, // 7 = hair + { EXC_BORDER_MEDIUM, table::BorderLineStyle::DASHED }, // 8 = med dash + { EXC_BORDER_THIN, table::BorderLineStyle::DASH_DOT }, // 9 = thin dashdot + { EXC_BORDER_MEDIUM, table::BorderLineStyle::DASH_DOT }, // A = med dashdot + { EXC_BORDER_THIN, table::BorderLineStyle::DASH_DOT_DOT }, // B = thin dashdotdot + { EXC_BORDER_MEDIUM, table::BorderLineStyle::DASH_DOT_DOT }, // C = med dashdotdot + { EXC_BORDER_MEDIUM, table::BorderLineStyle::DASH_DOT } // D = med slant dashdot + }; + + if( nXclLine == EXC_LINE_NONE ) + return false; + if( nXclLine >= SAL_N_ELEMENTS( ppnLineParam ) ) + nXclLine = EXC_LINE_THIN; + + rLine.SetColor( rPalette.GetColor( nXclColor ) ); + rLine.SetWidth( ppnLineParam[ nXclLine ][ 0 ] ); + rLine.SetBorderLineStyle( static_cast< SvxBorderLineStyle>( + ppnLineParam[ nXclLine ][ 1 ]) ); + return true; +} + +} // namespace + +void XclImpCellBorder::FillToItemSet( SfxItemSet& rItemSet, const XclImpPalette& rPalette, bool bSkipPoolDefs ) const +{ + if( mbLeftUsed || mbRightUsed || mbTopUsed || mbBottomUsed ) + { + SvxBoxItem aBoxItem( ATTR_BORDER ); + ::editeng::SvxBorderLine aLine; + if( mbLeftUsed && lclConvertBorderLine( aLine, rPalette, mnLeftLine, mnLeftColor ) ) + aBoxItem.SetLine( &aLine, SvxBoxItemLine::LEFT ); + if( mbRightUsed && lclConvertBorderLine( aLine, rPalette, mnRightLine, mnRightColor ) ) + aBoxItem.SetLine( &aLine, SvxBoxItemLine::RIGHT ); + if( mbTopUsed && lclConvertBorderLine( aLine, rPalette, mnTopLine, mnTopColor ) ) + aBoxItem.SetLine( &aLine, SvxBoxItemLine::TOP ); + if( mbBottomUsed && lclConvertBorderLine( aLine, rPalette, mnBottomLine, mnBottomColor ) ) + aBoxItem.SetLine( &aLine, SvxBoxItemLine::BOTTOM ); + ScfTools::PutItem( rItemSet, aBoxItem, bSkipPoolDefs ); + } + if( !mbDiagUsed ) + return; + + SvxLineItem aTLBRItem( ATTR_BORDER_TLBR ); + SvxLineItem aBLTRItem( ATTR_BORDER_BLTR ); + ::editeng::SvxBorderLine aLine; + if( lclConvertBorderLine( aLine, rPalette, mnDiagLine, mnDiagColor ) ) + { + if( mbDiagTLtoBR ) + aTLBRItem.SetLine( &aLine ); + if( mbDiagBLtoTR ) + aBLTRItem.SetLine( &aLine ); + } + ScfTools::PutItem( rItemSet, aTLBRItem, bSkipPoolDefs ); + ScfTools::PutItem( rItemSet, aBLTRItem, bSkipPoolDefs ); +} + +XclImpCellArea::XclImpCellArea() +{ + SetUsedFlags( false ); +} + +void XclImpCellArea::SetUsedFlags( bool bUsed ) +{ + mbForeUsed = mbBackUsed = mbPattUsed = bUsed; +} + +void XclImpCellArea::FillFromXF2( sal_uInt8 nFlags ) +{ + mnPattern = ::get_flagvalue( nFlags, EXC_XF2_BACKGROUND, EXC_PATT_12_5_PERC, EXC_PATT_NONE ); + mnForeColor = EXC_COLOR_BIFF2_BLACK; + mnBackColor = EXC_COLOR_BIFF2_WHITE; + SetUsedFlags( true ); +} + +void XclImpCellArea::FillFromXF3( sal_uInt16 nArea ) +{ + mnPattern = ::extract_value< sal_uInt8 >( nArea, 0, 6 ); + mnForeColor = ::extract_value< sal_uInt16 >( nArea, 6, 5 ); + mnBackColor = ::extract_value< sal_uInt16 >( nArea, 11, 5 ); + SetUsedFlags( true ); +} + +void XclImpCellArea::FillFromXF5( sal_uInt32 nArea ) +{ + mnPattern = ::extract_value< sal_uInt8 >( nArea, 16, 6 ); + mnForeColor = ::extract_value< sal_uInt16 >( nArea, 0, 7 ); + mnBackColor = ::extract_value< sal_uInt16 >( nArea, 7, 7 ); + SetUsedFlags( true ); +} + +void XclImpCellArea::FillFromXF8( sal_uInt32 nBorder2, sal_uInt16 nArea ) +{ + mnPattern = ::extract_value< sal_uInt8 >( nBorder2, 26, 6 ); + mnForeColor = ::extract_value< sal_uInt16 >( nArea, 0, 7 ); + mnBackColor = ::extract_value< sal_uInt16 >( nArea, 7, 7 ); + SetUsedFlags( true ); +} + +void XclImpCellArea::FillFromCF8( sal_uInt16 nPattern, sal_uInt16 nColor, sal_uInt32 nFlags ) +{ + mnForeColor = ::extract_value< sal_uInt16 >( nColor, 0, 7 ); + mnBackColor = ::extract_value< sal_uInt16 >( nColor, 7, 7 ); + mnPattern = ::extract_value< sal_uInt8 >( nPattern, 10, 6 ); + mbForeUsed = !::get_flag( nFlags, EXC_CF_AREA_FGCOLOR ); + mbBackUsed = !::get_flag( nFlags, EXC_CF_AREA_BGCOLOR ); + mbPattUsed = !::get_flag( nFlags, EXC_CF_AREA_PATTERN ); + + if( mbBackUsed && (!mbPattUsed || (mnPattern == EXC_PATT_SOLID)) ) + { + mnForeColor = mnBackColor; + mnPattern = EXC_PATT_SOLID; + mbForeUsed = mbPattUsed = true; + } + else if( !mbBackUsed && mbPattUsed && (mnPattern == EXC_PATT_SOLID) ) + { + mbPattUsed = false; + } +} + +void XclImpCellArea::FillToItemSet( SfxItemSet& rItemSet, const XclImpPalette& rPalette, bool bSkipPoolDefs ) const +{ + if( !mbPattUsed ) // colors may be both unused in cond. formats + return; + + SvxBrushItem aBrushItem( ATTR_BACKGROUND ); + + // do not use IsTransparent() - old Calc filter writes transparency with different color indexes + if( mnPattern == EXC_PATT_NONE ) + { + aBrushItem.SetColor( COL_TRANSPARENT ); + } + else + { + Color aFore( rPalette.GetColor( mbForeUsed ? mnForeColor : EXC_COLOR_WINDOWTEXT ) ); + Color aBack( rPalette.GetColor( mbBackUsed ? mnBackColor : EXC_COLOR_WINDOWBACK ) ); + aBrushItem.SetColor( XclTools::GetPatternColor( aFore, aBack, mnPattern ) ); + } + + ScfTools::PutItem( rItemSet, aBrushItem, bSkipPoolDefs ); +} + +XclImpXF::XclImpXF( const XclImpRoot& rRoot ) : + XclXFBase( true ), // default is cell XF + XclImpRoot( rRoot ), + mpStyleSheet( nullptr ), + mnXclNumFmt( 0 ), + mnXclFont( 0 ) +{ +} + +XclImpXF::~XclImpXF() +{ +} + +void XclImpXF::ReadXF2( XclImpStream& rStrm ) +{ + sal_uInt8 nReadFont, nReadNumFmt, nFlags; + nReadFont = rStrm.ReaduInt8(); + rStrm.Ignore( 1 ); + nReadNumFmt = rStrm.ReaduInt8(); + nFlags = rStrm.ReaduInt8(); + + // XF type always cell, no parent, used flags always true + SetAllUsedFlags( true ); + + // attributes + maProtection.FillFromXF2( nReadNumFmt ); + mnXclFont = nReadFont; + mnXclNumFmt = nReadNumFmt & EXC_XF2_VALFMT_MASK; + maAlignment.FillFromXF2( nFlags ); + maBorder.FillFromXF2( nFlags ); + maArea.FillFromXF2( nFlags ); +} + +void XclImpXF::ReadXF3( XclImpStream& rStrm ) +{ + sal_uInt32 nBorder; + sal_uInt16 nTypeProt, nAlign, nArea; + sal_uInt8 nReadFont, nReadNumFmt; + nReadFont = rStrm.ReaduInt8(); + nReadNumFmt = rStrm.ReaduInt8(); + nTypeProt = rStrm.ReaduInt16(); + nAlign = rStrm.ReaduInt16(); + nArea = rStrm.ReaduInt16(); + nBorder = rStrm.ReaduInt32(); + + // XF type/parent, attribute used flags + mbCellXF = !::get_flag( nTypeProt, EXC_XF_STYLE ); // new in BIFF3 + mnParent = ::extract_value< sal_uInt16 >( nAlign, 4, 12 ); // new in BIFF3 + SetUsedFlags( ::extract_value< sal_uInt8 >( nTypeProt, 10, 6 ) ); + + // attributes + maProtection.FillFromXF3( nTypeProt ); + mnXclFont = nReadFont; + mnXclNumFmt = nReadNumFmt; + maAlignment.FillFromXF3( nAlign ); + maBorder.FillFromXF3( nBorder ); + maArea.FillFromXF3( nArea ); // new in BIFF3 +} + +void XclImpXF::ReadXF4( XclImpStream& rStrm ) +{ + sal_uInt32 nBorder; + sal_uInt16 nTypeProt, nAlign, nArea; + sal_uInt8 nReadFont, nReadNumFmt; + nReadFont = rStrm.ReaduInt8(); + nReadNumFmt = rStrm.ReaduInt8(); + nTypeProt = rStrm.ReaduInt16(); + nAlign = rStrm.ReaduInt16(); + nArea = rStrm.ReaduInt16(); + nBorder = rStrm.ReaduInt32(); + + // XF type/parent, attribute used flags + mbCellXF = !::get_flag( nTypeProt, EXC_XF_STYLE ); + mnParent = ::extract_value< sal_uInt16 >( nTypeProt, 4, 12 ); + SetUsedFlags( ::extract_value< sal_uInt8 >( nAlign, 10, 6 ) ); + + // attributes + maProtection.FillFromXF3( nTypeProt ); + mnXclFont = nReadFont; + mnXclNumFmt = nReadNumFmt; + maAlignment.FillFromXF4( nAlign ); + maBorder.FillFromXF3( nBorder ); + maArea.FillFromXF3( nArea ); +} + +void XclImpXF::ReadXF5( XclImpStream& rStrm ) +{ + sal_uInt32 nArea, nBorder; + sal_uInt16 nTypeProt, nAlign; + mnXclFont = rStrm.ReaduInt16(); + mnXclNumFmt = rStrm.ReaduInt16(); + nTypeProt = rStrm.ReaduInt16(); + nAlign = rStrm.ReaduInt16(); + nArea = rStrm.ReaduInt32(); + nBorder = rStrm.ReaduInt32(); + + // XF type/parent, attribute used flags + mbCellXF = !::get_flag( nTypeProt, EXC_XF_STYLE ); + mnParent = ::extract_value< sal_uInt16 >( nTypeProt, 4, 12 ); + SetUsedFlags( ::extract_value< sal_uInt8 >( nAlign, 10, 6 ) ); + + // attributes + maProtection.FillFromXF3( nTypeProt ); + maAlignment.FillFromXF5( nAlign ); + maBorder.FillFromXF5( nBorder, nArea ); + maArea.FillFromXF5( nArea ); +} + +void XclImpXF::ReadXF8( XclImpStream& rStrm ) +{ + sal_uInt32 nBorder1, nBorder2; + sal_uInt16 nTypeProt, nAlign, nMiscAttrib, nArea; + mnXclFont = rStrm.ReaduInt16(); + mnXclNumFmt = rStrm.ReaduInt16(); + nTypeProt = rStrm.ReaduInt16(); + nAlign = rStrm.ReaduInt16(); + nMiscAttrib = rStrm.ReaduInt16(); + nBorder1 = rStrm.ReaduInt32(); + nBorder2 = rStrm.ReaduInt32( ); + nArea = rStrm.ReaduInt16(); + + // XF type/parent, attribute used flags + mbCellXF = !::get_flag( nTypeProt, EXC_XF_STYLE ); + mnParent = ::extract_value< sal_uInt16 >( nTypeProt, 4, 12 ); + SetUsedFlags( ::extract_value< sal_uInt8 >( nMiscAttrib, 10, 6 ) ); + + // attributes + maProtection.FillFromXF3( nTypeProt ); + maAlignment.FillFromXF8( nAlign, nMiscAttrib ); + maBorder.FillFromXF8( nBorder1, nBorder2 ); + maArea.FillFromXF8( nBorder2, nArea ); +} + +void XclImpXF::ReadXF( XclImpStream& rStrm ) +{ + switch( GetBiff() ) + { + case EXC_BIFF2: ReadXF2( rStrm ); break; + case EXC_BIFF3: ReadXF3( rStrm ); break; + case EXC_BIFF4: ReadXF4( rStrm ); break; + case EXC_BIFF5: ReadXF5( rStrm ); break; + case EXC_BIFF8: ReadXF8( rStrm ); break; + default: DBG_ERROR_BIFF(); + } +} + +const ScPatternAttr& XclImpXF::CreatePattern( bool bSkipPoolDefs ) +{ + if( mpPattern ) + return *mpPattern; + + // create new pattern attribute set + mpPattern.reset( new ScPatternAttr( GetDoc().GetPool() ) ); + SfxItemSet& rItemSet = mpPattern->GetItemSet(); + XclImpXF* pParentXF = IsCellXF() ? GetXFBuffer().GetXF( mnParent ) : nullptr; + + // parent cell style + if( IsCellXF() && !mpStyleSheet ) + { + mpStyleSheet = GetXFBuffer().CreateStyleSheet( mnParent ); + + /* Enables mb***Used flags, if the formatting attributes differ from + the passed XF record. In cell XFs Excel uses the cell attributes, + if they differ from the parent style XF. + ...or if the respective flag is not set in parent style XF. */ + if( pParentXF ) + { + if( !mbProtUsed ) + mbProtUsed = !pParentXF->mbProtUsed || !(maProtection == pParentXF->maProtection); + if( !mbFontUsed ) + mbFontUsed = !pParentXF->mbFontUsed || (mnXclFont != pParentXF->mnXclFont); + if( !mbFmtUsed ) + mbFmtUsed = !pParentXF->mbFmtUsed || (mnXclNumFmt != pParentXF->mnXclNumFmt); + if( !mbAlignUsed ) + mbAlignUsed = !pParentXF->mbAlignUsed || !(maAlignment == pParentXF->maAlignment); + if( !mbBorderUsed ) + mbBorderUsed = !pParentXF->mbBorderUsed || !(maBorder == pParentXF->maBorder); + if( !mbAreaUsed ) + mbAreaUsed = !pParentXF->mbAreaUsed || !(maArea == pParentXF->maArea); + } + } + + // cell protection + if( mbProtUsed ) + maProtection.FillToItemSet( rItemSet, bSkipPoolDefs ); + + // font + if( mbFontUsed ) + GetFontBuffer().FillToItemSet( rItemSet, XclFontItemType::Cell, mnXclFont, bSkipPoolDefs ); + + // value format + if( mbFmtUsed ) + { + GetNumFmtBuffer().FillToItemSet( rItemSet, mnXclNumFmt, bSkipPoolDefs ); + // Trace occurrences of Windows date formats + GetTracer().TraceDates( mnXclNumFmt ); + } + + // alignment + if( mbAlignUsed ) + maAlignment.FillToItemSet( rItemSet, GetFontBuffer().GetFont( mnXclFont ), bSkipPoolDefs ); + + // border + if( mbBorderUsed ) + { + maBorder.FillToItemSet( rItemSet, GetPalette(), bSkipPoolDefs ); + GetTracer().TraceBorderLineStyle(maBorder.mnLeftLine > EXC_LINE_HAIR || + maBorder.mnRightLine > EXC_LINE_HAIR || maBorder.mnTopLine > EXC_LINE_HAIR || + maBorder.mnBottomLine > EXC_LINE_HAIR ); + } + + // area + if( mbAreaUsed ) + { + maArea.FillToItemSet( rItemSet, GetPalette(), bSkipPoolDefs ); + GetTracer().TraceFillPattern(maArea.mnPattern != EXC_PATT_NONE && + maArea.mnPattern != EXC_PATT_SOLID); + } + + /* #i38709# Decide which rotation reference mode to use. If any outer + border line of the cell is set (either explicitly or via cell style), + and the cell contents are rotated, set rotation reference to bottom of + cell. This causes the borders to be painted rotated with the text. */ + if( mbAlignUsed || mbBorderUsed ) + { + SvxRotateMode eRotateMode = SVX_ROTATE_MODE_STANDARD; + const XclImpCellAlign* pAlign = mbAlignUsed ? &maAlignment : (pParentXF ? &pParentXF->maAlignment : nullptr); + const XclImpCellBorder* pBorder = mbBorderUsed ? &maBorder : (pParentXF ? &pParentXF->maBorder : nullptr); + if( pAlign && pBorder && (0 < pAlign->mnRotation) && (pAlign->mnRotation <= 180) && pBorder->HasAnyOuterBorder() ) + eRotateMode = SVX_ROTATE_MODE_BOTTOM; + ScfTools::PutItem( rItemSet, SvxRotateModeItem( eRotateMode, ATTR_ROTATE_MODE ), bSkipPoolDefs ); + } + + // Excel's cell margins are different from Calc's default margins. + SvxMarginItem aItem(40, 40, 40, 40, ATTR_MARGIN); + ScfTools::PutItem(rItemSet, aItem, bSkipPoolDefs); + + return *mpPattern; +} + +void XclImpXF::ApplyPatternToAttrVector( + std::vector& rAttrs, SCROW nRow1, SCROW nRow2, sal_uInt32 nForceScNumFmt) +{ + // force creation of cell style and hard formatting, do it here to have mpStyleSheet + CreatePattern(); + ScPatternAttr& rPat = *mpPattern; + + // insert into document + ScDocument& rDoc = GetDoc(); + + if (IsCellXF()) + { + if (mpStyleSheet) + { + // Apply style sheet. Don't clear the direct formats. + rPat.SetStyleSheet(mpStyleSheet, false); + } + else + { + // When the cell format is not associated with any style, use the + // 'Default' style. Some buggy XLS docs generated by apps other + // than Excel (such as 1C) may not have any built-in styles at + // all. + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + if (pStylePool) + { + ScStyleSheet* pStyleSheet = static_cast( + pStylePool->Find( + ScResId(STR_STYLENAME_STANDARD), SfxStyleFamily::Para)); + + if (pStyleSheet) + rPat.SetStyleSheet(pStyleSheet, false); + } + + } + } + + if (nForceScNumFmt != NUMBERFORMAT_ENTRY_NOT_FOUND) + { + ScPatternAttr aNumPat(rDoc.GetPool()); + GetNumFmtBuffer().FillScFmtToItemSet(aNumPat.GetItemSet(), nForceScNumFmt); + rPat.GetItemSet().Put(aNumPat.GetItemSet()); + } + + // Make sure we skip unnamed styles. + if (!rPat.GetStyleName()) + return; + + // Check for a gap between the last entry and this one. + bool bHasGap = false; + if (rAttrs.empty() && nRow1 > 0) + // First attribute range doesn't start at row 0. + bHasGap = true; + + if (!rAttrs.empty() && rAttrs.back().nEndRow + 1 < nRow1) + bHasGap = true; + + if (bHasGap) + { + // Fill this gap with the default pattern. + ScAttrEntry aEntry; + aEntry.nEndRow = nRow1 - 1; + aEntry.pPattern = rDoc.GetDefPattern(); + rAttrs.push_back(aEntry); + } + + ScAttrEntry aEntry; + aEntry.nEndRow = nRow2; + aEntry.pPattern = &rDoc.GetPool()->Put(rPat); + rAttrs.push_back(aEntry); +} + +void XclImpXF::ApplyPattern( + SCCOL nScCol1, SCROW nScRow1, SCCOL nScCol2, SCROW nScRow2, + SCTAB nScTab ) +{ + // force creation of cell style and hard formatting, do it here to have mpStyleSheet + const ScPatternAttr& rPattern = CreatePattern(); + + // insert into document + ScDocument& rDoc = GetDoc(); + if( IsCellXF() && mpStyleSheet ) + rDoc.ApplyStyleAreaTab( nScCol1, nScRow1, nScCol2, nScRow2, nScTab, *mpStyleSheet ); + if( HasUsedFlags() ) + rDoc.ApplyPatternAreaTab( nScCol1, nScRow1, nScCol2, nScRow2, nScTab, rPattern ); + +} + +/*static*/ void XclImpXF::ApplyPatternForBiff2CellFormat( const XclImpRoot& rRoot, + const ScAddress& rScPos, sal_uInt8 nFlags1, sal_uInt8 nFlags2, sal_uInt8 nFlags3 ) +{ + /* Create an XF object and let it do the work. We will have access to its + private members here. */ + XclImpXF aXF( rRoot ); + + // no used flags available in BIFF2 (always true) + aXF.SetAllUsedFlags( true ); + + // set the attributes + aXF.maProtection.FillFromXF2( nFlags1 ); + aXF.maAlignment.FillFromXF2( nFlags3 ); + aXF.maBorder.FillFromXF2( nFlags3 ); + aXF.maArea.FillFromXF2( nFlags3 ); + aXF.mnXclNumFmt = ::extract_value< sal_uInt16 >( nFlags2, 0, 6 ); + aXF.mnXclFont = ::extract_value< sal_uInt16 >( nFlags2, 6, 2 ); + + // write the attributes to the cell + aXF.ApplyPattern( rScPos.Col(), rScPos.Row(), rScPos.Col(), rScPos.Row(), rScPos.Tab() ); +} + +void XclImpXF::SetUsedFlags( sal_uInt8 nUsedFlags ) +{ + /* Notes about finding the mb***Used flags: + - In cell XFs a *set* bit means a used attribute. + - In style XFs a *cleared* bit means a used attribute. + The mb***Used members always store true, if the attribute is used. + The "mbCellXF == ::get_flag(...)" construct evaluates to true in + both mentioned cases: cell XF and set bit; or style XF and cleared bit. + */ + mbProtUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_PROT )); + mbFontUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_FONT )); + mbFmtUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_VALFMT )); + mbAlignUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_ALIGN )); + mbBorderUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_BORDER )); + mbAreaUsed = (mbCellXF == ::get_flag( nUsedFlags, EXC_XF_DIFF_AREA )); +} + +XclImpStyle::XclImpStyle( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ), + mnXfId( EXC_XF_NOTFOUND ), + mnBuiltinId( EXC_STYLE_USERDEF ), + mnLevel( EXC_STYLE_NOLEVEL ), + mbBuiltin( false ), + mbCustom( false ), + mbHidden( false ), + mpStyleSheet( nullptr ) +{ +} + +void XclImpStyle::ReadStyle( XclImpStream& rStrm ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF3 ); + + sal_uInt16 nXFIndex; + nXFIndex = rStrm.ReaduInt16(); + mnXfId = nXFIndex & EXC_STYLE_XFMASK; + mbBuiltin = ::get_flag( nXFIndex, EXC_STYLE_BUILTIN ); + + if( mbBuiltin ) + { + mnBuiltinId = rStrm.ReaduInt8(); + mnLevel = rStrm.ReaduInt8(); + } + else + { + maName = (GetBiff() <= EXC_BIFF5) ? rStrm.ReadByteString( false ) : rStrm.ReadUniString(); + // #i103281# check if this is a new built-in style introduced in XL2007 + if( (GetBiff() == EXC_BIFF8) && (rStrm.GetNextRecId() == EXC_ID_STYLEEXT) && rStrm.StartNextRecord() ) + { + sal_uInt8 nExtFlags; + rStrm.Ignore( 12 ); + nExtFlags = rStrm.ReaduInt8(); + mbBuiltin = ::get_flag( nExtFlags, EXC_STYLEEXT_BUILTIN ); + mbCustom = ::get_flag( nExtFlags, EXC_STYLEEXT_CUSTOM ); + mbHidden = ::get_flag( nExtFlags, EXC_STYLEEXT_HIDDEN ); + if( mbBuiltin ) + { + rStrm.Ignore( 1 ); // category + mnBuiltinId = rStrm.ReaduInt8(); + mnLevel = rStrm.ReaduInt8(); + } + } + } +} + +ScStyleSheet* XclImpStyle::CreateStyleSheet() +{ + // #i1624# #i1768# ignore unnamed user styles + if( !mpStyleSheet && (!maFinalName.isEmpty()) ) + { + bool bCreatePattern = false; + XclImpXF* pXF = GetXFBuffer().GetXF( mnXfId ); + + bool bDefStyle = mbBuiltin && (mnBuiltinId == EXC_STYLE_NORMAL); + if( bDefStyle ) + { + // set all flags to true to get all items in XclImpXF::CreatePattern() + if( pXF ) pXF->SetAllUsedFlags( true ); + // use existing "Default" style sheet + mpStyleSheet = static_cast< ScStyleSheet* >( GetStyleSheetPool().Find( + ScResId( STR_STYLENAME_STANDARD ), SfxStyleFamily::Para ) ); + OSL_ENSURE( mpStyleSheet, "XclImpStyle::CreateStyleSheet - Default style not found" ); + bCreatePattern = true; + } + else + { + /* #i103281# do not create another style sheet of the same name, + if it exists already. This is needed to prevent that styles + pasted from clipboard get duplicated over and over. */ + mpStyleSheet = static_cast< ScStyleSheet* >( GetStyleSheetPool().Find( maFinalName, SfxStyleFamily::Para ) ); + if( !mpStyleSheet ) + { + mpStyleSheet = &static_cast< ScStyleSheet& >( GetStyleSheetPool().Make( maFinalName, SfxStyleFamily::Para, SfxStyleSearchBits::UserDefined ) ); + bCreatePattern = true; + } + } + + // bDefStyle==true omits default pool items in CreatePattern() + if( bCreatePattern && mpStyleSheet && pXF ) + mpStyleSheet->GetItemSet().Put( pXF->CreatePattern( bDefStyle ).GetItemSet() ); + } + return mpStyleSheet; +} + +void XclImpStyle::CreateUserStyle( const OUString& rFinalName ) +{ + maFinalName = rFinalName; + if( !IsBuiltin() || mbCustom ) + CreateStyleSheet(); +} + +XclImpXFBuffer::XclImpXFBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpXFBuffer::Initialize() +{ + maXFList.clear(); + maBuiltinStyles.clear(); + maUserStyles.clear(); + maStylesByXf.clear(); +} + +void XclImpXFBuffer::ReadXF( XclImpStream& rStrm ) +{ + std::unique_ptr xXF = std::make_unique(GetRoot()); + xXF->ReadXF(rStrm); + maXFList.emplace_back(std::move(xXF)); +} + +void XclImpXFBuffer::ReadStyle( XclImpStream& rStrm ) +{ + std::unique_ptr xStyle(std::make_unique(GetRoot())); + xStyle->ReadStyle(rStrm); + XclImpStyleList& rStyleList = (xStyle->IsBuiltin() ? maBuiltinStyles : maUserStyles); + rStyleList.emplace_back(std::move(xStyle)); + XclImpStyle* pStyle = rStyleList.back().get(); + OSL_ENSURE( maStylesByXf.count( pStyle->GetXfId() ) == 0, "XclImpXFBuffer::ReadStyle - multiple styles with equal XF identifier" ); + maStylesByXf[ pStyle->GetXfId() ] = pStyle; +} + +sal_uInt16 XclImpXFBuffer::GetFontIndex( sal_uInt16 nXFIndex ) const +{ + const XclImpXF* pXF = GetXF( nXFIndex ); + return pXF ? pXF->GetFontIndex() : EXC_FONT_NOTFOUND; +} + +const XclImpFont* XclImpXFBuffer::GetFont( sal_uInt16 nXFIndex ) const +{ + return GetFontBuffer().GetFont( GetFontIndex( nXFIndex ) ); +} + +namespace { + +/** Functor for case-insensitive string comparison, usable in maps etc. */ +struct IgnoreCaseCompare +{ + bool operator()( std::u16string_view rName1, std::u16string_view rName2 ) const + { return o3tl::compareToIgnoreAsciiCase( rName1, rName2 ) < 0; } +}; + +} // namespace + +void XclImpXFBuffer::CreateUserStyles() +{ + // calculate final names of all styles + std::map< OUString, XclImpStyle*, IgnoreCaseCompare > aCellStyles; + std::vector< XclImpStyle* > aConflictNameStyles; + + /* First, reserve style names that are built-in in Calc. This causes that + imported cell styles get different unused names and thus do not try to + overwrite these built-in styles. For BIFF4 workbooks (which contain a + separate list of cell styles per sheet), reserve all existing styles if + current sheet is not the first sheet (this styles buffer will be + initialized again for every new sheet). This will create unique names + for styles in different sheets with the same name. Assuming that the + BIFF4W import filter is never used to import from clipboard... */ + bool bReserveAll = (GetBiff() == EXC_BIFF4) && (GetCurrScTab() > 0); + SfxStyleSheetIterator aStyleIter( GetDoc().GetStyleSheetPool(), SfxStyleFamily::Para ); + OUString aStandardName = ScResId( STR_STYLENAME_STANDARD ); + for( SfxStyleSheetBase* pStyleSheet = aStyleIter.First(); pStyleSheet; pStyleSheet = aStyleIter.Next() ) + if( (pStyleSheet->GetName() != aStandardName) && (bReserveAll || !pStyleSheet->IsUserDefined()) ) + if( aCellStyles.count( pStyleSheet->GetName() ) == 0 ) + aCellStyles[ pStyleSheet->GetName() ] = nullptr; + + /* Calculate names of built-in styles. Store styles with reserved names + in the aConflictNameStyles list. */ + for( const auto& rxStyle : maBuiltinStyles ) + { + OUString aStyleName = XclTools::GetBuiltInStyleName( rxStyle->GetBuiltinId(), rxStyle->GetName(), rxStyle->GetLevel() ); + OSL_ENSURE( bReserveAll || (aCellStyles.count( aStyleName ) == 0), + "XclImpXFBuffer::CreateUserStyles - multiple styles with equal built-in identifier" ); + if( aCellStyles.count( aStyleName ) > 0 ) + aConflictNameStyles.push_back( rxStyle.get() ); + else + aCellStyles[ aStyleName ] = rxStyle.get(); + } + + /* Calculate names of user defined styles. Store styles with reserved + names in the aConflictNameStyles list. */ + for( const auto& rxStyle : maUserStyles ) + { + // #i1624# #i1768# ignore unnamed user styles + if( !rxStyle->GetName().isEmpty() ) + { + if( aCellStyles.count( rxStyle->GetName() ) > 0 ) + aConflictNameStyles.push_back( rxStyle.get() ); + else + aCellStyles[ rxStyle->GetName() ] = rxStyle.get(); + } + } + + // find unused names for all styles with conflicting names + for( XclImpStyle* pStyle : aConflictNameStyles ) + { + OUString aUnusedName; + sal_Int32 nIndex = 0; + do + { + aUnusedName = pStyle->GetName() + " " + OUString::number( ++nIndex ); + } + while( aCellStyles.count( aUnusedName ) > 0 ); + aCellStyles[ aUnusedName ] = pStyle; + } + + // set final names and create user-defined and modified built-in cell styles + for( auto& [rStyleName, rpStyle] : aCellStyles ) + if( rpStyle ) + rpStyle->CreateUserStyle( rStyleName ); +} + +ScStyleSheet* XclImpXFBuffer::CreateStyleSheet( sal_uInt16 nXFIndex ) +{ + XclImpStyleMap::iterator aIt = maStylesByXf.find( nXFIndex ); + return (aIt == maStylesByXf.end()) ? nullptr : aIt->second->CreateStyleSheet(); +} + +// Buffer for XF indexes in cells ============================================= + +bool XclImpXFRange::Expand( SCROW nScRow, const XclImpXFIndex& rXFIndex ) +{ + if( maXFIndex != rXFIndex ) + return false; + + if( mnScRow2 + 1 == nScRow ) + { + ++mnScRow2; + return true; + } + if( mnScRow1 > 0 && (mnScRow1 - 1 == nScRow) ) + { + --mnScRow1; + return true; + } + + return false; +} + +bool XclImpXFRange::Expand( const XclImpXFRange& rNextRange ) +{ + OSL_ENSURE( mnScRow2 < rNextRange.mnScRow1, "XclImpXFRange::Expand - rows out of order" ); + if( (maXFIndex == rNextRange.maXFIndex) && (mnScRow2 + 1 == rNextRange.mnScRow1) ) + { + mnScRow2 = rNextRange.mnScRow2; + return true; + } + return false; +} + +void XclImpXFRangeColumn::SetDefaultXF( const XclImpXFIndex& rXFIndex, const XclImpRoot& rRoot ) +{ + // List should be empty when inserting the default column format. + // Later explicit SetXF() calls will break up this range. + OSL_ENSURE( maIndexList.empty(), "XclImpXFRangeColumn::SetDefaultXF - Setting Default Column XF is not empty" ); + + // insert a complete row range with one insert. + maIndexList.push_back( std::make_unique( 0, rRoot.GetDoc().MaxRow(), rXFIndex ) ); +} + +void XclImpXFRangeColumn::SetXF( SCROW nScRow, const XclImpXFIndex& rXFIndex ) +{ + XclImpXFRange* pPrevRange; + XclImpXFRange* pNextRange; + sal_uLong nNextIndex; + + Find( pPrevRange, pNextRange, nNextIndex, nScRow ); + + // previous range: + // try to overwrite XF (if row is contained in) or try to expand range + if( pPrevRange ) + { + if( pPrevRange->Contains( nScRow ) ) // overwrite old XF + { + if( rXFIndex == pPrevRange->maXFIndex ) + return; + + SCROW nFirstScRow = pPrevRange->mnScRow1; + SCROW nLastScRow = pPrevRange->mnScRow2; + sal_uLong nIndex = nNextIndex - 1; + XclImpXFRange* pThisRange = pPrevRange; + pPrevRange = (nIndex > 0 && nIndex <= maIndexList.size()) ? maIndexList[ nIndex - 1 ].get() : nullptr; + + if( nFirstScRow == nLastScRow ) // replace solely XF + { + pThisRange->maXFIndex = rXFIndex; + TryConcatPrev( nNextIndex ); // try to concat. next with this + TryConcatPrev( nIndex ); // try to concat. this with previous + } + else if( nFirstScRow == nScRow ) // replace first XF + { + ++(pThisRange->mnScRow1); + // try to concatenate with previous of this + if( !pPrevRange || !pPrevRange->Expand( nScRow, rXFIndex ) ) + Insert( new XclImpXFRange( nScRow, rXFIndex ), nIndex ); + } + else if( nLastScRow == nScRow ) // replace last XF + { + --(pThisRange->mnScRow2); + if( !pNextRange || !pNextRange->Expand( nScRow, rXFIndex ) ) + Insert( new XclImpXFRange( nScRow, rXFIndex ), nNextIndex ); + } + else // insert in the middle of the range + { + pThisRange->mnScRow1 = nScRow + 1; + // List::Insert() moves entries towards end of list, so insert twice at nIndex + Insert( new XclImpXFRange( nScRow, rXFIndex ), nIndex ); + Insert( new XclImpXFRange( nFirstScRow, nScRow - 1, pThisRange->maXFIndex ), nIndex ); + } + return; + } + else if( pPrevRange->Expand( nScRow, rXFIndex ) ) // try to expand + { + TryConcatPrev( nNextIndex ); // try to concatenate next with expanded + return; + } + } + + // try to expand next range + if( pNextRange && pNextRange->Expand( nScRow, rXFIndex ) ) + return; + + // create new range + Insert( new XclImpXFRange( nScRow, rXFIndex ), nNextIndex ); +} + +void XclImpXFRangeColumn::Insert(XclImpXFRange* pXFRange, sal_uLong nIndex) +{ + maIndexList.insert( maIndexList.begin() + nIndex, std::unique_ptr(pXFRange) ); +} + +void XclImpXFRangeColumn::Find( + XclImpXFRange*& rpPrevRange, XclImpXFRange*& rpNextRange, + sal_uLong& rnNextIndex, SCROW nScRow ) +{ + + // test whether list is empty + if( maIndexList.empty() ) + { + rpPrevRange = rpNextRange = nullptr; + rnNextIndex = 0; + return; + } + + rpPrevRange = maIndexList.front().get(); + rpNextRange = maIndexList.back().get(); + + // test whether row is at end of list (contained in or behind last range) + // rpPrevRange will contain a possible existing row + if( rpNextRange->mnScRow1 <= nScRow ) + { + rpPrevRange = rpNextRange; + rpNextRange = nullptr; + rnNextIndex = maIndexList.size(); + return; + } + + // test whether row is at beginning of list (really before first range) + if( nScRow < rpPrevRange->mnScRow1 ) + { + rpNextRange = rpPrevRange; + rpPrevRange = nullptr; + rnNextIndex = 0; + return; + } + + // loop: find range entries before and after new row + // break the loop if there is no more range between first and last -or- + // if rpPrevRange contains nScRow (rpNextRange will never contain nScRow) + sal_uLong nPrevIndex = 0; + sal_uLong nMidIndex; + rnNextIndex = maIndexList.size() - 1; + XclImpXFRange* pMidRange; + while( ((rnNextIndex - nPrevIndex) > 1) && (rpPrevRange->mnScRow2 < nScRow) ) + { + nMidIndex = (nPrevIndex + rnNextIndex) / 2; + pMidRange = maIndexList[nMidIndex].get(); + OSL_ENSURE( pMidRange, "XclImpXFRangeColumn::Find - missing XF index range" ); + if( nScRow < pMidRange->mnScRow1 ) // row is really before pMidRange + { + rpNextRange = pMidRange; + rnNextIndex = nMidIndex; + } + else // row is in or after pMidRange + { + rpPrevRange = pMidRange; + nPrevIndex = nMidIndex; + } + } + + // find next rpNextRange if rpPrevRange contains nScRow + if( nScRow <= rpPrevRange->mnScRow2 ) + { + rnNextIndex = nPrevIndex + 1; + rpNextRange = maIndexList[rnNextIndex].get(); + } +} + +void XclImpXFRangeColumn::TryConcatPrev( sal_uLong nIndex ) +{ + if( !nIndex || nIndex >= maIndexList.size() ) + return; + + XclImpXFRange& prevRange = *maIndexList[ nIndex - 1 ]; + XclImpXFRange& nextRange = *maIndexList[ nIndex ]; + + if( prevRange.Expand( nextRange ) ) + maIndexList.erase( maIndexList.begin() + nIndex ); +} + +XclImpXFRangeBuffer::XclImpXFRangeBuffer( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +XclImpXFRangeBuffer::~XclImpXFRangeBuffer() +{ +} + +void XclImpXFRangeBuffer::Initialize() +{ + maColumns.clear(); + maHyperlinks.clear(); + maMergeList.RemoveAll(); +} + +void XclImpXFRangeBuffer::SetXF( const ScAddress& rScPos, sal_uInt16 nXFIndex, XclImpXFInsertMode eMode ) +{ + SCCOL nScCol = rScPos.Col(); + SCROW nScRow = rScPos.Row(); + + // set cell XF's + size_t nIndex = static_cast< size_t >( nScCol ); + if( maColumns.size() <= nIndex ) + maColumns.resize( nIndex + 1 ); + if( !maColumns[ nIndex ] ) + maColumns[ nIndex ] = std::make_shared(); + // remember all Boolean cells, they will get 'Standard' number format + maColumns[ nIndex ]->SetXF( nScRow, XclImpXFIndex( nXFIndex, eMode == xlXFModeBoolCell ) ); + + // set "center across selection" and "fill" attribute for all following empty cells + // ignore it on row default XFs + if( eMode == xlXFModeRow ) + return; + + const XclImpXF* pXF = GetXFBuffer().GetXF( nXFIndex ); + if( pXF && ((pXF->GetHorAlign() == EXC_XF_HOR_CENTER_AS) || (pXF->GetHorAlign() == EXC_XF_HOR_FILL)) ) + { + // expand last merged range if this attribute is set repeatedly + ScRange* pRange = maMergeList.empty() ? nullptr : &maMergeList.back(); + if (pRange && (pRange->aEnd.Row() == nScRow) && (pRange->aEnd.Col() + 1 == nScCol) && (eMode == xlXFModeBlank)) + pRange->aEnd.IncCol(); + else if( eMode != xlXFModeBlank ) // do not merge empty cells + maMergeList.push_back( ScRange( nScCol, nScRow, 0 ) ); + } +} + +void XclImpXFRangeBuffer::SetXF( const ScAddress& rScPos, sal_uInt16 nXFIndex ) +{ + SetXF( rScPos, nXFIndex, xlXFModeCell ); +} + +void XclImpXFRangeBuffer::SetBlankXF( const ScAddress& rScPos, sal_uInt16 nXFIndex ) +{ + SetXF( rScPos, nXFIndex, xlXFModeBlank ); +} + +void XclImpXFRangeBuffer::SetBoolXF( const ScAddress& rScPos, sal_uInt16 nXFIndex ) +{ + SetXF( rScPos, nXFIndex, xlXFModeBoolCell ); +} + +void XclImpXFRangeBuffer::SetRowDefXF( SCROW nScRow, sal_uInt16 nXFIndex ) +{ + for( SCCOL nScCol = 0; nScCol <= GetDoc().MaxCol(); ++nScCol ) + SetXF( ScAddress( nScCol, nScRow, 0 ), nXFIndex, xlXFModeRow ); +} + +void XclImpXFRangeBuffer::SetColumnDefXF( SCCOL nScCol, sal_uInt16 nXFIndex ) +{ + // our array should not have values when creating the default column format. + size_t nIndex = static_cast< size_t >( nScCol ); + if( maColumns.size() <= nIndex ) + maColumns.resize( nIndex + 1 ); + OSL_ENSURE( !maColumns[ nIndex ], "XclImpXFRangeBuffer::SetColumnDefXF - default column of XFs already has values" ); + maColumns[ nIndex ] = std::make_shared(); + maColumns[ nIndex ]->SetDefaultXF( XclImpXFIndex( nXFIndex ), GetRoot()); +} + +void XclImpXFRangeBuffer::SetBorderLine( const ScRange& rRange, SCTAB nScTab, SvxBoxItemLine nLine ) +{ + SCCOL nFromScCol = (nLine == SvxBoxItemLine::RIGHT) ? rRange.aEnd.Col() : rRange.aStart.Col(); + SCROW nFromScRow = (nLine == SvxBoxItemLine::BOTTOM) ? rRange.aEnd.Row() : rRange.aStart.Row(); + ScDocument& rDoc = GetDoc(); + + const SvxBoxItem* pFromItem = + rDoc.GetAttr( nFromScCol, nFromScRow, nScTab, ATTR_BORDER ); + const SvxBoxItem* pToItem = + rDoc.GetAttr( rRange.aStart.Col(), rRange.aStart.Row(), nScTab, ATTR_BORDER ); + + SvxBoxItem aNewItem( *pToItem ); + aNewItem.SetLine( pFromItem->GetLine( nLine ), nLine ); + rDoc.ApplyAttr( rRange.aStart.Col(), rRange.aStart.Row(), nScTab, aNewItem ); +} + +void XclImpXFRangeBuffer::SetHyperlink( const XclRange& rXclRange, const OUString& rUrl ) +{ + maHyperlinks.emplace_back( rXclRange, rUrl ); +} + +void XclImpXFRangeBuffer::SetMerge( SCCOL nScCol1, SCROW nScRow1, SCCOL nScCol2, SCROW nScRow2 ) +{ + if( (nScCol1 < nScCol2) || (nScRow1 < nScRow2) ) + maMergeList.push_back( ScRange( nScCol1, nScRow1, 0, nScCol2, nScRow2, 0 ) ); +} + +void XclImpXFRangeBuffer::Finalize() +{ + ScDocumentImport& rDocImport = GetDocImport(); + ScDocument& rDoc = rDocImport.getDoc(); + SCTAB nScTab = GetCurrScTab(); + + // apply patterns + XclImpXFBuffer& rXFBuffer = GetXFBuffer(); + ScDocumentImport::Attrs aPendingAttrParam; + SCCOL pendingColStart = -1; + SCCOL pendingColEnd = -1; + SCCOL nScCol = 0; + for( const auto& rxColumn : maColumns ) + { + // apply all cell styles of an existing column + if( rxColumn ) + { + XclImpXFRangeColumn& rColumn = *rxColumn; + std::vector aAttrs; + aAttrs.reserve(rColumn.end() - rColumn.begin()); + + for (const auto& rxStyle : rColumn) + { + XclImpXFRange& rStyle = *rxStyle; + const XclImpXFIndex& rXFIndex = rStyle.maXFIndex; + XclImpXF* pXF = rXFBuffer.GetXF( rXFIndex.GetXFIndex() ); + if (!pXF) + continue; + + sal_uInt32 nForceScNumFmt = rXFIndex.IsBoolCell() ? + GetNumFmtBuffer().GetStdScNumFmt() : NUMBERFORMAT_ENTRY_NOT_FOUND; + + pXF->ApplyPatternToAttrVector(aAttrs, rStyle.mnScRow1, rStyle.mnScRow2, nForceScNumFmt); + } + + if (aAttrs.empty() || aAttrs.back().nEndRow != rDoc.MaxRow()) + { + ScAttrEntry aEntry; + aEntry.nEndRow = rDoc.MaxRow(); + aEntry.pPattern = rDoc.GetDefPattern(); + aAttrs.push_back(aEntry); + } + + aAttrs.shrink_to_fit(); + assert(aAttrs.size() > 0); + ScDocumentImport::Attrs aAttrParam; + aAttrParam.mvData.swap(aAttrs); + aAttrParam.mbLatinNumFmtOnly = false; // when unsure, set it to false. + + // Compress setting the attributes, set the same set in one call. + if( pendingColStart != -1 && pendingColEnd == nScCol - 1 && aAttrParam == aPendingAttrParam ) + ++pendingColEnd; + else + { + if( pendingColStart != -1 ) + rDocImport.setAttrEntries(nScTab, pendingColStart, pendingColEnd, std::move(aPendingAttrParam)); + pendingColStart = pendingColEnd = nScCol; + aPendingAttrParam = std::move( aAttrParam ); + } + } + ++nScCol; + } + if( pendingColStart != -1 ) + rDocImport.setAttrEntries(nScTab, pendingColStart, pendingColEnd, std::move(aPendingAttrParam)); + + // insert hyperlink cells + for( const auto& [rXclRange, rUrl] : maHyperlinks ) + XclImpHyperlink::InsertUrl( GetRoot(), rXclRange, rUrl ); + + // apply cell merging + for ( size_t i = 0, nRange = maMergeList.size(); i < nRange; ++i ) + { + const ScRange & rRange = maMergeList[ i ]; + const ScAddress& rStart = rRange.aStart; + const ScAddress& rEnd = rRange.aEnd; + bool bMultiCol = rStart.Col() != rEnd.Col(); + bool bMultiRow = rStart.Row() != rEnd.Row(); + // set correct right border + if( bMultiCol ) + SetBorderLine( rRange, nScTab, SvxBoxItemLine::RIGHT ); + // set correct lower border + if( bMultiRow ) + SetBorderLine( rRange, nScTab, SvxBoxItemLine::BOTTOM ); + // do merge + if( bMultiCol || bMultiRow ) + rDoc.DoMerge( rStart.Col(), rStart.Row(), rEnd.Col(), rEnd.Row(), nScTab ); + // #i93609# merged range in a single row: test if manual row height is needed + if( !bMultiRow ) + { + bool bTextWrap = rDoc.GetAttr( rStart, ATTR_LINEBREAK )->GetValue(); + if( !bTextWrap && (rDoc.GetCellType( rStart ) == CELLTYPE_EDIT) ) + if (const EditTextObject* pEditObj = rDoc.GetEditText(rStart)) + bTextWrap = pEditObj->GetParagraphCount() > 1; + if( bTextWrap ) + GetOldRoot().pColRowBuff->SetManualRowHeight( rStart.Row() ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xiview.cxx b/sc/source/filter/excel/xiview.cxx new file mode 100644 index 000000000..49878de6e --- /dev/null +++ b/sc/source/filter/excel/xiview.cxx @@ -0,0 +1,300 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +// Document view settings ===================================================== + +XclImpDocViewSettings::XclImpDocViewSettings( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ +} + +void XclImpDocViewSettings::ReadWindow1( XclImpStream& rStrm ) +{ + maData.mnWinX = rStrm.ReaduInt16(); + maData.mnWinY = rStrm.ReaduInt16(); + maData.mnWinWidth = rStrm.ReaduInt16(); + maData.mnWinHeight = rStrm.ReaduInt16(); + maData.mnFlags = rStrm.ReaduInt16(); + if( GetBiff() >= EXC_BIFF5 ) + { + maData.mnDisplXclTab = rStrm.ReaduInt16(); + maData.mnFirstVisXclTab = rStrm.ReaduInt16(); + maData.mnXclSelectCnt = rStrm.ReaduInt16(); + maData.mnTabBarWidth = rStrm.ReaduInt16(); + } +} + +SCTAB XclImpDocViewSettings::GetDisplScTab() const +{ + /* Simply cast Excel index to Calc index. + TODO: This may fail if the document contains scenarios. */ + sal_uInt16 nMaxXclTab = static_cast< sal_uInt16 >( GetMaxPos().Tab() ); + return static_cast< SCTAB >( (maData.mnDisplXclTab <= nMaxXclTab) ? maData.mnDisplXclTab : 0 ); +} + +void XclImpDocViewSettings::Finalize() +{ + ScViewOptions aViewOpt( GetDoc().GetViewOptions() ); + aViewOpt.SetOption( VOPT_HSCROLL, ::get_flag( maData.mnFlags, EXC_WIN1_HOR_SCROLLBAR ) ); + aViewOpt.SetOption( VOPT_VSCROLL, ::get_flag( maData.mnFlags, EXC_WIN1_VER_SCROLLBAR ) ); + aViewOpt.SetOption( VOPT_TABCONTROLS, ::get_flag( maData.mnFlags, EXC_WIN1_TABBAR ) ); + GetDoc().SetViewOptions( aViewOpt ); + + // displayed sheet + GetExtDocOptions().GetDocSettings().mnDisplTab = GetDisplScTab(); + + // width of the tabbar with sheet names + if( maData.mnTabBarWidth <= 1000 ) + GetExtDocOptions().GetDocSettings().mfTabBarWidth = static_cast< double >( maData.mnTabBarWidth ) / 1000.0; +} + +// Sheet view settings ======================================================== + +namespace { + +tools::Long lclGetScZoom( sal_uInt16 nXclZoom, sal_uInt16 nDefZoom ) +{ + return static_cast< tools::Long >( nXclZoom ? nXclZoom : nDefZoom ); +} + +} // namespace + +XclImpTabViewSettings::XclImpTabViewSettings( const XclImpRoot& rRoot ) : + XclImpRoot( rRoot ) +{ + Initialize(); +} + +void XclImpTabViewSettings::Initialize() +{ + maData.SetDefaults(); +} + +void XclImpTabViewSettings::ReadTabBgColor( XclImpStream& rStrm, const XclImpPalette& rPal ) +{ + OSL_ENSURE_BIFF( GetBiff() >= EXC_BIFF8 ); + if( GetBiff() < EXC_BIFF8 ) + return; + + sal_uInt8 ColorIndex; + + rStrm.Ignore( 16 ); + ColorIndex = rStrm.ReaduInt8() & EXC_SHEETEXT_TABCOLOR; //0x7F + if ( ColorIndex >= 8 && ColorIndex <= 63 ) //only accept valid index values + { + maData.maTabBgColor = rPal.GetColor( ColorIndex ); + } +} + +void XclImpTabViewSettings::ReadWindow2( XclImpStream& rStrm, bool bChart ) +{ + if( GetBiff() == EXC_BIFF2 ) + { + maData.mbShowFormulas = rStrm.ReaduInt8() != 0; + maData.mbShowGrid = rStrm.ReaduInt8() != 0; + maData.mbShowHeadings = rStrm.ReaduInt8() != 0; + maData.mbFrozenPanes = rStrm.ReaduInt8() != 0; + maData.mbShowZeros = rStrm.ReaduInt8() != 0; + rStrm >> maData.maFirstXclPos; + maData.mbDefGridColor = rStrm.ReaduInt8() != 0; + rStrm >> maData.maGridColor; + } + else + { + sal_uInt16 nFlags; + nFlags = rStrm.ReaduInt16(); + rStrm >> maData.maFirstXclPos; + + // #i59590# real life: Excel ignores some view settings in chart sheets + maData.mbSelected = ::get_flag( nFlags, EXC_WIN2_SELECTED ); + maData.mbDisplayed = ::get_flag( nFlags, EXC_WIN2_DISPLAYED ); + maData.mbMirrored = !bChart && ::get_flag( nFlags, EXC_WIN2_MIRRORED ); + maData.mbFrozenPanes = !bChart && ::get_flag( nFlags, EXC_WIN2_FROZEN ); + maData.mbPageMode = !bChart && ::get_flag( nFlags, EXC_WIN2_PAGEBREAKMODE ); + maData.mbDefGridColor = bChart || ::get_flag( nFlags, EXC_WIN2_DEFGRIDCOLOR ); + maData.mbShowFormulas = !bChart && ::get_flag( nFlags, EXC_WIN2_SHOWFORMULAS ); + maData.mbShowGrid = bChart || ::get_flag( nFlags, EXC_WIN2_SHOWGRID ); + maData.mbShowHeadings = bChart || ::get_flag( nFlags, EXC_WIN2_SHOWHEADINGS ); + maData.mbShowZeros = bChart || ::get_flag( nFlags, EXC_WIN2_SHOWZEROS ); + maData.mbShowOutline = bChart || ::get_flag( nFlags, EXC_WIN2_SHOWOUTLINE ); + + switch( GetBiff() ) + { + case EXC_BIFF3: + case EXC_BIFF4: + case EXC_BIFF5: + rStrm >> maData.maGridColor; + break; + case EXC_BIFF8: + { + sal_uInt16 nGridColorIdx; + nGridColorIdx = rStrm.ReaduInt16(); + // zoom data not included in chart sheets + if( rStrm.GetRecLeft() >= 6 ) + { + rStrm.Ignore( 2 ); + maData.mnPageZoom = rStrm.ReaduInt16(); + maData.mnNormalZoom = rStrm.ReaduInt16(); + } + + if( !maData.mbDefGridColor ) + maData.maGridColor = GetPalette().GetColor( nGridColorIdx ); + } + break; + default: DBG_ERROR_BIFF(); + } + } + + // do not scroll chart sheets + if( bChart ) + maData.maFirstXclPos.Set( 0, 0 ); +} + +void XclImpTabViewSettings::ReadScl( XclImpStream& rStrm ) +{ + sal_uInt16 nNum, nDenom; + nNum = rStrm.ReaduInt16(); + nDenom = rStrm.ReaduInt16(); + OSL_ENSURE( nDenom > 0, "XclImpPageSettings::ReadScl - invalid denominator" ); + if( nDenom > 0 ) + maData.mnCurrentZoom = limit_cast< sal_uInt16 >( (nNum * 100) / nDenom ); +} + +void XclImpTabViewSettings::ReadPane( XclImpStream& rStrm ) +{ + maData.mnSplitX = rStrm.ReaduInt16(); + maData.mnSplitY = rStrm.ReaduInt16(); + + rStrm >> maData.maSecondXclPos; + maData.mnActivePane = rStrm.ReaduInt8(); +} + +void XclImpTabViewSettings::ReadSelection( XclImpStream& rStrm ) +{ + // pane of this selection + sal_uInt8 nPane; + nPane = rStrm.ReaduInt8(); + XclSelectionData& rSelData = maData.CreateSelectionData( nPane ); + // cursor position and selection + rStrm >> rSelData.maXclCursor; + rSelData.mnCursorIdx = rStrm.ReaduInt16(); + rSelData.maXclSelection.Read( rStrm, false ); +} + +void XclImpTabViewSettings::Finalize() +{ + SCTAB nScTab = GetCurrScTab(); + ScDocument& rDoc = GetDoc(); + XclImpAddressConverter& rAddrConv = GetAddressConverter(); + ScExtTabSettings& rTabSett = GetExtDocOptions().GetOrCreateTabSettings( nScTab ); + bool bDisplayed = GetDocViewSettings().GetDisplScTab() == nScTab; + + // *** sheet options: cursor, selection, splits, zoom *** + + // sheet flags + if( maData.mbMirrored ) + // do not call this function with sal_False, it would mirror away all drawing objects + rDoc.SetLayoutRTL( nScTab, true ); + rTabSett.mbSelected = maData.mbSelected || bDisplayed; + + // first visible cell in top-left pane and in additional pane(s) + rTabSett.maFirstVis = rAddrConv.CreateValidAddress( maData.maFirstXclPos, nScTab, false ); + rTabSett.maSecondVis = rAddrConv.CreateValidAddress( maData.maSecondXclPos, nScTab, false ); + + // cursor position and selection + if( const XclSelectionData* pSelData = maData.GetSelectionData( maData.mnActivePane ) ) + { + rTabSett.maCursor = rAddrConv.CreateValidAddress( pSelData->maXclCursor, nScTab, false ); + rAddrConv.ConvertRangeList( rTabSett.maSelection, pSelData->maXclSelection, nScTab, false ); + } + + // active pane + switch( maData.mnActivePane ) + { + case EXC_PANE_TOPLEFT: rTabSett.meActivePane = SCEXT_PANE_TOPLEFT; break; + case EXC_PANE_TOPRIGHT: rTabSett.meActivePane = SCEXT_PANE_TOPRIGHT; break; + case EXC_PANE_BOTTOMLEFT: rTabSett.meActivePane = SCEXT_PANE_BOTTOMLEFT; break; + case EXC_PANE_BOTTOMRIGHT: rTabSett.meActivePane = SCEXT_PANE_BOTTOMRIGHT; break; + } + + // freeze/split position + rTabSett.mbFrozenPanes = maData.mbFrozenPanes; + if( maData.mbFrozenPanes ) + { + /* Frozen panes: handle split position as row/column positions. + #i35812# Excel uses number of visible rows/columns, Calc uses position of freeze. */ + if( (maData.mnSplitX > 0) && (maData.maFirstXclPos.mnCol + maData.mnSplitX <= GetScMaxPos().Col()) ) + rTabSett.maFreezePos.SetCol( static_cast< SCCOL >( maData.maFirstXclPos.mnCol + maData.mnSplitX ) ); + if( (maData.mnSplitY > 0) && (maData.maFirstXclPos.mnRow + maData.mnSplitY <= o3tl::make_unsigned(GetScMaxPos().Row())) ) + rTabSett.maFreezePos.SetRow( static_cast< SCROW >( maData.maFirstXclPos.mnRow + maData.mnSplitY ) ); + } + else + { + // split window: position is in twips + rTabSett.maSplitPos.setX( static_cast< tools::Long >( maData.mnSplitX ) ); + rTabSett.maSplitPos.setY( static_cast< tools::Long >( maData.mnSplitY ) ); + } + + // grid color + if( maData.mbDefGridColor ) + rTabSett.maGridColor = COL_AUTO; + else + rTabSett.maGridColor = maData.maGridColor; + + // show grid option + rTabSett.mbShowGrid = maData.mbShowGrid; + + // view mode and zoom + if( maData.mnCurrentZoom != 0 ) + (maData.mbPageMode ? maData.mnPageZoom : maData.mnNormalZoom) = maData.mnCurrentZoom; + rTabSett.mbPageMode = maData.mbPageMode; + rTabSett.mnNormalZoom = lclGetScZoom( maData.mnNormalZoom, EXC_WIN2_NORMALZOOM_DEF ); + rTabSett.mnPageZoom = lclGetScZoom( maData.mnPageZoom, EXC_WIN2_PAGEZOOM_DEF ); + + // *** additional handling for displayed sheet *** + + if( bDisplayed ) + { + // set Excel sheet settings globally at Calc document, take settings from displayed sheet + ScViewOptions aViewOpt( rDoc.GetViewOptions() ); + aViewOpt.SetOption( VOPT_FORMULAS, maData.mbShowFormulas ); + aViewOpt.SetOption( VOPT_HEADER, maData.mbShowHeadings ); + aViewOpt.SetOption( VOPT_NULLVALS, maData.mbShowZeros ); + aViewOpt.SetOption( VOPT_OUTLINER, maData.mbShowOutline ); + rDoc.SetViewOptions( aViewOpt ); + } + + // *** set tab bg color + if ( !maData.IsDefaultTabBgColor() ) + rDoc.SetTabBgColor(nScTab, maData.maTabBgColor); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xladdress.cxx b/sc/source/filter/excel/xladdress.cxx new file mode 100644 index 000000000..20ef4fa7f --- /dev/null +++ b/sc/source/filter/excel/xladdress.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 +#include +#include +#include + +#include +#include + +void XclAddress::Read( XclImpStream& rStrm ) +{ + mnRow = rStrm.ReaduInt16(); + mnCol = rStrm.ReaduInt16(); +} + +void XclAddress::Write( XclExpStream& rStrm ) const +{ + rStrm << static_cast (mnRow); + rStrm << mnCol; +} + +bool XclRange::Contains( const XclAddress& rPos ) const +{ + return (maFirst.mnCol <= rPos.mnCol) && (rPos.mnCol <= maLast.mnCol) && + (maFirst.mnRow <= rPos.mnRow) && (rPos.mnRow <= maLast.mnRow); +} + +void XclRange::Read( XclImpStream& rStrm, bool bCol16Bit ) +{ + maFirst.mnRow = rStrm.ReaduInt16(); + maLast.mnRow = rStrm.ReaduInt16(); + + if( bCol16Bit ) + { + maFirst.mnCol = rStrm.ReaduInt16(); + maLast.mnCol = rStrm.ReaduInt16(); + } + else + { + maFirst.mnCol = rStrm.ReaduInt8(); + maLast.mnCol = rStrm.ReaduInt8(); + } +} + +void XclRange::Write( XclExpStream& rStrm, bool bCol16Bit ) const +{ + rStrm << static_cast(maFirst.mnRow) << static_cast(maLast.mnRow); + if( bCol16Bit ) + rStrm << maFirst.mnCol << maLast.mnCol; + else + rStrm << static_cast< sal_uInt8 >( maFirst.mnCol ) << static_cast< sal_uInt8 >( maLast.mnCol ); +} + +XclRange XclRangeList::GetEnclosingRange() const +{ + XclRange aXclRange; + if( !mRanges.empty() ) + { + XclRangeVector::const_iterator aIt = mRanges.begin(), aEnd = mRanges.end(); + aXclRange = *aIt; + for( ++aIt; aIt != aEnd; ++aIt ) + { + aXclRange.maFirst.mnCol = ::std::min( aXclRange.maFirst.mnCol, aIt->maFirst.mnCol ); + aXclRange.maFirst.mnRow = ::std::min( aXclRange.maFirst.mnRow, aIt->maFirst.mnRow ); + aXclRange.maLast.mnCol = ::std::max( aXclRange.maLast.mnCol, aIt->maLast.mnCol ); + aXclRange.maLast.mnRow = ::std::max( aXclRange.maLast.mnRow, aIt->maLast.mnRow ); + } + } + return aXclRange; +} + +void XclRangeList::Read( XclImpStream& rStrm, bool bCol16Bit, sal_uInt16 nCountInStream ) +{ + sal_uInt16 nCount; + if (nCountInStream) + nCount = nCountInStream; + else + nCount = rStrm.ReaduInt16(); + + if (!nCount) + return; + + XclRange aRange; + while (true) + { + aRange.Read(rStrm, bCol16Bit); + if (!rStrm.IsValid()) + break; + mRanges.emplace_back(aRange); + --nCount; + if (!nCount) + break; + } +} + +void XclRangeList::Write( XclExpStream& rStrm, bool bCol16Bit, sal_uInt16 nCountInStream ) const +{ + WriteSubList( rStrm, 0, mRanges.size(), bCol16Bit, nCountInStream ); +} + +void XclRangeList::WriteSubList( XclExpStream& rStrm, size_t nBegin, size_t nCount, bool bCol16Bit, + sal_uInt16 nCountInStream ) const +{ + OSL_ENSURE( nBegin <= mRanges.size(), "XclRangeList::WriteSubList - invalid start position" ); + size_t nEnd = ::std::min< size_t >( nBegin + nCount, mRanges.size() ); + if (!nCountInStream) + { + sal_uInt16 nXclCount = ulimit_cast< sal_uInt16 >( nEnd - nBegin ); + rStrm << nXclCount; + } + rStrm.SetSliceSize( bCol16Bit ? 8 : 6 ); + std::for_each(mRanges.begin() + nBegin, mRanges.begin() + nEnd, + [&rStrm, &bCol16Bit](const XclRange& rRange) { rRange.Write(rStrm, bCol16Bit); }); +} + +XclAddressConverterBase::XclAddressConverterBase( XclTracer& rTracer, const ScAddress& rMaxPos ) : + mrTracer( rTracer ), + maMaxPos( rMaxPos ), + mnMaxCol( static_cast< sal_uInt16 >( rMaxPos.Col() ) ), + mnMaxRow( static_cast< sal_uInt16 >( rMaxPos.Row() ) ), + mbColTrunc( false ), + mbRowTrunc( false ), + mbTabTrunc( false ) +{ + OSL_ENSURE( o3tl::make_unsigned( rMaxPos.Col() ) <= SAL_MAX_UINT16, "XclAddressConverterBase::XclAddressConverterBase - invalid max column" ); + OSL_ENSURE( o3tl::make_unsigned( rMaxPos.Row() ) <= SAL_MAX_UINT32, "XclAddressConverterBase::XclAddressConverterBase - invalid max row" ); +} + +XclAddressConverterBase::~XclAddressConverterBase() +{ +} + +void XclAddressConverterBase::CheckScTab( SCTAB nScTab ) +{ + bool bValid = (0 <= nScTab) && (nScTab <= maMaxPos.Tab()); + if( !bValid ) + { + mbTabTrunc |= (nScTab > maMaxPos.Tab()); // do not warn for deleted refs + mrTracer.TraceInvalidTab( nScTab, maMaxPos.Tab() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlchart.cxx b/sc/source/filter/excel/xlchart.cxx new file mode 100644 index 000000000..3547dad16 --- /dev/null +++ b/sc/source/filter/excel/xlchart.cxx @@ -0,0 +1,1283 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace css; + +// Common ===================================================================== + +XclChRectangle::XclChRectangle() : + mnX( 0 ), + mnY( 0 ), + mnWidth( 0 ), + mnHeight( 0 ) +{ +} + +XclChDataPointPos::XclChDataPointPos( sal_uInt16 nSeriesIdx, sal_uInt16 nPointIdx ) : + mnSeriesIdx( nSeriesIdx ), + mnPointIdx( nPointIdx ) +{ +} + +bool operator<( const XclChDataPointPos& rL, const XclChDataPointPos& rR ) +{ + return (rL.mnSeriesIdx < rR.mnSeriesIdx) || + ((rL.mnSeriesIdx == rR.mnSeriesIdx) && (rL.mnPointIdx < rR.mnPointIdx)); +} + +XclChFrBlock::XclChFrBlock( sal_uInt16 nType ) : + mnType( nType ), + mnContext( 0 ), + mnValue1( 0 ), + mnValue2( 0 ) +{ +} + +// Frame formatting =========================================================== + +XclChFramePos::XclChFramePos() : + mnTLMode( EXC_CHFRAMEPOS_PARENT ), + mnBRMode( EXC_CHFRAMEPOS_PARENT ) +{ +} + +XclChLineFormat::XclChLineFormat() : + maColor( COL_BLACK ), + mnPattern( EXC_CHLINEFORMAT_SOLID ), + mnWeight( EXC_CHLINEFORMAT_SINGLE ), + mnFlags( EXC_CHLINEFORMAT_AUTO ) +{ +} + +XclChAreaFormat::XclChAreaFormat() : + maPattColor( COL_WHITE ), + maBackColor( COL_BLACK ), + mnPattern( EXC_PATT_SOLID ), + mnFlags( EXC_CHAREAFORMAT_AUTO ) +{ +} + +XclChEscherFormat::XclChEscherFormat() +{ +} + +XclChEscherFormat::~XclChEscherFormat() +{ +} + +XclChPicFormat::XclChPicFormat() : + mnBmpMode( EXC_CHPICFORMAT_NONE ), + mnFlags( EXC_CHPICFORMAT_TOPBOTTOM | EXC_CHPICFORMAT_FRONTBACK | EXC_CHPICFORMAT_LEFTRIGHT ), + mfScale( 0.5 ) +{ +} + +XclChFrame::XclChFrame() : + mnFormat( EXC_CHFRAME_STANDARD ), + mnFlags( EXC_CHFRAME_AUTOSIZE | EXC_CHFRAME_AUTOPOS ) +{ +} + +// Source links =============================================================== + +XclChSourceLink::XclChSourceLink() : + mnDestType( EXC_CHSRCLINK_TITLE ), + mnLinkType( EXC_CHSRCLINK_DEFAULT ), + mnFlags( 0 ), + mnNumFmtIdx( 0 ) +{ +} + +// Text ======================================================================= + +XclChObjectLink::XclChObjectLink() : + mnTarget( EXC_CHOBJLINK_NONE ) +{ +} + +XclChFrLabelProps::XclChFrLabelProps() : + mnFlags( 0 ) +{ +} + +XclChText::XclChText() : + maTextColor( COL_BLACK ), + mnHAlign( EXC_CHTEXT_ALIGN_CENTER ), + mnVAlign( EXC_CHTEXT_ALIGN_CENTER ), + mnBackMode( EXC_CHTEXT_TRANSPARENT ), + mnFlags( EXC_CHTEXT_AUTOCOLOR | EXC_CHTEXT_AUTOFILL ), + mnFlags2( EXC_CHTEXT_POS_DEFAULT ), + mnRotation( EXC_ROT_NONE ) +{ +} + +// Data series ================================================================ + +XclChMarkerFormat::XclChMarkerFormat() : + maLineColor( COL_BLACK ), + maFillColor( COL_WHITE ), + mnMarkerSize( EXC_CHMARKERFORMAT_SINGLESIZE ), + mnMarkerType( EXC_CHMARKERFORMAT_NOSYMBOL ), + mnFlags( EXC_CHMARKERFORMAT_AUTO ) +{ +}; + +XclCh3dDataFormat::XclCh3dDataFormat() : + mnBase( EXC_CH3DDATAFORMAT_RECT ), + mnTop( EXC_CH3DDATAFORMAT_STRAIGHT ) +{ +} + +XclChDataFormat::XclChDataFormat() : + mnFormatIdx( EXC_CHDATAFORMAT_DEFAULT ), + mnFlags( 0 ) +{ +} + +XclChSerTrendLine::XclChSerTrendLine() : + mfForecastFor( 0.0 ), + mfForecastBack( 0.0 ), + mnLineType( EXC_CHSERTREND_POLYNOMIAL ), + mnOrder( 1 ), + mnShowEquation( 0 ), + mnShowRSquared( 0 ) +{ + /* Set all bits in mfIntercept to 1 (that is -1.#NAN) to indicate that + there is no interception point. Cannot use ::rtl::math::setNan() here + cause it misses the sign bit. */ + sal_math_Double* pDouble = reinterpret_cast< sal_math_Double* >( &mfIntercept ); + pDouble->w32_parts.msw = pDouble->w32_parts.lsw = 0xFFFFFFFF; +} + +XclChSerErrorBar::XclChSerErrorBar() : + mfValue( 0.0 ), + mnValueCount( 1 ), + mnBarType( EXC_CHSERERR_NONE ), + mnSourceType( EXC_CHSERERR_FIXED ), + mnLineEnd( EXC_CHSERERR_END_TSHAPE ) +{ +} + +XclChSeries::XclChSeries() : + mnCategType( EXC_CHSERIES_NUMERIC ), + mnValueType( EXC_CHSERIES_NUMERIC ), + mnBubbleType( EXC_CHSERIES_NUMERIC ), + mnCategCount( 0 ), + mnValueCount( 0 ), + mnBubbleCount( 0 ) +{ +} + +// Chart type groups ========================================================== + +XclChType::XclChType() : + mnOverlap( 0 ), + mnGap( 150 ), + mnRotation( 0 ), + mnPieHole( 0 ), + mnBubbleSize( 100 ), + mnBubbleType( EXC_CHSCATTER_AREA ), + mnFlags( 0 ) +{ +} + +XclChChart3d::XclChChart3d() : + mnRotation( 20 ), + mnElevation( 15 ), + mnEyeDist( 30 ), + mnRelHeight( 100 ), + mnRelDepth( 100 ), + mnDepthGap( 150 ), + mnFlags( EXC_CHCHART3D_AUTOHEIGHT ) +{ +} + +XclChLegend::XclChLegend() : + mnDockMode( EXC_CHLEGEND_RIGHT ), + mnSpacing( EXC_CHLEGEND_MEDIUM ), + mnFlags( EXC_CHLEGEND_DOCKED | EXC_CHLEGEND_AUTOSERIES | + EXC_CHLEGEND_AUTOPOSX | EXC_CHLEGEND_AUTOPOSY | EXC_CHLEGEND_STACKED ) +{ +} + +XclChTypeGroup::XclChTypeGroup() : + mnFlags( 0 ), + mnGroupIdx( EXC_CHSERGROUP_NONE ) +{ +} + +XclChProperties::XclChProperties() : + mnFlags( 0 ), + mnEmptyMode( EXC_CHPROPS_EMPTY_SKIP ) +{ +} + +// Axes ======================================================================= + +XclChLabelRange::XclChLabelRange() : + mnCross( 1 ), + mnLabelFreq( 1 ), + mnTickFreq( 1 ), + mnFlags( 0 ) +{ +} + +XclChDateRange::XclChDateRange() : + mnMinDate( 0 ), + mnMaxDate( 0 ), + mnMajorStep( 0 ), + mnMajorUnit( EXC_CHDATERANGE_DAYS ), + mnMinorStep( 0 ), + mnMinorUnit( EXC_CHDATERANGE_DAYS ), + mnBaseUnit( EXC_CHDATERANGE_DAYS ), + mnCross( 0 ), + mnFlags( EXC_CHDATERANGE_AUTOMIN | EXC_CHDATERANGE_AUTOMAX | + EXC_CHDATERANGE_AUTOMAJOR | EXC_CHDATERANGE_AUTOMINOR | + EXC_CHDATERANGE_AUTOBASE | EXC_CHDATERANGE_AUTOCROSS | + EXC_CHDATERANGE_AUTODATE ) +{ +} + +XclChValueRange::XclChValueRange() : + mfMin( 0.0 ), + mfMax( 0.0 ), + mfMajorStep( 0.0 ), + mfMinorStep( 0.0 ), + mfCross( 0.0 ), + mnFlags( EXC_CHVALUERANGE_AUTOMIN | EXC_CHVALUERANGE_AUTOMAX | + EXC_CHVALUERANGE_AUTOMAJOR | EXC_CHVALUERANGE_AUTOMINOR | + EXC_CHVALUERANGE_AUTOCROSS | EXC_CHVALUERANGE_BIT8 ) +{ +} + +XclChTick::XclChTick() : + maTextColor( COL_BLACK ), + mnMajor( EXC_CHTICK_INSIDE | EXC_CHTICK_OUTSIDE ), + mnMinor( 0 ), + mnLabelPos( EXC_CHTICK_NEXT ), + mnBackMode( EXC_CHTICK_TRANSPARENT ), + mnFlags( EXC_CHTICK_AUTOCOLOR | EXC_CHTICK_AUTOROT ), + mnRotation( EXC_ROT_NONE ) +{ +} + +XclChAxis::XclChAxis() : + mnType( EXC_CHAXIS_NONE ) +{ +} + +sal_Int32 XclChAxis::GetApiAxisDimension() const +{ + sal_Int32 nApiAxisDim = EXC_CHART_AXIS_NONE; + switch( mnType ) + { + case EXC_CHAXIS_X: nApiAxisDim = EXC_CHART_AXIS_X; break; + case EXC_CHAXIS_Y: nApiAxisDim = EXC_CHART_AXIS_Y; break; + case EXC_CHAXIS_Z: nApiAxisDim = EXC_CHART_AXIS_Z; break; + } + return nApiAxisDim; +} + +XclChAxesSet::XclChAxesSet() : + mnAxesSetId( EXC_CHAXESSET_PRIMARY ) +{ +} + +sal_Int32 XclChAxesSet::GetApiAxesSetIndex() const +{ + sal_Int32 nApiAxesSetIdx = EXC_CHART_AXESSET_NONE; + switch( mnAxesSetId ) + { + case EXC_CHAXESSET_PRIMARY: nApiAxesSetIdx = EXC_CHART_AXESSET_PRIMARY; break; + case EXC_CHAXESSET_SECONDARY: nApiAxesSetIdx = EXC_CHART_AXESSET_SECONDARY; break; + } + return nApiAxesSetIdx; +} + +// Static helper functions ==================================================== + +sal_uInt16 XclChartHelper::GetSeriesLineAutoColorIdx( sal_uInt16 nFormatIdx ) +{ + static const sal_uInt16 spnLineColors[] = + { + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 63 + }; + return spnLineColors[ nFormatIdx % SAL_N_ELEMENTS( spnLineColors ) ]; +} + +sal_uInt16 XclChartHelper::GetSeriesFillAutoColorIdx( sal_uInt16 nFormatIdx ) +{ + static const sal_uInt16 spnFillColors[] = + { + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23 + }; + return spnFillColors[ nFormatIdx % SAL_N_ELEMENTS( spnFillColors ) ]; +} + +sal_uInt8 XclChartHelper::GetSeriesFillAutoTransp( sal_uInt16 nFormatIdx ) +{ + static const sal_uInt8 spnTrans[] = { 0x00, 0x40, 0x20, 0x60, 0x70 }; + return spnTrans[ (nFormatIdx / 56) % SAL_N_ELEMENTS( spnTrans ) ]; +} + +sal_uInt16 XclChartHelper::GetAutoMarkerType( sal_uInt16 nFormatIdx ) +{ + static const sal_uInt16 spnSymbols[] = { + EXC_CHMARKERFORMAT_DIAMOND, EXC_CHMARKERFORMAT_SQUARE, EXC_CHMARKERFORMAT_TRIANGLE, + EXC_CHMARKERFORMAT_CROSS, EXC_CHMARKERFORMAT_STAR, EXC_CHMARKERFORMAT_CIRCLE, + EXC_CHMARKERFORMAT_PLUS, EXC_CHMARKERFORMAT_DOWJ, EXC_CHMARKERFORMAT_STDDEV }; + return spnSymbols[ nFormatIdx % SAL_N_ELEMENTS( spnSymbols ) ]; +} + +bool XclChartHelper::HasMarkerFillColor( sal_uInt16 nMarkerType ) +{ + static const bool spbFilled[] = { + false, true, true, true, false, false, false, false, true, false }; + return (nMarkerType < SAL_N_ELEMENTS( spbFilled )) && spbFilled[ nMarkerType ]; +} + +OUString XclChartHelper::GetErrorBarValuesRole( sal_uInt8 nBarType ) +{ + switch( nBarType ) + { + case EXC_CHSERERR_XPLUS: return EXC_CHPROP_ROLE_ERRORBARS_POSX; + case EXC_CHSERERR_XMINUS: return EXC_CHPROP_ROLE_ERRORBARS_NEGX; + case EXC_CHSERERR_YPLUS: return EXC_CHPROP_ROLE_ERRORBARS_POSY; + case EXC_CHSERERR_YMINUS: return EXC_CHPROP_ROLE_ERRORBARS_NEGY; + default: OSL_FAIL( "XclChartHelper::GetErrorBarValuesRole - unknown bar type" ); + } + return OUString(); +} + +// Chart formatting info provider ============================================= + +namespace { + +const XclChFormatInfo spFmtInfos[] = +{ + // object type property mode auto line color auto line weight auto pattern color missing frame type create delete isframe + { EXC_CHOBJTYPE_BACKGROUND, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, true, true, true }, + { EXC_CHOBJTYPE_PLOTFRAME, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, true, true, true }, + { EXC_CHOBJTYPE_WALL3D, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_AUTO, true, false, true }, + { EXC_CHOBJTYPE_FLOOR3D, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, 23, EXC_CHFRAMETYPE_AUTO, true, false, true }, + { EXC_CHOBJTYPE_TEXT, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, true, true }, + { EXC_CHOBJTYPE_LEGEND, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_AUTO, true, true, true }, + { EXC_CHOBJTYPE_LINEARSERIES, EXC_CHPROPMODE_LINEARSERIES, 0xFFFF, EXC_CHLINEFORMAT_SINGLE, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_AUTO, false, false, false }, + { EXC_CHOBJTYPE_FILLEDSERIES, EXC_CHPROPMODE_FILLEDSERIES, EXC_COLOR_CHBORDERAUTO, EXC_CHLINEFORMAT_SINGLE, 0xFFFF, EXC_CHFRAMETYPE_AUTO, false, false, true }, + { EXC_CHOBJTYPE_AXISLINE, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_AUTO, false, false, false }, + { EXC_CHOBJTYPE_GRIDLINE, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, true, false }, + { EXC_CHOBJTYPE_TRENDLINE, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_DOUBLE, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, false }, + { EXC_CHOBJTYPE_ERRORBAR, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_SINGLE, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, false }, + { EXC_CHOBJTYPE_CONNECTLINE, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, false }, + { EXC_CHOBJTYPE_HILOLINE, EXC_CHPROPMODE_LINEARSERIES, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, false }, + { EXC_CHOBJTYPE_WHITEDROPBAR, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWBACK, EXC_CHFRAMETYPE_INVISIBLE, false, false, true }, + { EXC_CHOBJTYPE_BLACKDROPBAR, EXC_CHPROPMODE_COMMON, EXC_COLOR_CHWINDOWTEXT, EXC_CHLINEFORMAT_HAIR, EXC_COLOR_CHWINDOWTEXT, EXC_CHFRAMETYPE_INVISIBLE, false, false, true } +}; + +} + +XclChFormatInfoProvider::XclChFormatInfoProvider() +{ + for(auto const &rIt : spFmtInfos) + maInfoMap[ rIt.meObjType ] = &rIt; +} + +const XclChFormatInfo& XclChFormatInfoProvider::GetFormatInfo( XclChObjectType eObjType ) const +{ + XclFmtInfoMap::const_iterator aIt = maInfoMap.find( eObjType ); + OSL_ENSURE( aIt != maInfoMap.end(), "XclChFormatInfoProvider::GetFormatInfo - unknown object type" ); + return (aIt == maInfoMap.end()) ? *spFmtInfos : *aIt->second; +} + +// Chart type info provider =================================================== + +namespace { + +// chart type service names +const char SERVICE_CHART2_AREA[] = "com.sun.star.chart2.AreaChartType"; +const char SERVICE_CHART2_CANDLE[] = "com.sun.star.chart2.CandleStickChartType"; +const char SERVICE_CHART2_COLUMN[] = "com.sun.star.chart2.ColumnChartType"; +const char SERVICE_CHART2_LINE[] = "com.sun.star.chart2.LineChartType"; +const char SERVICE_CHART2_NET[] = "com.sun.star.chart2.NetChartType"; +const char SERVICE_CHART2_FILLEDNET[] = "com.sun.star.chart2.FilledNetChartType"; +const char SERVICE_CHART2_PIE[] = "com.sun.star.chart2.PieChartType"; +const char SERVICE_CHART2_SCATTER[] = "com.sun.star.chart2.ScatterChartType"; +const char SERVICE_CHART2_BUBBLE[] = "com.sun.star.chart2.BubbleChartType"; +const char SERVICE_CHART2_SURFACE[] = "com.sun.star.chart2.ColumnChartType"; // Todo + +namespace csscd = css::chart::DataLabelPlacement; + +const XclChTypeInfo spTypeInfos[] = +{ + // chart type chart type category record id service varied point color def label pos comb2d 3d polar area2d area3d 1stvis xcateg swap stack revers betw + { EXC_CHTYPEID_BAR, EXC_CHTYPECATEG_BAR, EXC_ID_CHBAR, SERVICE_CHART2_COLUMN, EXC_CHVARPOINT_SINGLE, csscd::OUTSIDE, true, true, false, true, true, false, true, false, true, false, true }, + { EXC_CHTYPEID_HORBAR, EXC_CHTYPECATEG_BAR, EXC_ID_CHBAR, SERVICE_CHART2_COLUMN, EXC_CHVARPOINT_SINGLE, csscd::OUTSIDE, false, true, false, true, true, false, true, true, true, false, true }, + { EXC_CHTYPEID_LINE, EXC_CHTYPECATEG_LINE, EXC_ID_CHLINE, SERVICE_CHART2_LINE, EXC_CHVARPOINT_SINGLE, csscd::RIGHT, true, true, false, false, true, false, true, false, true, false, false }, + { EXC_CHTYPEID_AREA, EXC_CHTYPECATEG_LINE, EXC_ID_CHAREA, SERVICE_CHART2_AREA, EXC_CHVARPOINT_NONE, csscd::CENTER, true, true, false, true, true, false, true, false, true, true, false }, + { EXC_CHTYPEID_STOCK, EXC_CHTYPECATEG_LINE, EXC_ID_CHLINE, SERVICE_CHART2_CANDLE, EXC_CHVARPOINT_NONE, csscd::RIGHT, true, false, false, false, false, false, true, false, true, false, false }, + { EXC_CHTYPEID_RADARLINE, EXC_CHTYPECATEG_RADAR, EXC_ID_CHRADARLINE, SERVICE_CHART2_NET, EXC_CHVARPOINT_SINGLE, csscd::TOP, false, false, true, false, true, false, true, false, false, false, false }, + { EXC_CHTYPEID_RADARAREA, EXC_CHTYPECATEG_RADAR, EXC_ID_CHRADARAREA, SERVICE_CHART2_FILLEDNET, EXC_CHVARPOINT_NONE, csscd::TOP, false, false, true, true, true, false, true, false, false, true, false }, + { EXC_CHTYPEID_PIE, EXC_CHTYPECATEG_PIE, EXC_ID_CHPIE, SERVICE_CHART2_PIE, EXC_CHVARPOINT_MULTI, csscd::AVOID_OVERLAP, false, true, true, true, true, true, true, false, false, false, false }, + { EXC_CHTYPEID_DONUT, EXC_CHTYPECATEG_PIE, EXC_ID_CHPIE, SERVICE_CHART2_PIE, EXC_CHVARPOINT_MULTI, csscd::AVOID_OVERLAP, false, true, true, true, true, false, true, false, false, false, false }, + { EXC_CHTYPEID_PIEEXT, EXC_CHTYPECATEG_PIE, EXC_ID_CHPIEEXT, SERVICE_CHART2_PIE, EXC_CHVARPOINT_MULTI, csscd::AVOID_OVERLAP, false, false, true, true, true, true, true, false, false, false, false }, + { EXC_CHTYPEID_SCATTER, EXC_CHTYPECATEG_SCATTER, EXC_ID_CHSCATTER, SERVICE_CHART2_SCATTER, EXC_CHVARPOINT_SINGLE, csscd::RIGHT, true, false, false, false, true, false, false, false, false, false, false }, + { EXC_CHTYPEID_BUBBLES, EXC_CHTYPECATEG_SCATTER, EXC_ID_CHSCATTER, SERVICE_CHART2_BUBBLE, EXC_CHVARPOINT_SINGLE, csscd::RIGHT, false, false, false, true, true, false, false, false, false, false, false }, + { EXC_CHTYPEID_SURFACE, EXC_CHTYPECATEG_SURFACE, EXC_ID_CHSURFACE, SERVICE_CHART2_SURFACE, EXC_CHVARPOINT_NONE, csscd::RIGHT, false, true, false, true, true, false, true, false, false, false, false }, + { EXC_CHTYPEID_UNKNOWN, EXC_CHTYPECATEG_BAR, EXC_ID_CHBAR, SERVICE_CHART2_COLUMN, EXC_CHVARPOINT_SINGLE, csscd::OUTSIDE, true, true, false, true, true, false, true, false, true, false, true } +}; + +} // namespace + +XclChExtTypeInfo::XclChExtTypeInfo( const XclChTypeInfo& rTypeInfo ) : + XclChTypeInfo( rTypeInfo ), + mb3dChart( false ), + mbSpline( false ) +{ +} + +void XclChExtTypeInfo::Set( const XclChTypeInfo& rTypeInfo, bool b3dChart, bool bSpline ) +{ + static_cast< XclChTypeInfo& >( *this ) = rTypeInfo; + mb3dChart = mbSupports3d && b3dChart; + mbSpline = bSpline; +} + +XclChTypeInfoProvider::XclChTypeInfoProvider() +{ + for(const auto &rIt : spTypeInfos) + maInfoMap[ rIt.meTypeId ] = &rIt; +} + +const XclChTypeInfo& XclChTypeInfoProvider::GetTypeInfo( XclChTypeId eTypeId ) const +{ + XclChTypeInfoMap::const_iterator aIt = maInfoMap.find( eTypeId ); + OSL_ENSURE( aIt != maInfoMap.end(), "XclChTypeInfoProvider::GetTypeInfo - unknown chart type" ); + return (aIt == maInfoMap.end()) ? *maInfoMap.rbegin()->second : *aIt->second; +} + +const XclChTypeInfo& XclChTypeInfoProvider::GetTypeInfoFromRecId( sal_uInt16 nRecId ) const +{ + for(const auto &rIt : spTypeInfos) + { + if(rIt.mnRecId == nRecId) + return rIt; + } + OSL_FAIL( "XclChTypeInfoProvider::GetTypeInfoFromRecId - unknown record id" ); + return GetTypeInfo( EXC_CHTYPEID_UNKNOWN ); +} + +const XclChTypeInfo& XclChTypeInfoProvider::GetTypeInfoFromService( std::u16string_view rServiceName ) const +{ + for(auto const &rIt : spTypeInfos) + if( o3tl::equalsAscii( rServiceName, rIt.mpcServiceName ) ) + return rIt; + OSL_FAIL( "XclChTypeInfoProvider::GetTypeInfoFromService - unknown service name" ); + return GetTypeInfo( EXC_CHTYPEID_UNKNOWN ); +} + +// Property helpers =========================================================== + +XclChObjectTable::XclChObjectTable(uno::Reference const & xFactory, + const OUString& rServiceName, const OUString& rObjNameBase ) : + mxFactory( xFactory ), + maServiceName( rServiceName ), + maObjNameBase( rObjNameBase ), + mnIndex( 0 ) +{ +} + +uno::Any XclChObjectTable::GetObject( const OUString& rObjName ) +{ + // get object table + if( !mxContainer.is() ) + mxContainer.set(ScfApiHelper::CreateInstance( mxFactory, maServiceName ), uno::UNO_QUERY); + OSL_ENSURE( mxContainer.is(), "XclChObjectTable::GetObject - container not found" ); + + uno::Any aObj; + if( mxContainer.is() ) + { + // get object from container + try + { + aObj = mxContainer->getByName( rObjName ); + } + catch (uno::Exception &) + { + OSL_FAIL( "XclChObjectTable::GetObject - object not found" ); + } + } + return aObj; +} + +OUString XclChObjectTable::InsertObject(const uno::Any& rObj) +{ + + // create object table + if( !mxContainer.is() ) + mxContainer.set(ScfApiHelper::CreateInstance( mxFactory, maServiceName ), uno::UNO_QUERY); + OSL_ENSURE( mxContainer.is(), "XclChObjectTable::InsertObject - container not found" ); + + OUString aObjName; + if( mxContainer.is() ) + { + // create new unused identifier + do + { + aObjName = maObjNameBase + OUString::number( ++mnIndex ); + } + while( mxContainer->hasByName( aObjName ) ); + + // insert object + try + { + mxContainer->insertByName( aObjName, rObj ); + } + catch (uno::Exception &) + { + OSL_FAIL( "XclChObjectTable::InsertObject - cannot insert object" ); + aObjName.clear(); + } + } + return aObjName; +} + +// Property names ------------------------------------------------------------- + +namespace { + +/** Property names for line style in common objects. */ +const char* const sppcLineNamesCommon[] = + { "LineStyle", "LineWidth", "LineColor", "LineTransparence", "LineDashName", nullptr }; +/** Property names for line style in linear series objects. */ +const char* const sppcLineNamesLinear[] = + { "LineStyle", "LineWidth", "Color", "Transparency", "LineDashName", nullptr }; +/** Property names for line style in filled series objects. */ +const char* const sppcLineNamesFilled[] = + { "BorderStyle", "BorderWidth", "BorderColor", "BorderTransparency", "BorderDashName", nullptr }; + +/** Property names for solid area style in common objects. */ +const char* const sppcAreaNamesCommon[] = { "FillStyle", "FillColor", "FillTransparence", nullptr }; +/** Property names for solid area style in filled series objects. */ +const char* const sppcAreaNamesFilled[] = { "FillStyle", "Color", "Transparency", nullptr }; +/** Property names for gradient area style in common objects. */ +const char* const sppcGradNamesCommon[] = { "FillStyle", "FillGradientName", nullptr }; +/** Property names for gradient area style in filled series objects. */ +const char* const sppcGradNamesFilled[] = { "FillStyle", "GradientName", nullptr }; +/** Property names for hatch area style in common objects. */ +const char* const sppcHatchNamesCommon[] = { "FillStyle", "FillHatchName", "FillColor", "FillBackground", nullptr }; +/** Property names for hatch area style in filled series objects. */ +const char* const sppcHatchNamesFilled[] = { "FillStyle", "HatchName", "Color", "FillBackground", nullptr }; +/** Property names for bitmap area style. */ +const char* const sppcBitmapNames[] = { "FillStyle", "FillBitmapName", "FillBitmapMode", nullptr }; + +} // namespace + +XclChPropSetHelper::XclChPropSetHelper() : + maLineHlpCommon( sppcLineNamesCommon ), + maLineHlpLinear( sppcLineNamesLinear ), + maLineHlpFilled( sppcLineNamesFilled ), + maAreaHlpCommon( sppcAreaNamesCommon ), + maAreaHlpFilled( sppcAreaNamesFilled ), + maGradHlpCommon( sppcGradNamesCommon ), + maGradHlpFilled( sppcGradNamesFilled ), + maHatchHlpCommon( sppcHatchNamesCommon ), + maHatchHlpFilled( sppcHatchNamesFilled ), + maBitmapHlp( sppcBitmapNames ) +{ +} + +// read properties ------------------------------------------------------------ + +void XclChPropSetHelper::ReadLineProperties( + XclChLineFormat& rLineFmt, XclChObjectTable& rDashTable, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) +{ + // read properties from property set + drawing::LineStyle eApiStyle = drawing::LineStyle_NONE; + sal_Int32 nApiWidth = 0; + sal_Int16 nApiTrans = 0; + uno::Any aDashNameAny; + + ScfPropSetHelper& rLineHlp = GetLineHelper( ePropMode ); + rLineHlp.ReadFromPropertySet( rPropSet ); + rLineHlp >> eApiStyle >> nApiWidth >> rLineFmt.maColor >> nApiTrans >> aDashNameAny; + + // clear automatic flag + ::set_flag( rLineFmt.mnFlags, EXC_CHLINEFORMAT_AUTO, false ); + + // line width + if( nApiWidth <= 0 ) rLineFmt.mnWeight = EXC_CHLINEFORMAT_HAIR; + else if( nApiWidth <= 35 ) rLineFmt.mnWeight = EXC_CHLINEFORMAT_SINGLE; + else if( nApiWidth <= 70 ) rLineFmt.mnWeight = EXC_CHLINEFORMAT_DOUBLE; + else rLineFmt.mnWeight = EXC_CHLINEFORMAT_TRIPLE; + + // line style + switch( eApiStyle ) + { + case drawing::LineStyle_NONE: + rLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; + break; + case drawing::LineStyle_SOLID: + { + if( nApiTrans < 13 ) rLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + else if( nApiTrans < 38 ) rLineFmt.mnPattern = EXC_CHLINEFORMAT_DARKTRANS; + else if( nApiTrans < 63 ) rLineFmt.mnPattern = EXC_CHLINEFORMAT_MEDTRANS; + else if( nApiTrans < 100 ) rLineFmt.mnPattern = EXC_CHLINEFORMAT_LIGHTTRANS; + else rLineFmt.mnPattern = EXC_CHLINEFORMAT_NONE; + } + break; + case drawing::LineStyle_DASH: + { + rLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + OUString aDashName; + drawing::LineDash aApiDash; + if( (aDashNameAny >>= aDashName) && (rDashTable.GetObject( aDashName ) >>= aApiDash) ) + { + // reorder dashes that are shorter than dots + if( (aApiDash.Dashes == 0) || (aApiDash.DashLen < aApiDash.DotLen) ) + { + ::std::swap( aApiDash.Dashes, aApiDash.Dots ); + ::std::swap( aApiDash.DashLen, aApiDash.DotLen ); + } + // ignore dots that are nearly equal to dashes + if( aApiDash.DotLen * 3 > aApiDash.DashLen * 2 ) + aApiDash.Dots = 0; + + // convert line dash to predefined Excel dash types + if( (aApiDash.Dashes == 1) && (aApiDash.Dots >= 1) ) + // one dash and one or more dots + rLineFmt.mnPattern = (aApiDash.Dots == 1) ? + EXC_CHLINEFORMAT_DASHDOT : EXC_CHLINEFORMAT_DASHDOTDOT; + else if( aApiDash.Dashes >= 1 ) + // one or more dashes and no dots (also: dash-dash-dot) + rLineFmt.mnPattern = (aApiDash.DashLen < 250) ? + EXC_CHLINEFORMAT_DOT : EXC_CHLINEFORMAT_DASH; + } + } + break; + default: + OSL_FAIL( "XclChPropSetHelper::ReadLineProperties - unknown line style" ); + rLineFmt.mnPattern = EXC_CHLINEFORMAT_SOLID; + } +} + +bool XclChPropSetHelper::ReadAreaProperties( XclChAreaFormat& rAreaFmt, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) +{ + // read properties from property set + drawing::FillStyle eApiStyle = drawing::FillStyle_NONE; + sal_Int16 nTransparency = 0; + + ScfPropSetHelper& rAreaHlp = GetAreaHelper( ePropMode ); + rAreaHlp.ReadFromPropertySet( rPropSet ); + rAreaHlp >> eApiStyle >> rAreaFmt.maPattColor >> nTransparency; + + // clear automatic flag + ::set_flag( rAreaFmt.mnFlags, EXC_CHAREAFORMAT_AUTO, false ); + + // set fill style transparent or solid (set solid for anything but transparent) + rAreaFmt.mnPattern = (eApiStyle == drawing::FillStyle_NONE) ? EXC_PATT_NONE : EXC_PATT_SOLID; + + // return true to indicate complex fill (gradient, bitmap, solid transparency) + return (eApiStyle != drawing::FillStyle_NONE) && ((eApiStyle != drawing::FillStyle_SOLID) || (nTransparency > 0)); +} + +void XclChPropSetHelper::ReadEscherProperties( + XclChEscherFormat& rEscherFmt, XclChPicFormat& rPicFmt, + XclChObjectTable& rGradientTable, XclChObjectTable& rHatchTable, XclChObjectTable& rBitmapTable, + const ScfPropertySet& rPropSet, XclChPropertyMode ePropMode ) +{ + // read style and transparency properties from property set + drawing::FillStyle eApiStyle = drawing::FillStyle_NONE; + Color aColor; + sal_Int16 nTransparency = 0; + + ScfPropSetHelper& rAreaHlp = GetAreaHelper( ePropMode ); + rAreaHlp.ReadFromPropertySet( rPropSet ); + rAreaHlp >> eApiStyle >> aColor >> nTransparency; + + switch( eApiStyle ) + { + case drawing::FillStyle_SOLID: + { + OSL_ENSURE( nTransparency > 0, "XclChPropSetHelper::ReadEscherProperties - unexpected solid area without transparency" ); + if( (0 < nTransparency) && (nTransparency <= 100) ) + { + // convert to Escher properties + sal_uInt32 nEscherColor = 0x02000000; + ::insert_value( nEscherColor, aColor.GetBlue(), 16, 8 ); + ::insert_value( nEscherColor, aColor.GetGreen(), 8, 8 ); + ::insert_value( nEscherColor, aColor.GetRed(), 0, 8 ); + sal_uInt32 nEscherOpacity = static_cast< sal_uInt32 >( (100 - nTransparency) * 655.36 ); + rEscherFmt.mxEscherSet = std::make_shared(); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillType, ESCHER_FillSolid ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillColor, nEscherColor ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillOpacity, nEscherOpacity ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillBackColor, 0x02FFFFFF ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fillBackOpacity, 0x00010000 ); + rEscherFmt.mxEscherSet->AddOpt( ESCHER_Prop_fNoFillHitTest, 0x001F001C ); + } + } + break; + case drawing::FillStyle_GRADIENT: + { + // extract gradient from global gradient table + OUString aGradientName; + ScfPropSetHelper& rGradHlp = GetGradientHelper( ePropMode ); + rGradHlp.ReadFromPropertySet( rPropSet ); + rGradHlp >> eApiStyle >> aGradientName; + awt::Gradient aGradient; + if( rGradientTable.GetObject( aGradientName ) >>= aGradient ) + { + // convert to Escher properties + rEscherFmt.mxEscherSet = std::make_shared(); + rEscherFmt.mxEscherSet->CreateGradientProperties( aGradient ); + } + } + break; + case drawing::FillStyle_HATCH: + { + // extract hatch from global hatch table + OUString aHatchName; + bool bFillBackground; + ScfPropSetHelper& rHatchHlp = GetHatchHelper( ePropMode ); + rHatchHlp.ReadFromPropertySet( rPropSet ); + rHatchHlp >> eApiStyle >> aHatchName >> aColor >> bFillBackground; + drawing::Hatch aHatch; + if( rHatchTable.GetObject( aHatchName ) >>= aHatch ) + { + // convert to Escher properties + rEscherFmt.mxEscherSet = std::make_shared(); + rEscherFmt.mxEscherSet->CreateEmbeddedHatchProperties( aHatch, aColor, bFillBackground ); + rPicFmt.mnBmpMode = EXC_CHPICFORMAT_STACK; + } + } + break; + case drawing::FillStyle_BITMAP: + { + // extract bitmap URL from global bitmap table + OUString aBitmapName; + drawing::BitmapMode eApiBmpMode; + maBitmapHlp.ReadFromPropertySet( rPropSet ); + maBitmapHlp >> eApiStyle >> aBitmapName >> eApiBmpMode; + uno::Reference xBitmap; + if (rBitmapTable.GetObject( aBitmapName ) >>= xBitmap) + { + // convert to Escher properties + rEscherFmt.mxEscherSet = std::make_shared(); + rEscherFmt.mxEscherSet->CreateEmbeddedBitmapProperties( xBitmap, eApiBmpMode ); + rPicFmt.mnBmpMode = (eApiBmpMode == drawing::BitmapMode_REPEAT) ? + EXC_CHPICFORMAT_STACK : EXC_CHPICFORMAT_STRETCH; + } + } + break; + default: + OSL_FAIL( "XclChPropSetHelper::ReadEscherProperties - unknown fill style" ); + } +} + +void XclChPropSetHelper::ReadMarkerProperties( + XclChMarkerFormat& rMarkerFmt, const ScfPropertySet& rPropSet, sal_uInt16 nFormatIdx ) +{ + chart2::Symbol aApiSymbol; + if( !rPropSet.GetProperty( aApiSymbol, EXC_CHPROP_SYMBOL ) ) + return; + + // clear automatic flag + ::set_flag( rMarkerFmt.mnFlags, EXC_CHMARKERFORMAT_AUTO, false ); + + // symbol style + switch( aApiSymbol.Style ) + { + case chart2::SymbolStyle_NONE: + rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_NOSYMBOL; + break; + case chart2::SymbolStyle_STANDARD: + switch( aApiSymbol.StandardSymbol ) + { + case 0: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_SQUARE; break; // square + case 1: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_DIAMOND; break; // diamond + case 2: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STDDEV; break; // arrow down + case 3: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_TRIANGLE; break; // arrow up + case 4: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_DOWJ; break; // arrow right, same as import + case 5: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_PLUS; break; // arrow left + case 6: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_CROSS; break; // bow tie + case 7: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STAR; break; // sand glass + case 8: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_CIRCLE; break; // circle new in LibO3.5 + case 9: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_DIAMOND; break; // star new in LibO3.5 + case 10: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_CROSS; break; // X new in LibO3.5 + case 11: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_PLUS; break; // plus new in LibO3.5 + case 12: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STAR; break; // asterisk new in LibO3.5 + case 13: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STDDEV; break; // horizontal bar new in LibO3.5 + case 14: rMarkerFmt.mnMarkerType = EXC_CHMARKERFORMAT_STAR; break; // vertical bar new in LibO3.5 + default: rMarkerFmt.mnMarkerType = XclChartHelper::GetAutoMarkerType( nFormatIdx ); + } + break; + default: + rMarkerFmt.mnMarkerType = XclChartHelper::GetAutoMarkerType( nFormatIdx ); + } + bool bHasFillColor = XclChartHelper::HasMarkerFillColor( rMarkerFmt.mnMarkerType ); + ::set_flag( rMarkerFmt.mnFlags, EXC_CHMARKERFORMAT_NOFILL, !bHasFillColor ); + + // symbol size + sal_Int32 nApiSize = (aApiSymbol.Size.Width + aApiSymbol.Size.Height + 1) / 2; + rMarkerFmt.mnMarkerSize = XclTools::GetTwipsFromHmm( nApiSize ); + + // symbol colors + rMarkerFmt.maLineColor = Color( ColorTransparency, aApiSymbol.BorderColor ); + rMarkerFmt.maFillColor = Color( ColorTransparency, aApiSymbol.FillColor ); +} + +sal_uInt16 XclChPropSetHelper::ReadRotationProperties( const ScfPropertySet& rPropSet, bool bSupportsStacked ) +{ + // chart2 handles rotation as double in the range [0,360) + double fAngle = 0.0; + rPropSet.GetProperty( fAngle, EXC_CHPROP_TEXTROTATION ); + bool bStacked = bSupportsStacked && rPropSet.GetBoolProperty( EXC_CHPROP_STACKCHARACTERS ); + return bStacked ? EXC_ROT_STACKED : + XclTools::GetXclRotation( Degree100(static_cast< sal_Int32 >( fAngle * 100.0 + 0.5 )) ); +} + +// write properties ----------------------------------------------------------- + +void XclChPropSetHelper::WriteLineProperties( + ScfPropertySet& rPropSet, XclChObjectTable& rDashTable, + const XclChLineFormat& rLineFmt, XclChPropertyMode ePropMode ) +{ + // line width + sal_Int32 nApiWidth = 0; // 0 is the width of a hair line + switch( rLineFmt.mnWeight ) + { + case EXC_CHLINEFORMAT_SINGLE: nApiWidth = 35; break; + case EXC_CHLINEFORMAT_DOUBLE: nApiWidth = 70; break; + case EXC_CHLINEFORMAT_TRIPLE: nApiWidth = 105; break; + } + + // line style + drawing::LineStyle eApiStyle = drawing::LineStyle_NONE; + sal_Int16 nApiTrans = 0; + sal_Int32 nDotLen = ::std::min< sal_Int32 >( rLineFmt.mnWeight + 105, 210 ); + drawing::LineDash aApiDash( drawing::DashStyle_RECT, 0, nDotLen, 0, 4 * nDotLen, nDotLen ); + + switch( rLineFmt.mnPattern ) + { + case EXC_CHLINEFORMAT_SOLID: + eApiStyle = drawing::LineStyle_SOLID; + break; + case EXC_CHLINEFORMAT_DARKTRANS: + eApiStyle = drawing::LineStyle_SOLID; nApiTrans = 25; + break; + case EXC_CHLINEFORMAT_MEDTRANS: + eApiStyle = drawing::LineStyle_SOLID; nApiTrans = 50; + break; + case EXC_CHLINEFORMAT_LIGHTTRANS: + eApiStyle = drawing::LineStyle_SOLID; nApiTrans = 75; + break; + case EXC_CHLINEFORMAT_DASH: + eApiStyle = drawing::LineStyle_DASH; aApiDash.Dashes = 1; + break; + case EXC_CHLINEFORMAT_DOT: + eApiStyle = drawing::LineStyle_DASH; aApiDash.Dots = 1; + break; + case EXC_CHLINEFORMAT_DASHDOT: + eApiStyle = drawing::LineStyle_DASH; aApiDash.Dashes = aApiDash.Dots = 1; + break; + case EXC_CHLINEFORMAT_DASHDOTDOT: + eApiStyle = drawing::LineStyle_DASH; aApiDash.Dashes = 1; aApiDash.Dots = 2; + break; + } + + // line color + sal_Int32 nApiColor = sal_Int32( rLineFmt.maColor ); + + // try to insert the dash style and receive its name + uno::Any aDashNameAny; + if( eApiStyle == drawing::LineStyle_DASH ) + { + OUString aDashName = rDashTable.InsertObject( uno::Any( aApiDash ) ); + if( !aDashName.isEmpty() ) + aDashNameAny <<= aDashName; + } + + // write the properties + ScfPropSetHelper& rLineHlp = GetLineHelper( ePropMode ); + rLineHlp.InitializeWrite(); + rLineHlp << eApiStyle << nApiWidth << nApiColor << nApiTrans << aDashNameAny; + rLineHlp.WriteToPropertySet( rPropSet ); +} + +void XclChPropSetHelper::WriteAreaProperties( ScfPropertySet& rPropSet, + const XclChAreaFormat& rAreaFmt, XclChPropertyMode ePropMode ) +{ + drawing::FillStyle eFillStyle = drawing::FillStyle_NONE; + Color aColor; + + // fill color + if( rAreaFmt.mnPattern != EXC_PATT_NONE ) + { + eFillStyle = drawing::FillStyle_SOLID; + aColor = XclTools::GetPatternColor( rAreaFmt.maPattColor, rAreaFmt.maBackColor, rAreaFmt.mnPattern ); + } + + // write the properties + ScfPropSetHelper& rAreaHlp = GetAreaHelper( ePropMode ); + rAreaHlp.InitializeWrite(); + rAreaHlp << eFillStyle << aColor << sal_Int16(0)/*nTransparency*/; + rAreaHlp.WriteToPropertySet( rPropSet ); +} + +void XclChPropSetHelper::WriteEscherProperties( ScfPropertySet& rPropSet, + XclChObjectTable& rGradientTable, XclChObjectTable& rBitmapTable, + const XclChEscherFormat& rEscherFmt, const XclChPicFormat* pPicFmt, + sal_uInt32 nDffFillType, XclChPropertyMode ePropMode ) +{ + if( !rEscherFmt.mxItemSet ) + return; + + const XFillStyleItem* pStyleItem = rEscherFmt.mxItemSet->GetItem( XATTR_FILLSTYLE, false ); + if( !pStyleItem ) + return; + + switch( pStyleItem->GetValue() ) + { + case drawing::FillStyle_SOLID: + // #i84812# Excel 2007 writes Escher properties for solid fill + if( const XFillColorItem* pColorItem = rEscherFmt.mxItemSet->GetItem( XATTR_FILLCOLOR, false ) ) + { + // get solid transparence too + const XFillTransparenceItem* pTranspItem = rEscherFmt.mxItemSet->GetItem( XATTR_FILLTRANSPARENCE, false ); + sal_uInt16 nTransp = pTranspItem ? pTranspItem->GetValue() : 0; + ScfPropSetHelper& rAreaHlp = GetAreaHelper( ePropMode ); + rAreaHlp.InitializeWrite(); + rAreaHlp << drawing::FillStyle_SOLID << pColorItem->GetColorValue() << static_cast< sal_Int16 >( nTransp ); + rAreaHlp.WriteToPropertySet( rPropSet ); + } + break; + case drawing::FillStyle_GRADIENT: + if( const XFillGradientItem* pGradItem = rEscherFmt.mxItemSet->GetItem( XATTR_FILLGRADIENT, false ) ) + { + uno::Any aGradientAny; + if( pGradItem->QueryValue( aGradientAny, MID_FILLGRADIENT ) ) + { + OUString aGradName = rGradientTable.InsertObject( aGradientAny ); + if( !aGradName.isEmpty() ) + { + ScfPropSetHelper& rGradHlp = GetGradientHelper( ePropMode ); + rGradHlp.InitializeWrite(); + rGradHlp << drawing::FillStyle_GRADIENT << aGradName; + rGradHlp.WriteToPropertySet( rPropSet ); + } + } + } + break; + case drawing::FillStyle_BITMAP: + if( const XFillBitmapItem* pBmpItem = rEscherFmt.mxItemSet->GetItem( XATTR_FILLBITMAP, false ) ) + { + uno::Any aBitmapAny; + if (pBmpItem->QueryValue(aBitmapAny, MID_BITMAP)) + { + OUString aBmpName = rBitmapTable.InsertObject( aBitmapAny ); + if( !aBmpName.isEmpty() ) + { + /* #i71810# Caller decides whether to use a CHPICFORMAT record for bitmap mode. + If not passed, detect fill mode from the DFF property 'fill-type'. */ + bool bStretch = pPicFmt ? (pPicFmt->mnBmpMode == EXC_CHPICFORMAT_STRETCH) : (nDffFillType == mso_fillPicture); + drawing::BitmapMode eApiBmpMode = bStretch ? drawing::BitmapMode_STRETCH : drawing::BitmapMode_REPEAT; + maBitmapHlp.InitializeWrite(); + maBitmapHlp << drawing::FillStyle_BITMAP << aBmpName << eApiBmpMode; + maBitmapHlp.WriteToPropertySet( rPropSet ); + } + } + } + break; + default: + OSL_FAIL( "XclChPropSetHelper::WriteEscherProperties - unknown fill mode" ); + } +} + +void XclChPropSetHelper::WriteMarkerProperties( + ScfPropertySet& rPropSet, const XclChMarkerFormat& rMarkerFmt ) +{ + // symbol style + chart2::Symbol aApiSymbol; + aApiSymbol.Style = chart2::SymbolStyle_STANDARD; + switch( rMarkerFmt.mnMarkerType ) + { + case EXC_CHMARKERFORMAT_NOSYMBOL: aApiSymbol.Style = chart2::SymbolStyle_NONE; break; + case EXC_CHMARKERFORMAT_SQUARE: aApiSymbol.StandardSymbol = 0; break; // square + case EXC_CHMARKERFORMAT_DIAMOND: aApiSymbol.StandardSymbol = 1; break; // diamond + case EXC_CHMARKERFORMAT_TRIANGLE: aApiSymbol.StandardSymbol = 3; break; // arrow up + case EXC_CHMARKERFORMAT_CROSS: aApiSymbol.StandardSymbol = 10; break; // X, legacy bow tie + case EXC_CHMARKERFORMAT_STAR: aApiSymbol.StandardSymbol = 12; break; // asterisk, legacy sand glass + case EXC_CHMARKERFORMAT_DOWJ: aApiSymbol.StandardSymbol = 4; break; // arrow right, same as export + case EXC_CHMARKERFORMAT_STDDEV: aApiSymbol.StandardSymbol = 13; break; // horizontal bar, legacy arrow down + case EXC_CHMARKERFORMAT_CIRCLE: aApiSymbol.StandardSymbol = 8; break; // circle, legacy arrow right + case EXC_CHMARKERFORMAT_PLUS: aApiSymbol.StandardSymbol = 11; break; // plus, legacy arrow left + default: break; + } + + // symbol size + sal_Int32 nApiSize = XclTools::GetHmmFromTwips( rMarkerFmt.mnMarkerSize ); + aApiSymbol.Size = awt::Size( nApiSize, nApiSize ); + + // symbol colors + aApiSymbol.FillColor = sal_Int32( rMarkerFmt.maFillColor ); + aApiSymbol.BorderColor = ::get_flag( rMarkerFmt.mnFlags, EXC_CHMARKERFORMAT_NOLINE ) ? + aApiSymbol.FillColor : sal_Int32( rMarkerFmt.maLineColor ); + + // set the property + rPropSet.SetProperty( EXC_CHPROP_SYMBOL, aApiSymbol ); +} + +void XclChPropSetHelper::WriteRotationProperties( + ScfPropertySet& rPropSet, sal_uInt16 nRotation, bool bSupportsStacked ) +{ + if( nRotation != EXC_CHART_AUTOROTATION ) + { + // chart2 handles rotation as double in the range [0,360) + double fAngle = XclTools::GetScRotation( nRotation, 0_deg100 ).get() / 100.0; + rPropSet.SetProperty( EXC_CHPROP_TEXTROTATION, fAngle ); + if( bSupportsStacked ) + rPropSet.SetProperty( EXC_CHPROP_STACKCHARACTERS, nRotation == EXC_ROT_STACKED ); + } +} + +// private -------------------------------------------------------------------- + +ScfPropSetHelper& XclChPropSetHelper::GetLineHelper( XclChPropertyMode ePropMode ) +{ + switch( ePropMode ) + { + case EXC_CHPROPMODE_COMMON: return maLineHlpCommon; + case EXC_CHPROPMODE_LINEARSERIES: return maLineHlpLinear; + case EXC_CHPROPMODE_FILLEDSERIES: return maLineHlpFilled; + default: OSL_FAIL( "XclChPropSetHelper::GetLineHelper - unknown property mode" ); + } + return maLineHlpCommon; +} + +ScfPropSetHelper& XclChPropSetHelper::GetAreaHelper( XclChPropertyMode ePropMode ) +{ + switch( ePropMode ) + { + case EXC_CHPROPMODE_COMMON: return maAreaHlpCommon; + case EXC_CHPROPMODE_FILLEDSERIES: return maAreaHlpFilled; + default: OSL_FAIL( "XclChPropSetHelper::GetAreaHelper - unknown property mode" ); + } + return maAreaHlpCommon; +} + +ScfPropSetHelper& XclChPropSetHelper::GetGradientHelper( XclChPropertyMode ePropMode ) +{ + switch( ePropMode ) + { + case EXC_CHPROPMODE_COMMON: return maGradHlpCommon; + case EXC_CHPROPMODE_FILLEDSERIES: return maGradHlpFilled; + default: OSL_FAIL( "XclChPropSetHelper::GetGradientHelper - unknown property mode" ); + } + return maGradHlpCommon; +} + +ScfPropSetHelper& XclChPropSetHelper::GetHatchHelper( XclChPropertyMode ePropMode ) +{ + switch( ePropMode ) + { + case EXC_CHPROPMODE_COMMON: return maHatchHlpCommon; + case EXC_CHPROPMODE_FILLEDSERIES: return maHatchHlpFilled; + default: OSL_FAIL( "XclChPropSetHelper::GetHatchHelper - unknown property mode" ); + } + return maHatchHlpCommon; +} + +namespace { + +/* The following local functions implement getting the XShape interface of all + supported title objects (chart and axes). This needs some effort due to the + design of the old Chart1 API used to access these objects. */ + +/** Returns the drawing shape of the main title, if existing. */ +uno::Reference lclGetMainTitleShape(const uno::Reference & rxChart1Doc) +{ + ScfPropertySet aPropSet(rxChart1Doc); + if (rxChart1Doc.is() && aPropSet.GetBoolProperty("HasMainTitle")) + return rxChart1Doc->getTitle(); + return uno::Reference(); +} + +uno::Reference lclGetXAxisTitleShape(const uno::Reference & rxChart1Doc) +{ + uno::Reference xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasXAxisTitle")) + return xAxisSupp->getXAxisTitle(); + return uno::Reference(); +} + +uno::Reference lclGetYAxisTitleShape(const uno::Reference & rxChart1Doc ) +{ + uno::Reference xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasYAxisTitle")) + return xAxisSupp->getYAxisTitle(); + return uno::Reference(); +} + +uno::Reference lclGetZAxisTitleShape(const uno::Reference & rxChart1Doc ) +{ + uno::Reference xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasZAxisTitle")) + return xAxisSupp->getZAxisTitle(); + return uno::Reference(); +} + +uno::Reference lclGetSecXAxisTitleShape(const uno::Reference & rxChart1Doc) +{ + uno::Reference xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasSecondaryXAxisTitle")) + return xAxisSupp->getSecondXAxisTitle(); + return uno::Reference(); +} + +uno::Reference lclGetSecYAxisTitleShape(const uno::Reference & rxChart1Doc) +{ + uno::Reference xAxisSupp(rxChart1Doc->getDiagram(), uno::UNO_QUERY); + ScfPropertySet aPropSet(xAxisSupp); + if (xAxisSupp.is() && aPropSet.GetBoolProperty("HasSecondaryYAxisTitle")) + return xAxisSupp->getSecondYAxisTitle(); + return uno::Reference(); +} + +} // namespace + +XclChRootData::XclChRootData() + : mxTypeInfoProv(std::make_shared()) + , mxFmtInfoProv(std::make_shared()) + , mnBorderGapX(0) + , mnBorderGapY(0) + , mfUnitSizeX(0.0) + , mfUnitSizeY(0.0) +{ + // remember some title shape getter functions + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_TITLE ) ] = lclGetMainTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_PRIMARY, EXC_CHAXIS_X ) ] = lclGetXAxisTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_PRIMARY, EXC_CHAXIS_Y ) ] = lclGetYAxisTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_PRIMARY, EXC_CHAXIS_Z ) ] = lclGetZAxisTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_SECONDARY, EXC_CHAXIS_X ) ] = lclGetSecXAxisTitleShape; + maGetShapeFuncs[ XclChTextKey( EXC_CHTEXTTYPE_AXISTITLE, EXC_CHAXESSET_SECONDARY, EXC_CHAXIS_Y ) ] = lclGetSecYAxisTitleShape; +} + +XclChRootData::~XclChRootData() +{ +} + +void XclChRootData::InitConversion(const XclRoot& rRoot, const uno::Reference & rxChartDoc, const tools::Rectangle& rChartRect) +{ + // remember chart document reference and chart shape position/size + OSL_ENSURE( rxChartDoc.is(), "XclChRootData::InitConversion - missing chart document" ); + mxChartDoc = rxChartDoc; + maChartRect = rChartRect; + + // Excel excludes a border of 5 pixels in each direction from chart area + mnBorderGapX = rRoot.GetHmmFromPixelX( 5.0 ); + mnBorderGapY = rRoot.GetHmmFromPixelY( 5.0 ); + + // size of a chart unit in 1/100 mm + mfUnitSizeX = std::max( maChartRect.GetWidth() - 2 * mnBorderGapX, mnBorderGapX ) / EXC_CHART_TOTALUNITS; + mfUnitSizeY = std::max( maChartRect.GetHeight() - 2 * mnBorderGapY, mnBorderGapY ) / EXC_CHART_TOTALUNITS; + + // create object tables + uno::Reference xFactory(mxChartDoc, uno::UNO_QUERY); + mxLineDashTable = std::make_shared(xFactory, SERVICE_DRAWING_DASHTABLE, "Excel line dash "); + mxGradientTable = std::make_shared(xFactory, SERVICE_DRAWING_GRADIENTTABLE, "Excel gradient "); + mxHatchTable = std::make_shared(xFactory, SERVICE_DRAWING_HATCHTABLE, "Excel hatch "); + mxBitmapTable = std::make_shared(xFactory, SERVICE_DRAWING_BITMAPTABLE, "Excel bitmap "); +} + +void XclChRootData::FinishConversion() +{ + // forget formatting object tables + mxBitmapTable.reset(); + mxHatchTable.reset(); + mxGradientTable.reset(); + mxLineDashTable.reset(); + // forget chart document reference + mxChartDoc.clear(); +} + +uno::Reference XclChRootData::GetTitleShape(const XclChTextKey& rTitleKey) const +{ + XclChGetShapeFuncMap::const_iterator aIt = maGetShapeFuncs.find( rTitleKey ); + OSL_ENSURE( aIt != maGetShapeFuncs.end(), "XclChRootData::GetTitleShape - invalid title key" ); + uno::Reference xChart1Doc( mxChartDoc, uno::UNO_QUERY ); + uno::Reference xTitleShape; + if (xChart1Doc.is() && (aIt != maGetShapeFuncs.end())) + xTitleShape = (aIt->second)(xChart1Doc); + return xTitleShape; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlescher.cxx b/sc/source/filter/excel/xlescher.cxx new file mode 100644 index 000000000..8ae663cdd --- /dev/null +++ b/sc/source/filter/excel/xlescher.cxx @@ -0,0 +1,335 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::drawing::XShape; +using ::com::sun::star::drawing::XControlShape; +using ::com::sun::star::awt::XControlModel; +using ::com::sun::star::script::ScriptEventDescriptor; + +namespace { + +/** Returns the scaling factor to calculate coordinates from twips. */ +double lclGetTwipsScale( MapUnit eMapUnit ) +{ + double fScale = 1.0; + if (const auto eTo = MapToO3tlLength(eMapUnit); eTo != o3tl::Length::invalid) + fScale = o3tl::convert(1.0, o3tl::Length::twip, eTo); + else + OSL_FAIL("lclGetTwipsScale - map unit not implemented"); + return fScale; +} + +/** Calculates a drawing layer X position (in twips) from an object column position. */ +tools::Long lclGetXFromCol( const ScDocument& rDoc, SCTAB nScTab, sal_uInt16 nXclCol, sal_uInt16 nOffset, double fScale ) +{ + SCCOL nScCol = static_cast< SCCOL >( nXclCol ); + return static_cast< tools::Long >( fScale * (rDoc.GetColOffset( nScCol, nScTab ) + + ::std::min( nOffset / 1024.0, 1.0 ) * rDoc.GetColWidth( nScCol, nScTab )) + 0.5 ); +} + +/** Calculates a drawing layer Y position (in twips) from an object row position. */ +tools::Long lclGetYFromRow( const ScDocument& rDoc, SCTAB nScTab, sal_uInt16 nXclRow, sal_uInt16 nOffset, double fScale ) +{ + SCROW nScRow = static_cast< SCROW >( nXclRow ); + return static_cast< tools::Long >( fScale * (rDoc.GetRowOffset( nScRow, nScTab ) + + ::std::min( nOffset / 256.0, 1.0 ) * rDoc.GetRowHeight( nScRow, nScTab )) + 0.5 ); +} + +/** Calculates an object column position from a drawing layer X position (in twips). */ +void lclGetColFromX( + const ScDocument& rDoc, SCTAB nScTab, sal_uInt16& rnXclCol, + sal_uInt16& rnOffset, sal_uInt16 nXclStartCol, sal_uInt16 nXclMaxCol, + tools::Long& rnStartW, tools::Long nX, double fScale ) +{ + // rnStartW in conjunction with nXclStartCol is used as buffer for previously calculated width + tools::Long nTwipsX = static_cast< tools::Long >( nX / fScale + 0.5 ); + tools::Long nColW = 0; + for( rnXclCol = nXclStartCol; rnXclCol <= nXclMaxCol; ++rnXclCol ) + { + nColW = rDoc.GetColWidth( static_cast< SCCOL >( rnXclCol ), nScTab ); + if( rnStartW + nColW > nTwipsX ) + break; + rnStartW += nColW; + } + rnOffset = nColW ? static_cast< sal_uInt16 >( (nTwipsX - rnStartW) * 1024.0 / nColW + 0.5 ) : 0; +} + +/** Calculates an object row position from a drawing layer Y position (in twips). */ +void lclGetRowFromY( + const ScDocument& rDoc, SCTAB nScTab, sal_uInt32& rnXclRow, + sal_uInt32& rnOffset, sal_uInt32 nXclStartRow, sal_uInt32 nXclMaxRow, + tools::Long& rnStartH, tools::Long nY, double fScale ) +{ + // rnStartH in conjunction with nXclStartRow is used as buffer for previously calculated height + tools::Long nTwipsY = static_cast< tools::Long >( nY / fScale + 0.5 ); + tools::Long nRowH = 0; + bool bFound = false; + for( sal_uInt32 nRow = nXclStartRow; nRow <= nXclMaxRow; ++nRow ) + { + nRowH = rDoc.GetRowHeight( nRow, nScTab ); + if( rnStartH + nRowH > nTwipsY ) + { + rnXclRow = nRow; + bFound = true; + break; + } + rnStartH += nRowH; + } + if( !bFound ) + rnXclRow = nXclMaxRow; + rnOffset = static_cast< sal_uInt32 >( nRowH ? std::max((nTwipsY - rnStartH) * 256.0 / nRowH + 0.5, 0.0) : 0 ); +} + +/** Mirrors a rectangle (from LTR to RTL layout or vice versa). */ +void lclMirrorRectangle( tools::Rectangle& rRect ) +{ + tools::Long nLeft = rRect.Left(); + rRect.SetLeft( -rRect.Right() ); + rRect.SetRight( -nLeft ); +} + +sal_uInt16 lclGetEmbeddedScale( tools::Long nPageSize, sal_Int32 nPageScale, tools::Long nPos, double fPosScale ) +{ + return static_cast< sal_uInt16 >( nPos * fPosScale / nPageSize * nPageScale + 0.5 ); +} + +} // namespace + +XclObjAnchor::XclObjAnchor() : + mnLX( 0 ), + mnTY( 0 ), + mnRX( 0 ), + mnBY( 0 ) +{ +} + +tools::Rectangle XclObjAnchor::GetRect( const XclRoot& rRoot, SCTAB nScTab, MapUnit eMapUnit ) const +{ + ScDocument& rDoc = rRoot.GetDoc(); + double fScale = lclGetTwipsScale( eMapUnit ); + tools::Rectangle aRect( + lclGetXFromCol(rDoc, nScTab, std::min(maFirst.mnCol, rDoc.MaxCol()), mnLX, fScale), + lclGetYFromRow(rDoc, nScTab, std::min(maFirst.mnRow, rDoc.MaxRow()), mnTY, fScale), + lclGetXFromCol(rDoc, nScTab, std::min(maLast.mnCol, rDoc.MaxCol()), mnRX + 1, fScale), + lclGetYFromRow(rDoc, nScTab, std::min(maLast.mnRow, rDoc.MaxRow()), mnBY, fScale)); + + // adjust coordinates in mirrored sheets + if( rDoc.IsLayoutRTL( nScTab ) ) + lclMirrorRectangle( aRect ); + return aRect; +} + +void XclObjAnchor::SetRect( const XclRoot& rRoot, SCTAB nScTab, const tools::Rectangle& rRect, MapUnit eMapUnit ) +{ + ScDocument& rDoc = rRoot.GetDoc(); + sal_uInt16 nXclMaxCol = rRoot.GetXclMaxPos().Col(); + sal_uInt16 nXclMaxRow = static_cast( rRoot.GetXclMaxPos().Row()); + + // adjust coordinates in mirrored sheets + tools::Rectangle aRect( rRect ); + if( rDoc.IsLayoutRTL( nScTab ) ) + lclMirrorRectangle( aRect ); + + double fScale = lclGetTwipsScale( eMapUnit ); + tools::Long nDummy = 0; + lclGetColFromX( rDoc, nScTab, maFirst.mnCol, mnLX, 0, nXclMaxCol, nDummy, aRect.Left(), fScale ); + lclGetColFromX( rDoc, nScTab, maLast.mnCol, mnRX, maFirst.mnCol, nXclMaxCol, nDummy, aRect.Right(), fScale ); + nDummy = 0; + lclGetRowFromY( rDoc, nScTab, maFirst.mnRow, mnTY, 0, nXclMaxRow, nDummy, aRect.Top(), fScale ); + lclGetRowFromY( rDoc, nScTab, maLast.mnRow, mnBY, maFirst.mnRow, nXclMaxRow, nDummy, aRect.Bottom(), fScale ); +} + +void XclObjAnchor::SetRect( const Size& rPageSize, sal_Int32 nScaleX, sal_Int32 nScaleY, + const tools::Rectangle& rRect, MapUnit eMapUnit ) +{ + double fScale = 1.0; + if (const auto eFrom = MapToO3tlLength(eMapUnit); eFrom != o3tl::Length::invalid) + fScale = o3tl::convert(1.0, eFrom, o3tl::Length::mm100); + else + OSL_FAIL("XclObjAnchor::SetRect - map unit not implemented"); + + /* In objects with DFF client anchor, the position of the shape is stored + in the cell address components of the client anchor. In old BIFF3-BIFF5 + objects, the position is stored in the offset components of the anchor. */ + maFirst.mnCol = lclGetEmbeddedScale( rPageSize.Width(), nScaleX, rRect.Left(), fScale ); + maFirst.mnRow = lclGetEmbeddedScale( rPageSize.Height(), nScaleY, rRect.Top(), fScale ); + maLast.mnCol = lclGetEmbeddedScale( rPageSize.Width(), nScaleX, rRect.Right(), fScale ); + maLast.mnRow = lclGetEmbeddedScale( rPageSize.Height(), nScaleY, rRect.Bottom(), fScale ); + + // for safety, clear the other members + mnLX = mnTY = mnRX = mnBY = 0; +} + +XclObjLineData::XclObjLineData() : + mnColorIdx( EXC_OBJ_LINE_AUTOCOLOR ), + mnStyle( EXC_OBJ_LINE_SOLID ), + mnWidth( EXC_OBJ_LINE_HAIR ), + mnAuto( EXC_OBJ_LINE_AUTO ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclObjLineData& rLineData ) +{ + rLineData.mnColorIdx = rStrm.ReaduInt8(); + rLineData.mnStyle = rStrm.ReaduInt8(); + rLineData.mnWidth = rStrm.ReaduInt8(); + rLineData.mnAuto = rStrm.ReaduInt8(); + return rStrm; +} + +XclObjFillData::XclObjFillData() : + mnBackColorIdx( EXC_OBJ_LINE_AUTOCOLOR ), + mnPattColorIdx( EXC_OBJ_FILL_AUTOCOLOR ), + mnPattern( EXC_PATT_SOLID ), + mnAuto( EXC_OBJ_FILL_AUTO ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclObjFillData& rFillData ) +{ + rFillData.mnBackColorIdx = rStrm.ReaduInt8(); + rFillData.mnPattColorIdx = rStrm.ReaduInt8(); + rFillData.mnPattern = rStrm.ReaduInt8(); + rFillData.mnAuto = rStrm.ReaduInt8(); + return rStrm; +} + +XclObjTextData::XclObjTextData() : + mnTextLen( 0 ), + mnFormatSize( 0 ), + mnLinkSize( 0 ), + mnDefFontIdx( EXC_FONT_APP ), + mnFlags( 0 ), + mnOrient( EXC_OBJ_ORIENT_NONE ), + mnButtonFlags( 0 ), + mnShortcut( 0 ), + mnShortcutEA( 0 ) +{ +} + +void XclObjTextData::ReadObj3( XclImpStream& rStrm ) +{ + mnTextLen = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnFormatSize = rStrm.ReaduInt16(); + mnDefFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnFlags = rStrm.ReaduInt16(); + mnOrient = rStrm.ReaduInt16(); + rStrm.Ignore( 8 ); +} + +void XclObjTextData::ReadObj5( XclImpStream& rStrm ) +{ + mnTextLen = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnFormatSize = rStrm.ReaduInt16(); + mnDefFontIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnFlags = rStrm.ReaduInt16(); + mnOrient = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnLinkSize = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + mnButtonFlags = rStrm.ReaduInt16(); + mnShortcut = rStrm.ReaduInt16(); + mnShortcutEA = rStrm.ReaduInt16(); +} + +void XclObjTextData::ReadTxo8( XclImpStream& rStrm ) +{ + mnFlags = rStrm.ReaduInt16(); + mnOrient = rStrm.ReaduInt16(); + mnButtonFlags = rStrm.ReaduInt16(); + mnShortcut = rStrm.ReaduInt16(); + mnShortcutEA = rStrm.ReaduInt16(); + mnTextLen = rStrm.ReaduInt16(); + mnFormatSize = rStrm.ReaduInt16(); +} + +Reference< XControlModel > XclControlHelper::GetControlModel( Reference< XShape > const & xShape ) +{ + Reference< XControlModel > xCtrlModel; + Reference< XControlShape > xCtrlShape( xShape, UNO_QUERY ); + if( xCtrlShape.is() ) + xCtrlModel = xCtrlShape->getControl(); + return xCtrlModel; +} + +namespace { + +const struct +{ + const char* mpcListenerType; + const char* mpcEventMethod; +} +spTbxListenerData[] = +{ + // Attention: MUST be in order of the XclTbxEventType enum! + /*EXC_TBX_EVENT_ACTION*/ { "XActionListener", "actionPerformed" }, + /*EXC_TBX_EVENT_MOUSE*/ { "XMouseListener", "mouseReleased" }, + /*EXC_TBX_EVENT_TEXT*/ { "XTextListener", "textChanged" }, + /*EXC_TBX_EVENT_VALUE*/ { "XAdjustmentListener", "adjustmentValueChanged" }, + /*EXC_TBX_EVENT_CHANGE*/ { "XChangeListener", "changed" } +}; + +} // namespace + +bool XclControlHelper::FillMacroDescriptor( ScriptEventDescriptor& rDescriptor, + XclTbxEventType eEventType, const OUString& rXclMacroName, SfxObjectShell* pDocShell ) +{ + if( !rXclMacroName.isEmpty() ) + { + rDescriptor.ListenerType = OUString::createFromAscii( spTbxListenerData[ eEventType ].mpcListenerType ); + rDescriptor.EventMethod = OUString::createFromAscii( spTbxListenerData[ eEventType ].mpcEventMethod ); + rDescriptor.ScriptType = "Script"; + rDescriptor.ScriptCode = XclTools::GetSbMacroUrl( rXclMacroName, pDocShell ); + return true; + } + return false; +} + +OUString XclControlHelper::ExtractFromMacroDescriptor( + const ScriptEventDescriptor& rDescriptor, XclTbxEventType eEventType ) +{ + if( (!rDescriptor.ScriptCode.isEmpty()) && + rDescriptor.ScriptType.equalsIgnoreAsciiCase("Script") && + rDescriptor.ListenerType.equalsAscii( spTbxListenerData[ eEventType ].mpcListenerType ) && + rDescriptor.EventMethod.equalsAscii( spTbxListenerData[ eEventType ].mpcEventMethod ) ) + return XclTools::GetXclMacroName( rDescriptor.ScriptCode ); + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlformula.cxx b/sc/source/filter/excel/xlformula.cxx new file mode 100644 index 000000000..e2e082ac2 --- /dev/null +++ b/sc/source/filter/excel/xlformula.cxx @@ -0,0 +1,1022 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace ::formula; + +// Function data ============================================================== + +OUString XclFunctionInfo::GetMacroFuncName() const +{ + if( IsMacroFunc() ) + return OUString( mpcMacroName, strlen(mpcMacroName), RTL_TEXTENCODING_UTF8 ); + return OUString(); +} + +OUString XclFunctionInfo::GetAddInEquivalentFuncName() const +{ + if( IsAddInEquivalent() ) + return OUString( mpcMacroName, strlen(mpcMacroName), RTL_TEXTENCODING_UTF8 ); + return OUString(); +} + +// abbreviations for function return token class +const sal_uInt8 R = EXC_TOKCLASS_REF; +const sal_uInt8 V = EXC_TOKCLASS_VAL; +const sal_uInt8 A = EXC_TOKCLASS_ARR; + +// abbreviations for parameter infos +#define RO { EXC_PARAM_REGULAR, EXC_PARAMCONV_ORG, false } +#define RA { EXC_PARAM_REGULAR, EXC_PARAMCONV_ARR, false } +#define RR { EXC_PARAM_REGULAR, EXC_PARAMCONV_RPT, false } +#define RX { EXC_PARAM_REGULAR, EXC_PARAMCONV_RPX, false } +#define VO { EXC_PARAM_REGULAR, EXC_PARAMCONV_ORG, true } +#define VV { EXC_PARAM_REGULAR, EXC_PARAMCONV_VAL, true } +#define VA { EXC_PARAM_REGULAR, EXC_PARAMCONV_ARR, true } +#define VR { EXC_PARAM_REGULAR, EXC_PARAMCONV_RPT, true } +#define VX { EXC_PARAM_REGULAR, EXC_PARAMCONV_RPX, true } +#define RO_E { EXC_PARAM_EXCELONLY, EXC_PARAMCONV_ORG, false } +#define VR_E { EXC_PARAM_EXCELONLY, EXC_PARAMCONV_RPT, true } +#define C { EXC_PARAM_CALCONLY, EXC_PARAMCONV_ORG, false } + +const sal_uInt16 NOID = SAL_MAX_UINT16; /// No BIFF/OOBIN function identifier available. +const sal_uInt8 MX = 30; /// Maximum parameter count. + +#define EXC_FUNCNAME( ascii ) "_xlfn." ascii +#define EXC_FUNCNAME_ODF( ascii ) "_xlfnodf." ascii +#define EXC_FUNCNAME_ADDIN( ascii ) "com.sun.star.sheet.addin." ascii + +/** Functions new in BIFF2. */ +const XclFunctionInfo saFuncTable_2[] = +{ + { ocCount, 0, 0, MX, V, { RX }, 0, nullptr }, + { ocIf, 1, 2, 3, R, { VO, RO }, 0, nullptr }, + { ocIsNA, 2, 1, 1, V, { VR }, 0, nullptr }, + { ocIsError, 3, 1, 1, V, { VR }, 0, nullptr }, + { ocSum, 4, 0, MX, V, { RX }, 0, nullptr }, + { ocAverage, 5, 1, MX, V, { RX }, 0, nullptr }, + { ocMin, 6, 1, MX, V, { RX }, 0, nullptr }, + { ocMax, 7, 1, MX, V, { RX }, 0, nullptr }, + { ocRow, 8, 0, 1, V, { RO }, 0, nullptr }, + { ocColumn, 9, 0, 1, V, { RO }, 0, nullptr }, + { ocNotAvail, 10, 0, 0, V, {}, 0, nullptr }, + { ocNPV, 11, 2, MX, V, { VR, RX }, 0, nullptr }, + { ocStDev, 12, 1, MX, V, { RX }, 0, nullptr }, + { ocCurrency, 13, 1, 2, V, { VR }, 0, nullptr }, + { ocFixed, 14, 1, 2, V, { VR, VR, C }, 0, nullptr }, + { ocSin, 15, 1, 1, V, { VR }, 0, nullptr }, + { ocCosecant, 15, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocCos, 16, 1, 1, V, { VR }, 0, nullptr }, + { ocSecant, 16, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocTan, 17, 1, 1, V, { VR }, 0, nullptr }, + { ocCot, 17, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocArcTan, 18, 1, 1, V, { VR }, 0, nullptr }, + { ocArcCot, 18, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocPi, 19, 0, 0, V, {}, 0, nullptr }, + { ocSqrt, 20, 1, 1, V, { VR }, 0, nullptr }, + { ocExp, 21, 1, 1, V, { VR }, 0, nullptr }, + { ocLn, 22, 1, 1, V, { VR }, 0, nullptr }, + { ocLog10, 23, 1, 1, V, { VR }, 0, nullptr }, + { ocAbs, 24, 1, 1, V, { VR }, 0, nullptr }, + { ocInt, 25, 1, 1, V, { VR }, 0, nullptr }, + { ocPlusMinus, 26, 1, 1, V, { VR }, 0, nullptr }, + { ocRound, 27, 2, 2, V, { VR }, 0, nullptr }, + { ocLookup, 28, 2, 3, V, { VR, RA }, 0, nullptr }, + { ocIndex, 29, 2, 4, R, { RA, VV }, 0, nullptr }, + { ocRept, 30, 2, 2, V, { VR }, 0, nullptr }, + { ocMid, 31, 3, 3, V, { VR }, 0, nullptr }, + { ocLen, 32, 1, 1, V, { VR }, 0, nullptr }, + { ocValue, 33, 1, 1, V, { VR }, 0, nullptr }, + { ocTrue, 34, 0, 0, V, {}, 0, nullptr }, + { ocFalse, 35, 0, 0, V, {}, 0, nullptr }, + { ocAnd, 36, 1, MX, V, { RX }, 0, nullptr }, + { ocOr, 37, 1, MX, V, { RX }, 0, nullptr }, + { ocNot, 38, 1, 1, V, { VR }, 0, nullptr }, + { ocMod, 39, 2, 2, V, { VR }, 0, nullptr }, + { ocDBCount, 40, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBSum, 41, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBAverage, 42, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBMin, 43, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBMax, 44, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBStdDev, 45, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocVar, 46, 1, MX, V, { RX }, 0, nullptr }, + { ocDBVar, 47, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocText, 48, 2, 2, V, { VR }, 0, nullptr }, + { ocLinest, 49, 1, 2, A, { RA, RA, C, C }, 0, nullptr }, + { ocTrend, 50, 1, 3, A, { RA, RA, RA, C }, 0, nullptr }, + { ocLogest, 51, 1, 2, A, { RA, RA, C, C }, 0, nullptr }, + { ocGrowth, 52, 1, 3, A, { RA, RA, RA, C }, 0, nullptr }, + { ocPV, 56, 3, 5, V, { VR }, 0, nullptr }, + { ocFV, 57, 3, 5, V, { VR }, 0, nullptr }, + { ocNper, 58, 3, 5, V, { VR }, 0, nullptr }, + { ocPMT, 59, 3, 5, V, { VR }, 0, nullptr }, + { ocRate, 60, 3, 6, V, { VR }, 0, nullptr }, + { ocMIRR, 61, 3, 3, V, { RA, VR }, 0, nullptr }, + { ocIRR, 62, 1, 2, V, { RA, VR }, 0, nullptr }, + { ocRandom, 63, 0, 0, V, {}, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocMatch, 64, 2, 3, V, { VR, RX, RR }, 0, nullptr }, + { ocGetDate, 65, 3, 3, V, { VR }, 0, nullptr }, + { ocGetTime, 66, 3, 3, V, { VR }, 0, nullptr }, + { ocGetDay, 67, 1, 1, V, { VR }, 0, nullptr }, + { ocGetMonth, 68, 1, 1, V, { VR }, 0, nullptr }, + { ocGetYear, 69, 1, 1, V, { VR }, 0, nullptr }, + { ocGetDayOfWeek, 70, 1, 1, V, { VR, C }, 0, nullptr }, + { ocGetHour, 71, 1, 1, V, { VR }, 0, nullptr }, + { ocGetMin, 72, 1, 1, V, { VR }, 0, nullptr }, + { ocGetSec, 73, 1, 1, V, { VR }, 0, nullptr }, + { ocGetActTime, 74, 0, 0, V, {}, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocAreas, 75, 1, 1, V, { RO }, 0, nullptr }, + { ocRows, 76, 1, 1, V, { RO }, 0, nullptr }, + { ocColumns, 77, 1, 1, V, { RO }, 0, nullptr }, + { ocOffset, 78, 3, 5, R, { RO, VR }, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocSearch, 82, 2, 3, V, { VR }, 0, nullptr }, + { ocMatTrans, 83, 1, 1, A, { VO }, 0, nullptr }, + { ocType, 86, 1, 1, V, { VX }, 0, nullptr }, + { ocArcTan2, 97, 2, 2, V, { VR }, 0, nullptr }, + { ocArcSin, 98, 1, 1, V, { VR }, 0, nullptr }, + { ocArcCos, 99, 1, 1, V, { VR }, 0, nullptr }, + { ocChoose, 100, 2, MX, R, { VO, RO }, 0, nullptr }, + { ocHLookup, 101, 3, 3, V, { VV, RO, RO, C }, 0, nullptr }, + { ocVLookup, 102, 3, 3, V, { VV, RO, RO, C }, 0, nullptr }, + { ocIsRef, 105, 1, 1, V, { RX }, 0, nullptr }, + { ocLog, 109, 1, 2, V, { VR }, 0, nullptr }, + { ocChar, 111, 1, 1, V, { VR }, 0, nullptr }, + { ocLower, 112, 1, 1, V, { VR }, 0, nullptr }, + { ocUpper, 113, 1, 1, V, { VR }, 0, nullptr }, + { ocProper, 114, 1, 1, V, { VR }, 0, nullptr }, + { ocLeft, 115, 1, 2, V, { VR }, 0, nullptr }, + { ocRight, 116, 1, 2, V, { VR }, 0, nullptr }, + { ocExact, 117, 2, 2, V, { VR }, 0, nullptr }, + { ocTrim, 118, 1, 1, V, { VR }, 0, nullptr }, + { ocReplace, 119, 4, 4, V, { VR }, 0, nullptr }, + { ocSubstitute, 120, 3, 4, V, { VR }, 0, nullptr }, + { ocCode, 121, 1, 1, V, { VR }, 0, nullptr }, + { ocFind, 124, 2, 3, V, { VR }, 0, nullptr }, + { ocCell, 125, 1, 2, V, { VV, RO }, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocIsErr, 126, 1, 1, V, { VR }, 0, nullptr }, + { ocIsString, 127, 1, 1, V, { VR }, 0, nullptr }, + { ocIsValue, 128, 1, 1, V, { VR }, 0, nullptr }, + { ocIsEmpty, 129, 1, 1, V, { VR }, 0, nullptr }, + { ocT, 130, 1, 1, V, { RO }, 0, nullptr }, + { ocN, 131, 1, 1, V, { RO }, 0, nullptr }, + { ocGetDateValue, 140, 1, 1, V, { VR }, 0, nullptr }, + { ocGetTimeValue, 141, 1, 1, V, { VR }, 0, nullptr }, + { ocSLN, 142, 3, 3, V, { VR }, 0, nullptr }, + { ocSYD, 143, 4, 4, V, { VR }, 0, nullptr }, + { ocDDB, 144, 4, 5, V, { VR }, 0, nullptr }, + { ocIndirect, 148, 1, 2, R, { VR }, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocClean, 162, 1, 1, V, { VR }, 0, nullptr }, + { ocMatDet, 163, 1, 1, V, { VA }, 0, nullptr }, + { ocMatInv, 164, 1, 1, A, { VA }, 0, nullptr }, + { ocMatMult, 165, 2, 2, A, { VA }, 0, nullptr }, + { ocIpmt, 167, 4, 6, V, { VR }, 0, nullptr }, + { ocPpmt, 168, 4, 6, V, { VR }, 0, nullptr }, + { ocCount2, 169, 0, MX, V, { RX }, 0, nullptr }, + { ocProduct, 183, 0, MX, V, { RX }, 0, nullptr }, + { ocFact, 184, 1, 1, V, { VR }, 0, nullptr }, + { ocDBProduct, 189, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocIsNonString, 190, 1, 1, V, { VR }, 0, nullptr }, + { ocStDevP, 193, 1, MX, V, { RX }, 0, nullptr }, + { ocVarP, 194, 1, MX, V, { RX }, 0, nullptr }, + { ocDBStdDevP, 195, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocDBVarP, 196, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocTrunc, 197, 1, 1, V, { VR, C }, 0, nullptr }, + { ocIsLogical, 198, 1, 1, V, { VR }, 0, nullptr }, + { ocDBCount2, 199, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocCurrency, 204, 1, 2, V, { VR }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, + { ocFindB, 205, 2, 3, V, { VR }, 0, nullptr }, + { ocSearchB, 206, 2, 3, V, { VR }, 0, nullptr }, + { ocReplaceB, 207, 4, 4, V, { VR }, 0, nullptr }, + { ocLeftB, 208, 1, 2, V, { VR }, 0, nullptr }, + { ocRightB, 209, 1, 2, V, { VR }, 0, nullptr }, + { ocMidB, 210, 3, 3, V, { VR }, 0, nullptr }, + { ocLenB, 211, 1, 1, V, { VR }, 0, nullptr }, + { ocRoundUp, 212, 2, 2, V, { VR }, 0, nullptr }, + { ocRoundDown, 213, 2, 2, V, { VR }, 0, nullptr }, + { ocExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_IMPORTONLY, nullptr } +}; + +/** Functions new in BIFF3. */ +const XclFunctionInfo saFuncTable_3[] = +{ + { ocLinest, 49, 1, 4, A, { RA, RA, VV }, 0, nullptr }, // BIFF2: 1-2, BIFF3: 1-4 + { ocTrend, 50, 1, 4, A, { RA, RA, RA, VV }, 0, nullptr }, // BIFF2: 1-3, BIFF3: 1-4 + { ocLogest, 51, 1, 4, A, { RA, RA, VV }, 0, nullptr }, // BIFF2: 1-2, BIFF3: 1-4 + { ocGrowth, 52, 1, 4, A, { RA, RA, RA, VV }, 0, nullptr }, // BIFF2: 1-3, BIFF3: 1-4 + { ocTrunc, 197, 1, 2, V, { VR }, 0, nullptr }, // BIFF2: 1, BIFF3: 1-2 + { ocAddress, 219, 2, 5, V, { VR }, 0, nullptr }, + { ocGetDiffDate360, 220, 2, 2, V, { VR, VR, C }, 0, nullptr }, + { ocGetActDate, 221, 0, 0, V, {}, EXC_FUNCFLAG_VOLATILE, nullptr }, + { ocVBD, 222, 5, 7, V, { VR }, 0, nullptr }, + { ocMedian, 227, 1, MX, V, { RX }, 0, nullptr }, + { ocSumProduct, 228, 1, MX, V, { VA }, 0, nullptr }, + { ocSinHyp, 229, 1, 1, V, { VR }, 0, nullptr }, + { ocCosecantHyp, 229, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocCosHyp, 230, 1, 1, V, { VR }, 0, nullptr }, + { ocSecantHyp, 230, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocTanHyp, 231, 1, 1, V, { VR }, 0, nullptr }, + { ocCotHyp, 231, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocArcSinHyp, 232, 1, 1, V, { VR }, 0, nullptr }, + { ocArcCosHyp, 233, 1, 1, V, { VR }, 0, nullptr }, + { ocArcTanHyp, 234, 1, 1, V, { VR }, 0, nullptr }, + { ocArcCotHyp, 234, 1, 1, V, { VR }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocDBGet, 235, 3, 3, V, { RO, RR }, 0, nullptr }, + { ocInfo, 244, 1, 1, V, { VR }, EXC_FUNCFLAG_VOLATILE, nullptr } +}; + +/** Functions new in BIFF4. */ +const XclFunctionInfo saFuncTable_4[] = +{ + { ocFixed, 14, 1, 3, V, { VR }, 0, nullptr }, // BIFF2-3: 1-2, BIFF4: 1-3 + { ocAsc, 214, 1, 1, V, { VR }, 0, nullptr }, + { ocJis, 215, 1, 1, V, { VR }, 0, nullptr }, + { ocRank, 216, 2, 3, V, { VR, RO, VR }, 0, nullptr }, + { ocDB, 247, 4, 5, V, { VR }, 0, nullptr }, + { ocFrequency, 252, 2, 2, A, { RA }, 0, nullptr }, + { ocErrorType_ODF, 261, 1, 1, V, { VR }, 0, nullptr }, + { ocAveDev, 269, 1, MX, V, { RX }, 0, nullptr }, + { ocBetaDist, 270, 3, 5, V, { VR }, 0, nullptr }, + { ocGammaLn, 271, 1, 1, V, { VR }, 0, nullptr }, + { ocBetaInv, 272, 3, 5, V, { VR }, 0, nullptr }, + { ocBinomDist, 273, 4, 4, V, { VR }, 0, nullptr }, + { ocChiDist, 274, 2, 2, V, { VR }, 0, nullptr }, + { ocChiInv, 275, 2, 2, V, { VR }, 0, nullptr }, + { ocCombin, 276, 2, 2, V, { VR }, 0, nullptr }, + { ocConfidence, 277, 3, 3, V, { VR }, 0, nullptr }, + { ocCritBinom, 278, 3, 3, V, { VR }, 0, nullptr }, + { ocEven, 279, 1, 1, V, { VR }, 0, nullptr }, + { ocExpDist, 280, 3, 3, V, { VR }, 0, nullptr }, + { ocFDist, 281, 3, 3, V, { VR }, 0, nullptr }, + { ocFInv, 282, 3, 3, V, { VR }, 0, nullptr }, + { ocFisher, 283, 1, 1, V, { VR }, 0, nullptr }, + { ocFisherInv, 284, 1, 1, V, { VR }, 0, nullptr }, + { ocFloor_MS, 285, 2, 2, V, { VR }, 0, nullptr }, + { ocGammaDist, 286, 4, 4, V, { VR }, 0, nullptr }, + { ocGammaInv, 287, 3, 3, V, { VR }, 0, nullptr }, + { ocCeil_MS, 288, 2, 2, V, { VR }, 0, nullptr }, + { ocHypGeomDist, 289, 4, 4, V, { VR }, 0, nullptr }, + { ocLogNormDist, 290, 3, 3, V, { VR }, 0, nullptr }, + { ocLogInv, 291, 3, 3, V, { VR }, 0, nullptr }, + { ocNegBinomVert, 292, 3, 3, V, { VR }, 0, nullptr }, + { ocNormDist, 293, 4, 4, V, { VR }, 0, nullptr }, + { ocStdNormDist, 294, 1, 1, V, { VR }, 0, nullptr }, + { ocNormInv, 295, 3, 3, V, { VR }, 0, nullptr }, + { ocSNormInv, 296, 1, 1, V, { VR }, 0, nullptr }, + { ocStandard, 297, 3, 3, V, { VR }, 0, nullptr }, + { ocOdd, 298, 1, 1, V, { VR }, 0, nullptr }, + { ocPermut, 299, 2, 2, V, { VR }, 0, nullptr }, + { ocPoissonDist, 300, 3, 3, V, { VR }, 0, nullptr }, + { ocTDist, 301, 3, 3, V, { VR }, 0, nullptr }, + { ocWeibull, 302, 4, 4, V, { VR }, 0, nullptr }, + { ocSumXMY2, 303, 2, 2, V, { VA }, 0, nullptr }, + { ocSumX2MY2, 304, 2, 2, V, { VA }, 0, nullptr }, + { ocSumX2DY2, 305, 2, 2, V, { VA }, 0, nullptr }, + { ocChiTest, 306, 2, 2, V, { VA }, 0, nullptr }, + { ocCorrel, 307, 2, 2, V, { VA }, 0, nullptr }, + { ocCovar, 308, 2, 2, V, { VA }, 0, nullptr }, + { ocForecast, 309, 3, 3, V, { VR, VA }, 0, nullptr }, + { ocFTest, 310, 2, 2, V, { VA }, 0, nullptr }, + { ocIntercept, 311, 2, 2, V, { VA }, 0, nullptr }, + { ocPearson, 312, 2, 2, V, { VA }, 0, nullptr }, + { ocRSQ, 313, 2, 2, V, { VA }, 0, nullptr }, + { ocSTEYX, 314, 2, 2, V, { VA }, 0, nullptr }, + { ocSlope, 315, 2, 2, V, { VA }, 0, nullptr }, + { ocTTest, 316, 4, 4, V, { VA, VA, VR }, 0, nullptr }, + { ocProb, 317, 3, 4, V, { VA, VA, VR }, 0, nullptr }, + { ocDevSq, 318, 1, MX, V, { RX }, 0, nullptr }, + { ocGeoMean, 319, 1, MX, V, { RX }, 0, nullptr }, + { ocHarMean, 320, 1, MX, V, { RX }, 0, nullptr }, + { ocSumSQ, 321, 0, MX, V, { RX }, 0, nullptr }, + { ocKurt, 322, 1, MX, V, { RX }, 0, nullptr }, + { ocSkew, 323, 1, MX, V, { RX }, 0, nullptr }, + { ocZTest, 324, 2, 3, V, { RX, VR }, 0, nullptr }, + { ocLarge, 325, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocSmall, 326, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocQuartile, 327, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocPercentile, 328, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocPercentrank, 329, 2, 3, V, { RX, VR, VR_E }, 0, nullptr }, + { ocModalValue, 330, 1, MX, V, { VA }, 0, nullptr }, + { ocTrimMean, 331, 2, 2, V, { RX, VR }, 0, nullptr }, + { ocTInv, 332, 2, 2, V, { VR }, 0, nullptr }, + // Functions equivalent to add-in functions, use same parameters as + // ocExternal but add programmatical function name (here without + // "com.sun.star.sheet.addin.") so it can be looked up and stored as + // add-in, as older Excel versions only know them as add-in. + { ocIsEven, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getIseven" ) }, + { ocIsOdd, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getIsodd" ) }, + { ocGCD, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getGcd" ) }, + { ocLCM, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getLcm" ) }, + { ocEffect, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getEffect" ) }, + { ocCumPrinc, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getCumprinc" ) }, + { ocCumIpmt, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getCumipmt" ) }, + { ocNominal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getNominal" ) }, + { ocNetWorkdays, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY | EXC_FUNCFLAG_ADDINEQUIV, EXC_FUNCNAME_ADDIN( "Analysis.getNetworkdays" ) } +}; + +/** Functions new in BIFF5/BIFF7. Unsupported functions: DATESTRING, NUMBERSTRING. */ +const XclFunctionInfo saFuncTable_5[] = +{ + { ocGetDayOfWeek, 70, 1, 2, V, { VR }, 0, nullptr }, // BIFF2-4: 1, BIFF5: 1-2 + { ocHLookup, 101, 3, 4, V, { VV, RO, RO, VV }, 0, nullptr }, // BIFF2-4: 3, BIFF5: 3-4 + { ocVLookup, 102, 3, 4, V, { VV, RO, RO, VV }, 0, nullptr }, // BIFF2-4: 3, BIFF5: 3-4 + { ocGetDiffDate360, 220, 2, 3, V, { VR }, 0, nullptr }, // BIFF3-4: 2, BIFF5: 2-3 + { ocMacro, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocExternal, 255, 1, MX, R, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, nullptr }, + { ocConcat, 336, 0, MX, V, { VR }, 0, nullptr }, + { ocPower, 337, 2, 2, V, { VR }, 0, nullptr }, + { ocRad, 342, 1, 1, V, { VR }, 0, nullptr }, + { ocDeg, 343, 1, 1, V, { VR }, 0, nullptr }, + { ocSubTotal, 344, 2, MX, V, { VR, RO }, 0, nullptr }, + { ocSumIf, 345, 2, 3, V, { RO, VR, RO }, 0, nullptr }, + { ocCountIf, 346, 2, 2, V, { RO, VR }, 0, nullptr }, + { ocCountEmptyCells, 347, 1, 1, V, { RO }, 0, nullptr }, + { ocISPMT, 350, 4, 4, V, { VR }, 0, nullptr }, + { ocGetDateDif, 351, 3, 3, V, { VR }, 0, nullptr }, + { ocNoName, 352, 1, 1, V, { VR }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, // DATESTRING + { ocNoName, 353, 2, 2, V, { VR }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, // NUMBERSTRING + { ocRoman, 354, 1, 2, V, { VR }, 0, nullptr } +}; + +/** Functions new in BIFF8. Unsupported functions: PHONETIC. */ +const XclFunctionInfo saFuncTable_8[] = +{ + { ocGetPivotData, 358, 2, MX, V, { RR, RR, VR }, 0, nullptr }, + { ocHyperLink, 359, 1, 2, V, { VV, VO }, 0, nullptr }, + { ocNoName, 360, 1, 1, V, { RO }, EXC_FUNCFLAG_IMPORTONLY, nullptr }, // PHONETIC + { ocAverageA, 361, 1, MX, V, { RX }, 0, nullptr }, + { ocMaxA, 362, 1, MX, V, { RX }, 0, nullptr }, + { ocMinA, 363, 1, MX, V, { RX }, 0, nullptr }, + { ocStDevPA, 364, 1, MX, V, { RX }, 0, nullptr }, + { ocVarPA, 365, 1, MX, V, { RX }, 0, nullptr }, + { ocStDevA, 366, 1, MX, V, { RX }, 0, nullptr }, + { ocVarA, 367, 1, MX, V, { RX }, 0, nullptr }, + { ocBahtText, 368, 1, 1, V, { VR }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "BAHTTEXT" ) }, + { ocBahtText, 255, 2, 2, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "BAHTTEXT" ) }, + { ocEuroConvert, 255, 4, 6, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY, "EUROCONVERT" } +}; + +#define EXC_FUNCENTRY_V_VR( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +/** Functions new in OOXML. */ +const XclFunctionInfo saFuncTable_Oox[] = +{ + { ocCountIfs, NOID, 2, MX, V, { RO, VR }, EXC_FUNCFLAG_IMPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "COUNTIFS" ) }, + { ocCountIfs, 255, 3, MX, V, { RO_E, RO, VR }, EXC_FUNCFLAG_EXPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "COUNTIFS" ) }, + { ocSumIfs, NOID, 3, MX, V, { RO, RO, VR }, EXC_FUNCFLAG_IMPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "SUMIFS" ) }, + { ocSumIfs, 255, 4, MX, V, { RO_E, RO, RO, VR }, EXC_FUNCFLAG_EXPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "SUMIFS" ) }, + { ocAverageIf, NOID, 2, 3, V, { RO, VR, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "AVERAGEIF" ) }, + { ocAverageIf, 255, 3, 4, V, { RO_E, RO, VR, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "AVERAGEIF" ) }, + { ocAverageIfs, NOID, 3, MX, V, { RO, RO, VR }, EXC_FUNCFLAG_IMPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "AVERAGEIFS" ) }, + { ocAverageIfs, 255, 4, MX, V, { RO_E, RO, RO, VR }, EXC_FUNCFLAG_EXPORTONLY|EXC_FUNCFLAG_PARAMPAIRS, EXC_FUNCNAME( "AVERAGEIFS" ) }, + { ocIfError, NOID, 2, 2, V, { VO, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "IFERROR" ) }, + { ocIfError, 255, 3, 3, V, { RO_E, VO, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "IFERROR" ) }, + { ocNetWorkdays_MS, NOID, 2, 4, V, { VR, VR, RO, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "NETWORKDAYS.INTL" ) }, + { ocNetWorkdays_MS, 255, 3, 5, V, { RO_E, VR, VR, RO, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "NETWORKDAYS.INTL" ) }, + { ocWorkday_MS, NOID, 2, 4, V, { VR, VR, VR, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "WORKDAY.INTL" ) }, + { ocWorkday_MS, 255, 3, 5, V, { RO_E, VR, VR, VR, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "WORKDAY.INTL" ) }, + EXC_FUNCENTRY_V_VR( ocCeil_ISO, 1, 2, 0, "ISO.CEILING" ) +}; + +#define EXC_FUNCENTRY_V_VR_IMPORT( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +#define EXC_FUNCENTRY_V_RO_EXPORT( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +#define EXC_FUNCENTRY_A_VR( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, A, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, A, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +#define EXC_FUNCENTRY_V_RO( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { RO }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +// implicit maxparam=MX +#define EXC_FUNCENTRY_V_RX( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, MX, V, { RX }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, MX, V, { RO_E, RX }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +#define EXC_FUNCENTRY_V_VA( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VA }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +/** Functions new in Excel 2010. + + See http://office.microsoft.com/en-us/excel-help/what-s-new-changes-made-to-excel-functions-HA010355760.aspx + A lot of statistical functions have been renamed (the 'old' function names still exist). + + @See sc/source/filter/oox/formulabase.cxx saFuncTable2010 for V,VR,RO,... + */ +const XclFunctionInfo saFuncTable_2010[] = +{ + EXC_FUNCENTRY_V_VA( ocCovarianceP, 2, 2, 0, "COVARIANCE.P" ), + EXC_FUNCENTRY_V_VA( ocCovarianceS, 2, 2, 0, "COVARIANCE.S" ), + EXC_FUNCENTRY_V_RX( ocStDevP_MS, 1, MX, 0, "STDEV.P" ), + EXC_FUNCENTRY_V_RX( ocStDevS, 1, MX, 0, "STDEV.S" ), + EXC_FUNCENTRY_V_RX( ocVarP_MS, 1, MX, 0, "VAR.P" ), + EXC_FUNCENTRY_V_RX( ocVarS, 1, MX, 0, "VAR.S" ), + EXC_FUNCENTRY_V_VR( ocBetaDist_MS, 4, 6, 0, "BETA.DIST" ), + EXC_FUNCENTRY_V_VR( ocBetaInv_MS, 3, 5, 0, "BETA.INV" ), + EXC_FUNCENTRY_V_VR( ocBinomDist_MS, 4, 4, 0, "BINOM.DIST" ), + EXC_FUNCENTRY_V_VR( ocBinomInv, 3, 3, 0, "BINOM.INV" ), + EXC_FUNCENTRY_V_VR( ocChiSqDist_MS, 3, 3, 0, "CHISQ.DIST" ), + EXC_FUNCENTRY_V_VR( ocChiSqInv_MS, 2, 2, 0, "CHISQ.INV" ), + EXC_FUNCENTRY_V_VR( ocChiDist_MS, 2, 2, 0, "CHISQ.DIST.RT" ), + EXC_FUNCENTRY_V_VR( ocChiInv_MS, 2, 2, 0, "CHISQ.INV.RT" ), + EXC_FUNCENTRY_V_VR( ocChiTest_MS, 2, 2, 0, "CHISQ.TEST" ), + EXC_FUNCENTRY_V_VR( ocConfidence_N, 3, 3, 0, "CONFIDENCE.NORM" ), + EXC_FUNCENTRY_V_VR( ocConfidence_T, 3, 3, 0, "CONFIDENCE.T" ), + EXC_FUNCENTRY_V_VR( ocFDist_LT, 4, 4, 0, "F.DIST" ), + EXC_FUNCENTRY_V_VR( ocFDist_RT, 3, 3, 0, "F.DIST.RT" ), + EXC_FUNCENTRY_V_VR( ocFInv_LT, 3, 3, 0, "F.INV" ), + EXC_FUNCENTRY_V_VR( ocFInv_RT, 3, 3, 0, "F.INV.RT" ), + EXC_FUNCENTRY_V_VR( ocFTest_MS, 2, 2, 0, "F.TEST" ), + EXC_FUNCENTRY_V_VR( ocExpDist_MS, 3, 3, 0, "EXPON.DIST" ), + EXC_FUNCENTRY_V_VR( ocHypGeomDist_MS, 5, 5, 0, "HYPGEOM.DIST" ), + EXC_FUNCENTRY_V_VR( ocPoissonDist_MS, 3, 3, 0, "POISSON.DIST" ), + EXC_FUNCENTRY_V_VR( ocWeibull_MS, 4, 4, 0, "WEIBULL.DIST" ), + EXC_FUNCENTRY_V_VR( ocGammaDist_MS, 4, 4, 0, "GAMMA.DIST" ), + EXC_FUNCENTRY_V_VR( ocGammaInv_MS, 3, 3, 0, "GAMMA.INV" ), + EXC_FUNCENTRY_V_VR( ocGammaLn_MS, 1, 1, 0, "GAMMALN.PRECISE" ), + EXC_FUNCENTRY_V_VR( ocLogNormDist_MS, 4, 4, 0, "LOGNORM.DIST" ), + EXC_FUNCENTRY_V_VR( ocLogInv_MS, 3, 3, 0, "LOGNORM.INV" ), + EXC_FUNCENTRY_V_VR( ocNormDist_MS, 4, 4, 0, "NORM.DIST" ), + EXC_FUNCENTRY_V_VR( ocNormInv_MS, 3, 3, 0, "NORM.INV" ), + EXC_FUNCENTRY_V_VR( ocStdNormDist_MS, 2, 2, 0, "NORM.S.DIST" ), + EXC_FUNCENTRY_V_VR( ocSNormInv_MS, 1, 1, 0, "NORM.S.INV" ), + EXC_FUNCENTRY_V_VR( ocTDist_2T, 2, 2, 0, "T.DIST.2T" ), + EXC_FUNCENTRY_V_VR( ocTDist_MS, 3, 3, 0, "T.DIST" ), + EXC_FUNCENTRY_V_VR( ocTDist_RT, 2, 2, 0, "T.DIST.RT" ), + EXC_FUNCENTRY_V_VR( ocTInv_2T, 2, 2, 0, "T.INV.2T" ), + EXC_FUNCENTRY_V_VR( ocTInv_MS, 2, 2, 0, "T.INV" ), + EXC_FUNCENTRY_V_VR( ocTTest_MS, 4, 4, 0, "T.TEST" ), + EXC_FUNCENTRY_V_VR( ocPercentile_Inc, 2, 2, 0, "PERCENTILE.INC" ), + EXC_FUNCENTRY_V_VR( ocPercentrank_Inc, 2, 3, 0, "PERCENTRANK.INC" ), + EXC_FUNCENTRY_V_VR( ocQuartile_Inc, 2, 2, 0, "QUARTILE.INC" ), + EXC_FUNCENTRY_V_VR( ocRank_Eq, 2, 3, 0, "RANK.EQ" ), + EXC_FUNCENTRY_V_VR( ocPercentile_Exc, 2, 2, 0, "PERCENTILE.EXC" ), + EXC_FUNCENTRY_V_VR( ocPercentrank_Exc, 2, 3, 0, "PERCENTRANK.EXC" ), + EXC_FUNCENTRY_V_VR( ocQuartile_Exc, 2, 2, 0, "QUARTILE.EXC" ), + EXC_FUNCENTRY_V_VR( ocRank_Avg, 2, 3, 0, "RANK.AVG" ), + EXC_FUNCENTRY_V_RX( ocModalValue_MS, 1, MX, 0, "MODE.SNGL" ), + EXC_FUNCENTRY_V_RX( ocModalValue_Multi, 1, MX, 0, "MODE.MULT" ), + EXC_FUNCENTRY_V_VR( ocNegBinomDist_MS, 4, 4, 0, "NEGBINOM.DIST" ), + EXC_FUNCENTRY_V_VR( ocZTest_MS, 2, 3, 0, "Z.TEST" ), + EXC_FUNCENTRY_V_VR( ocCeil_Precise, 1, 2, 0, "CEILING.PRECISE" ), + EXC_FUNCENTRY_V_VR( ocFloor_Precise, 1, 2, 0, "FLOOR.PRECISE" ), + EXC_FUNCENTRY_V_VR( ocErf_MS, 1, 1, 0, "ERF.PRECISE" ), + EXC_FUNCENTRY_V_VR( ocErfc_MS, 1, 1, 0, "ERFC.PRECISE" ), + EXC_FUNCENTRY_V_RX( ocAggregate, 3, MX, 0, "AGGREGATE" ), +}; + +/** Functions new in Excel 2013. + + See http://office.microsoft.com/en-us/excel-help/new-functions-in-excel-2013-HA103980604.aspx + Most functions apparently were added for ODF1.2 ODFF / OpenFormula + compatibility. + + Functions with EXC_FUNCENTRY_V_VR_IMPORT are rewritten in + sc/source/filter/excel/xeformula.cxx during export for BIFF, OOXML export + uses a different mapping but still uses this mapping here to determine the + feature set. + + FIXME: either have the exporter determine the feature set from the active + mapping, preferred, or enhance this mapping here such that for OOXML the + rewrite can be overridden. + + @See sc/source/filter/oox/formulabase.cxx saFuncTable2013 for V,VR,RO,... + */ +const XclFunctionInfo saFuncTable_2013[] = +{ + EXC_FUNCENTRY_V_VR_IMPORT( ocArcCot, 1, 1, 0, "ACOT" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocArcCotHyp, 1, 1, 0, "ACOTH" ), + EXC_FUNCENTRY_V_VR( ocArabic, 1, 1, 0, "ARABIC" ), + EXC_FUNCENTRY_V_VR( ocBase, 2, 3, 0, "BASE" ), + EXC_FUNCENTRY_V_VR( ocB, 3, 4, 0, "BINOM.DIST.RANGE" ), + EXC_FUNCENTRY_V_VR( ocBitAnd, 2, 2, 0, "BITAND" ), + EXC_FUNCENTRY_V_VR( ocBitLshift, 2, 2, 0, "BITLSHIFT" ), + EXC_FUNCENTRY_V_VR( ocBitOr, 2, 2, 0, "BITOR" ), + EXC_FUNCENTRY_V_VR( ocBitRshift, 2, 2, 0, "BITRSHIFT" ), + EXC_FUNCENTRY_V_VR( ocBitXor, 2, 2, 0, "BITXOR" ), + EXC_FUNCENTRY_V_VR( ocCeil_Math, 1, 3, 0, "CEILING.MATH" ), + EXC_FUNCENTRY_V_RO_EXPORT( ocCeil, 1, 3, 0, "CEILING.MATH" ), + EXC_FUNCENTRY_V_VR( ocCombinA, 2, 2, 0, "COMBINA" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocCot, 1, 1, 0, "COT" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocCotHyp, 1, 1, 0, "COTH" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocCosecant, 1, 1, 0, "CSC" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocCosecantHyp, 1, 1, 0, "CSCH" ), + EXC_FUNCENTRY_V_VR( ocGetDiffDate, 2, 2, 0, "DAYS" ), + EXC_FUNCENTRY_V_VR( ocDecimal, 2, 2, 0, "DECIMAL" ), + EXC_FUNCENTRY_V_VR( ocEncodeURL, 1, 1, 0, "ENCODEURL" ), + // NOTE: this FDIST is not our LEGACY.FDIST + EXC_FUNCENTRY_V_VR( ocNoName, 3, 4, 0, "FDIST" ), + // NOTE: this FINV is not our LEGACY.FINV + EXC_FUNCENTRY_V_VR( ocNoName, 3, 3, 0, "FINV" ), + EXC_FUNCENTRY_V_VR( ocFilterXML, 2, 2, 0, "FILTERXML" ), + EXC_FUNCENTRY_V_VR( ocFloor_Math, 1, 3, 0, "FLOOR.MATH" ), + EXC_FUNCENTRY_V_RO_EXPORT( ocFloor, 1, 3, 0, "FLOOR.MATH" ), + EXC_FUNCENTRY_V_RO( ocFormula, 1, 1, 0, "FORMULATEXT" ), + EXC_FUNCENTRY_V_VR( ocGamma, 1, 1, 0, "GAMMA" ), + EXC_FUNCENTRY_V_VR( ocGauss, 1, 1, 0, "GAUSS" ), + { ocIfNA, NOID, 2, 2, V, { VO, RO }, EXC_FUNCFLAG_IMPORTONLY, EXC_FUNCNAME( "IFNA" ) }, + { ocIfNA, 255, 3, 3, V, { RO_E, VO, RO }, EXC_FUNCFLAG_EXPORTONLY, EXC_FUNCNAME( "IFNA" ) }, + // IMCOSH, IMCOT, IMCSC, IMCSCH, IMSEC, IMSECH, IMSINH and IMTAN are + // implemented in the Analysis Add-In. + EXC_FUNCENTRY_V_RO( ocIsFormula, 1, 1, 0, "ISFORMULA" ), + EXC_FUNCENTRY_V_VR( ocWeek, 1, 2, 0, "WEEKNUM" ), + EXC_FUNCENTRY_V_VR( ocIsoWeeknum, 1, 1, 0, "ISOWEEKNUM" ), + EXC_FUNCENTRY_A_VR( ocMatrixUnit, 1, 1, 0, "MUNIT" ), + EXC_FUNCENTRY_V_VR( ocNumberValue, 1, 3, 0, "NUMBERVALUE" ), + EXC_FUNCENTRY_V_VR( ocPDuration, 3, 3, 0, "PDURATION" ), + EXC_FUNCENTRY_V_VR( ocPermutationA, 2, 2, 0, "PERMUTATIONA" ), + EXC_FUNCENTRY_V_VR( ocPhi, 1, 1, 0, "PHI" ), + EXC_FUNCENTRY_V_VR( ocRRI, 3, 3, 0, "RRI" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocSecant, 1, 1, 0, "SEC" ), + EXC_FUNCENTRY_V_VR_IMPORT( ocSecantHyp, 1, 1, 0, "SECH" ), + EXC_FUNCENTRY_V_RO( ocSheet, 0, 1, 0, "SHEET" ), + EXC_FUNCENTRY_V_RO( ocSheets, 0, 1, 0, "SHEETS" ), + EXC_FUNCENTRY_V_RX( ocSkewp, 1, MX, 0, "SKEW.P" ), + EXC_FUNCENTRY_V_VR( ocUnichar, 1, 1, 0, "UNICHAR" ), + EXC_FUNCENTRY_V_VR( ocUnicode, 1, 1, 0, "UNICODE" ), + EXC_FUNCENTRY_V_VR( ocWebservice, 1, 1, 0, "WEBSERVICE" ), + EXC_FUNCENTRY_V_RX( ocXor, 1, MX, 0, "XOR" ), + EXC_FUNCENTRY_V_VR( ocErrorType_ODF, 1, 1, 0, "ERROR.TYPE" ) +}; + +/** Functions new in Excel 2016. + + See https://support.office.com/en-us/article/Forecasting-functions-897a2fe9-6595-4680-a0b0-93e0308d5f6e?ui=en-US&rs=en-US&ad=US#_forecast.ets + and https://support.office.com/en-us/article/What-s-New-and-Improved-in-Office-2016-for-Office-365-95c8d81d-08ba-42c1-914f-bca4603e1426?ui=en-US&rs=en-US&ad=US + + @See sc/source/filter/oox/formulabase.cxx saFuncTable2016 for V,VR,RO,... + */ +const XclFunctionInfo saFuncTable_2016[] = +{ + EXC_FUNCENTRY_V_VR( ocForecast_ETS_ADD, 3, 6, 0, "FORECAST.ETS" ), + EXC_FUNCENTRY_V_VR( ocForecast_ETS_PIA, 3, 7, 0, "FORECAST.ETS.CONFINT" ), + EXC_FUNCENTRY_V_VR( ocForecast_ETS_SEA, 2, 4, 0, "FORECAST.ETS.SEASONALITY" ), + EXC_FUNCENTRY_V_VR( ocForecast_ETS_STA, 3, 6, 0, "FORECAST.ETS.STAT" ), + EXC_FUNCENTRY_V_VR( ocForecast_LIN, 3, 3, 0, "FORECAST.LINEAR" ), + EXC_FUNCENTRY_V_VR( ocConcat_MS, 1, MX, 0, "CONCAT" ), + EXC_FUNCENTRY_V_VR( ocTextJoin_MS, 3, MX, 0, "TEXTJOIN" ), + EXC_FUNCENTRY_V_VR( ocIfs_MS, 2, MX, 0, "IFS" ), + EXC_FUNCENTRY_V_VR( ocSwitch_MS, 3, MX, 0, "SWITCH" ), + EXC_FUNCENTRY_V_VR( ocMinIfs_MS, 3, MX, 0, "MINIFS" ), + EXC_FUNCENTRY_V_VR( ocMaxIfs_MS, 3, MX, 0, "MAXIFS" ) +}; + +#define EXC_FUNCENTRY_ODF( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME_ODF( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME_ODF( asciiname ) } + +/** Functions defined by OpenFormula, but not supported by Calc (ocNoName) or by Excel (defined op-code). */ +const XclFunctionInfo saFuncTable_Odf[] = +{ + EXC_FUNCENTRY_ODF( ocChiSqDist, 2, 3, 0, "CHISQDIST" ), + EXC_FUNCENTRY_ODF( ocChiSqInv, 2, 2, 0, "CHISQINV" ) +}; + +#undef EXC_FUNCENTRY_ODF + +#define EXC_FUNCENTRY_OOO( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), EXC_FUNCNAME( asciiname ) }, \ + { opcode, 255, (minparam)+1, (maxparam)+1, V, { RO_E, RO }, EXC_FUNCFLAG_EXPORTONLY|(flags), EXC_FUNCNAME( asciiname ) } + +// Import Broken Raw ... even without leading _xlfn. +#define EXC_FUNCENTRY_OOO_IBR( opcode, minparam, maxparam, flags, asciiname ) \ + { opcode, NOID, minparam, maxparam, V, { VR }, EXC_FUNCFLAG_IMPORTONLY|(flags), asciiname } + +/** Functions defined by Calc, but not in OpenFormula nor supported by Excel. */ +const XclFunctionInfo saFuncTable_OOoLO[] = +{ + EXC_FUNCENTRY_OOO( ocErrorType, 1, 1, 0, "ORG.OPENOFFICE.ERRORTYPE" ), + EXC_FUNCENTRY_OOO_IBR( ocErrorType, 1, 1, 0, "ERRORTYPE" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocMultiArea, 1, MX, 0, "ORG.OPENOFFICE.MULTIRANGE" ), + EXC_FUNCENTRY_OOO_IBR( ocMultiArea, 1, MX, 0, "MULTIRANGE" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocBackSolver, 3, 3, 0, "ORG.OPENOFFICE.GOALSEEK" ), + EXC_FUNCENTRY_OOO_IBR( ocBackSolver,3, 3, 0, "GOALSEEK" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocEasterSunday, 1, 1, 0, "ORG.OPENOFFICE.EASTERSUNDAY" ), + EXC_FUNCENTRY_OOO_IBR( ocEasterSunday,1,1, 0, "EASTERSUNDAY" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocCurrent, 0, 0, 0, "ORG.OPENOFFICE.CURRENT" ), + EXC_FUNCENTRY_OOO_IBR( ocCurrent, 0, 0, 0, "CURRENT" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocStyle, 1, 3, 0, "ORG.OPENOFFICE.STYLE" ), + EXC_FUNCENTRY_OOO_IBR( ocStyle, 1, 3, 0, "STYLE" ), // was written wrongly, read it + EXC_FUNCENTRY_OOO( ocConvertOOo, 3, 3, 0, "ORG.OPENOFFICE.CONVERT" ), + EXC_FUNCENTRY_OOO( ocColor, 3, 4, 0, "ORG.LIBREOFFICE.COLOR" ), + EXC_FUNCENTRY_OOO( ocRawSubtract, 2, MX, 0, "ORG.LIBREOFFICE.RAWSUBTRACT" ), + EXC_FUNCENTRY_OOO( ocWeeknumOOo, 2, 2, 0, "ORG.LIBREOFFICE.WEEKNUM_OOO" ), + EXC_FUNCENTRY_OOO( ocForecast_ETS_MUL, 3, 6, 0, "ORG.LIBREOFFICE.FORECAST.ETS.MULT" ), + EXC_FUNCENTRY_OOO( ocForecast_ETS_PIM, 3, 7, 0, "ORG.LIBREOFFICE.FORECAST.ETS.PI.MULT" ), + EXC_FUNCENTRY_OOO( ocForecast_ETS_STM, 3, 6, 0, "ORG.LIBREOFFICE.FORECAST.ETS.STAT.MULT" ), + EXC_FUNCENTRY_OOO( ocRoundSig, 2, 2, 0, "ORG.LIBREOFFICE.ROUNDSIG" ), + EXC_FUNCENTRY_OOO( ocRegex, 2, 4, 0, "ORG.LIBREOFFICE.REGEX" ), + EXC_FUNCENTRY_OOO( ocFourier, 2, 5, 0, "ORG.LIBREOFFICE.FOURIER" ), + EXC_FUNCENTRY_OOO( ocRandomNV, 0, 0, 0, "ORG.LIBREOFFICE.RAND.NV" ), + EXC_FUNCENTRY_OOO( ocRandbetweenNV, 2, 2, 0, "ORG.LIBREOFFICE.RANDBETWEEN.NV" ) +}; + +#undef EXC_FUNCENTRY_OOO_IBR +#undef EXC_FUNCENTRY_OOO + +XclFunctionProvider::XclFunctionProvider( const XclRoot& rRoot ) +{ + void (XclFunctionProvider::*pFillFunc)( const XclFunctionInfo*, const XclFunctionInfo* ) = + rRoot.IsImport() ? &XclFunctionProvider::FillXclFuncMap : &XclFunctionProvider::FillScFuncMap; + + /* Only read/write functions supported in the current BIFF version. + Function tables from later BIFF versions may overwrite single functions + from earlier tables. */ + XclBiff eBiff = rRoot.GetBiff(); + if( eBiff >= EXC_BIFF2 ) + (this->*pFillFunc)(saFuncTable_2, saFuncTable_2 + SAL_N_ELEMENTS(saFuncTable_2)); + if( eBiff >= EXC_BIFF3 ) + (this->*pFillFunc)(saFuncTable_3, saFuncTable_3 + SAL_N_ELEMENTS(saFuncTable_3)); + if( eBiff >= EXC_BIFF4 ) + (this->*pFillFunc)(saFuncTable_4, saFuncTable_4 + SAL_N_ELEMENTS(saFuncTable_4)); + if( eBiff >= EXC_BIFF5 ) + (this->*pFillFunc)(saFuncTable_5, saFuncTable_5 + SAL_N_ELEMENTS(saFuncTable_5)); + if( eBiff >= EXC_BIFF8 ) + (this->*pFillFunc)(saFuncTable_8, saFuncTable_8 + SAL_N_ELEMENTS(saFuncTable_8)); + (this->*pFillFunc)(saFuncTable_Oox, saFuncTable_Oox + SAL_N_ELEMENTS(saFuncTable_Oox)); + (this->*pFillFunc)(saFuncTable_2010, saFuncTable_2010 + SAL_N_ELEMENTS(saFuncTable_2010)); + (this->*pFillFunc)(saFuncTable_2013, saFuncTable_2013 + SAL_N_ELEMENTS(saFuncTable_2013)); + (this->*pFillFunc)(saFuncTable_2016, saFuncTable_2016 + SAL_N_ELEMENTS(saFuncTable_2016)); + (this->*pFillFunc)(saFuncTable_Odf, saFuncTable_Odf + SAL_N_ELEMENTS(saFuncTable_Odf)); + (this->*pFillFunc)(saFuncTable_OOoLO, saFuncTable_OOoLO + SAL_N_ELEMENTS(saFuncTable_OOoLO)); +} + +const XclFunctionInfo* XclFunctionProvider::GetFuncInfoFromXclFunc( sal_uInt16 nXclFunc ) const +{ + // only in import filter allowed + OSL_ENSURE( !maXclFuncMap.empty(), "XclFunctionProvider::GetFuncInfoFromXclFunc - wrong filter" ); + XclFuncMap::const_iterator aIt = maXclFuncMap.find( nXclFunc ); + return (aIt == maXclFuncMap.end()) ? nullptr : aIt->second; +} + +const XclFunctionInfo* XclFunctionProvider::GetFuncInfoFromXclMacroName( const OUString& rXclMacroName ) const +{ + // only in import filter allowed, but do not test maXclMacroNameMap, it may be empty for old BIFF versions + OSL_ENSURE( !maXclFuncMap.empty(), "XclFunctionProvider::GetFuncInfoFromXclMacroName - wrong filter" ); + XclMacroNameMap::const_iterator aIt = maXclMacroNameMap.find( rXclMacroName ); + return (aIt == maXclMacroNameMap.end()) ? nullptr : aIt->second; +} + +const XclFunctionInfo* XclFunctionProvider::GetFuncInfoFromOpCode( OpCode eOpCode ) const +{ + // only in export filter allowed + OSL_ENSURE( !maScFuncMap.empty(), "XclFunctionProvider::GetFuncInfoFromOpCode - wrong filter" ); + ScFuncMap::const_iterator aIt = maScFuncMap.find( eOpCode ); + return (aIt == maScFuncMap.end()) ? nullptr : aIt->second; +} + +void XclFunctionProvider::FillXclFuncMap( const XclFunctionInfo* pBeg, const XclFunctionInfo* pEnd ) +{ + for( const XclFunctionInfo* pIt = pBeg; pIt != pEnd; ++pIt ) + { + if( !::get_flag( pIt->mnFlags, EXC_FUNCFLAG_EXPORTONLY ) ) + { + if( pIt->mnXclFunc != NOID ) + maXclFuncMap[ pIt->mnXclFunc ] = pIt; + if( pIt->IsMacroFunc() ) + maXclMacroNameMap[ pIt->GetMacroFuncName() ] = pIt; + } + } +} + +void XclFunctionProvider::FillScFuncMap( const XclFunctionInfo* pBeg, const XclFunctionInfo* pEnd ) +{ + for( const XclFunctionInfo* pIt = pBeg; pIt != pEnd; ++pIt ) + if( !::get_flag( pIt->mnFlags, EXC_FUNCFLAG_IMPORTONLY ) ) + maScFuncMap[ pIt->meOpCode ] = pIt; +} + +// Token array ================================================================ + +XclTokenArray::XclTokenArray( bool bVolatile ) : + mbVolatile( bVolatile ) +{ +} + +XclTokenArray::XclTokenArray( ScfUInt8Vec& rTokVec, ScfUInt8Vec& rExtDataVec, bool bVolatile ) : + mbVolatile( bVolatile ) +{ + maTokVec.swap( rTokVec ); + maExtDataVec.swap( rExtDataVec ); +} + +sal_uInt16 XclTokenArray::GetSize() const +{ + OSL_ENSURE( maTokVec.size() <= 0xFFFF, "XclTokenArray::GetSize - array too long" ); + return limit_cast< sal_uInt16 >( maTokVec.size() ); +} + +sal_uInt16 XclTokenArray::ReadSize(XclImpStream& rStrm) +{ + return rStrm.ReaduInt16(); +} + +void XclTokenArray::ReadArray(sal_uInt16 nSize, XclImpStream& rStrm) +{ + maTokVec.resize(0); + + const std::size_t nMaxBuffer = 4096; + std::size_t nBytesLeft = nSize; + std::size_t nTotalRead = 0; + + while (true) + { + if (!nBytesLeft) + break; + std::size_t nReadRequest = o3tl::sanitizing_min(nBytesLeft, nMaxBuffer); + maTokVec.resize(maTokVec.size() + nReadRequest); + auto nRead = rStrm.Read(maTokVec.data() + nTotalRead, nReadRequest); + nTotalRead += nRead; + if (nRead != nReadRequest) + { + maTokVec.resize(nTotalRead); + break; + } + nBytesLeft -= nRead; + } +} + +void XclTokenArray::Read( XclImpStream& rStrm ) +{ + ReadArray(ReadSize(rStrm), rStrm); +} + +void XclTokenArray::WriteSize( XclExpStream& rStrm ) const +{ + rStrm << GetSize(); +} + +void XclTokenArray::WriteArray( XclExpStream& rStrm ) const +{ + if( !maTokVec.empty() ) + rStrm.Write(maTokVec.data(), GetSize()); + if( !maExtDataVec.empty() ) + rStrm.Write(maExtDataVec.data(), maExtDataVec.size()); +} + +void XclTokenArray::Write( XclExpStream& rStrm ) const +{ + WriteSize( rStrm ); + WriteArray( rStrm ); +} + +bool XclTokenArray::operator==( const XclTokenArray& rTokArr ) const +{ + return (mbVolatile == rTokArr.mbVolatile) && (maTokVec == rTokArr.maTokVec) && (maExtDataVec == rTokArr.maExtDataVec); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclTokenArray& rTokArr ) +{ + rTokArr.Read( rStrm ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclTokenArray& rTokArr ) +{ + rTokArr.Write( rStrm ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclTokenArrayRef& rxTokArr ) +{ + if( rxTokArr ) + rxTokArr->Write( rStrm ); + else + rStrm << sal_uInt16( 0 ); + return rStrm; +} + +XclTokenArrayIterator::XclTokenArrayIterator() : + mppScTokenBeg( nullptr ), + mppScTokenEnd( nullptr ), + mppScToken( nullptr ), + mbSkipSpaces( false ) +{ +} + +XclTokenArrayIterator::XclTokenArrayIterator( const ScTokenArray& rScTokArr, bool bSkipSpaces ) +{ + Init( rScTokArr, bSkipSpaces ); +} + +XclTokenArrayIterator::XclTokenArrayIterator( const XclTokenArrayIterator& rTokArrIt, bool bSkipSpaces ) : + mppScTokenBeg( rTokArrIt.mppScTokenBeg ), + mppScTokenEnd( rTokArrIt.mppScTokenEnd ), + mppScToken( rTokArrIt.mppScToken ), + mbSkipSpaces( bSkipSpaces ) +{ + SkipSpaces(); +} + +void XclTokenArrayIterator::Init( const ScTokenArray& rScTokArr, bool bSkipSpaces ) +{ + sal_uInt16 nTokArrLen = rScTokArr.GetLen(); + mppScTokenBeg = static_cast< const FormulaToken* const* >( nTokArrLen ? rScTokArr.GetArray() : nullptr ); + mppScTokenEnd = mppScTokenBeg ? (mppScTokenBeg + nTokArrLen) : nullptr; + mppScToken = (mppScTokenBeg != mppScTokenEnd) ? mppScTokenBeg : nullptr; + mbSkipSpaces = bSkipSpaces; + SkipSpaces(); +} + +XclTokenArrayIterator& XclTokenArrayIterator::operator++() +{ + NextRawToken(); + SkipSpaces(); + return *this; +} + +void XclTokenArrayIterator::NextRawToken() +{ + if( mppScToken ) + if( (++mppScToken == mppScTokenEnd) || !*mppScToken ) + mppScToken = nullptr; +} + +void XclTokenArrayIterator::SkipSpaces() +{ + if( mbSkipSpaces ) + { + OpCode eOp; + while( Is() && (((eOp = (*this)->GetOpCode()) == ocSpaces) || eOp == ocWhitespace) ) + NextRawToken(); + } +} + +// strings and string lists --------------------------------------------------- + +bool XclTokenArrayHelper::GetTokenString( OUString& rString, const FormulaToken& rScToken ) +{ + bool bIsStr = (rScToken.GetType() == svString) && (rScToken.GetOpCode() == ocPush); + if( bIsStr ) rString = rScToken.GetString().getString(); + return bIsStr; +} + +bool XclTokenArrayHelper::GetString( OUString& rString, const ScTokenArray& rScTokArr ) +{ + XclTokenArrayIterator aIt( rScTokArr, true ); + // something is following the string token -> error + return aIt.Is() && GetTokenString( rString, *aIt ) && !++aIt; +} + +bool XclTokenArrayHelper::GetStringList( OUString& rStringList, const ScTokenArray& rScTokArr, sal_Unicode cSep ) +{ + bool bRet = true; + XclTokenArrayIterator aIt( rScTokArr, true ); + enum { STATE_START, STATE_STR, STATE_SEP, STATE_END } eState = STATE_START; + while( eState != STATE_END ) switch( eState ) + { + case STATE_START: + eState = aIt.Is() ? STATE_STR : STATE_END; + break; + case STATE_STR: + { + OUString aString; + bRet = GetTokenString( aString, *aIt ); + if( bRet ) rStringList += aString ; + eState = (bRet && (++aIt).Is()) ? STATE_SEP : STATE_END; + break; + } + case STATE_SEP: + bRet = aIt->GetOpCode() == ocSep; + if( bRet ) rStringList += OUStringChar(cSep); + eState = (bRet && (++aIt).Is()) ? STATE_STR : STATE_END; + break; + default:; + } + return bRet; +} + +void XclTokenArrayHelper::ConvertStringToList( + ScTokenArray& rScTokArr, svl::SharedStringPool& rSPool, sal_Unicode cStringSep ) +{ + OUString aString; + if( !GetString( aString, rScTokArr ) ) + return; + + rScTokArr.Clear(); + if (aString.isEmpty()) + return; + sal_Int32 nStringIx = 0; + for (;;) + { + OUString aToken( aString.getToken( 0, cStringSep, nStringIx ) ); + rScTokArr.AddString(rSPool.intern(comphelper::string::stripStart(aToken, ' '))); + if (nStringIx<0) + break; + rScTokArr.AddOpCode( ocSep ); + } +} + +// multiple operations -------------------------------------------------------- + +namespace { + +bool lclGetAddress( const ScDocument& rDoc, ScAddress& rAddress, const FormulaToken& rToken, const ScAddress& rPos ) +{ + OpCode eOpCode = rToken.GetOpCode(); + bool bIsSingleRef = (eOpCode == ocPush) && (rToken.GetType() == svSingleRef); + if( bIsSingleRef ) + { + const ScSingleRefData& rRef = *rToken.GetSingleRef(); + rAddress = rRef.toAbs(rDoc, rPos); + bIsSingleRef = !rRef.IsDeleted(); + } + return bIsSingleRef; +} + +} // namespace + +bool XclTokenArrayHelper::GetMultipleOpRefs( + const ScDocument& rDoc, + XclMultipleOpRefs& rRefs, const ScTokenArray& rScTokArr, const ScAddress& rScPos ) +{ + rRefs.mbDblRefMode = false; + enum + { + stBegin, stTableOp, stOpen, stFormula, stFormulaSep, + stColFirst, stColFirstSep, stColRel, stColRelSep, + stRowFirst, stRowFirstSep, stRowRel, stClose, stError + } eState = stBegin; // last read token + for( XclTokenArrayIterator aIt( rScTokArr, true ); aIt.Is() && (eState != stError); ++aIt ) + { + OpCode eOpCode = aIt->GetOpCode(); + bool bIsSep = eOpCode == ocSep; + switch( eState ) + { + case stBegin: + eState = (eOpCode == ocTableOp) ? stTableOp : stError; + break; + case stTableOp: + eState = (eOpCode == ocOpen) ? stOpen : stError; + break; + case stOpen: + eState = lclGetAddress(rDoc, rRefs.maFmlaScPos, *aIt, rScPos) ? stFormula : stError; + break; + case stFormula: + eState = bIsSep ? stFormulaSep : stError; + break; + case stFormulaSep: + eState = lclGetAddress(rDoc, rRefs.maColFirstScPos, *aIt, rScPos) ? stColFirst : stError; + break; + case stColFirst: + eState = bIsSep ? stColFirstSep : stError; + break; + case stColFirstSep: + eState = lclGetAddress(rDoc, rRefs.maColRelScPos, *aIt, rScPos) ? stColRel : stError; + break; + case stColRel: + eState = bIsSep ? stColRelSep : ((eOpCode == ocClose) ? stClose : stError); + break; + case stColRelSep: + eState = lclGetAddress(rDoc, rRefs.maRowFirstScPos, *aIt, rScPos) ? stRowFirst : stError; + rRefs.mbDblRefMode = true; + break; + case stRowFirst: + eState = bIsSep ? stRowFirstSep : stError; + break; + case stRowFirstSep: + eState = lclGetAddress(rDoc, rRefs.maRowRelScPos, *aIt, rScPos) ? stRowRel : stError; + break; + case stRowRel: + eState = (eOpCode == ocClose) ? stClose : stError; + break; + default: + eState = stError; + } + } + return eState == stClose; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlpage.cxx b/sc/source/filter/excel/xlpage.cxx new file mode 100644 index 000000000..937aa9427 --- /dev/null +++ b/sc/source/filter/excel/xlpage.cxx @@ -0,0 +1,262 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +namespace{ + +struct XclPaperSize +{ + Paper mePaper; /// SVX paper size identifier. + tools::Long mnWidth; /// Paper width in twips. + tools::Long mnHeight; /// Paper height in twips. +}; + +constexpr tools::Long in2twips(double n_inch) +{ + return o3tl::convert(n_inch, o3tl::Length::in, o3tl::Length::twip) + 0.5; +} +constexpr tools::Long mm2twips(double n_mm) +{ + return o3tl::convert(n_mm, o3tl::Length::mm, o3tl::Length::twip) + 0.5; +} +constexpr tools::Long twips2mm(tools::Long n_twips) +{ + return o3tl::convert(n_twips, o3tl::Length::twip, o3tl::Length::mm); +} + +// see ApiPaperSize spPaperSizeTable in filter and aDinTab in i18nutil +constexpr XclPaperSize pPaperSizeTable[] = +{ +/* 0*/ { PAPER_USER, 0, 0 }, // undefined + { PAPER_LETTER, in2twips( 8.5 ), in2twips( 11 ) }, // Letter + { PAPER_USER, in2twips( 8.5 ), in2twips( 11 ) }, // Letter Small + { PAPER_TABLOID, in2twips( 11 ), in2twips( 17 ) }, // Tabloid + { PAPER_LEDGER, in2twips( 17 ), in2twips( 11 ) }, // Ledger +/* 5*/ { PAPER_LEGAL, in2twips( 8.5 ), in2twips( 14 ) }, // Legal + { PAPER_STATEMENT, in2twips( 5.5 ), in2twips( 8.5 ) }, // Statement + { PAPER_EXECUTIVE, in2twips( 7.25 ), in2twips( 10.5 ) }, // Executive + { PAPER_A3, mm2twips( 297 ), mm2twips( 420 ) }, // A3 + { PAPER_A4, mm2twips( 210 ), mm2twips( 297 ) }, // A4 +/* 10*/ { PAPER_USER, mm2twips( 210 ), mm2twips( 297 ) }, // A4 Small + { PAPER_A5, mm2twips( 148 ), mm2twips( 210 ) }, // A5 + /* for JIS vs ISO B confusion see: + https://docs.microsoft.com/en-us/windows/win32/intl/paper-sizes + http://wiki.openoffice.org/wiki/DefaultPaperSize comments + http://partners.adobe.com/public/developer/en/ps/5003.PPD_Spec_v4.3.pdf */ + { PAPER_B4_JIS, mm2twips( 257 ), mm2twips( 364 ) }, // B4 (JIS) + { PAPER_B5_JIS, mm2twips( 182 ), mm2twips( 257 ) }, // B5 (JIS) + { PAPER_USER, in2twips( 8.5 ), in2twips( 13 ) }, // Folio +/* 15*/ { PAPER_QUARTO, mm2twips( 215 ), mm2twips( 275 ) }, // Quarto + { PAPER_10x14, in2twips( 10 ), in2twips( 14 ) }, // 10x14 + { PAPER_USER, in2twips( 11 ), in2twips( 17 ) }, // 11x17 + { PAPER_USER, in2twips( 8.5 ), in2twips( 11 ) }, // Note + { PAPER_ENV_9, in2twips( 3.875 ), in2twips( 8.875 ) }, // Envelope #9 +/* 20*/ { PAPER_ENV_10, in2twips( 4.125 ), in2twips( 9.5 ) }, // Envelope #10 + { PAPER_ENV_11, in2twips( 4.5 ), in2twips( 10.375 ) }, // Envelope #11 + { PAPER_ENV_12, in2twips( 4.75 ), in2twips( 11 ) }, // Envelope #12 + { PAPER_ENV_14, in2twips( 5 ), in2twips( 11.5 ) }, // Envelope #14 + { PAPER_C, in2twips( 17 ), in2twips( 22 ) }, // ANSI-C +/* 25*/ { PAPER_D, in2twips( 22 ), in2twips( 34 ) }, // ANSI-D + { PAPER_E, in2twips( 34 ), in2twips( 44 ) }, // ANSI-E + { PAPER_ENV_DL, mm2twips( 110 ), mm2twips( 220 ) }, // Envelope DL + { PAPER_ENV_C5, mm2twips( 162 ), mm2twips( 229 ) }, // Envelope C5 + { PAPER_ENV_C3, mm2twips( 324 ), mm2twips( 458 ) }, // Envelope C3 +/* 30*/ { PAPER_ENV_C4, mm2twips( 229 ), mm2twips( 324 ) }, // Envelope C4 + { PAPER_ENV_C6, mm2twips( 114 ), mm2twips( 162 ) }, // Envelope C6 + { PAPER_ENV_C65, mm2twips( 114 ), mm2twips( 229 ) }, // Envelope C65 + { PAPER_B4_ISO, mm2twips( 250 ), mm2twips( 353 ) }, // B4 (ISO) + { PAPER_B5_ISO, mm2twips( 176 ), mm2twips( 250 ) }, // B5 (ISO) +/* 35*/ { PAPER_B6_ISO, mm2twips( 125 ), mm2twips( 176 ) }, // B6 (ISO) + { PAPER_ENV_ITALY, mm2twips( 110 ), mm2twips( 230 ) }, // Envelope Italy + { PAPER_ENV_MONARCH, in2twips( 3.875 ), in2twips( 7.5 ) }, // Envelope Monarch + { PAPER_ENV_PERSONAL, in2twips( 3.625 ), in2twips( 6.5 ) }, // 6 3/4 Envelope + { PAPER_FANFOLD_US, in2twips( 14.875 ), in2twips( 11 ) }, // US Std Fanfold +/* 40*/ { PAPER_FANFOLD_DE, in2twips( 8.5 ), in2twips( 12 ) }, // German Std Fanfold + { PAPER_FANFOLD_LEGAL_DE, in2twips( 8.5 ), in2twips( 13 ) }, // German Legal Fanfold + { PAPER_B4_ISO, mm2twips( 250 ), mm2twips( 353 ) }, // B4 (ISO) + { PAPER_POSTCARD_JP,mm2twips( 100 ), mm2twips( 148 ) }, // Japanese Postcard + { PAPER_9x11, in2twips( 9 ), in2twips( 11 ) }, // 9x11 +/* 45*/ { PAPER_10x11, in2twips( 10 ), in2twips( 11 ) }, // 10x11 + { PAPER_15x11, in2twips( 15 ), in2twips( 11 ) }, // 15x11 + { PAPER_ENV_INVITE, mm2twips( 220 ), mm2twips( 220 ) }, // Envelope Invite + { PAPER_USER, 0, 0 }, // undefined + { PAPER_USER, 0, 0 }, // undefined + /* See: https://docs.microsoft.com/en-us/windows/win32/intl/paper-sizes */ +/* 50*/ { PAPER_USER, in2twips( 9.5 ), in2twips( 12 ) }, // Letter Extra + { PAPER_USER, in2twips( 9.5 ), in2twips( 15 ) }, // Legal Extra + { PAPER_USER, in2twips( 11.69 ), in2twips( 18 ) }, // Tabloid Extra + { PAPER_USER, mm2twips( 235 ), mm2twips( 322 ) }, // A4 Extra + { PAPER_USER, in2twips( 8.5 ), in2twips( 11 ) }, // Letter Transverse +/* 55*/ { PAPER_USER, mm2twips( 210 ), mm2twips( 297 ) }, // A4 Transverse + { PAPER_USER, in2twips( 9.5 ), in2twips( 12 ) }, // Letter Extra Transverse + { PAPER_A_PLUS, mm2twips( 227 ), mm2twips( 356 ) }, // Super A/A4 + { PAPER_B_PLUS, mm2twips( 305 ), mm2twips( 487 ) }, // Super B/A3 + { PAPER_LETTER_PLUS,in2twips( 8.5 ), in2twips( 12.69 ) }, // Letter Plus +/* 60*/ { PAPER_A4_PLUS, mm2twips( 210 ), mm2twips( 330 ) }, // A4 Plus + { PAPER_USER, mm2twips( 148 ), mm2twips( 210 ) }, // A5 Transverse + { PAPER_USER, mm2twips( 182 ), mm2twips( 257 ) }, // B5 (JIS) Transverse + { PAPER_USER, mm2twips( 322 ), mm2twips( 445 ) }, // A3 Extra + { PAPER_USER, mm2twips( 174 ), mm2twips( 235 ) }, // A5 Extra +/* 65*/ { PAPER_USER, mm2twips( 201 ), mm2twips( 276 ) }, // B5 (ISO) Extra + { PAPER_A2, mm2twips( 420 ), mm2twips( 594 ) }, // A2 + { PAPER_USER, mm2twips( 297 ), mm2twips( 420 ) }, // A3 Transverse + { PAPER_USER, mm2twips( 322 ), mm2twips( 445 ) }, // A3 Extra Transverse + { PAPER_DOUBLEPOSTCARD_JP, mm2twips( 200 ), mm2twips( 148 ) }, // Double Japanese Postcard +/* 70*/ { PAPER_A6, mm2twips( 105 ), mm2twips( 148 ) }, // A6 + { PAPER_USER, 0, 0 }, // Japanese Envelope Kaku #2 + { PAPER_USER, 0, 0 }, // Japanese Envelope Kaku #3 + { PAPER_USER, 0, 0 }, // Japanese Envelope Chou #3 + { PAPER_USER, 0, 0 }, // Japanese Envelope Chou #4 +/* 75*/ { PAPER_USER, in2twips( 11 ), in2twips( 8.5 ) }, // Letter Rotated + { PAPER_USER, mm2twips( 420 ), mm2twips( 297 ) }, // A3 Rotated + { PAPER_USER, mm2twips( 297 ), mm2twips( 210 ) }, // A4 Rotated + { PAPER_USER, mm2twips( 210 ), mm2twips( 148 ) }, // A5 Rotated + { PAPER_USER, mm2twips( 364 ), mm2twips( 257 ) }, // B4 (JIS) Rotated +/* 80*/ { PAPER_USER, mm2twips( 257 ), mm2twips( 182 ) }, // B5 (JIS) Rotated + { PAPER_USER, mm2twips( 148 ), mm2twips( 100 ) }, // Japanese Postcard Rotated + { PAPER_USER, mm2twips( 148 ), mm2twips( 200 ) }, // Double Japanese Postcard Rotated + { PAPER_USER, mm2twips( 148 ), mm2twips( 105 ) }, // A6 Rotated + { PAPER_USER, 0, 0 }, // Japanese Envelope Kaku #2 Rotated +/* 85*/ { PAPER_USER, 0, 0 }, // Japanese Envelope Kaku #3 Rotated + { PAPER_USER, 0, 0 }, // Japanese Envelope Chou #3 Rotated + { PAPER_USER, 0, 0 }, // Japanese Envelope Chou #4 Rotated + { PAPER_B6_JIS, mm2twips( 128 ), mm2twips( 182 ) }, // B6 (JIS) + { PAPER_USER, mm2twips( 182 ), mm2twips( 128 ) }, // B6 (JIS) Rotated +/* 90*/ { PAPER_12x11, in2twips( 12 ), in2twips( 11 ) } // 12x11 +}; + +} //namespace + +// Page settings ============================================================== + +XclPageData::XclPageData() +{ + SetDefaults(); +} + +XclPageData::~XclPageData() +{ + // SvxBrushItem incomplete in header +} + +void XclPageData::SetDefaults() +{ + maHorPageBreaks.clear(); + maVerPageBreaks.clear(); + mxBrushItem.reset(); + maHeader.clear(); + maFooter.clear(); + maHeaderEven.clear(); + maFooterEven.clear(); + mfLeftMargin = mfRightMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_LR ); + mfTopMargin = mfBottomMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_TB ); + mfHeaderMargin = mfFooterMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_HF ); + mfHdrLeftMargin = mfHdrRightMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_HLR ); + mfFtrLeftMargin = mfFtrRightMargin = XclTools::GetInchFromHmm( EXC_MARGIN_DEFAULT_FLR ); + mnStrictPaperSize = mnPaperSize = EXC_PAPERSIZE_DEFAULT; + mnPaperWidth = 0; + mnPaperHeight = 0; + mnCopies = 1; + mnStartPage = 0; + mnScaling = 100; + mnFitToWidth = mnFitToHeight = 1; + mnHorPrintRes = mnVerPrintRes = 300; + mbUseEvenHF = mbUseFirstHF = false; + mbValid = false; + mbPortrait = true; + mbPrintInRows = mbBlackWhite = mbDraftQuality = mbPrintNotes = mbManualStart = mbFitToPages = false; + mbHorCenter = mbVerCenter = mbPrintHeadings = mbPrintGrid = false; +} + +Size XclPageData::GetScPaperSize() const +{ + const XclPaperSize* pEntry = pPaperSizeTable; + if( mnPaperSize < SAL_N_ELEMENTS( pPaperSizeTable ) ) + pEntry += mnPaperSize; + + Size aSize; + if( pEntry->mePaper == PAPER_USER ) + aSize = Size( pEntry->mnWidth, pEntry->mnHeight ); + else + aSize = SvxPaperInfo::GetPaperSize( pEntry->mePaper ); + + // invalid size -> back to default + if( !aSize.Width() || !aSize.Height() ) + aSize = SvxPaperInfo::GetDefaultPaperSize(); + + if( !mbPortrait ) + { + // swap width and height + tools::Long n = aSize.Width(); + aSize.setWidth(aSize.Height()); + aSize.setHeight(n); + } + + return aSize; +} + +void XclPageData::SetScPaperSize( const Size& rSize, bool bPortrait, bool bStrictSize ) +{ + mbPortrait = bPortrait; + mnPaperSize = 0; + tools::Long nWidth = bPortrait ? rSize.Width() : rSize.Height(); + tools::Long nHeight = bPortrait ? rSize.Height() : rSize.Width(); + tools::Long nMaxWDiff = 80; + tools::Long nMaxHDiff = 50; + + mnPaperWidth = twips2mm( nWidth ); + mnPaperHeight = twips2mm( nHeight ); + if( bStrictSize ) + { + nMaxWDiff = 5; + nMaxHDiff = 5; + mnStrictPaperSize = EXC_PAPERSIZE_USER; + } + else + { + mnPaperSize = EXC_PAPERSIZE_DEFAULT; + } + + for( const auto &rEntry : pPaperSizeTable) + { + tools::Long nWDiff = std::abs( rEntry.mnWidth - nWidth ); + tools::Long nHDiff = std::abs( rEntry.mnHeight - nHeight ); + if( ((nWDiff <= nMaxWDiff) && (nHDiff < nMaxHDiff)) || + ((nWDiff < nMaxWDiff) && (nHDiff <= nMaxHDiff)) ) + { + sal_uInt16 nIndex = static_cast< sal_uInt16 >( &rEntry - pPaperSizeTable ); + mnPaperSize = nIndex; + if( bStrictSize ) + mnStrictPaperSize = nIndex; + + nMaxWDiff = nWDiff; + nMaxHDiff = nHDiff; + } + } + if( !bStrictSize ) + SetScPaperSize( rSize, bPortrait, true ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlpivot.cxx b/sc/source/filter/excel/xlpivot.cxx new file mode 100644 index 000000000..d18ab7416 --- /dev/null +++ b/sc/source/filter/excel/xlpivot.cxx @@ -0,0 +1,1043 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::com::sun::star::sheet::DataPilotFieldOrientation; + +namespace ScDPSortMode = ::com::sun::star::sheet::DataPilotFieldSortMode; +namespace ScDPShowItemsMode = ::com::sun::star::sheet::DataPilotFieldShowItemsMode; +namespace ScDPLayoutMode = ::com::sun::star::sheet::DataPilotFieldLayoutMode; +namespace ScDPRefItemType = ::com::sun::star::sheet::DataPilotFieldReferenceItemType; +namespace ScDPGroupBy = ::com::sun::star::sheet::DataPilotFieldGroupBy; + +// Pivot cache + +XclPCItem::XclPCItem() : + meType( EXC_PCITEM_INVALID ), + maDateTime( DateTime::EMPTY ) +{ +} + +XclPCItem::~XclPCItem() +{ +} + +void XclPCItem::SetEmpty() +{ + meType = EXC_PCITEM_EMPTY; + maText.clear(); +} + +void XclPCItem::SetText( const OUString& rText ) +{ + meType = EXC_PCITEM_TEXT; + maText = rText; +} + +void XclPCItem::SetDouble( double fValue, const OUString& rText ) +{ + meType = EXC_PCITEM_DOUBLE; + maText = rText; + mfValue = fValue; +} + +void XclPCItem::SetDateTime( const DateTime& rDateTime, const OUString& rText ) +{ + meType = EXC_PCITEM_DATETIME; + maText = rText; + maDateTime = rDateTime; +} + +void XclPCItem::SetInteger( sal_Int16 nValue ) +{ + meType = EXC_PCITEM_INTEGER; + maText = OUString::number(nValue); + mnValue = nValue; +} + +void XclPCItem::SetError( sal_uInt16 nError ) +{ + meType = EXC_PCITEM_ERROR; + maText.clear(); + mnError = nError; + switch( nError ) + { + case 0x00: maText = "#nullptr!"; break; + case 0x07: maText = "#DIV/0!"; break; + case 0x0F: maText = "#VALUE!"; break; + case 0x17: maText = "#REF!"; break; + case 0x1D: maText = "#NAME?"; break; + case 0x24: maText = "#NUM!"; break; + case 0x2A: maText = "#N/A"; break; + default: break; + } +} + +void XclPCItem::SetBool( bool bValue, const OUString& rText ) +{ + meType = EXC_PCITEM_BOOL; + maText = rText; + mbValue = bValue; +} + +bool XclPCItem::IsEqual( const XclPCItem& rItem ) const +{ + if( meType == rItem.meType ) switch( meType ) + { + case EXC_PCITEM_INVALID: return true; + case EXC_PCITEM_EMPTY: return true; + case EXC_PCITEM_TEXT: return maText == rItem.maText; + case EXC_PCITEM_DOUBLE: return mfValue == rItem.mfValue; + case EXC_PCITEM_DATETIME: return maDateTime == rItem.maDateTime; + case EXC_PCITEM_INTEGER: return mnValue == rItem.mnValue; + case EXC_PCITEM_BOOL: return mbValue == rItem.mbValue; + case EXC_PCITEM_ERROR: return mnError == rItem.mnError; + default: OSL_FAIL( "XclPCItem::IsEqual - unknown pivot cache item type" ); + } + return false; +} + +bool XclPCItem::IsEmpty() const +{ + return meType == EXC_PCITEM_EMPTY; +} + +const OUString* XclPCItem::GetText() const +{ + return (meType == EXC_PCITEM_TEXT || meType == EXC_PCITEM_ERROR) ? &maText : nullptr; +} + +const double* XclPCItem::GetDouble() const +{ + return (meType == EXC_PCITEM_DOUBLE) ? &mfValue : nullptr; +} + +const DateTime* XclPCItem::GetDateTime() const +{ + return (meType == EXC_PCITEM_DATETIME) ? &maDateTime : nullptr; +} + +const sal_Int16* XclPCItem::GetInteger() const +{ + return (meType == EXC_PCITEM_INTEGER) ? &mnValue : nullptr; +} + +const sal_uInt16* XclPCItem::GetError() const +{ + return (meType == EXC_PCITEM_ERROR) ? &mnError : nullptr; +} + +const bool* XclPCItem::GetBool() const +{ + return (meType == EXC_PCITEM_BOOL) ? &mbValue : nullptr; +} + +XclPCItemType XclPCItem::GetType() const +{ + return meType; +} + +// Field settings ============================================================= + +XclPCFieldInfo::XclPCFieldInfo() : + mnFlags( 0 ), + mnGroupChild( 0 ), + mnGroupBase( 0 ), + mnVisItems( 0 ), + mnGroupItems( 0 ), + mnBaseItems( 0 ), + mnOrigItems( 0 ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPCFieldInfo& rInfo ) +{ + rInfo.mnFlags = rStrm.ReaduInt16(); + rInfo.mnGroupChild = rStrm.ReaduInt16(); + rInfo.mnGroupBase = rStrm.ReaduInt16(); + rInfo.mnVisItems = rStrm.ReaduInt16(); + rInfo.mnGroupItems = rStrm.ReaduInt16(); + rInfo.mnBaseItems = rStrm.ReaduInt16(); + rInfo.mnOrigItems = rStrm.ReaduInt16(); + if( rStrm.GetRecLeft() >= 3 ) + rInfo.maName = rStrm.ReadUniString(); + else + rInfo.maName.clear(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPCFieldInfo& rInfo ) +{ + return rStrm + << rInfo.mnFlags + << rInfo.mnGroupChild + << rInfo.mnGroupBase + << rInfo.mnVisItems + << rInfo.mnGroupItems + << rInfo.mnBaseItems + << rInfo.mnOrigItems + << XclExpString( rInfo.maName ); +} + +// Numeric grouping field settings ============================================ + +XclPCNumGroupInfo::XclPCNumGroupInfo() : + mnFlags( EXC_SXNUMGROUP_AUTOMIN | EXC_SXNUMGROUP_AUTOMAX ) +{ + SetNumType(); +} + +void XclPCNumGroupInfo::SetNumType() +{ + SetXclDataType( EXC_SXNUMGROUP_TYPE_NUM ); +} + +sal_Int32 XclPCNumGroupInfo::GetScDateType() const +{ + sal_Int32 nScType = 0; + switch( GetXclDataType() ) + { + case EXC_SXNUMGROUP_TYPE_SEC: nScType = ScDPGroupBy::SECONDS; break; + case EXC_SXNUMGROUP_TYPE_MIN: nScType = ScDPGroupBy::MINUTES; break; + case EXC_SXNUMGROUP_TYPE_HOUR: nScType = ScDPGroupBy::HOURS; break; + case EXC_SXNUMGROUP_TYPE_DAY: nScType = ScDPGroupBy::DAYS; break; + case EXC_SXNUMGROUP_TYPE_MONTH: nScType = ScDPGroupBy::MONTHS; break; + case EXC_SXNUMGROUP_TYPE_QUART: nScType = ScDPGroupBy::QUARTERS; break; + case EXC_SXNUMGROUP_TYPE_YEAR: nScType = ScDPGroupBy::YEARS; break; + default: SAL_WARN("sc.filter", "XclPCNumGroupInfo::GetScDateType - unexpected date type " << GetXclDataType() ); + } + return nScType; +} + +void XclPCNumGroupInfo::SetScDateType( sal_Int32 nScType ) +{ + sal_uInt16 nXclType = EXC_SXNUMGROUP_TYPE_NUM; + switch( nScType ) + { + case ScDPGroupBy::SECONDS: nXclType = EXC_SXNUMGROUP_TYPE_SEC; break; + case ScDPGroupBy::MINUTES: nXclType = EXC_SXNUMGROUP_TYPE_MIN; break; + case ScDPGroupBy::HOURS: nXclType = EXC_SXNUMGROUP_TYPE_HOUR; break; + case ScDPGroupBy::DAYS: nXclType = EXC_SXNUMGROUP_TYPE_DAY; break; + case ScDPGroupBy::MONTHS: nXclType = EXC_SXNUMGROUP_TYPE_MONTH; break; + case ScDPGroupBy::QUARTERS: nXclType = EXC_SXNUMGROUP_TYPE_QUART; break; + case ScDPGroupBy::YEARS: nXclType = EXC_SXNUMGROUP_TYPE_YEAR; break; + default: + SAL_INFO("sc.filter", "unexpected date type " << nScType); + } + SetXclDataType( nXclType ); +} + +sal_uInt16 XclPCNumGroupInfo::GetXclDataType() const +{ + return ::extract_value< sal_uInt16 >( mnFlags, 2, 4 ); +} + +void XclPCNumGroupInfo::SetXclDataType( sal_uInt16 nXclType ) +{ + ::insert_value( mnFlags, nXclType, 2, 4 ); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPCNumGroupInfo& rInfo ) +{ + rInfo.mnFlags = rStrm.ReaduInt16(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPCNumGroupInfo& rInfo ) +{ + return rStrm << rInfo.mnFlags; +} + +// Base class for pivot cache fields ========================================== + +XclPCField::XclPCField( XclPCFieldType eFieldType, sal_uInt16 nFieldIdx ) : + meFieldType( eFieldType ), + mnFieldIdx( nFieldIdx ) +{ +} + +XclPCField::~XclPCField() +{ +} + +bool XclPCField::IsSupportedField() const +{ + return (meFieldType != EXC_PCFIELD_CALCED) && (meFieldType != EXC_PCFIELD_UNKNOWN); +} + +bool XclPCField::IsStandardField() const +{ + return meFieldType == EXC_PCFIELD_STANDARD; +} + +bool XclPCField::IsStdGroupField() const +{ + return meFieldType == EXC_PCFIELD_STDGROUP; +} + +bool XclPCField::IsNumGroupField() const +{ + return meFieldType == EXC_PCFIELD_NUMGROUP; +} + +bool XclPCField::IsDateGroupField() const +{ + return (meFieldType == EXC_PCFIELD_DATEGROUP) || (meFieldType == EXC_PCFIELD_DATECHILD); +} + +bool XclPCField::IsGroupField() const +{ + return IsStdGroupField() || IsNumGroupField() || IsDateGroupField(); +} + +bool XclPCField::IsGroupBaseField() const +{ + return ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_HASCHILD ); +} + +bool XclPCField::IsGroupChildField() const +{ + return (meFieldType == EXC_PCFIELD_STDGROUP) || (meFieldType == EXC_PCFIELD_DATECHILD); +} + +bool XclPCField::HasOrigItems() const +{ + return IsSupportedField() && ((maFieldInfo.mnOrigItems > 0) || HasPostponedItems()); +} + +bool XclPCField::HasInlineItems() const +{ + return (IsStandardField() || IsGroupField()) && ((maFieldInfo.mnGroupItems > 0) || (maFieldInfo.mnOrigItems > 0)); +} + +bool XclPCField::HasPostponedItems() const +{ + return IsStandardField() && ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_POSTPONE ); +} + +bool XclPCField::Has16BitIndexes() const +{ + return IsStandardField() && ::get_flag( maFieldInfo.mnFlags, EXC_SXFIELD_16BIT ); +} + +// Pivot cache settings ======================================================= + +/** Contains data for a pivot cache (SXDB record). */ +XclPCInfo::XclPCInfo() : + mnSrcRecs( 0 ), + mnStrmId( 0xFFFF ), + mnFlags( EXC_SXDB_DEFAULTFLAGS ), + mnBlockRecs( EXC_SXDB_BLOCKRECS ), + mnStdFields( 0 ), + mnTotalFields( 0 ), + mnSrcType( EXC_SXDB_SRC_SHEET ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPCInfo& rInfo ) +{ + rInfo.mnSrcRecs = rStrm.ReaduInt32(); + rInfo.mnStrmId = rStrm.ReaduInt16(); + rInfo.mnFlags = rStrm.ReaduInt16(); + rInfo.mnBlockRecs = rStrm.ReaduInt16(); + rInfo.mnStdFields = rStrm.ReaduInt16(); + rInfo.mnTotalFields = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + rInfo.mnSrcType = rStrm.ReaduInt16(); + rInfo.maUserName = rStrm.ReadUniString(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPCInfo& rInfo ) +{ + return rStrm + << rInfo.mnSrcRecs + << rInfo.mnStrmId + << rInfo.mnFlags + << rInfo.mnBlockRecs + << rInfo.mnStdFields + << rInfo.mnTotalFields + << sal_uInt16( 0 ) + << rInfo.mnSrcType + << XclExpString( rInfo.maUserName ); +} + +// Pivot table + +// cached name ================================================================ + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTCachedName& rCachedName ) +{ + sal_uInt16 nStrLen; + nStrLen = rStrm.ReaduInt16(); + rCachedName.mbUseCache = nStrLen == EXC_PT_NOSTRING; + if( rCachedName.mbUseCache ) + rCachedName.maName.clear(); + else + rCachedName.maName = rStrm.ReadUniString( nStrLen ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTCachedName& rCachedName ) +{ + if( rCachedName.mbUseCache ) + rStrm << EXC_PT_NOSTRING; + else + rStrm << XclExpString( rCachedName.maName, XclStrFlags::NONE, EXC_PT_MAXSTRLEN ); + return rStrm; +} + +const OUString* XclPTVisNameInfo::GetVisName() const +{ + return HasVisName() ? &maVisName.maName : nullptr; +} + +void XclPTVisNameInfo::SetVisName( const OUString& rName ) +{ + maVisName.maName = rName; + maVisName.mbUseCache = rName.isEmpty(); +} + +// Field item settings ======================================================== + +XclPTItemInfo::XclPTItemInfo() : + mnType( EXC_SXVI_TYPE_DATA ), + mnFlags( EXC_SXVI_DEFAULTFLAGS ), + mnCacheIdx( EXC_SXVI_DEFAULT_CACHE ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTItemInfo& rInfo ) +{ + rInfo.mnType = rStrm.ReaduInt16(); + rInfo.mnFlags = rStrm.ReaduInt16(); + rInfo.mnCacheIdx = rStrm.ReaduInt16(); + rStrm >> rInfo.maVisName; + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTItemInfo& rInfo ) +{ + return rStrm + << rInfo.mnType + << rInfo.mnFlags + << rInfo.mnCacheIdx + << rInfo.maVisName; +} + +// General field settings ===================================================== + +XclPTFieldInfo::XclPTFieldInfo() : + mnAxes( EXC_SXVD_AXIS_NONE ), + mnSubtCount( 1 ), + mnSubtotals( EXC_SXVD_SUBT_DEFAULT ), + mnItemCount( 0 ), + mnCacheIdx( EXC_SXVD_DEFAULT_CACHE ) +{ +} + +DataPilotFieldOrientation XclPTFieldInfo::GetApiOrient( sal_uInt16 nMask ) const +{ + using namespace ::com::sun::star::sheet; + DataPilotFieldOrientation eOrient = DataPilotFieldOrientation_HIDDEN; + sal_uInt16 nUsedAxes = mnAxes & nMask; + if( nUsedAxes & EXC_SXVD_AXIS_ROW ) + eOrient = DataPilotFieldOrientation_ROW; + else if( nUsedAxes & EXC_SXVD_AXIS_COL ) + eOrient = DataPilotFieldOrientation_COLUMN; + else if( nUsedAxes & EXC_SXVD_AXIS_PAGE ) + eOrient = DataPilotFieldOrientation_PAGE; + else if( nUsedAxes & EXC_SXVD_AXIS_DATA ) + eOrient = DataPilotFieldOrientation_DATA; + return eOrient; +} + +void XclPTFieldInfo::AddApiOrient( DataPilotFieldOrientation eOrient ) +{ + using namespace ::com::sun::star::sheet; + switch( eOrient ) + { + case DataPilotFieldOrientation_ROW: mnAxes |= EXC_SXVD_AXIS_ROW; break; + case DataPilotFieldOrientation_COLUMN: mnAxes |= EXC_SXVD_AXIS_COL; break; + case DataPilotFieldOrientation_PAGE: mnAxes |= EXC_SXVD_AXIS_PAGE; break; + case DataPilotFieldOrientation_DATA: mnAxes |= EXC_SXVD_AXIS_DATA; break; + default:; + } +} + +//TODO: should be a Sequence in ScDPSaveData +void XclPTFieldInfo::GetSubtotals( XclPTSubtotalVec& rSubtotals ) const +{ + rSubtotals.clear(); + rSubtotals.reserve( 16 ); + + if( mnSubtotals & EXC_SXVD_SUBT_DEFAULT ) rSubtotals.push_back( ScGeneralFunction::AUTO ); + if( mnSubtotals & EXC_SXVD_SUBT_SUM ) rSubtotals.push_back( ScGeneralFunction::SUM ); + if( mnSubtotals & EXC_SXVD_SUBT_COUNT ) rSubtotals.push_back( ScGeneralFunction::COUNT ); + if( mnSubtotals & EXC_SXVD_SUBT_AVERAGE ) rSubtotals.push_back( ScGeneralFunction::AVERAGE ); + if( mnSubtotals & EXC_SXVD_SUBT_MAX ) rSubtotals.push_back( ScGeneralFunction::MAX ); + if( mnSubtotals & EXC_SXVD_SUBT_MIN ) rSubtotals.push_back( ScGeneralFunction::MIN ); + if( mnSubtotals & EXC_SXVD_SUBT_PROD ) rSubtotals.push_back( ScGeneralFunction::PRODUCT ); + if( mnSubtotals & EXC_SXVD_SUBT_COUNTNUM ) rSubtotals.push_back( ScGeneralFunction::COUNTNUMS ); + if( mnSubtotals & EXC_SXVD_SUBT_STDDEV ) rSubtotals.push_back( ScGeneralFunction::STDEV ); + if( mnSubtotals & EXC_SXVD_SUBT_STDDEVP ) rSubtotals.push_back( ScGeneralFunction::STDEVP ); + if( mnSubtotals & EXC_SXVD_SUBT_VAR ) rSubtotals.push_back( ScGeneralFunction::VAR ); + if( mnSubtotals & EXC_SXVD_SUBT_VARP ) rSubtotals.push_back( ScGeneralFunction::VARP ); +} + +void XclPTFieldInfo::SetSubtotals( const XclPTSubtotalVec& rSubtotals ) +{ + mnSubtotals = EXC_SXVD_SUBT_NONE; + for( const auto& rSubtotal : rSubtotals ) + { + switch( rSubtotal ) + { + case ScGeneralFunction::AUTO: mnSubtotals |= EXC_SXVD_SUBT_DEFAULT; break; + case ScGeneralFunction::SUM: mnSubtotals |= EXC_SXVD_SUBT_SUM; break; + case ScGeneralFunction::COUNT: mnSubtotals |= EXC_SXVD_SUBT_COUNT; break; + case ScGeneralFunction::AVERAGE: mnSubtotals |= EXC_SXVD_SUBT_AVERAGE; break; + case ScGeneralFunction::MAX: mnSubtotals |= EXC_SXVD_SUBT_MAX; break; + case ScGeneralFunction::MIN: mnSubtotals |= EXC_SXVD_SUBT_MIN; break; + case ScGeneralFunction::PRODUCT: mnSubtotals |= EXC_SXVD_SUBT_PROD; break; + case ScGeneralFunction::COUNTNUMS: mnSubtotals |= EXC_SXVD_SUBT_COUNTNUM; break; + case ScGeneralFunction::STDEV: mnSubtotals |= EXC_SXVD_SUBT_STDDEV; break; + case ScGeneralFunction::STDEVP: mnSubtotals |= EXC_SXVD_SUBT_STDDEVP; break; + case ScGeneralFunction::VAR: mnSubtotals |= EXC_SXVD_SUBT_VAR; break; + case ScGeneralFunction::VARP: mnSubtotals |= EXC_SXVD_SUBT_VARP; break; + default: break; + } + } + + mnSubtCount = 0; + for( sal_uInt16 nMask = 0x8000; nMask; nMask >>= 1 ) + if( mnSubtotals & nMask ) + ++mnSubtCount; +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTFieldInfo& rInfo ) +{ + // rInfo.mnCacheIdx is not part of the SXVD record + rInfo.mnAxes = rStrm.ReaduInt16(); + rInfo.mnSubtCount = rStrm.ReaduInt16(); + rInfo.mnSubtotals = rStrm.ReaduInt16(); + rInfo.mnItemCount = rStrm.ReaduInt16(); + rStrm >> rInfo.maVisName; + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTFieldInfo& rInfo ) +{ + // rInfo.mnCacheIdx is not part of the SXVD record + return rStrm + << rInfo.mnAxes + << rInfo.mnSubtCount + << rInfo.mnSubtotals + << rInfo.mnItemCount + << rInfo.maVisName; +} + +// Extended field settings ==================================================== + +XclPTFieldExtInfo::XclPTFieldExtInfo() : + mnFlags( EXC_SXVDEX_DEFAULTFLAGS ), + mnSortField( EXC_SXVDEX_SORT_OWN ), + mnShowField( EXC_SXVDEX_SHOW_NONE ), + mnNumFmt(0) +{ +} + +sal_Int32 XclPTFieldExtInfo::GetApiSortMode() const +{ + sal_Int32 nSortMode = ScDPSortMode::MANUAL; + if( ::get_flag( mnFlags, EXC_SXVDEX_SORT ) ) + nSortMode = (mnSortField == EXC_SXVDEX_SORT_OWN) ? ScDPSortMode::NAME : ScDPSortMode::DATA; + return nSortMode; +} + +void XclPTFieldExtInfo::SetApiSortMode( sal_Int32 nSortMode ) +{ + bool bSort = (nSortMode == ScDPSortMode::NAME) || (nSortMode == ScDPSortMode::DATA); + ::set_flag( mnFlags, EXC_SXVDEX_SORT, bSort ); + if( nSortMode == ScDPSortMode::NAME ) + mnSortField = EXC_SXVDEX_SORT_OWN; // otherwise sort field has to be set by caller +} + +sal_Int32 XclPTFieldExtInfo::GetApiAutoShowMode() const +{ + return ::get_flagvalue( mnFlags, EXC_SXVDEX_AUTOSHOW_ASC, + ScDPShowItemsMode::FROM_TOP, ScDPShowItemsMode::FROM_BOTTOM ); +} + +void XclPTFieldExtInfo::SetApiAutoShowMode( sal_Int32 nShowMode ) +{ + ::set_flag( mnFlags, EXC_SXVDEX_AUTOSHOW_ASC, nShowMode == ScDPShowItemsMode::FROM_TOP ); +} + +sal_Int32 XclPTFieldExtInfo::GetApiAutoShowCount() const +{ + return ::extract_value< sal_Int32 >( mnFlags, 24, 8 ); +} + +void XclPTFieldExtInfo::SetApiAutoShowCount( sal_Int32 nShowCount ) +{ + ::insert_value( mnFlags, limit_cast< sal_uInt8 >( nShowCount ), 24, 8 ); +} + +sal_Int32 XclPTFieldExtInfo::GetApiLayoutMode() const +{ + sal_Int32 nLayoutMode = ScDPLayoutMode::TABULAR_LAYOUT; + if( ::get_flag( mnFlags, EXC_SXVDEX_LAYOUT_REPORT ) ) + nLayoutMode = ::get_flag( mnFlags, EXC_SXVDEX_LAYOUT_TOP ) ? + ScDPLayoutMode::OUTLINE_SUBTOTALS_TOP : ScDPLayoutMode::OUTLINE_SUBTOTALS_BOTTOM; + return nLayoutMode; +} + +void XclPTFieldExtInfo::SetApiLayoutMode( sal_Int32 nLayoutMode ) +{ + ::set_flag( mnFlags, EXC_SXVDEX_LAYOUT_REPORT, nLayoutMode != ScDPLayoutMode::TABULAR_LAYOUT ); + ::set_flag( mnFlags, EXC_SXVDEX_LAYOUT_TOP, nLayoutMode == ScDPLayoutMode::OUTLINE_SUBTOTALS_TOP ); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTFieldExtInfo& rInfo ) +{ + sal_uInt8 nNameLen = 0; + rInfo.mnFlags = rStrm.ReaduInt32(); + rInfo.mnSortField = rStrm.ReaduInt16(); + rInfo.mnShowField = rStrm.ReaduInt16(); + rInfo.mnNumFmt = rStrm.ReaduInt16(); + nNameLen = rStrm.ReaduInt8(); + + rStrm.Ignore(10); + if (nNameLen != 0xFF) + // Custom field total name is used. Pick it up. + rInfo.mpFieldTotalName = rStrm.ReadUniString(nNameLen, 0); + + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTFieldExtInfo& rInfo ) +{ + rStrm << rInfo.mnFlags + << rInfo.mnSortField + << rInfo.mnShowField + << EXC_SXVDEX_FORMAT_NONE; + + if (rInfo.mpFieldTotalName && !rInfo.mpFieldTotalName->isEmpty()) + { + OUString aFinalName = *rInfo.mpFieldTotalName; + if (aFinalName.getLength() >= 254) + aFinalName = aFinalName.copy(0, 254); + sal_uInt8 nNameLen = static_cast(aFinalName.getLength()); + rStrm << nNameLen; + rStrm.WriteZeroBytes(10); + rStrm << XclExpString(aFinalName, XclStrFlags::NoHeader); + } + else + { + rStrm << sal_uInt16(0xFFFF); + rStrm.WriteZeroBytes(8); + } + return rStrm; +} + +// Page field settings ======================================================== + +XclPTPageFieldInfo::XclPTPageFieldInfo() : + mnField( 0 ), + mnSelItem( EXC_SXPI_ALLITEMS ), + mnObjId( 0xFFFF ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTPageFieldInfo& rInfo ) +{ + rInfo.mnField = rStrm.ReaduInt16(); + rInfo.mnSelItem = rStrm.ReaduInt16(); + rInfo.mnObjId = rStrm.ReaduInt16(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTPageFieldInfo& rInfo ) +{ + return rStrm + << rInfo.mnField + << rInfo.mnSelItem + << rInfo.mnObjId; +} + +// Data field settings ======================================================== + +XclPTDataFieldInfo::XclPTDataFieldInfo() : + mnField( 0 ), + mnAggFunc( EXC_SXDI_FUNC_SUM ), + mnRefType( EXC_SXDI_REF_NORMAL ), + mnRefField( 0 ), + mnRefItem( 0 ), + mnNumFmt( 0 ) +{ +} + +ScGeneralFunction XclPTDataFieldInfo::GetApiAggFunc() const +{ + ScGeneralFunction eAggFunc; + switch( mnAggFunc ) + { + case EXC_SXDI_FUNC_SUM: eAggFunc = ScGeneralFunction::SUM; break; + case EXC_SXDI_FUNC_COUNT: eAggFunc = ScGeneralFunction::COUNT; break; + case EXC_SXDI_FUNC_AVERAGE: eAggFunc = ScGeneralFunction::AVERAGE; break; + case EXC_SXDI_FUNC_MAX: eAggFunc = ScGeneralFunction::MAX; break; + case EXC_SXDI_FUNC_MIN: eAggFunc = ScGeneralFunction::MIN; break; + case EXC_SXDI_FUNC_PRODUCT: eAggFunc = ScGeneralFunction::PRODUCT; break; + case EXC_SXDI_FUNC_COUNTNUM: eAggFunc = ScGeneralFunction::COUNTNUMS; break; + case EXC_SXDI_FUNC_STDDEV: eAggFunc = ScGeneralFunction::STDEV; break; + case EXC_SXDI_FUNC_STDDEVP: eAggFunc = ScGeneralFunction::STDEVP; break; + case EXC_SXDI_FUNC_VAR: eAggFunc = ScGeneralFunction::VAR; break; + case EXC_SXDI_FUNC_VARP: eAggFunc = ScGeneralFunction::VARP; break; + default: eAggFunc = ScGeneralFunction::SUM; + } + return eAggFunc; +} + +void XclPTDataFieldInfo::SetApiAggFunc( ScGeneralFunction eAggFunc ) +{ + switch( eAggFunc ) + { + case ScGeneralFunction::SUM: mnAggFunc = EXC_SXDI_FUNC_SUM; break; + case ScGeneralFunction::COUNT: mnAggFunc = EXC_SXDI_FUNC_COUNT; break; + case ScGeneralFunction::AVERAGE: mnAggFunc = EXC_SXDI_FUNC_AVERAGE; break; + case ScGeneralFunction::MAX: mnAggFunc = EXC_SXDI_FUNC_MAX; break; + case ScGeneralFunction::MIN: mnAggFunc = EXC_SXDI_FUNC_MIN; break; + case ScGeneralFunction::PRODUCT: mnAggFunc = EXC_SXDI_FUNC_PRODUCT; break; + case ScGeneralFunction::COUNTNUMS: mnAggFunc = EXC_SXDI_FUNC_COUNTNUM; break; + case ScGeneralFunction::STDEV: mnAggFunc = EXC_SXDI_FUNC_STDDEV; break; + case ScGeneralFunction::STDEVP: mnAggFunc = EXC_SXDI_FUNC_STDDEVP; break; + case ScGeneralFunction::VAR: mnAggFunc = EXC_SXDI_FUNC_VAR; break; + case ScGeneralFunction::VARP: mnAggFunc = EXC_SXDI_FUNC_VARP; break; + default: mnAggFunc = EXC_SXDI_FUNC_SUM; + } +} + +sal_Int32 XclPTDataFieldInfo::GetApiRefType() const +{ + namespace ScDPRefType = ::com::sun::star::sheet::DataPilotFieldReferenceType; + sal_Int32 nRefType; + switch( mnRefType ) + { + case EXC_SXDI_REF_DIFF: nRefType = ScDPRefType::ITEM_DIFFERENCE; break; + case EXC_SXDI_REF_PERC: nRefType = ScDPRefType::ITEM_PERCENTAGE; break; + case EXC_SXDI_REF_PERC_DIFF: nRefType = ScDPRefType::ITEM_PERCENTAGE_DIFFERENCE; break; + case EXC_SXDI_REF_RUN_TOTAL: nRefType = ScDPRefType::RUNNING_TOTAL; break; + case EXC_SXDI_REF_PERC_ROW: nRefType = ScDPRefType::ROW_PERCENTAGE; break; + case EXC_SXDI_REF_PERC_COL: nRefType = ScDPRefType::COLUMN_PERCENTAGE; break; + case EXC_SXDI_REF_PERC_TOTAL: nRefType = ScDPRefType::TOTAL_PERCENTAGE; break; + case EXC_SXDI_REF_INDEX: nRefType = ScDPRefType::INDEX; break; + default: nRefType = ScDPRefType::NONE; + } + return nRefType; +} + +void XclPTDataFieldInfo::SetApiRefType( sal_Int32 nRefType ) +{ + namespace ScDPRefType = ::com::sun::star::sheet::DataPilotFieldReferenceType; + switch( nRefType ) + { + case ScDPRefType::ITEM_DIFFERENCE: mnRefType = EXC_SXDI_REF_DIFF; break; + case ScDPRefType::ITEM_PERCENTAGE: mnRefType = EXC_SXDI_REF_PERC; break; + case ScDPRefType::ITEM_PERCENTAGE_DIFFERENCE: mnRefType = EXC_SXDI_REF_PERC_DIFF; break; + case ScDPRefType::RUNNING_TOTAL: mnRefType = EXC_SXDI_REF_RUN_TOTAL; break; + case ScDPRefType::ROW_PERCENTAGE: mnRefType = EXC_SXDI_REF_PERC_ROW; break; + case ScDPRefType::COLUMN_PERCENTAGE: mnRefType = EXC_SXDI_REF_PERC_COL; break; + case ScDPRefType::TOTAL_PERCENTAGE: mnRefType = EXC_SXDI_REF_PERC_TOTAL;break; + case ScDPRefType::INDEX: mnRefType = EXC_SXDI_REF_INDEX; break; + default: mnRefType = EXC_SXDI_REF_NORMAL; + } +} + +sal_Int32 XclPTDataFieldInfo::GetApiRefItemType() const +{ + sal_Int32 nRefItemType; + switch( mnRefItem ) + { + case EXC_SXDI_PREVITEM: nRefItemType = ScDPRefItemType::PREVIOUS; break; + case EXC_SXDI_NEXTITEM: nRefItemType = ScDPRefItemType::NEXT; break; + default: nRefItemType = ScDPRefItemType::NAMED; + } + return nRefItemType; +} + +void XclPTDataFieldInfo::SetApiRefItemType( sal_Int32 nRefItemType ) +{ + switch( nRefItemType ) + { + case ScDPRefItemType::PREVIOUS: mnRefItem = EXC_SXDI_PREVITEM; break; + case ScDPRefItemType::NEXT: mnRefItem = EXC_SXDI_NEXTITEM; break; + // nothing for named item reference + } +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTDataFieldInfo& rInfo ) +{ + rInfo.mnField = rStrm.ReaduInt16(); + rInfo.mnAggFunc = rStrm.ReaduInt16(); + rInfo.mnRefType = rStrm.ReaduInt16(); + rInfo.mnRefField = rStrm.ReaduInt16(); + rInfo.mnRefItem = rStrm.ReaduInt16(); + rInfo.mnNumFmt = rStrm.ReaduInt16(); + rStrm >> rInfo.maVisName; + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTDataFieldInfo& rInfo ) +{ + return rStrm + << rInfo.mnField + << rInfo.mnAggFunc + << rInfo.mnRefType + << rInfo.mnRefField + << rInfo.mnRefItem + << rInfo.mnNumFmt + << rInfo.maVisName; +} + +// Pivot table settings ======================================================= + +XclPTInfo::XclPTInfo() : + mnFirstHeadRow( 0 ), + mnCacheIdx( 0xFFFF ), + mnDataAxis( EXC_SXVD_AXIS_NONE ), + mnDataPos( EXC_SXVIEW_DATALAST ), + mnFields( 0 ), + mnRowFields( 0 ), + mnColFields( 0 ), + mnPageFields( 0 ), + mnDataFields( 0 ), + mnDataRows( 0 ), + mnDataCols( 0 ), + mnFlags( EXC_SXVIEW_DEFAULTFLAGS ), + mnAutoFmtIdx( EXC_SXVIEW_AUTOFMT ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTInfo& rInfo ) +{ + sal_uInt16 nTabLen, nDataLen; + + rStrm >> rInfo.maOutXclRange; + rInfo.mnFirstHeadRow = rStrm.ReaduInt16(); + rStrm >> rInfo.maDataXclPos; + rInfo.mnCacheIdx = rStrm.ReaduInt16(); + rStrm.Ignore( 2 ); + rInfo.mnDataAxis = rStrm.ReaduInt16(); + rInfo.mnDataPos = rStrm.ReaduInt16(); + rInfo.mnFields = rStrm.ReaduInt16(); + rInfo.mnRowFields = rStrm.ReaduInt16(); + rInfo.mnColFields = rStrm.ReaduInt16(); + rInfo.mnPageFields = rStrm.ReaduInt16(); + rInfo.mnDataFields = rStrm.ReaduInt16(); + rInfo.mnDataRows = rStrm.ReaduInt16(); + rInfo.mnDataCols = rStrm.ReaduInt16(); + rInfo.mnFlags = rStrm.ReaduInt16(); + rInfo.mnAutoFmtIdx = rStrm.ReaduInt16(); + nTabLen = rStrm.ReaduInt16(); + nDataLen = rStrm.ReaduInt16(); + rInfo.maTableName = rStrm.ReadUniString( nTabLen ); + rInfo.maDataName = rStrm.ReadUniString( nDataLen ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTInfo& rInfo ) +{ + XclExpString aXclTableName( rInfo.maTableName ); + XclExpString aXclDataName( rInfo.maDataName ); + + rStrm << rInfo.maOutXclRange + << rInfo.mnFirstHeadRow + << rInfo.maDataXclPos + << rInfo.mnCacheIdx + << sal_uInt16( 0 ) + << rInfo.mnDataAxis << rInfo.mnDataPos + << rInfo.mnFields + << rInfo.mnRowFields << rInfo.mnColFields + << rInfo.mnPageFields << rInfo.mnDataFields + << rInfo.mnDataRows << rInfo.mnDataCols + << rInfo.mnFlags + << rInfo.mnAutoFmtIdx + << aXclTableName.Len() << aXclDataName.Len(); + aXclTableName.WriteFlagField( rStrm ); + aXclTableName.WriteBuffer( rStrm ); + aXclDataName.WriteFlagField( rStrm ); + aXclDataName.WriteBuffer( rStrm ); + return rStrm; +} + +// Extended pivot table settings ============================================== + +XclPTExtInfo::XclPTExtInfo() : + mnSxformulaRecs( 0 ), + mnSxselectRecs( 0 ), + mnPagePerRow( 0 ), + mnPagePerCol( 0 ), + mnFlags( EXC_SXEX_DEFAULTFLAGS ) +{ +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTExtInfo& rInfo ) +{ + rInfo.mnSxformulaRecs = rStrm.ReaduInt16(); + rStrm.Ignore( 6 ); + rInfo.mnSxselectRecs = rStrm.ReaduInt16(); + rInfo.mnPagePerRow = rStrm.ReaduInt16(); + rInfo.mnPagePerCol = rStrm.ReaduInt16(); + rInfo.mnFlags = rStrm.ReaduInt32(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTExtInfo& rInfo ) +{ + return rStrm + << rInfo.mnSxformulaRecs + << EXC_PT_NOSTRING // length of alt. error text + << EXC_PT_NOSTRING // length of alt. empty text + << EXC_PT_NOSTRING // length of tag + << rInfo.mnSxselectRecs + << rInfo.mnPagePerRow + << rInfo.mnPagePerCol + << rInfo.mnFlags + << EXC_PT_NOSTRING // length of page field style name + << EXC_PT_NOSTRING // length of table style name + << EXC_PT_NOSTRING; // length of vacate style name +} + +// Pivot table autoformat settings ============================================ + +/** +classic : 10 08 00 00 00 00 00 00 20 00 00 00 01 00 00 00 00 +default : 10 08 00 00 00 00 00 00 20 00 00 00 01 00 00 00 00 +report01 : 10 08 02 00 00 00 00 00 20 00 00 00 00 10 00 00 00 +report02 : 10 08 02 00 00 00 00 00 20 00 00 00 01 10 00 00 00 +report03 : 10 08 02 00 00 00 00 00 20 00 00 00 02 10 00 00 00 +report04 : 10 08 02 00 00 00 00 00 20 00 00 00 03 10 00 00 00 +report05 : 10 08 02 00 00 00 00 00 20 00 00 00 04 10 00 00 00 +report06 : 10 08 02 00 00 00 00 00 20 00 00 00 05 10 00 00 00 +report07 : 10 08 02 00 00 00 00 00 20 00 00 00 06 10 00 00 00 +report08 : 10 08 02 00 00 00 00 00 20 00 00 00 07 10 00 00 00 +report09 : 10 08 02 00 00 00 00 00 20 00 00 00 08 10 00 00 00 +report10 : 10 08 02 00 00 00 00 00 20 00 00 00 09 10 00 00 00 +table01 : 10 08 00 00 00 00 00 00 20 00 00 00 0a 10 00 00 00 +table02 : 10 08 00 00 00 00 00 00 20 00 00 00 0b 10 00 00 00 +table03 : 10 08 00 00 00 00 00 00 20 00 00 00 0c 10 00 00 00 +table04 : 10 08 00 00 00 00 00 00 20 00 00 00 0d 10 00 00 00 +table05 : 10 08 00 00 00 00 00 00 20 00 00 00 0e 10 00 00 00 +table06 : 10 08 00 00 00 00 00 00 20 00 00 00 0f 10 00 00 00 +table07 : 10 08 00 00 00 00 00 00 20 00 00 00 10 10 00 00 00 +table08 : 10 08 00 00 00 00 00 00 20 00 00 00 11 10 00 00 00 +table09 : 10 08 00 00 00 00 00 00 20 00 00 00 12 10 00 00 00 +table10 : 10 08 00 00 00 00 00 00 20 00 00 00 13 10 00 00 00 +none : 10 08 00 00 00 00 00 00 20 00 00 00 15 10 00 00 00 +**/ + +XclPTViewEx9Info::XclPTViewEx9Info() : + mbReport( 0 ), + mnAutoFormat( 0 ), + mnGridLayout( 0x10 ) +{ +} + +void XclPTViewEx9Info::Init( const ScDPObject& rDPObj ) +{ + if( rDPObj.GetHeaderLayout() ) + { + mbReport = 0; + mnAutoFormat = 1; + mnGridLayout = 0; + } + else + { + // Report1 for now + // TODO : sync with autoformat indices + mbReport = 2; + mnAutoFormat = 1; + mnGridLayout = 0x10; + } + + const ScDPSaveData* pData = rDPObj.GetSaveData(); + if (pData) + { + const std::optional & pGrandTotal = pData->GetGrandTotalName(); + if (pGrandTotal) + maGrandTotalName = *pGrandTotal; + } +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclPTViewEx9Info& rInfo ) +{ + rStrm.Ignore( 2 ); + rInfo.mbReport = rStrm.ReaduInt32(); /// 2 for report* fmts ? + rStrm.Ignore( 6 ); + rInfo.mnAutoFormat = rStrm.ReaduInt8(); + rInfo.mnGridLayout = rStrm.ReaduInt8(); + rInfo.maGrandTotalName = rStrm.ReadUniString(); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclPTViewEx9Info& rInfo ) +{ + return rStrm + << EXC_PT_AUTOFMT_HEADER + << rInfo.mbReport + << EXC_PT_AUTOFMT_ZERO + << EXC_PT_AUTOFMT_FLAGS + << rInfo.mnAutoFormat + << rInfo.mnGridLayout + << XclExpString(rInfo.maGrandTotalName, XclStrFlags::NONE, EXC_PT_MAXSTRLEN); +} + +XclPTAddl::XclPTAddl() : + mbCompactMode(false) +{ +} + +XclImpStream& operator>>(XclImpStream& rStrm, XclPTAddl& rInfo) +{ + rStrm.Ignore(4); + sal_uInt8 sxc = rStrm.ReaduInt8(); + sal_uInt8 sxd = rStrm.ReaduInt8(); + if(sxc == 0x00 && sxd == 0x19) // SxcView / sxdVer12Info + { + sal_uInt32 nFlags = rStrm.ReaduInt32(); + rInfo.mbCompactMode = ((nFlags & 0x00000008) != 0); + } + return rStrm; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlroot.cxx b/sc/source/filter/excel/xlroot.cxx new file mode 100644 index 000000000..bac3ca1b3 --- /dev/null +++ b/sc/source/filter/excel/xlroot.cxx @@ -0,0 +1,438 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::UNO_SET_THROW; +using ::com::sun::star::awt::XDevice; +using ::com::sun::star::awt::DeviceInfo; +using ::com::sun::star::frame::XFrame; + +using namespace ::com::sun::star; + +// Global data ================================================================ + +#ifdef DBG_UTIL +XclDebugObjCounter::~XclDebugObjCounter() +{ + OSL_ENSURE( mnObjCnt == 0, "XclDebugObjCounter::~XclDebugObjCounter - wrong root object count" ); +} +#endif + +XclRootData::XclRootData( XclBiff eBiff, SfxMedium& rMedium, + tools::SvRef const & xRootStrg, ScDocument& rDoc, rtl_TextEncoding eTextEnc, bool bExport ) : + meBiff( eBiff ), + meOutput( EXC_OUTPUT_BINARY ), + mrMedium( rMedium ), + mxRootStrg( xRootStrg ), + mrDoc( rDoc ), + meTextEnc( eTextEnc ), + meSysLang( Application::GetSettings().GetLanguageTag().getLanguageType() ), + meDocLang( Application::GetSettings().GetLanguageTag().getLanguageType() ), + meUILang( Application::GetSettings().GetUILanguageTag().getLanguageType() ), + mnDefApiScript( ApiScriptType::LATIN ), + maScMaxPos( mrDoc.MaxCol(), mrDoc.MaxRow(), MAXTAB ), + maXclMaxPos( EXC_MAXCOL2, EXC_MAXROW2, EXC_MAXTAB2 ), + maMaxPos( EXC_MAXCOL2, EXC_MAXROW2, EXC_MAXTAB2 ), + mxFontPropSetHlp( std::make_shared() ), + mxChPropSetHlp( std::make_shared() ), + mxRD( std::make_shared() ), + mfScreenPixelX( 50.0 ), + mfScreenPixelY( 50.0 ), + mnCharWidth( 110 ), + mnSpaceWidth(45), + mnScTab( 0 ), + mbExport( bExport ) +{ + if (!utl::ConfigManager::IsFuzzing()) + maUserName = SvtUserOptions().GetLastName(); + if (maUserName.isEmpty()) + maUserName = "Calc"; + + switch( ScGlobal::GetDefaultScriptType() ) + { + case SvtScriptType::LATIN: mnDefApiScript = ApiScriptType::LATIN; break; + case SvtScriptType::ASIAN: mnDefApiScript = ApiScriptType::ASIAN; break; + case SvtScriptType::COMPLEX: mnDefApiScript = ApiScriptType::COMPLEX; break; + default: SAL_WARN( "sc", "XclRootData::XclRootData - unknown script type" ); + } + + // maximum cell position + switch( meBiff ) + { + case EXC_BIFF2: maXclMaxPos.Set( EXC_MAXCOL2, EXC_MAXROW2, EXC_MAXTAB2 ); break; + case EXC_BIFF3: maXclMaxPos.Set( EXC_MAXCOL3, EXC_MAXROW3, EXC_MAXTAB3 ); break; + case EXC_BIFF4: maXclMaxPos.Set( EXC_MAXCOL4, EXC_MAXROW4, EXC_MAXTAB4 ); break; + case EXC_BIFF5: maXclMaxPos.Set( EXC_MAXCOL5, EXC_MAXROW5, EXC_MAXTAB5 ); break; + case EXC_BIFF8: maXclMaxPos.Set( EXC_MAXCOL8, EXC_MAXROW8, EXC_MAXTAB8 ); break; + default: DBG_ERROR_BIFF(); + } + maMaxPos.SetCol( ::std::min( maScMaxPos.Col(), maXclMaxPos.Col() ) ); + maMaxPos.SetRow( ::std::min( maScMaxPos.Row(), maXclMaxPos.Row() ) ); + maMaxPos.SetTab( ::std::min( maScMaxPos.Tab(), maXclMaxPos.Tab() ) ); + + // document URL and path + if( const SfxItemSet* pItemSet = mrMedium.GetItemSet() ) + if( const SfxStringItem* pItem = pItemSet->GetItem( SID_FILE_NAME ) ) + maDocUrl = pItem->GetValue(); + maBasePath = maDocUrl.copy( 0, maDocUrl.lastIndexOf( '/' ) + 1 ); + + // extended document options - always own object, try to copy existing data from document + if( const ScExtDocOptions* pOldDocOpt = mrDoc.GetExtDocOptions() ) + mxExtDocOpt = std::make_shared( *pOldDocOpt ); + else + mxExtDocOpt = std::make_shared(); + + // screen pixel size + try + { + Reference< frame::XDesktop2 > xFramesSupp = frame::Desktop::create( ::comphelper::getProcessComponentContext() ); + Reference< XFrame > xFrame( xFramesSupp->getActiveFrame(), UNO_SET_THROW ); + Reference< XDevice > xDevice( xFrame->getContainerWindow(), UNO_QUERY_THROW ); + DeviceInfo aDeviceInfo = xDevice->getInfo(); + mfScreenPixelX = (aDeviceInfo.PixelPerMeterX > 0) ? (100000.0 / aDeviceInfo.PixelPerMeterX) : 50.0; + mfScreenPixelY = (aDeviceInfo.PixelPerMeterY > 0) ? (100000.0 / aDeviceInfo.PixelPerMeterY) : 50.0; + } + catch( const Exception&) + { + TOOLS_WARN_EXCEPTION( "sc", "XclRootData::XclRootData - cannot get output device info"); + } +} + +XclRootData::~XclRootData() +{ +} + +XclRoot::XclRoot( XclRootData& rRootData ) : + mrData( rRootData ) +{ +#if defined(DBG_UTIL) && OSL_DEBUG_LEVEL > 0 + ++mrData.mnObjCnt; +#endif + + // filter tracer + mrData.mxTracer = std::make_shared( GetDocUrl() ); +} + +XclRoot::XclRoot( const XclRoot& rRoot ) : + mrData( rRoot.mrData ) +{ +#if defined(DBG_UTIL) && OSL_DEBUG_LEVEL > 0 + ++mrData.mnObjCnt; +#endif +} + +XclRoot::~XclRoot() +{ +#if defined(DBG_UTIL) && OSL_DEBUG_LEVEL > 0 + --mrData.mnObjCnt; +#endif +} + +XclRoot& XclRoot::operator=( const XclRoot& rRoot ) +{ + (void)rRoot; // avoid compiler warning + // allowed for assignment in derived classes - but test if the same root data is used + OSL_ENSURE( &mrData == &rRoot.mrData, "XclRoot::operator= - incompatible root data" ); + return *this; +} + +void XclRoot::SetTextEncoding( rtl_TextEncoding eTextEnc ) +{ + if( eTextEnc != RTL_TEXTENCODING_DONTKNOW ) + mrData.meTextEnc = eTextEnc; +} + +void XclRoot::SetCharWidth( const XclFontData& rFontData ) +{ + mrData.mnCharWidth = 0; + if( OutputDevice* pPrinter = GetPrinter() ) + { + vcl::Font aFont( rFontData.maName, Size( 0, rFontData.mnHeight ) ); + aFont.SetFamily( rFontData.GetScFamily( GetTextEncoding() ) ); + aFont.SetCharSet( rFontData.GetFontEncoding() ); + aFont.SetWeight( rFontData.GetScWeight() ); + pPrinter->SetFont( aFont ); + // Usually digits have the same width, but in some fonts they don't ... + // Match the import in sc/source/filter/oox/unitconverter.cxx + // UnitConverter::finalizeImport() + for (sal_Unicode cChar = '0'; cChar <= '9'; ++cChar) + mrData.mnCharWidth = std::max( pPrinter->GetTextWidth( OUString(cChar)), mrData.mnCharWidth); + + // Set the width of space ' ' character. + mrData.mnSpaceWidth = pPrinter->GetTextWidth(OUString(' ')); + } + if( mrData.mnCharWidth <= 0 ) + { + // #i48717# Win98 with HP LaserJet returns 0 + SAL_WARN( "sc", "XclRoot::SetCharWidth - invalid character width (no printer?)" ); + mrData.mnCharWidth = 11 * rFontData.mnHeight / 20; + } + if (mrData.mnSpaceWidth <= 0) + { + SAL_WARN( "sc", "XclRoot::SetCharWidth - invalid character width (no printer?)" ); + mrData.mnSpaceWidth = 45; + } +} + +sal_Int32 XclRoot::GetHmmFromPixelX( double fPixelX ) const +{ + return static_cast< sal_Int32 >( fPixelX * mrData.mfScreenPixelX + 0.5 ); +} + +sal_Int32 XclRoot::GetHmmFromPixelY( double fPixelY ) const +{ + return static_cast< sal_Int32 >( fPixelY * mrData.mfScreenPixelY + 0.5 ); +} + +uno::Sequence< beans::NamedValue > XclRoot::RequestEncryptionData( ::comphelper::IDocPasswordVerifier& rVerifier ) const +{ + ::std::vector< OUString > aDefaultPasswords { XclRootData::gaDefPassword }; + return ScfApiHelper::QueryEncryptionDataForMedium( mrData.mrMedium, rVerifier, &aDefaultPasswords ); +} + +bool XclRoot::HasVbaStorage() const +{ + tools::SvRef xRootStrg = GetRootStorage(); + return xRootStrg.is() && xRootStrg->IsContained( EXC_STORAGE_VBA_PROJECT ); +} + +tools::SvRef XclRoot::OpenStorage( tools::SvRef const & xStrg, const OUString& rStrgName ) const +{ + return mrData.mbExport ? + ScfTools::OpenStorageWrite( xStrg, rStrgName ) : + ScfTools::OpenStorageRead( xStrg, rStrgName ); +} + +tools::SvRef XclRoot::OpenStorage( const OUString& rStrgName ) const +{ + return OpenStorage( GetRootStorage(), rStrgName ); +} + +tools::SvRef XclRoot::OpenStream( tools::SvRef const & xStrg, const OUString& rStrmName ) const +{ + return mrData.mbExport ? + ScfTools::OpenStorageStreamWrite( xStrg, rStrmName ) : + ScfTools::OpenStorageStreamRead( xStrg, rStrmName ); +} + +tools::SvRef XclRoot::OpenStream( const OUString& rStrmName ) const +{ + return OpenStream( GetRootStorage(), rStrmName ); +} + +ScDocument& XclRoot::GetDoc() const +{ + return mrData.mrDoc; +} + +SfxObjectShell* XclRoot::GetDocShell() const +{ + return GetDoc().GetDocumentShell(); +} + +ScModelObj* XclRoot::GetDocModelObj() const +{ + SfxObjectShell* pDocShell = GetDocShell(); + return pDocShell ? comphelper::getFromUnoTunnel( pDocShell->GetModel() ) : nullptr; +} + +OutputDevice* XclRoot::GetPrinter() const +{ + return GetDoc().GetRefDevice(); +} + +ScStyleSheetPool& XclRoot::GetStyleSheetPool() const +{ + return *GetDoc().GetStyleSheetPool(); +} + +ScRangeName& XclRoot::GetNamedRanges() const +{ + return *GetDoc().GetRangeName(); +} + +SdrPage* XclRoot::GetSdrPage( SCTAB nScTab ) const +{ + return ((nScTab >= 0) && GetDoc().GetDrawLayer()) ? + GetDoc().GetDrawLayer()->GetPage( static_cast< sal_uInt16 >( nScTab ) ) : nullptr; +} + +SvNumberFormatter& XclRoot::GetFormatter() const +{ + return *GetDoc().GetFormatTable(); +} + +DateTime XclRoot::GetNullDate() const +{ + return GetFormatter().GetNullDate(); +} + +sal_uInt16 XclRoot::GetBaseYear() const +{ + // return 1904 for 1904-01-01, and 1900 for 1899-12-30 + return (GetNullDate().GetYear() == 1904) ? 1904 : 1900; +} + +const DateTime theOurCompatNullDate( Date( 30, 12, 1899 )); +const DateTime theExcelCutOverDate( Date( 1, 3, 1900 )); + +double XclRoot::GetDoubleFromDateTime( const DateTime& rDateTime ) const +{ + double fValue = rDateTime - GetNullDate(); + // adjust dates before 1900-03-01 to get correct time values in the range [0.0,1.0) + /* XXX: this is only used when reading BIFF, otherwise we'd have to check + * for dateCompatibility==true as mentioned below. */ + if( rDateTime < theExcelCutOverDate && GetNullDate() == theOurCompatNullDate ) + fValue -= 1.0; + return fValue; +} + +DateTime XclRoot::GetDateTimeFromDouble( double fValue ) const +{ + DateTime aDateTime = GetNullDate() + fValue; + // adjust dates before 1900-03-01 to get correct time values + /* FIXME: correction should only be done when writing BIFF or OOXML + * transitional with dateCompatibility==true (or absent for default true), + * but not if strict ISO/IEC 29500 which does not have the Excel error + * compatibility and the null date is the same 1899-12-30 as ours. */ + if( aDateTime < theExcelCutOverDate && GetNullDate() == theOurCompatNullDate ) + aDateTime.AddDays(1); + return aDateTime; +} + +ScEditEngineDefaulter& XclRoot::GetEditEngine() const +{ + if( !mrData.mxEditEngine ) + { + mrData.mxEditEngine = std::make_shared( GetDoc().GetEnginePool() ); + ScEditEngineDefaulter& rEE = *mrData.mxEditEngine; + rEE.SetRefMapMode(MapMode(MapUnit::Map100thMM)); + rEE.SetEditTextObjectPool( GetDoc().GetEditPool() ); + rEE.SetUpdateLayout( false ); + rEE.EnableUndo( false ); + rEE.SetControlWord( rEE.GetControlWord() & ~EEControlBits::ALLOWBIGOBJS ); + } + return *mrData.mxEditEngine; +} + +ScHeaderEditEngine& XclRoot::GetHFEditEngine() const +{ + if( !mrData.mxHFEditEngine ) + { + mrData.mxHFEditEngine = std::make_shared( EditEngine::CreatePool().get() ); + ScHeaderEditEngine& rEE = *mrData.mxHFEditEngine; + rEE.SetRefMapMode(MapMode(MapUnit::MapTwip)); // headers/footers use twips as default metric + rEE.SetUpdateLayout( false ); + rEE.EnableUndo( false ); + rEE.SetControlWord( rEE.GetControlWord() & ~EEControlBits::ALLOWBIGOBJS ); + + // set Calc header/footer defaults + auto pEditSet = std::make_unique( rEE.GetEmptyItemSet() ); + SfxItemSetFixed aItemSet( *GetDoc().GetPool() ); + ScPatternAttr::FillToEditItemSet( *pEditSet, aItemSet ); + // FillToEditItemSet() adjusts font height to 1/100th mm, we need twips + pEditSet->Put( aItemSet.Get( ATTR_FONT_HEIGHT ).CloneSetWhich(EE_CHAR_FONTHEIGHT) ); + pEditSet->Put( aItemSet.Get( ATTR_CJK_FONT_HEIGHT ).CloneSetWhich(EE_CHAR_FONTHEIGHT_CJK) ); + pEditSet->Put( aItemSet.Get( ATTR_CTL_FONT_HEIGHT ).CloneSetWhich(EE_CHAR_FONTHEIGHT_CTL) ); + rEE.SetDefaults( std::move(pEditSet) ); // takes ownership + } + return *mrData.mxHFEditEngine; +} + +EditEngine& XclRoot::GetDrawEditEngine() const +{ + if( !mrData.mxDrawEditEng ) + { + mrData.mxDrawEditEng = std::make_shared( &GetDoc().GetDrawLayer()->GetItemPool() ); + EditEngine& rEE = *mrData.mxDrawEditEng; + rEE.SetRefMapMode(MapMode(MapUnit::Map100thMM)); + rEE.SetUpdateLayout( false ); + rEE.EnableUndo( false ); + rEE.SetControlWord( rEE.GetControlWord() & ~EEControlBits::ALLOWBIGOBJS ); + } + return *mrData.mxDrawEditEng; +} + +XclFontPropSetHelper& XclRoot::GetFontPropSetHelper() const +{ + return *mrData.mxFontPropSetHlp; +} + +XclChPropSetHelper& XclRoot::GetChartPropSetHelper() const +{ + return *mrData.mxChPropSetHlp; +} + +ScExtDocOptions& XclRoot::GetExtDocOptions() const +{ + return *mrData.mxExtDocOpt; +} + +XclTracer& XclRoot::GetTracer() const +{ + return *mrData.mxTracer; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlstyle.cxx b/sc/source/filter/excel/xlstyle.cxx new file mode 100644 index 000000000..ae4a6f1f6 --- /dev/null +++ b/sc/source/filter/excel/xlstyle.cxx @@ -0,0 +1,1744 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// Color data ================================================================= + +/** Standard EGA colors, bright. */ +#define EXC_PALETTE_EGA_COLORS_LIGHT \ + Color(0x000000), Color(0xFFFFFF), Color(0xFF0000), Color(0x00FF00), Color(0x0000FF), Color(0xFFFF00), Color(0xFF00FF), Color(0x00FFFF) +/** Standard EGA colors, dark. */ +#define EXC_PALETTE_EGA_COLORS_DARK \ + Color(0x800000), Color(0x008000), Color(0x000080), Color(0x808000), Color(0x800080), Color(0x008080), Color(0xC0C0C0), Color(0x808080) + +/** Default color table for BIFF2. */ +const Color spnDefColorTable2[] = +{ +/* 0 */ EXC_PALETTE_EGA_COLORS_LIGHT +}; + +/** Default color table for BIFF3/BIFF4. */ +const Color spnDefColorTable3[] = +{ +/* 0 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 8 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 16 */ EXC_PALETTE_EGA_COLORS_DARK +}; + +/** Default color table for BIFF5/BIFF7. */ +const Color spnDefColorTable5[] = +{ +/* 0 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 8 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 16 */ EXC_PALETTE_EGA_COLORS_DARK, +/* 24 */ Color(0x8080FF), Color(0x802060), Color(0xFFFFC0), Color(0xA0E0E0), Color(0x600080), Color(0xFF8080), Color(0x0080C0), Color(0xC0C0FF), +/* 32 */ Color(0x000080), Color(0xFF00FF), Color(0xFFFF00), Color(0x00FFFF), Color(0x800080), Color(0x800000), Color(0x008080), Color(0x0000FF), +/* 40 */ Color(0x00CFFF), Color(0x69FFFF), Color(0xE0FFE0), Color(0xFFFF80), Color(0xA6CAF0), Color(0xDD9CB3), Color(0xB38FEE), Color(0xE3E3E3), +/* 48 */ Color(0x2A6FF9), Color(0x3FB8CD), Color(0x488436), Color(0x958C41), Color(0x8E5E42), Color(0xA0627A), Color(0x624FAC), Color(0x969696), +/* 56 */ Color(0x1D2FBE), Color(0x286676), Color(0x004500), Color(0x453E01), Color(0x6A2813), Color(0x85396A), Color(0x4A3285), Color(0x424242) +}; + +/** Default color table for BIFF8. */ +const Color spnDefColorTable8[] = +{ +/* 0 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 8 */ EXC_PALETTE_EGA_COLORS_LIGHT, +/* 16 */ EXC_PALETTE_EGA_COLORS_DARK, +/* 24 */ Color(0x9999FF), Color(0x993366), Color(0xFFFFCC), Color(0xCCFFFF), Color(0x660066), Color(0xFF8080), Color(0x0066CC), Color(0xCCCCFF), +/* 32 */ Color(0x000080), Color(0xFF00FF), Color(0xFFFF00), Color(0x00FFFF), Color(0x800080), Color(0x800000), Color(0x008080), Color(0x0000FF), +/* 40 */ Color(0x00CCFF), Color(0xCCFFFF), Color(0xCCFFCC), Color(0xFFFF99), Color(0x99CCFF), Color(0xFF99CC), Color(0xCC99FF), Color(0xFFCC99), +/* 48 */ Color(0x3366FF), Color(0x33CCCC), Color(0x99CC00), Color(0xFFCC00), Color(0xFF9900), Color(0xFF6600), Color(0x666699), Color(0x969696), +/* 56 */ Color(0x003366), Color(0x339966), Color(0x003300), Color(0x333300), Color(0x993300), Color(0x993366), Color(0x333399), Color(0x333333) +}; + +#undef EXC_PALETTE_EGA_COLORS_LIGHT +#undef EXC_PALETTE_EGA_COLORS_DARK + +XclDefaultPalette::XclDefaultPalette( const XclRoot& rRoot ) : + mpnColorTable( nullptr ), + mnTableSize( 0 ) +{ + const StyleSettings& rSett = Application::GetSettings().GetStyleSettings(); + mnFaceColor = rSett.GetFaceColor(); + // Don't use the system HelpBack and HelpText colours as it causes problems + // with modern gnome. This is because mnNoteText and mnNoteBack are used + // when colour indices ( instead of real colours ) are specified. + // Note: That this it is not an unusual scenario that we get the Note + // background specified as a real colour and the text specified as a + // colour index. That means the text colour would be picked from + // the system where the note background would be picked from a real colour. + // Previously the note text colour was picked from the system tooltip + // text colour, on modern gnome(e.g. 3) that tends to be 'white' with the + // default theme. + // Using the Libreoffice defaults ( instead of system specific colours + // ) lessens the chance of the one colour being an unsuitable combination + // because by default the note text is black and the note background is + // a light yellow colour ( very similar to Excel's normal defaults ) + mnNoteText = svtools::ColorConfig::GetDefaultColor( svtools::FONTCOLOR ); + mnNoteBack = svtools::ColorConfig::GetDefaultColor( svtools::CALCNOTESBACKGROUND ); + + // default colors + switch( rRoot.GetBiff() ) + { + case EXC_BIFF2: + mpnColorTable = spnDefColorTable2; + mnTableSize = SAL_N_ELEMENTS( spnDefColorTable2 ); + break; + case EXC_BIFF3: + case EXC_BIFF4: + mpnColorTable = spnDefColorTable3; + mnTableSize = SAL_N_ELEMENTS( spnDefColorTable3 ); + break; + case EXC_BIFF5: + mpnColorTable = spnDefColorTable5; + mnTableSize = SAL_N_ELEMENTS( spnDefColorTable5 ); + break; + case EXC_BIFF8: + mpnColorTable = spnDefColorTable8; + mnTableSize = SAL_N_ELEMENTS( spnDefColorTable8 ); + break; + default: + DBG_ERROR_BIFF(); + } +} + +Color XclDefaultPalette::GetDefColor( sal_uInt16 nXclIndex ) const +{ + Color nColor; + if( nXclIndex < mnTableSize ) + nColor = mpnColorTable[ nXclIndex ]; + else switch( nXclIndex ) + { + case EXC_COLOR_WINDOWTEXT3: + case EXC_COLOR_WINDOWTEXT: + case EXC_COLOR_CHWINDOWTEXT: nColor = COL_BLACK; break; + case EXC_COLOR_WINDOWBACK3: + case EXC_COLOR_WINDOWBACK: + case EXC_COLOR_CHWINDOWBACK: nColor = COL_WHITE; break; + case EXC_COLOR_BUTTONBACK: nColor = mnFaceColor; break; + case EXC_COLOR_CHBORDERAUTO: nColor = COL_BLACK; break; // TODO: really always black? + case EXC_COLOR_NOTEBACK: nColor = mnNoteBack; break; + case EXC_COLOR_NOTETEXT: nColor = mnNoteText; break; + case EXC_COLOR_FONTAUTO: nColor = COL_AUTO; break; + default: + SAL_WARN("sc", "XclDefaultPalette::GetDefColor - unknown default color index: " << nXclIndex ); + nColor = COL_AUTO; + } + return nColor; +} + +// Font Data ================================================================== + +namespace Awt = ::com::sun::star::awt; +namespace AwtFontFamily = Awt::FontFamily; +namespace AwtFontLineStyle = Awt::FontUnderline; +namespace AwtFontStrikeout = Awt::FontStrikeout; + +XclFontData::XclFontData() +{ + Clear(); +} + +XclFontData::XclFontData( const vcl::Font& rFont ) +{ + Clear(); + FillFromVclFont( rFont ); +} + +XclFontData::XclFontData( const SvxFont& rFont ) +{ + FillFromSvxFont( rFont ); +} + +void XclFontData::Clear() +{ + maName.clear(); + maStyle.clear(); + maColor = COL_AUTO; + mnHeight = 0; + mnWeight = EXC_FONTWGHT_DONTKNOW; + mnEscapem = EXC_FONTESC_NONE; + mnFamily = EXC_FONTFAM_SYSTEM; + mnCharSet = EXC_FONTCSET_ANSI_LATIN; + mnUnderline = EXC_FONTUNDERL_NONE; + mbItalic = mbStrikeout = mbOutline = mbShadow = false; +} + +void XclFontData::FillFromVclFont( const vcl::Font& rFont ) +{ + maName = XclTools::GetXclFontName( rFont.GetFamilyName() ); // substitute with MS fonts + maStyle.clear(); + maColor = rFont.GetColor(); + SetScUnderline( rFont.GetUnderline() ); + mnEscapem = EXC_FONTESC_NONE; + SetScHeight( rFont.GetFontSize().Height() ); + SetScWeight( rFont.GetWeight() ); + SetScFamily( rFont.GetFamilyType() ); + SetFontEncoding( rFont.GetCharSet() ); + SetScPosture( rFont.GetItalic() ); + SetScStrikeout( rFont.GetStrikeout() ); + mbOutline = rFont.IsOutline(); + mbShadow = rFont.IsShadow(); +} + +void XclFontData::FillFromSvxFont( const SvxFont& rFont ) +{ + FillFromVclFont( rFont ); + SetScEscapement( rFont.GetEscapement() ); +} + +// *** conversion of VCL/SVX constants *** ------------------------------------ + +FontFamily XclFontData::GetScFamily( rtl_TextEncoding eDefTextEnc ) const +{ + FontFamily eScFamily; + // ! format differs from Windows documentation: family is in lower nibble, pitch unknown + switch( mnFamily & 0x0F ) + { + case EXC_FONTFAM_ROMAN: eScFamily = FAMILY_ROMAN; break; + case EXC_FONTFAM_SWISS: eScFamily = FAMILY_SWISS; break; + case EXC_FONTFAM_MODERN: eScFamily = FAMILY_MODERN; break; + case EXC_FONTFAM_SCRIPT: eScFamily = FAMILY_SCRIPT; break; + case EXC_FONTFAM_DECORATIVE: eScFamily = FAMILY_DECORATIVE; break; + default: + eScFamily = + ((eDefTextEnc == RTL_TEXTENCODING_APPLE_ROMAN) && + (maName.equalsIgnoreAsciiCase( "Geneva" ) || maName.equalsIgnoreAsciiCase( "Chicago" ))) ? + FAMILY_SWISS : FAMILY_DONTKNOW; + } + return eScFamily; +} + +rtl_TextEncoding XclFontData::GetFontEncoding() const +{ + // convert Windows character set to text encoding identifier + return rtl_getTextEncodingFromWindowsCharset( mnCharSet ); +} + +FontItalic XclFontData::GetScPosture() const +{ + return mbItalic ? ITALIC_NORMAL : ITALIC_NONE; +} + +FontWeight XclFontData::GetScWeight() const +{ + FontWeight eScWeight; + + if( !mnWeight ) eScWeight = WEIGHT_DONTKNOW; + else if( mnWeight < 150 ) eScWeight = WEIGHT_THIN; + else if( mnWeight < 250 ) eScWeight = WEIGHT_ULTRALIGHT; + else if( mnWeight < 325 ) eScWeight = WEIGHT_LIGHT; + else if( mnWeight < 375 ) eScWeight = WEIGHT_SEMILIGHT; + else if( mnWeight < 450 ) eScWeight = WEIGHT_NORMAL; + else if( mnWeight < 550 ) eScWeight = WEIGHT_MEDIUM; + else if( mnWeight < 650 ) eScWeight = WEIGHT_SEMIBOLD; + else if( mnWeight < 750 ) eScWeight = WEIGHT_BOLD; + else if( mnWeight < 850 ) eScWeight = WEIGHT_ULTRABOLD; + else eScWeight = WEIGHT_BLACK; + + return eScWeight; +} + +FontLineStyle XclFontData::GetScUnderline() const +{ + FontLineStyle eScUnderl = LINESTYLE_NONE; + switch( mnUnderline ) + { + case EXC_FONTUNDERL_SINGLE: + case EXC_FONTUNDERL_SINGLE_ACC: eScUnderl = LINESTYLE_SINGLE; break; + case EXC_FONTUNDERL_DOUBLE: + case EXC_FONTUNDERL_DOUBLE_ACC: eScUnderl = LINESTYLE_DOUBLE; break; + } + return eScUnderl; +} + +SvxEscapement XclFontData::GetScEscapement() const +{ + SvxEscapement eScEscapem = SvxEscapement::Off; + switch( mnEscapem ) + { + case EXC_FONTESC_SUPER: eScEscapem = SvxEscapement::Superscript; break; + case EXC_FONTESC_SUB: eScEscapem = SvxEscapement::Subscript; break; + } + return eScEscapem; +} + +FontStrikeout XclFontData::GetScStrikeout() const +{ + return mbStrikeout ? STRIKEOUT_SINGLE : STRIKEOUT_NONE; +} + +void XclFontData::SetScHeight( sal_Int32 nTwips ) +{ + mnHeight = static_cast< sal_uInt16 >( ::std::min( nTwips, static_cast(0x7FFFL) ) ); +} + +void XclFontData::SetScFamily( FontFamily eScFamily ) +{ + switch( eScFamily ) + { + case FAMILY_DONTKNOW: mnFamily = EXC_FONTFAM_DONTKNOW; break; + case FAMILY_DECORATIVE: mnFamily = EXC_FONTFAM_DECORATIVE; break; + case FAMILY_MODERN: mnFamily = EXC_FONTFAM_MODERN; break; + case FAMILY_ROMAN: mnFamily = EXC_FONTFAM_ROMAN; break; + case FAMILY_SCRIPT: mnFamily = EXC_FONTFAM_SCRIPT; break; + case FAMILY_SWISS: mnFamily = EXC_FONTFAM_SWISS; break; + case FAMILY_SYSTEM: mnFamily = EXC_FONTFAM_SYSTEM; break; + default: + OSL_FAIL( "XclFontData::SetScFamily - unknown font family" ); + mnFamily = EXC_FONTFAM_DONTKNOW; + } +} + +void XclFontData::SetFontEncoding( rtl_TextEncoding eFontEnc ) +{ + // convert text encoding identifier to Windows character set + mnCharSet = rtl_getBestWindowsCharsetFromTextEncoding( eFontEnc ); +} + +void XclFontData::SetScPosture( FontItalic eScPosture ) +{ + mbItalic = (eScPosture == ITALIC_OBLIQUE) || (eScPosture == ITALIC_NORMAL); +} + +void XclFontData::SetScWeight( FontWeight eScWeight ) +{ + switch( eScWeight ) + { + case WEIGHT_DONTKNOW: mnWeight = EXC_FONTWGHT_DONTKNOW; break; + case WEIGHT_THIN: mnWeight = EXC_FONTWGHT_THIN; break; + case WEIGHT_ULTRALIGHT: mnWeight = EXC_FONTWGHT_ULTRALIGHT; break; + case WEIGHT_LIGHT: mnWeight = EXC_FONTWGHT_LIGHT; break; + case WEIGHT_SEMILIGHT: mnWeight = EXC_FONTWGHT_SEMILIGHT; break; + case WEIGHT_NORMAL: mnWeight = EXC_FONTWGHT_NORMAL; break; + case WEIGHT_MEDIUM: mnWeight = EXC_FONTWGHT_MEDIUM; break; + case WEIGHT_SEMIBOLD: mnWeight = EXC_FONTWGHT_SEMIBOLD; break; + case WEIGHT_BOLD: mnWeight = EXC_FONTWGHT_BOLD; break; + case WEIGHT_ULTRABOLD: mnWeight = EXC_FONTWGHT_ULTRABOLD; break; + case WEIGHT_BLACK: mnWeight = EXC_FONTWGHT_BLACK; break; + default: mnWeight = EXC_FONTWGHT_NORMAL; + } +} + +void XclFontData::SetScUnderline( FontLineStyle eScUnderl ) +{ + switch( eScUnderl ) + { + case LINESTYLE_NONE: + case LINESTYLE_DONTKNOW: mnUnderline = EXC_FONTUNDERL_NONE; break; + case LINESTYLE_DOUBLE: + case LINESTYLE_DOUBLEWAVE: mnUnderline = EXC_FONTUNDERL_DOUBLE; break; + default: mnUnderline = EXC_FONTUNDERL_SINGLE; + } +} + +void XclFontData::SetScEscapement( short nScEscapem ) +{ + if( nScEscapem > 0 ) + mnEscapem = EXC_FONTESC_SUPER; + else if( nScEscapem < 0 ) + mnEscapem = EXC_FONTESC_SUB; + else + mnEscapem = EXC_FONTESC_NONE; +} + +void XclFontData::SetScStrikeout( FontStrikeout eScStrikeout ) +{ + mbStrikeout = + (eScStrikeout == STRIKEOUT_SINGLE) || (eScStrikeout == STRIKEOUT_DOUBLE) || + (eScStrikeout == STRIKEOUT_BOLD) || (eScStrikeout == STRIKEOUT_SLASH) || + (eScStrikeout == STRIKEOUT_X); +} + +// *** conversion of API constants *** ---------------------------------------- + +float XclFontData::GetApiHeight() const +{ + return o3tl::convert(mnHeight, o3tl::Length::twip, o3tl::Length::pt); +} + +sal_Int16 XclFontData::GetApiFamily() const +{ + sal_Int16 nApiFamily = AwtFontFamily::DONTKNOW; + switch( mnFamily ) + { + case FAMILY_DECORATIVE: nApiFamily = AwtFontFamily::DECORATIVE; break; + case FAMILY_MODERN: nApiFamily = AwtFontFamily::MODERN; break; + case FAMILY_ROMAN: nApiFamily = AwtFontFamily::ROMAN; break; + case FAMILY_SCRIPT: nApiFamily = AwtFontFamily::SCRIPT; break; + case FAMILY_SWISS: nApiFamily = AwtFontFamily::SWISS; break; + case FAMILY_SYSTEM: nApiFamily = AwtFontFamily::SYSTEM; break; + } + return nApiFamily; +} + +sal_Int16 XclFontData::GetApiFontEncoding() const +{ + // API constants are equal to rtl_TextEncoding constants + return static_cast< sal_Int16 >( GetFontEncoding() ); +} + +Awt::FontSlant XclFontData::GetApiPosture() const +{ + return mbItalic ? Awt::FontSlant_ITALIC : Awt::FontSlant_NONE; +} + +float XclFontData::GetApiWeight() const +{ + return vcl::unohelper::ConvertFontWeight( GetScWeight() ); +} + +sal_Int16 XclFontData::GetApiUnderline() const +{ + sal_Int16 nApiUnderl = AwtFontLineStyle::NONE; + switch( mnUnderline ) + { + case EXC_FONTUNDERL_SINGLE: + case EXC_FONTUNDERL_SINGLE_ACC: nApiUnderl = AwtFontLineStyle::SINGLE; break; + case EXC_FONTUNDERL_DOUBLE: + case EXC_FONTUNDERL_DOUBLE_ACC: nApiUnderl = AwtFontLineStyle::DOUBLE; break; + } + return nApiUnderl; +} + +sal_Int16 XclFontData::GetApiEscapement() const +{ + sal_Int16 nApiEscapem = 0; + switch( mnEscapem ) + { + case EXC_FONTESC_SUPER: nApiEscapem = 33; break; + case EXC_FONTESC_SUB: nApiEscapem = -33; break; + } + return nApiEscapem; +} + +sal_Int16 XclFontData::GetApiStrikeout() const +{ + return mbStrikeout ? AwtFontStrikeout::SINGLE : AwtFontStrikeout::NONE; +} + +void XclFontData::SetApiHeight( float fPoint ) +{ + mnHeight = std::min(o3tl::convert(fPoint, o3tl::Length::pt, o3tl::Length::twip) + 0.5, 32767.0); +} + +void XclFontData::SetApiFamily( sal_Int16 nApiFamily ) +{ + switch( nApiFamily ) + { + case AwtFontFamily::DECORATIVE: mnFamily = FAMILY_DECORATIVE; break; + case AwtFontFamily::MODERN: mnFamily = FAMILY_MODERN; break; + case AwtFontFamily::ROMAN: mnFamily = FAMILY_ROMAN; break; + case AwtFontFamily::SCRIPT: mnFamily = FAMILY_SCRIPT; break; + case AwtFontFamily::SWISS: mnFamily = FAMILY_SWISS; break; + case AwtFontFamily::SYSTEM: mnFamily = FAMILY_SYSTEM; break; + default: mnFamily = FAMILY_DONTKNOW; + } +} + +void XclFontData::SetApiPosture( Awt::FontSlant eApiPosture ) +{ + mbItalic = + (eApiPosture == Awt::FontSlant_OBLIQUE) || + (eApiPosture == Awt::FontSlant_ITALIC) || + (eApiPosture == Awt::FontSlant_REVERSE_OBLIQUE) || + (eApiPosture == Awt::FontSlant_REVERSE_ITALIC); +} + +void XclFontData::SetApiWeight( float fApiWeight ) +{ + SetScWeight( vcl::unohelper::ConvertFontWeight( fApiWeight ) ); +} + +void XclFontData::SetApiUnderline( sal_Int16 nApiUnderl ) +{ + switch( nApiUnderl ) + { + case AwtFontLineStyle::NONE: + case AwtFontLineStyle::DONTKNOW: mnUnderline = EXC_FONTUNDERL_NONE; break; + case AwtFontLineStyle::DOUBLE: + case AwtFontLineStyle::DOUBLEWAVE: mnUnderline = EXC_FONTUNDERL_DOUBLE; break; + default: mnUnderline = EXC_FONTUNDERL_SINGLE; + } +} + +void XclFontData::SetApiEscapement( sal_Int16 nApiEscapem ) +{ + if( nApiEscapem > 0 ) + mnEscapem = EXC_FONTESC_SUPER; + else if( nApiEscapem < 0 ) + mnEscapem = EXC_FONTESC_SUB; + else + mnEscapem = EXC_FONTESC_NONE; +} + +void XclFontData::SetApiStrikeout( sal_Int16 nApiStrikeout ) +{ + mbStrikeout = + (nApiStrikeout != AwtFontStrikeout::NONE) && + (nApiStrikeout != AwtFontStrikeout::DONTKNOW); +} + +bool operator==( const XclFontData& rLeft, const XclFontData& rRight ) +{ + return + (rLeft.mnHeight == rRight.mnHeight) && + (rLeft.mnWeight == rRight.mnWeight) && + (rLeft.mnUnderline == rRight.mnUnderline) && + (rLeft.maColor == rRight.maColor) && + (rLeft.mnEscapem == rRight.mnEscapem) && + (rLeft.mnFamily == rRight.mnFamily) && + (rLeft.mnCharSet == rRight.mnCharSet) && + (rLeft.mbItalic == rRight.mbItalic) && + (rLeft.mbStrikeout == rRight.mbStrikeout) && + (rLeft.mbOutline == rRight.mbOutline) && + (rLeft.mbShadow == rRight.mbShadow) && + (rLeft.maName == rRight.maName); +} + +namespace { + +/** Property names for common font settings. */ +const char *const sppcPropNamesChCommon[] = +{ + "CharUnderline", "CharStrikeout", "CharColor", "CharContoured", "CharShadowed", nullptr +}; +/** Property names for Western font settings. */ +const char *const sppcPropNamesChWstrn[] = +{ + "CharFontName", "CharHeight", "CharPosture", "CharWeight", nullptr +}; +/** Property names for Asian font settings. */ +const char *const sppcPropNamesChAsian[] = +{ + "CharFontNameAsian", "CharHeightAsian", "CharPostureAsian", "CharWeightAsian", nullptr +}; +/** Property names for Complex font settings. */ +const char *const sppcPropNamesChCmplx[] = +{ + "CharFontNameComplex", "CharHeightComplex", "CharPostureComplex", "CharWeightComplex", nullptr +}; +/** Property names for escapement. */ +const char *const sppcPropNamesChEscapement[] = +{ + "CharEscapement", "CharEscapementHeight", nullptr +}; +const sal_Int8 EXC_API_ESC_HEIGHT = 58; /// Default escapement font height. + +/** Property names for Western font settings without font name. */ +const char *const *const sppcPropNamesChWstrnNoName = sppcPropNamesChWstrn + 1; +/** Property names for Asian font settings without font name. */ +const char *const *const sppcPropNamesChAsianNoName = sppcPropNamesChAsian + 1; +/** Property names for Complex font settings without font name. */ +const char *const *const sppcPropNamesChCmplxNoName = sppcPropNamesChCmplx + 1; + +/** Property names for font settings in form controls. */ +const char *const sppcPropNamesControl[] = +{ + "FontName", "FontFamily", "FontCharset", "FontHeight", "FontSlant", + "FontWeight", "FontLineStyle", "FontStrikeout", "TextColor", nullptr +}; + +/** Inserts all passed API font settings into the font data object. */ +void lclSetApiFontSettings( XclFontData& rFontData, + const OUString& rApiFontName, float fApiHeight, float fApiWeight, + Awt::FontSlant eApiPosture, sal_Int16 nApiUnderl, sal_Int16 nApiStrikeout ) +{ + rFontData.maName = XclTools::GetXclFontName( rApiFontName ); + rFontData.SetApiHeight( fApiHeight ); + rFontData.SetApiWeight( fApiWeight ); + rFontData.SetApiPosture( eApiPosture ); + rFontData.SetApiUnderline( nApiUnderl ); + rFontData.SetApiStrikeout( nApiStrikeout ); +} + +/** Writes script dependent properties to a font property set helper. */ +void lclWriteChartFont( ScfPropertySet& rPropSet, + ScfPropSetHelper& rHlpName, ScfPropSetHelper& rHlpNoName, + const XclFontData& rFontData, bool bHasFontName ) +{ + // select the font helper + ScfPropSetHelper& rPropSetHlp = bHasFontName ? rHlpName : rHlpNoName; + // initialize the font helper (must be called before writing any properties) + rPropSetHlp.InitializeWrite(); + // write font name + if( bHasFontName ) + rPropSetHlp << rFontData.maName; + // write remaining properties + rPropSetHlp << rFontData.GetApiHeight() << rFontData.GetApiPosture() << rFontData.GetApiWeight(); + // write properties to property set + rPropSetHlp.WriteToPropertySet( rPropSet ); +} + +} // namespace + +XclFontPropSetHelper::XclFontPropSetHelper() : + maHlpChCommon( sppcPropNamesChCommon ), + maHlpChWstrn( sppcPropNamesChWstrn ), + maHlpChAsian( sppcPropNamesChAsian ), + maHlpChCmplx( sppcPropNamesChCmplx ), + maHlpChWstrnNoName( sppcPropNamesChWstrnNoName ), + maHlpChAsianNoName( sppcPropNamesChAsianNoName ), + maHlpChCmplxNoName( sppcPropNamesChCmplxNoName ), + maHlpChEscapement( sppcPropNamesChEscapement ), + maHlpControl( sppcPropNamesControl ) +{ +} + +void XclFontPropSetHelper::ReadFontProperties( XclFontData& rFontData, + const ScfPropertySet& rPropSet, XclFontPropSetType eType, sal_Int16 nScript ) +{ + switch( eType ) + { + case EXC_FONTPROPSET_CHART: + { + OUString aApiFontName; + float fApiHeight, fApiWeight; + sal_Int16 nApiUnderl = 0, nApiStrikeout = 0; + Awt::FontSlant eApiPosture; + + // read script type dependent properties + ScfPropSetHelper& rPropSetHlp = GetChartHelper( nScript ); + rPropSetHlp.ReadFromPropertySet( rPropSet ); + rPropSetHlp >> aApiFontName >> fApiHeight >> eApiPosture >> fApiWeight; + // read common properties + maHlpChCommon.ReadFromPropertySet( rPropSet ); + maHlpChCommon >> nApiUnderl + >> nApiStrikeout + >> rFontData.maColor + >> rFontData.mbOutline + >> rFontData.mbShadow; + + // convert API property values to Excel settings + lclSetApiFontSettings( rFontData, aApiFontName, + fApiHeight, fApiWeight, eApiPosture, nApiUnderl, nApiStrikeout ); + + // font escapement + sal_Int16 nApiEscapement = 0; + sal_Int8 nApiEscHeight = 0; + maHlpChEscapement.ReadFromPropertySet( rPropSet ); + maHlpChEscapement.ReadFromPropertySet( rPropSet ); + maHlpChEscapement.ReadFromPropertySet( rPropSet ); + maHlpChEscapement >> nApiEscapement >> nApiEscHeight; + rFontData.SetApiEscapement( nApiEscapement ); + } + break; + + case EXC_FONTPROPSET_CONTROL: + { + OUString aApiFontName; + float fApiHeight(0.0), fApiWeight(0.0); + sal_Int16 nApiFamily(0), nApiCharSet(0), nApiPosture(0), nApiUnderl(0), nApiStrikeout(0); + + // read font properties + maHlpControl.ReadFromPropertySet( rPropSet ); + maHlpControl >> aApiFontName + >> nApiFamily + >> nApiCharSet + >> fApiHeight + >> nApiPosture + >> fApiWeight + >> nApiUnderl + >> nApiStrikeout + >> rFontData.maColor; + + // convert API property values to Excel settings + Awt::FontSlant eApiPosture = static_cast< Awt::FontSlant >( nApiPosture ); + lclSetApiFontSettings( rFontData, aApiFontName, + fApiHeight, fApiWeight, eApiPosture, nApiUnderl, nApiStrikeout ); + rFontData.SetApiFamily( nApiFamily ); + rFontData.SetFontEncoding( nApiCharSet ); + } + break; + } +} + +void XclFontPropSetHelper::WriteFontProperties( + ScfPropertySet& rPropSet, XclFontPropSetType eType, + const XclFontData& rFontData, bool bHasWstrn, bool bHasAsian, bool bHasCmplx, + const Color* pFontColor ) +{ + switch( eType ) + { + case EXC_FONTPROPSET_CHART: + { + // write common properties + maHlpChCommon.InitializeWrite(); + const Color& rColor = pFontColor ? *pFontColor : rFontData.maColor; + maHlpChCommon << rFontData.GetApiUnderline() + << rFontData.GetApiStrikeout() + << rColor + << rFontData.mbOutline + << rFontData.mbShadow; + maHlpChCommon.WriteToPropertySet( rPropSet ); + + // write script type dependent properties + lclWriteChartFont( rPropSet, maHlpChWstrn, maHlpChWstrnNoName, rFontData, bHasWstrn ); + lclWriteChartFont( rPropSet, maHlpChAsian, maHlpChAsianNoName, rFontData, bHasAsian ); + lclWriteChartFont( rPropSet, maHlpChCmplx, maHlpChCmplxNoName, rFontData, bHasCmplx ); + + // font escapement + if( rFontData.GetScEscapement() != SvxEscapement::Off ) + { + maHlpChEscapement.InitializeWrite(); + maHlpChEscapement << rFontData.GetApiEscapement() << EXC_API_ESC_HEIGHT; + maHlpChEscapement.WriteToPropertySet( rPropSet ); + } + } + break; + + case EXC_FONTPROPSET_CONTROL: + { + maHlpControl.InitializeWrite(); + maHlpControl << rFontData.maName + << rFontData.GetApiFamily() + << rFontData.GetApiFontEncoding() + << static_cast< sal_Int16 >( rFontData.GetApiHeight() + 0.5 ) + << rFontData.GetApiPosture() + << rFontData.GetApiWeight() + << rFontData.GetApiUnderline() + << rFontData.GetApiStrikeout() + << rFontData.maColor; + maHlpControl.WriteToPropertySet( rPropSet ); + } + break; + } +} + +ScfPropSetHelper& XclFontPropSetHelper::GetChartHelper( sal_Int16 nScript ) +{ + namespace ApiScriptType = ::com::sun::star::i18n::ScriptType; + switch( nScript ) + { + case ApiScriptType::LATIN: return maHlpChWstrn; + case ApiScriptType::ASIAN: return maHlpChAsian; + case ApiScriptType::COMPLEX: return maHlpChCmplx; + default: OSL_FAIL( "XclFontPropSetHelper::GetChartHelper - unknown script type" ); + } + return maHlpChWstrn; +} + +// Number formats ============================================================= + +namespace { + +/** Special number format index describing a reused format. */ +const NfIndexTableOffset PRV_NF_INDEX_REUSE = NF_INDEX_TABLE_ENTRIES; + +/** German primary language not defined, LANGUAGE_GERMAN belongs to Germany. */ +constexpr LanguageType PRV_LANGUAGE_GERMAN_PRIM = primary(LANGUAGE_GERMAN); +/** French primary language not defined, LANGUAGE_FRENCH belongs to France. */ +constexpr LanguageType PRV_LANGUAGE_FRENCH_PRIM = primary(LANGUAGE_FRENCH); +/** Parent language identifier for Asian languages. */ +constexpr LanguageType PRV_LANGUAGE_ASIAN_PRIM = primary(LANGUAGE_CHINESE); + +/** Stores the number format used in Calc for an Excel built-in number format. */ +struct XclBuiltInFormat +{ + sal_uInt16 mnXclNumFmt; /// Excel built-in index. + const char* mpFormat; /// Format string, may be 0 (meOffset used then). + NfIndexTableOffset meOffset; /// SvNumberFormatter format index, if mpFormat==0. + sal_uInt16 mnXclReuseFmt; /// Use this Excel format, if meOffset==PRV_NF_INDEX_REUSE. +}; + +/** Defines a literal Excel built-in number format. */ +#define EXC_NUMFMT_STRING( nXclNumFmt, pcUtf8 ) \ + { nXclNumFmt, pcUtf8, NF_NUMBER_STANDARD, 0 } + +/** Defines an Excel built-in number format that maps to an own built-in format. */ +#define EXC_NUMFMT_OFFSET( nXclNumFmt, eOffset ) \ + { nXclNumFmt, nullptr, eOffset, 0 } + +/** Defines an Excel built-in number format that is the same as the specified. */ +#define EXC_NUMFMT_REUSE( nXclNumFmt, nXclReuse ) \ + { nXclNumFmt, nullptr, PRV_NF_INDEX_REUSE, nXclReuse } + +/** Terminates an Excel built-in number format table. */ +#define EXC_NUMFMT_ENDTABLE() \ + { EXC_FORMAT_NOTFOUND, nullptr, NF_NUMBER_STANDARD, 0 } + +// Currency unit characters +#define UTF8_BAHT "\340\270\277" +#define UTF8_EURO "\342\202\254" +#define UTF8_POUND_UK "\302\243" +#define UTF8_SHEQEL "\342\202\252" +#define UTF8_WON "\357\277\246" +#define UTF8_YEN_CS "\357\277\245" +#define UTF8_YEN_JP "\302\245" + +// Japanese/Chinese date/time characters +#define UTF8_CJ_YEAR "\345\271\264" +#define UTF8_CJ_MON "\346\234\210" +#define UTF8_CJ_DAY "\346\227\245" +#define UTF8_CJ_HOUR "\346\231\202" +#define UTF8_CJ_MIN "\345\210\206" +#define UTF8_CJ_SEC "\347\247\222" + +// Chinese Simplified date/time characters +#define UTF8_CS_HOUR "\346\227\266" + +// Korean date/time characters +#define UTF8_KO_YEAR "\353\205\204" +#define UTF8_KO_MON "\354\233\224" +#define UTF8_KO_DAY "\354\235\274" +#define UTF8_KO_HOUR "\354\213\234" +#define UTF8_KO_MIN "\353\266\204" +#define UTF8_KO_SEC "\354\264\210" + +/** Default number format table. Last parent of all other tables, used for unknown languages. */ +const XclBuiltInFormat spBuiltInFormats_DONTKNOW[] = +{ + EXC_NUMFMT_OFFSET( 0, NF_NUMBER_STANDARD ), // General + EXC_NUMFMT_OFFSET( 1, NF_NUMBER_INT ), // 0 + EXC_NUMFMT_OFFSET( 2, NF_NUMBER_DEC2 ), // 0.00 + EXC_NUMFMT_OFFSET( 3, NF_NUMBER_1000INT ), // #,##0 + EXC_NUMFMT_OFFSET( 4, NF_NUMBER_1000DEC2 ), // #,##0.00 + // 5...8 contained in file + EXC_NUMFMT_OFFSET( 9, NF_PERCENT_INT ), // 0% + EXC_NUMFMT_OFFSET( 10, NF_PERCENT_DEC2 ), // 0.00% + EXC_NUMFMT_OFFSET( 11, NF_SCIENTIFIC_000E00 ), // 0.00E+00 + EXC_NUMFMT_OFFSET( 12, NF_FRACTION_1D ), // # ?/? + EXC_NUMFMT_OFFSET( 13, NF_FRACTION_2D ), // # ??/?? + + // 14...22 date and time formats + EXC_NUMFMT_OFFSET( 14, NF_DATE_SYS_DDMMYYYY ), + EXC_NUMFMT_OFFSET( 15, NF_DATE_SYS_DMMMYY ), + EXC_NUMFMT_OFFSET( 16, NF_DATE_SYS_DDMMM ), + EXC_NUMFMT_OFFSET( 17, NF_DATE_SYS_MMYY ), + EXC_NUMFMT_OFFSET( 18, NF_TIME_HHMMAMPM ), + EXC_NUMFMT_OFFSET( 19, NF_TIME_HHMMSSAMPM ), + EXC_NUMFMT_OFFSET( 20, NF_TIME_HHMM ), + EXC_NUMFMT_OFFSET( 21, NF_TIME_HHMMSS ), + EXC_NUMFMT_OFFSET( 22, NF_DATETIME_SYSTEM_SHORT_HHMM ), + + // 23...36 international formats + EXC_NUMFMT_REUSE( 23, 0 ), + EXC_NUMFMT_REUSE( 24, 0 ), + EXC_NUMFMT_REUSE( 25, 0 ), + EXC_NUMFMT_REUSE( 26, 0 ), + EXC_NUMFMT_REUSE( 27, 14 ), + EXC_NUMFMT_REUSE( 28, 14 ), + EXC_NUMFMT_REUSE( 29, 14 ), + EXC_NUMFMT_REUSE( 30, 14 ), + EXC_NUMFMT_REUSE( 31, 14 ), + EXC_NUMFMT_REUSE( 32, 21 ), + EXC_NUMFMT_REUSE( 33, 21 ), + EXC_NUMFMT_REUSE( 34, 21 ), + EXC_NUMFMT_REUSE( 35, 21 ), + EXC_NUMFMT_REUSE( 36, 14 ), + + // 37...44 accounting formats + // 41...44 contained in file + EXC_NUMFMT_STRING( 37, "#,##0;-#,##0" ), + EXC_NUMFMT_STRING( 38, "#,##0;[RED]-#,##0" ), + EXC_NUMFMT_STRING( 39, "#,##0.00;-#,##0.00" ), + EXC_NUMFMT_STRING( 40, "#,##0.00;[RED]-#,##0.00" ), + + // 45...49 more special formats + EXC_NUMFMT_STRING( 45, "mm:ss" ), + EXC_NUMFMT_STRING( 46, "[h]:mm:ss" ), + EXC_NUMFMT_STRING( 47, "mm:ss.0" ), + EXC_NUMFMT_STRING( 48, "##0.0E+0" ), + EXC_NUMFMT_OFFSET( 49, NF_TEXT ), + + // 50...81 international formats + EXC_NUMFMT_REUSE( 50, 14 ), + EXC_NUMFMT_REUSE( 51, 14 ), + EXC_NUMFMT_REUSE( 52, 14 ), + EXC_NUMFMT_REUSE( 53, 14 ), + EXC_NUMFMT_REUSE( 54, 14 ), + EXC_NUMFMT_REUSE( 55, 14 ), + EXC_NUMFMT_REUSE( 56, 14 ), + EXC_NUMFMT_REUSE( 57, 14 ), + EXC_NUMFMT_REUSE( 58, 14 ), + EXC_NUMFMT_REUSE( 59, 1 ), + EXC_NUMFMT_REUSE( 60, 2 ), + EXC_NUMFMT_REUSE( 61, 3 ), + EXC_NUMFMT_REUSE( 62, 4 ), + EXC_NUMFMT_REUSE( 67, 9 ), + EXC_NUMFMT_REUSE( 68, 10 ), + EXC_NUMFMT_REUSE( 69, 12 ), + EXC_NUMFMT_REUSE( 70, 13 ), + EXC_NUMFMT_REUSE( 71, 14 ), + EXC_NUMFMT_REUSE( 72, 14 ), + EXC_NUMFMT_REUSE( 73, 15 ), + EXC_NUMFMT_REUSE( 74, 16 ), + EXC_NUMFMT_REUSE( 75, 17 ), + EXC_NUMFMT_REUSE( 76, 20 ), + EXC_NUMFMT_REUSE( 77, 21 ), + EXC_NUMFMT_REUSE( 78, 22 ), + EXC_NUMFMT_REUSE( 79, 45 ), + EXC_NUMFMT_REUSE( 80, 46 ), + EXC_NUMFMT_REUSE( 81, 47 ), + + // 82...163 not used, must not occur in a file (Excel may crash) + + EXC_NUMFMT_ENDTABLE() +}; + +// ENGLISH -------------------------------------------------------------------- + +/** Base table for English locales. */ +const XclBuiltInFormat spBuiltInFormats_ENGLISH[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "DD/MM/YYYY hh:mm" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_UK[] = +{ + EXC_NUMFMT_STRING( 63, UTF8_POUND_UK "#,##0;-" UTF8_POUND_UK "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_POUND_UK "#,##0;[RED]-" UTF8_POUND_UK "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_POUND_UK "#,##0.00;-" UTF8_POUND_UK "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_POUND_UK "#,##0.00;[RED]-" UTF8_POUND_UK "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_EIRE[] = +{ + EXC_NUMFMT_STRING( 63, UTF8_EURO "#,##0;-" UTF8_EURO "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_EURO "#,##0;[RED]-" UTF8_EURO "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_EURO "#,##0.00;-" UTF8_EURO "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_EURO "#,##0.00;[RED]-" UTF8_EURO "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_US[] = +{ + EXC_NUMFMT_STRING( 14, "M/D/YYYY" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "M/D/YYYY h:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0_);(#,##0)" ), + EXC_NUMFMT_STRING( 38, "#,##0_);[RED](#,##0)" ), + EXC_NUMFMT_STRING( 39, "#,##0.00_);(#,##0.00)" ), + EXC_NUMFMT_STRING( 40, "#,##0.00_);[RED](#,##0.00)" ), + EXC_NUMFMT_STRING( 63, "$#,##0_);($#,##0)" ), + EXC_NUMFMT_STRING( 64, "$#,##0_);[RED]($#,##0)" ), + EXC_NUMFMT_STRING( 65, "$#,##0.00_);($#,##0.00)" ), + EXC_NUMFMT_STRING( 66, "$#,##0.00_);[RED]($#,##0.00)" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_CAN[] = +{ + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "DD/MM/YYYY h:mm" ), + EXC_NUMFMT_STRING( 63, "$#,##0;-$#,##0" ), + EXC_NUMFMT_STRING( 64, "$#,##0;[RED]-$#,##0" ), + EXC_NUMFMT_STRING( 65, "$#,##0.00;-$#,##0.00" ), + EXC_NUMFMT_STRING( 66, "$#,##0.00;[RED]-$#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_AUS[] = +{ + EXC_NUMFMT_STRING( 14, "D/MM/YYYY" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "D/MM/YYYY h:mm" ), + EXC_NUMFMT_STRING( 63, "$#,##0;-$#,##0" ), + EXC_NUMFMT_STRING( 64, "$#,##0;[RED]-$#,##0" ), + EXC_NUMFMT_STRING( 65, "$#,##0.00;-$#,##0.00" ), + EXC_NUMFMT_STRING( 66, "$#,##0.00;[RED]-$#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ENGLISH_SAFRICA[] = +{ + EXC_NUMFMT_STRING( 14, "YYYY/MM/DD" ), + EXC_NUMFMT_OFFSET( 18, NF_TIME_HHMMAMPM ), + EXC_NUMFMT_OFFSET( 19, NF_TIME_HHMMSSAMPM ), + EXC_NUMFMT_STRING( 22, "YYYY/MM/DD hh:mm" ), + EXC_NUMFMT_STRING( 63, "\\R #,##0;\\R -#,##0" ), + EXC_NUMFMT_STRING( 64, "\\R #,##0;[RED]\\R -#,##0" ), + EXC_NUMFMT_STRING( 65, "\\R #,##0.00;\\R -#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\\R #,##0.00;[RED]\\R -#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +// FRENCH --------------------------------------------------------------------- + +/** Base table for French locales. */ +const XclBuiltInFormat spBuiltInFormats_FRENCH[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_FRENCH_FRANCE[] = +{ + EXC_NUMFMT_STRING( 22, "DD/MM/YYYY hh:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0\\ _" UTF8_EURO ";-#,##0\\ _" UTF8_EURO ), + EXC_NUMFMT_STRING( 38, "#,##0\\ _" UTF8_EURO ";[RED]-#,##0\\ _" UTF8_EURO ), + EXC_NUMFMT_STRING( 39, "#,##0.00\\ _" UTF8_EURO ";-#,##0.00\\ _" UTF8_EURO ), + EXC_NUMFMT_STRING( 40, "#,##0.00\\ _" UTF8_EURO ";[RED]-#,##0.00\\ _" UTF8_EURO ), + EXC_NUMFMT_STRING( 63, "#,##0\\ " UTF8_EURO ";-#,##0\\ " UTF8_EURO ), + EXC_NUMFMT_STRING( 64, "#,##0\\ " UTF8_EURO ";[RED]-#,##0\\ " UTF8_EURO ), + EXC_NUMFMT_STRING( 65, "#,##0.00\\ " UTF8_EURO ";-#,##0.00\\ " UTF8_EURO ), + EXC_NUMFMT_STRING( 66, "#,##0.00\\ " UTF8_EURO ";[RED]-#,##0.00\\ " UTF8_EURO ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_FRENCH_CANADIAN[] = +{ + EXC_NUMFMT_STRING( 22, "YYYY-MM-DD hh:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0\\ _$_-;#,##0\\ _$-" ), + EXC_NUMFMT_STRING( 38, "#,##0\\ _$_-;[RED]#,##0\\ _$-" ), + EXC_NUMFMT_STRING( 39, "#,##0.00\\ _$_-;#,##0.00\\ _$-" ), + EXC_NUMFMT_STRING( 40, "#,##0.00\\ _$_-;[RED]#,##0.00\\ _$-" ), + EXC_NUMFMT_STRING( 63, "#,##0\\ $_-;#,##0\\ $-" ), + EXC_NUMFMT_STRING( 64, "#,##0\\ $_-;[RED]#,##0\\ $-" ), + EXC_NUMFMT_STRING( 65, "#,##0.00\\ $_-;#,##0.00\\ $-" ), + EXC_NUMFMT_STRING( 66, "#,##0.00\\ $_-;[RED]#,##0.00\\ $-" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_FRENCH_SWISS[] = +{ + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 22, "DD.MM.YYYY hh:mm" ), + EXC_NUMFMT_STRING( 63, "\"SFr. \"#,##0;\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 64, "\"SFr. \"#,##0;[RED]\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 65, "\"SFr. \"#,##0.00;\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\"SFr. \"#,##0.00;[RED]\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_FRENCH_BELGIAN[] = +{ + EXC_NUMFMT_STRING( 14, "D/MM/YYYY" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "D/MM/YYYY h:mm" ), + EXC_NUMFMT_ENDTABLE() +}; + +// GERMAN --------------------------------------------------------------------- + +/** Base table for German locales. */ +const XclBuiltInFormat spBuiltInFormats_GERMAN[] = +{ + EXC_NUMFMT_STRING( 15, "DD. MMM YY" ), + EXC_NUMFMT_STRING( 16, "DD. MMM" ), + EXC_NUMFMT_STRING( 17, "MMM YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "DD.MM.YYYY hh:mm" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_GERMANY[] = +{ + EXC_NUMFMT_STRING( 37, "#,##0 _" UTF8_EURO ";-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 38, "#,##0 _" UTF8_EURO ";[RED]-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 39, "#,##0.00 _" UTF8_EURO ";-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 40, "#,##0.00 _" UTF8_EURO ";[RED]-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 63, "#,##0 " UTF8_EURO ";-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 64, "#,##0 " UTF8_EURO ";[RED]-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 65, "#,##0.00 " UTF8_EURO ";-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_STRING( 66, "#,##0.00 " UTF8_EURO ";[RED]-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_AUSTRIAN[] = +{ + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 63, UTF8_EURO " #,##0;-" UTF8_EURO " #,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_EURO " #,##0;[RED]-" UTF8_EURO " #,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_EURO " #,##0.00;-" UTF8_EURO " #,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_EURO " #,##0.00;[RED]-" UTF8_EURO " #,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_SWISS[] = +{ + EXC_NUMFMT_STRING( 63, "\"SFr. \"#,##0;\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 64, "\"SFr. \"#,##0;[RED]\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 65, "\"SFr. \"#,##0.00;\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\"SFr. \"#,##0.00;[RED]\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_LUXEMBOURG[] = +{ + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 37, "#,##0 _" UTF8_EURO ";-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 38, "#,##0 _" UTF8_EURO ";[RED]-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 39, "#,##0.00 _" UTF8_EURO ";-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 40, "#,##0.00 _" UTF8_EURO ";[RED]-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 63, "#,##0 " UTF8_EURO ";-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 64, "#,##0 " UTF8_EURO ";[RED]-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 65, "#,##0.00 " UTF8_EURO ";-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_STRING( 66, "#,##0.00 " UTF8_EURO ";[RED]-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_GERMAN_LIECHTENSTEIN[] = +{ + EXC_NUMFMT_STRING( 63, "\"CHF \"#,##0;\"CHF \"-#,##0" ), + EXC_NUMFMT_STRING( 64, "\"CHF \"#,##0;[RED]\"CHF \"-#,##0" ), + EXC_NUMFMT_STRING( 65, "\"CHF \"#,##0.00;\"CHF \"-#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\"CHF \"#,##0.00;[RED]\"CHF \"-#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +// ITALIAN -------------------------------------------------------------------- + +const XclBuiltInFormat spBuiltInFormats_ITALIAN_ITALY[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 22, "DD/MM/YYYY h:mm" ), + EXC_NUMFMT_STRING( 63, UTF8_EURO " #,##0;-" UTF8_EURO " #,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_EURO " #,##0;[RED]-" UTF8_EURO " #,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_EURO " #,##0.00;-" UTF8_EURO " #,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_EURO " #,##0.00;[RED]-" UTF8_EURO " #,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_ITALIAN_SWISS[] = +{ + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "DD.MM.YYYY hh:mm" ), + EXC_NUMFMT_STRING( 63, "\"SFr. \"#,##0;\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 64, "\"SFr. \"#,##0;[RED]\"SFr. \"-#,##0" ), + EXC_NUMFMT_STRING( 65, "\"SFr. \"#,##0.00;\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_STRING( 66, "\"SFr. \"#,##0.00;[RED]\"SFr. \"-#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +// SWEDISH -------------------------------------------------------------------- + +const XclBuiltInFormat spBuiltInFormats_SWEDISH_SWEDEN[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "YYYY-MM-DD hh:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0 _k_r;-#,##0 _k_r" ), + EXC_NUMFMT_STRING( 38, "#,##0 _k_r;[RED]-#,##0 _k_r" ), + EXC_NUMFMT_STRING( 39, "#,##0.00 _k_r;-#,##0.00 _k_r" ), + EXC_NUMFMT_STRING( 40, "#,##0.00 _k_r;[RED]-#,##0.00 _k_r" ), + EXC_NUMFMT_STRING( 63, "#,##0 \"kr\";-#,##0 \"kr\"" ), + EXC_NUMFMT_STRING( 64, "#,##0 \"kr\";[RED]-#,##0 \"kr\"" ), + EXC_NUMFMT_STRING( 65, "#,##0.00 \"kr\";-#,##0.00 \"kr\"" ), + EXC_NUMFMT_STRING( 66, "#,##0.00 \"kr\";[RED]-#,##0.00 \"kr\"" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_SWEDISH_FINLAND[] = +{ + EXC_NUMFMT_STRING( 9, "0 %" ), + EXC_NUMFMT_STRING( 10, "0.00 %" ), + EXC_NUMFMT_STRING( 15, "DD.MMM.YY" ), + EXC_NUMFMT_STRING( 16, "DD.MMM" ), + EXC_NUMFMT_STRING( 17, "MMM.YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "D.M.YYYY hh:mm" ), + EXC_NUMFMT_STRING( 37, "#,##0 _" UTF8_EURO ";-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 38, "#,##0 _" UTF8_EURO ";[RED]-#,##0 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 39, "#,##0.00 _" UTF8_EURO ";-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 40, "#,##0.00 _" UTF8_EURO ";[RED]-#,##0.00 _" UTF8_EURO ), + EXC_NUMFMT_STRING( 63, "#,##0 " UTF8_EURO ";-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 64, "#,##0 " UTF8_EURO ";[RED]-#,##0 " UTF8_EURO ), + EXC_NUMFMT_STRING( 65, "#,##0.00 " UTF8_EURO ";-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_STRING( 66, "#,##0.00 " UTF8_EURO ";[RED]-#,##0.00 " UTF8_EURO ), + EXC_NUMFMT_ENDTABLE() +}; + +// ASIAN ---------------------------------------------------------------------- + +/** Base table for Asian locales. */ +const XclBuiltInFormat spBuiltInFormats_ASIAN[] = +{ + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 20, "h:mm" ), + EXC_NUMFMT_STRING( 21, "h:mm:ss" ), + EXC_NUMFMT_STRING( 23, "$#,##0_);($#,##0)" ), + EXC_NUMFMT_STRING( 24, "$#,##0_);[RED]($#,##0)" ), + EXC_NUMFMT_STRING( 25, "$#,##0.00_);($#,##0.00)" ), + EXC_NUMFMT_STRING( 26, "$#,##0.00_);[RED]($#,##0.00)" ), + EXC_NUMFMT_REUSE( 29, 28 ), + EXC_NUMFMT_REUSE( 36, 27 ), + EXC_NUMFMT_REUSE( 50, 27 ), + EXC_NUMFMT_REUSE( 51, 28 ), + EXC_NUMFMT_REUSE( 52, 34 ), + EXC_NUMFMT_REUSE( 53, 35 ), + EXC_NUMFMT_REUSE( 54, 28 ), + EXC_NUMFMT_REUSE( 55, 34 ), + EXC_NUMFMT_REUSE( 56, 35 ), + EXC_NUMFMT_REUSE( 57, 27 ), + EXC_NUMFMT_REUSE( 58, 28 ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_JAPANESE[] = +{ + EXC_NUMFMT_STRING( 14, "YYYY/M/D" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 22, "YYYY/M/D h:mm" ), + EXC_NUMFMT_STRING( 27, "[$-0411]GE.M.D" ), + EXC_NUMFMT_STRING( 28, "[$-0411]GGGE" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 30, "[$-0411]M/D/YY" ), + EXC_NUMFMT_STRING( 31, "[$-0411]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 32, "[$-0411]h" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 33, "[$-0411]h" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_STRING( 34, "[$-0411]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON ), + EXC_NUMFMT_STRING( 35, "[$-0411]M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 63, UTF8_YEN_JP "#,##0;-" UTF8_YEN_JP "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_YEN_JP "#,##0;[RED]-" UTF8_YEN_JP "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_YEN_JP "#,##0.00;-" UTF8_YEN_JP "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_YEN_JP "#,##0.00;[RED]-" UTF8_YEN_JP "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_KOREAN[] = +{ + EXC_NUMFMT_STRING( 14, "YYYY-MM-DD" ), + EXC_NUMFMT_STRING( 15, "DD-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 22, "YYYY-MM-DD h:mm" ), + EXC_NUMFMT_STRING( 27, "[$-0412]YYYY" UTF8_CJ_YEAR " MM" UTF8_CJ_MON " DD" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 28, "[$-0412]MM-DD" ), + EXC_NUMFMT_STRING( 30, "[$-0412]MM-DD-YY" ), + EXC_NUMFMT_STRING( 31, "[$-0412]YYYY" UTF8_KO_YEAR " MM" UTF8_KO_MON " DD" UTF8_KO_DAY ), + EXC_NUMFMT_STRING( 32, "[$-0412]h" UTF8_KO_HOUR " mm" UTF8_KO_MIN ), + EXC_NUMFMT_STRING( 33, "[$-0412]h" UTF8_KO_HOUR " mm" UTF8_KO_MIN " ss" UTF8_KO_SEC ), + EXC_NUMFMT_STRING( 34, "[$-0412]YYYY\"/\"MM\"/\"DD" ), + EXC_NUMFMT_STRING( 35, "[$-0412]YYYY-MM-DD" ), + EXC_NUMFMT_STRING( 63, UTF8_WON "#,##0;-" UTF8_WON "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_WON "#,##0;[RED]-" UTF8_WON "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_WON "#,##0.00;-" UTF8_WON "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_WON "#,##0.00;[RED]-" UTF8_WON "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_CHINESE_SIMPLIFIED[] = +{ + EXC_NUMFMT_STRING( 14, "YYYY-M-D" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 22, "YYYY-M-D h:mm" ), + EXC_NUMFMT_STRING( 27, "[$-0804]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON ), + EXC_NUMFMT_STRING( 28, "[$-0804]M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 30, "[$-0804]M-D-YY" ), + EXC_NUMFMT_STRING( 31, "[$-0804]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 32, "[$-0804]h" UTF8_CS_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 33, "[$-0804]h" UTF8_CS_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_STRING( 34, "[$-0804]AM/PMh" UTF8_CS_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 35, "[$-0804]AM/PMh" UTF8_CS_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_REUSE( 52, 27 ), + EXC_NUMFMT_REUSE( 53, 28 ), + EXC_NUMFMT_STRING( 63, UTF8_YEN_CS "#,##0;-" UTF8_YEN_CS "#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_YEN_CS "#,##0;[RED]-" UTF8_YEN_CS "#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_YEN_CS "#,##0.00;-" UTF8_YEN_CS "#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_YEN_CS "#,##0.00;[RED]-" UTF8_YEN_CS "#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_CHINESE_TRADITIONAL[] = +{ + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "hh:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "hh:mm:ss AM/PM" ), + EXC_NUMFMT_OFFSET( 20, NF_TIME_HHMM ), + EXC_NUMFMT_OFFSET( 21, NF_TIME_HHMMSS ), + EXC_NUMFMT_STRING( 22, "YYYY/M/D hh:mm" ), + EXC_NUMFMT_STRING( 23, "US$#,##0_);(US$#,##0)" ), + EXC_NUMFMT_STRING( 24, "US$#,##0_);[RED](US$#,##0)" ), + EXC_NUMFMT_STRING( 25, "US$#,##0.00_);(US$#,##0.00)" ), + EXC_NUMFMT_STRING( 26, "US$#,##0.00_);[RED](US$#,##0.00)" ), + EXC_NUMFMT_STRING( 27, "[$-0404]E/M/D" ), + EXC_NUMFMT_STRING( 28, "[$-0404]E" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 30, "[$-0404]M/D/YY" ), + EXC_NUMFMT_STRING( 31, "[$-0404]YYYY" UTF8_CJ_YEAR "M" UTF8_CJ_MON "D" UTF8_CJ_DAY ), + EXC_NUMFMT_STRING( 32, "[$-0404]hh" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 33, "[$-0404]hh" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_STRING( 34, "[$-0404]AM/PMhh" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN ), + EXC_NUMFMT_STRING( 35, "[$-0404]AM/PMhh" UTF8_CJ_HOUR "mm" UTF8_CJ_MIN "ss" UTF8_CJ_SEC ), + EXC_NUMFMT_STRING( 63, "$#,##0;-$#,##0" ), + EXC_NUMFMT_STRING( 64, "$#,##0;[RED]-$#,##0" ), + EXC_NUMFMT_STRING( 65, "$#,##0.00;-$#,##0.00" ), + EXC_NUMFMT_STRING( 66, "$#,##0.00;[RED]-$#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +// OTHER ---------------------------------------------------------------------- + +const XclBuiltInFormat spBuiltInFormats_HEBREW[] = +{ + EXC_NUMFMT_STRING( 15, "DD-MMMM-YY" ), + EXC_NUMFMT_STRING( 16, "DD-MMMM" ), + EXC_NUMFMT_STRING( 17, "MMMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 63, UTF8_SHEQEL " #,##0;" UTF8_SHEQEL " -#,##0" ), + EXC_NUMFMT_STRING( 64, UTF8_SHEQEL " #,##0;[RED]" UTF8_SHEQEL " -#,##0" ), + EXC_NUMFMT_STRING( 65, UTF8_SHEQEL " #,##0.00;" UTF8_SHEQEL " -#,##0.00" ), + EXC_NUMFMT_STRING( 66, UTF8_SHEQEL " #,##0.00;[RED]" UTF8_SHEQEL " -#,##0.00" ), + EXC_NUMFMT_ENDTABLE() +}; + +const XclBuiltInFormat spBuiltInFormats_THAI[] = +{ + EXC_NUMFMT_STRING( 14, "D/M/YYYY" ), + EXC_NUMFMT_STRING( 15, "D-MMM-YY" ), + EXC_NUMFMT_STRING( 16, "D-MMM" ), + EXC_NUMFMT_STRING( 17, "MMM-YY" ), + EXC_NUMFMT_STRING( 18, "h:mm AM/PM" ), + EXC_NUMFMT_STRING( 19, "h:mm:ss AM/PM" ), + EXC_NUMFMT_STRING( 22, "D/M/YYYY h:mm" ), + EXC_NUMFMT_STRING( 59, "t0" ), + EXC_NUMFMT_STRING( 60, "t0.00" ), + EXC_NUMFMT_STRING( 61, "t#,##0" ), + EXC_NUMFMT_STRING( 62, "t#,##0.00" ), + EXC_NUMFMT_STRING( 63, "t" UTF8_BAHT "#,##0_);t(" UTF8_BAHT "#,##0)" ), + EXC_NUMFMT_STRING( 64, "t" UTF8_BAHT "#,##0_);[RED]t(" UTF8_BAHT "#,##0)" ), + EXC_NUMFMT_STRING( 65, "t" UTF8_BAHT "#,##0.00_);t(" UTF8_BAHT "#,##0.00)" ), + EXC_NUMFMT_STRING( 66, "t" UTF8_BAHT "#,##0.00_);[RED]t(" UTF8_BAHT "#,##0.00)" ), + EXC_NUMFMT_STRING( 67, "t0%" ), + EXC_NUMFMT_STRING( 68, "t0.00%" ), + EXC_NUMFMT_STRING( 69, "t# ?/?" ), + EXC_NUMFMT_STRING( 70, "t# ?\?/?\?" ), + EXC_NUMFMT_STRING( 71, "tD/M/EE" ), + EXC_NUMFMT_STRING( 72, "tD-MMM-E" ), + EXC_NUMFMT_STRING( 73, "tD-MMM" ), + EXC_NUMFMT_STRING( 74, "tMMM-E" ), + EXC_NUMFMT_STRING( 75, "th:mm" ), + EXC_NUMFMT_STRING( 76, "th:mm:ss" ), + EXC_NUMFMT_STRING( 77, "tD/M/EE h:mm" ), + EXC_NUMFMT_STRING( 78, "tmm:ss" ), + EXC_NUMFMT_STRING( 79, "t[h]:mm:ss" ), + EXC_NUMFMT_STRING( 80, "tmm:ss.0" ), + EXC_NUMFMT_STRING( 81, "D/M/E" ), + EXC_NUMFMT_ENDTABLE() +}; + +#undef EXC_NUMFMT_ENDTABLE +#undef EXC_NUMFMT_REUSE +#undef EXC_NUMFMT_OFFSET +#undef EXC_NUMFMT_STRING + +/** Specifies a number format table for a specific language. */ +struct XclBuiltInFormatTable +{ + LanguageType meLanguage; /// The language of this table. + LanguageType meParentLang; /// The language of the parent table. + const XclBuiltInFormat* mpFormats; /// The number format table. +}; + +const XclBuiltInFormatTable spBuiltInFormatTables[] = +{ // language parent language format table + { LANGUAGE_DONTKNOW, LANGUAGE_NONE, spBuiltInFormats_DONTKNOW }, + + { LANGUAGE_ENGLISH, LANGUAGE_DONTKNOW, spBuiltInFormats_ENGLISH }, + { LANGUAGE_ENGLISH_UK, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_UK }, + { LANGUAGE_ENGLISH_EIRE, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_EIRE }, + { LANGUAGE_ENGLISH_US, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_US }, + { LANGUAGE_ENGLISH_CAN, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_CAN }, + { LANGUAGE_ENGLISH_AUS, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_AUS }, + { LANGUAGE_ENGLISH_SAFRICA, LANGUAGE_ENGLISH, spBuiltInFormats_ENGLISH_SAFRICA }, + { LANGUAGE_ENGLISH_NZ, LANGUAGE_ENGLISH_AUS, nullptr }, + + { PRV_LANGUAGE_FRENCH_PRIM, LANGUAGE_DONTKNOW, spBuiltInFormats_FRENCH }, + { LANGUAGE_FRENCH, PRV_LANGUAGE_FRENCH_PRIM, spBuiltInFormats_FRENCH_FRANCE }, + { LANGUAGE_FRENCH_CANADIAN, PRV_LANGUAGE_FRENCH_PRIM, spBuiltInFormats_FRENCH_CANADIAN }, + { LANGUAGE_FRENCH_SWISS, PRV_LANGUAGE_FRENCH_PRIM, spBuiltInFormats_FRENCH_SWISS }, + { LANGUAGE_FRENCH_BELGIAN, LANGUAGE_FRENCH, spBuiltInFormats_FRENCH_BELGIAN }, + { LANGUAGE_FRENCH_LUXEMBOURG, LANGUAGE_FRENCH, nullptr }, + { LANGUAGE_FRENCH_MONACO, LANGUAGE_FRENCH, nullptr }, + + { PRV_LANGUAGE_GERMAN_PRIM, LANGUAGE_DONTKNOW, spBuiltInFormats_GERMAN }, + { LANGUAGE_GERMAN, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_GERMANY }, + { LANGUAGE_GERMAN_AUSTRIAN, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_AUSTRIAN }, + { LANGUAGE_GERMAN_SWISS, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_SWISS }, + { LANGUAGE_GERMAN_LUXEMBOURG, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_LUXEMBOURG }, + { LANGUAGE_GERMAN_LIECHTENSTEIN, PRV_LANGUAGE_GERMAN_PRIM, spBuiltInFormats_GERMAN_LIECHTENSTEIN }, + + { LANGUAGE_ITALIAN, LANGUAGE_DONTKNOW, spBuiltInFormats_ITALIAN_ITALY }, + { LANGUAGE_ITALIAN_SWISS, LANGUAGE_DONTKNOW, spBuiltInFormats_ITALIAN_SWISS }, + + { LANGUAGE_SWEDISH, LANGUAGE_DONTKNOW, spBuiltInFormats_SWEDISH_SWEDEN }, + { LANGUAGE_SWEDISH_FINLAND, LANGUAGE_DONTKNOW, spBuiltInFormats_SWEDISH_FINLAND }, + + { PRV_LANGUAGE_ASIAN_PRIM, LANGUAGE_DONTKNOW, spBuiltInFormats_ASIAN }, + { LANGUAGE_JAPANESE, PRV_LANGUAGE_ASIAN_PRIM, spBuiltInFormats_JAPANESE }, + { LANGUAGE_KOREAN, PRV_LANGUAGE_ASIAN_PRIM, spBuiltInFormats_KOREAN }, + { LANGUAGE_CHINESE_SIMPLIFIED, PRV_LANGUAGE_ASIAN_PRIM, spBuiltInFormats_CHINESE_SIMPLIFIED }, + { LANGUAGE_CHINESE_TRADITIONAL, PRV_LANGUAGE_ASIAN_PRIM, spBuiltInFormats_CHINESE_TRADITIONAL }, + + { LANGUAGE_HEBREW, LANGUAGE_DONTKNOW, spBuiltInFormats_HEBREW }, + { LANGUAGE_THAI, LANGUAGE_DONTKNOW, spBuiltInFormats_THAI } +}; + +} // namespace + +XclNumFmtBuffer::XclNumFmtBuffer( const XclRoot& rRoot ) : + meSysLang( rRoot.GetSysLanguage() ), + mnStdScNumFmt( rRoot.GetFormatter().GetStandardIndex( ScGlobal::eLnge ) ) +{ + // *** insert default formats (BIFF5+ only)*** + + if( rRoot.GetBiff() >= EXC_BIFF5 ) + InsertBuiltinFormats(); +} + +void XclNumFmtBuffer::InitializeImport() +{ + maFmtMap.clear(); +} + +void XclNumFmtBuffer::InsertFormat( sal_uInt16 nXclNumFmt, const OUString& rFormat ) +{ + XclNumFmt& rNumFmt = maFmtMap[ nXclNumFmt ]; + rNumFmt.maFormat = rFormat; + // #i62053# rFormat may be an empty string, meOffset must be initialized + rNumFmt.meOffset = NF_NUMBER_STANDARD; + rNumFmt.meLanguage = LANGUAGE_SYSTEM; +} + +void XclNumFmtBuffer::InsertBuiltinFormats() +{ + // build a map containing tables for all languages + typedef ::std::map< LanguageType, const XclBuiltInFormatTable* > XclBuiltInMap; + XclBuiltInMap aBuiltInMap; + for(const auto &rTable : spBuiltInFormatTables) + aBuiltInMap[ rTable.meLanguage ] = &rTable; + + // build a list of table pointers for the current language, with all parent tables + typedef ::std::vector< const XclBuiltInFormatTable* > XclBuiltInVec; + XclBuiltInVec aBuiltInVec; + for( XclBuiltInMap::const_iterator aMIt = aBuiltInMap.find( meSysLang ), aMEnd = aBuiltInMap.end(); + aMIt != aMEnd; aMIt = aBuiltInMap.find( aMIt->second->meParentLang ) ) + aBuiltInVec.push_back( aMIt->second ); + // language not supported + if( aBuiltInVec.empty() ) + { + SAL_WARN("sc", "XclNumFmtBuffer::InsertBuiltinFormats - language not supported (#i29949#) 0x" << std::hex << meSysLang ); + XclBuiltInMap::const_iterator aMIt = aBuiltInMap.find( LANGUAGE_DONTKNOW ); + OSL_ENSURE( aMIt != aBuiltInMap.end(), "XclNumFmtBuffer::InsertBuiltinFormats - default map not found" ); + if( aMIt != aBuiltInMap.end() ) + aBuiltInVec.push_back( aMIt->second ); + } + + // insert the default formats in the format map, from root parent to system language + std::map< sal_uInt16, sal_uInt16 > aReuseMap; + for( XclBuiltInVec::reverse_iterator aVIt = aBuiltInVec.rbegin(), aVEnd = aBuiltInVec.rend(); aVIt != aVEnd; ++aVIt ) + { + // put LANGUAGE_SYSTEM for all entries in default table + LanguageType eLang = ((*aVIt)->meLanguage == LANGUAGE_DONTKNOW) ? LANGUAGE_SYSTEM : meSysLang; + for( const XclBuiltInFormat* pBuiltIn = (*aVIt)->mpFormats; pBuiltIn && (pBuiltIn->mnXclNumFmt != EXC_FORMAT_NOTFOUND); ++pBuiltIn ) + { + XclNumFmt& rNumFmt = maFmtMap[ pBuiltIn->mnXclNumFmt ]; + + rNumFmt.meOffset = pBuiltIn->meOffset; + rNumFmt.meLanguage = eLang; + + if( pBuiltIn->mpFormat ) + rNumFmt.maFormat = OUString( pBuiltIn->mpFormat, strlen(pBuiltIn->mpFormat), RTL_TEXTENCODING_UTF8 ); + else + rNumFmt.maFormat.clear(); + + if( pBuiltIn->meOffset == PRV_NF_INDEX_REUSE ) + aReuseMap[ pBuiltIn->mnXclNumFmt ] = pBuiltIn->mnXclReuseFmt; + else + aReuseMap.erase( pBuiltIn->mnXclNumFmt ); + } + } + + // copy reused number formats + for( const auto& [rXclNumFmt, rXclReuseFmt] : aReuseMap ) + maFmtMap[ rXclNumFmt ] = maFmtMap[ rXclReuseFmt ]; +} + +// Cell formatting data (XF) ================================================== + +XclCellProt::XclCellProt() : + mbLocked( true ), // default in Excel and Calc + mbHidden( false ) +{ +} + +bool operator==( const XclCellProt& rLeft, const XclCellProt& rRight ) +{ + return (rLeft.mbLocked == rRight.mbLocked) && (rLeft.mbHidden == rRight.mbHidden); +} + +XclCellAlign::XclCellAlign() : + mnHorAlign( EXC_XF_HOR_GENERAL ), + mnVerAlign( EXC_XF_VER_BOTTOM ), + mnOrient( EXC_ORIENT_NONE ), + mnTextDir( EXC_XF_TEXTDIR_CONTEXT ), + mnRotation( EXC_ROT_NONE ), + mnIndent( 0 ), + mbLineBreak( false ), + mbShrink( false ) +{ +} + +SvxCellHorJustify XclCellAlign::GetScHorAlign() const +{ + SvxCellHorJustify eHorJust = SvxCellHorJustify::Standard; + switch( mnHorAlign ) + { + case EXC_XF_HOR_GENERAL: eHorJust = SvxCellHorJustify::Standard; break; + case EXC_XF_HOR_LEFT: eHorJust = SvxCellHorJustify::Left; break; + case EXC_XF_HOR_CENTER_AS: + case EXC_XF_HOR_CENTER: eHorJust = SvxCellHorJustify::Center; break; + case EXC_XF_HOR_RIGHT: eHorJust = SvxCellHorJustify::Right; break; + case EXC_XF_HOR_FILL: eHorJust = SvxCellHorJustify::Repeat; break; + case EXC_XF_HOR_JUSTIFY: + case EXC_XF_HOR_DISTRIB: eHorJust = SvxCellHorJustify::Block; break; + default: OSL_FAIL( "XclCellAlign::GetScHorAlign - unknown horizontal alignment" ); + } + return eHorJust; +} + +SvxCellJustifyMethod XclCellAlign::GetScHorJustifyMethod() const +{ + return (mnHorAlign == EXC_XF_HOR_DISTRIB) ? SvxCellJustifyMethod::Distribute : SvxCellJustifyMethod::Auto; +} + +SvxCellVerJustify XclCellAlign::GetScVerAlign() const +{ + SvxCellVerJustify eVerJust = SvxCellVerJustify::Standard; + switch( mnVerAlign ) + { + case EXC_XF_VER_TOP: eVerJust = SvxCellVerJustify::Top; break; + case EXC_XF_VER_CENTER: eVerJust = SvxCellVerJustify::Center; break; + case EXC_XF_VER_BOTTOM: eVerJust = SvxCellVerJustify::Standard; break; + case EXC_XF_VER_JUSTIFY: + case EXC_XF_VER_DISTRIB: eVerJust = SvxCellVerJustify::Block; break; + default: OSL_FAIL( "XclCellAlign::GetScVerAlign - unknown vertical alignment" ); + } + return eVerJust; +} + +SvxCellJustifyMethod XclCellAlign::GetScVerJustifyMethod() const +{ + return (mnVerAlign == EXC_XF_VER_DISTRIB) ? SvxCellJustifyMethod::Distribute : SvxCellJustifyMethod::Auto; +} + +SvxFrameDirection XclCellAlign::GetScFrameDir() const +{ + SvxFrameDirection eFrameDir = SvxFrameDirection::Environment; + switch( mnTextDir ) + { + case EXC_XF_TEXTDIR_CONTEXT: eFrameDir = SvxFrameDirection::Environment; break; + case EXC_XF_TEXTDIR_LTR: eFrameDir = SvxFrameDirection::Horizontal_LR_TB; break; + case EXC_XF_TEXTDIR_RTL: eFrameDir = SvxFrameDirection::Horizontal_RL_TB; break; + default: OSL_FAIL( "XclCellAlign::GetScFrameDir - unknown CTL text direction" ); + } + return eFrameDir; +} + +void XclCellAlign::SetScHorAlign( SvxCellHorJustify eHorJust ) +{ + switch( eHorJust ) + { + case SvxCellHorJustify::Standard: mnHorAlign = EXC_XF_HOR_GENERAL; break; + case SvxCellHorJustify::Left: mnHorAlign = EXC_XF_HOR_LEFT; break; + case SvxCellHorJustify::Center: mnHorAlign = EXC_XF_HOR_CENTER; break; + case SvxCellHorJustify::Right: mnHorAlign = EXC_XF_HOR_RIGHT; break; + case SvxCellHorJustify::Block: mnHorAlign = EXC_XF_HOR_JUSTIFY; break; + case SvxCellHorJustify::Repeat: mnHorAlign = EXC_XF_HOR_FILL; break; + default: mnHorAlign = EXC_XF_HOR_GENERAL; + OSL_FAIL( "XclCellAlign::SetScHorAlign - unknown horizontal alignment" ); + } +} + +void XclCellAlign::SetScVerAlign( SvxCellVerJustify eVerJust ) +{ + switch( eVerJust ) + { + case SvxCellVerJustify::Standard: mnVerAlign = EXC_XF_VER_BOTTOM; break; + case SvxCellVerJustify::Top: mnVerAlign = EXC_XF_VER_TOP; break; + case SvxCellVerJustify::Center: mnVerAlign = EXC_XF_VER_CENTER; break; + case SvxCellVerJustify::Bottom: mnVerAlign = EXC_XF_VER_BOTTOM; break; + default: mnVerAlign = EXC_XF_VER_BOTTOM; + OSL_FAIL( "XclCellAlign::SetScVerAlign - unknown vertical alignment" ); + } +} + +void XclCellAlign::SetScFrameDir( SvxFrameDirection eFrameDir ) +{ + switch( eFrameDir ) + { + case SvxFrameDirection::Environment: mnTextDir = EXC_XF_TEXTDIR_CONTEXT; break; + case SvxFrameDirection::Horizontal_LR_TB: mnTextDir = EXC_XF_TEXTDIR_LTR; break; + case SvxFrameDirection::Horizontal_RL_TB: mnTextDir = EXC_XF_TEXTDIR_RTL; break; + default: mnTextDir = EXC_XF_TEXTDIR_CONTEXT; + OSL_FAIL( "XclCellAlign::SetScFrameDir - unknown CTL text direction" ); + } +} + +bool operator==( const XclCellAlign& rLeft, const XclCellAlign& rRight ) +{ + return + (rLeft.mnHorAlign == rRight.mnHorAlign) && (rLeft.mnVerAlign == rRight.mnVerAlign) && + (rLeft.mnTextDir == rRight.mnTextDir) && (rLeft.mnOrient == rRight.mnOrient) && + (rLeft.mnRotation == rRight.mnRotation) && (rLeft.mnIndent == rRight.mnIndent) && + (rLeft.mbLineBreak == rRight.mbLineBreak) && (rLeft.mbShrink == rRight.mbShrink); +} + +XclCellBorder::XclCellBorder() : + mnLeftColor( 0 ), + mnRightColor( 0 ), + mnTopColor( 0 ), + mnBottomColor( 0 ), + mnDiagColor( 0 ), + mnLeftLine( EXC_LINE_NONE ), + mnRightLine( EXC_LINE_NONE ), + mnTopLine( EXC_LINE_NONE ), + mnBottomLine( EXC_LINE_NONE ), + mnDiagLine( EXC_LINE_NONE ), + mbDiagTLtoBR( false ), + mbDiagBLtoTR( false ) +{ +} + +bool operator==( const XclCellBorder& rLeft, const XclCellBorder& rRight ) +{ + return + (rLeft.mnLeftColor == rRight.mnLeftColor) && (rLeft.mnRightColor == rRight.mnRightColor) && + (rLeft.mnTopColor == rRight.mnTopColor) && (rLeft.mnBottomColor == rRight.mnBottomColor) && + (rLeft.mnLeftLine == rRight.mnLeftLine) && (rLeft.mnRightLine == rRight.mnRightLine) && + (rLeft.mnTopLine == rRight.mnTopLine) && (rLeft.mnBottomLine == rRight.mnBottomLine) && + (rLeft.mnDiagColor == rRight.mnDiagColor) && (rLeft.mnDiagLine == rRight.mnDiagLine) && + (rLeft.mbDiagTLtoBR == rRight.mbDiagTLtoBR) && (rLeft.mbDiagBLtoTR == rRight.mbDiagBLtoTR); +} + +XclCellArea::XclCellArea() : + mnForeColor( EXC_COLOR_WINDOWTEXT ), + mnBackColor( EXC_COLOR_WINDOWBACK ), + mnPattern( EXC_PATT_NONE ) +{ +} + +XclCellArea::XclCellArea(sal_uInt8 nPattern) : + mnForeColor( EXC_COLOR_WINDOWTEXT ), + mnBackColor( EXC_COLOR_WINDOWBACK ), + mnPattern( nPattern ) +{ +} + +bool XclCellArea::IsTransparent() const +{ + return (mnPattern == EXC_PATT_NONE) && (mnBackColor == EXC_COLOR_WINDOWBACK); +} + +bool operator==( const XclCellArea& rLeft, const XclCellArea& rRight ) +{ + return + (rLeft.mnForeColor == rRight.mnForeColor) && (rLeft.mnBackColor == rRight.mnBackColor) && + (rLeft.mnPattern == rRight.mnPattern); +} + +XclXFBase::XclXFBase( bool bCellXF ) : + mnParent( bCellXF ? EXC_XF_DEFAULTSTYLE : EXC_XF_STYLEPARENT ), + mbCellXF( bCellXF ) +{ + SetAllUsedFlags( false ); +} + +XclXFBase::~XclXFBase() +{ +} + +void XclXFBase::SetAllUsedFlags( bool bUsed ) +{ + mbProtUsed = mbFontUsed = mbFmtUsed = mbAlignUsed = mbBorderUsed = mbAreaUsed = bUsed; +} + +bool XclXFBase::HasUsedFlags() const +{ + return mbProtUsed || mbFontUsed || mbFmtUsed || mbAlignUsed || mbBorderUsed || mbAreaUsed; +} + +bool XclXFBase::Equals( const XclXFBase& rCmp ) const +{ + return + (mbCellXF == rCmp.mbCellXF) && (mnParent == rCmp.mnParent) && + (mbProtUsed == rCmp.mbProtUsed) && (mbFontUsed == rCmp.mbFontUsed) && + (mbFmtUsed == rCmp.mbFmtUsed) && (mbAlignUsed == rCmp.mbAlignUsed) && + (mbBorderUsed == rCmp.mbBorderUsed) && (mbAreaUsed == rCmp.mbAreaUsed); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xltoolbar.cxx b/sc/source/filter/excel/xltoolbar.cxx new file mode 100644 index 000000000..efb54925b --- /dev/null +++ b/sc/source/filter/excel/xltoolbar.cxx @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 "xltoolbar.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; + +typedef std::map< sal_Int16, OUString > IdToString; + +namespace { + +class MSOExcelCommandConvertor : public MSOCommandConvertor +{ + IdToString msoToOOcmd; + IdToString tcidToOOcmd; +public: + MSOExcelCommandConvertor(); + virtual OUString MSOCommandToOOCommand( sal_Int16 msoCmd ) override; + virtual OUString MSOTCIDToOOCommand( sal_Int16 key ) override; +}; + +} + +MSOExcelCommandConvertor::MSOExcelCommandConvertor() +{ +/* + // mso command id to ooo command string + // #FIXME and *HUNDREDS* of id's to added here + msoToOOcmd[ 0x20b ] = ".uno:CloseDoc"; + msoToOOcmd[ 0x50 ] = ".uno:Open"; + + // mso tcid to ooo command string + // #FIXME and *HUNDREDS* of id's to added here + tcidToOOcmd[ 0x9d9 ] = ".uno:Print"; +*/ +} + +OUString MSOExcelCommandConvertor::MSOCommandToOOCommand( sal_Int16 key ) +{ + OUString sResult; + IdToString::iterator it = msoToOOcmd.find( key ); + if ( it != msoToOOcmd.end() ) + sResult = it->second; + return sResult; +} + +OUString MSOExcelCommandConvertor::MSOTCIDToOOCommand( sal_Int16 key ) +{ + OUString sResult; + IdToString::iterator it = tcidToOOcmd.find( key ); + if ( it != tcidToOOcmd.end() ) + sResult = it->second; + return sResult; +} + +CTBS::CTBS() : bSignature(0), bVersion(0), reserved1(0), reserved2(0), reserved3(0), ctb(0), ctbViews(0), ictbView(0) +{ +} + +ScCTB::ScCTB(sal_uInt16 nNum ) : nViews( nNum ), ectbid(0) +{ +} + +bool ScCTB::Read( SvStream &rS ) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + tb.Read( rS ); + + { + const size_t nMinRecordSize = 20; // TBVisualData reads 20 bytes + const size_t nMaxPossibleRecords = rS.remainingSize() / nMinRecordSize; + if (nViews > nMaxPossibleRecords) + { + SAL_WARN("sc.filter", "ScCTB::Read more entries claimed than stream could contain"); + return false; + } + } + + for ( sal_uInt16 index = 0; index < nViews; ++index ) + { + TBVisualData aVisData; + aVisData.Read( rS ); + rVisualData.push_back( aVisData ); + } + rS.ReadUInt32( ectbid ); + + sal_Int16 nCL = tb.getcCL(); + if (nCL > 0) + { + auto nIndexes = o3tl::make_unsigned(nCL); + + const size_t nMinRecordSize = 11; // ScTBC's TBCHeader reads min 11 bytes + const size_t nMaxPossibleRecords = rS.remainingSize() / nMinRecordSize; + if (nIndexes > nMaxPossibleRecords) + { + SAL_WARN("sc.filter", "ScCTB::Read more entries claimed than stream could contain"); + return false; + } + + for (decltype(nIndexes) index = 0; index < nIndexes; ++index) + { + ScTBC aTBC; + aTBC.Read( rS ); + rTBC.push_back( aTBC ); + } + } + + return true; +} + +#ifdef DEBUG_SC_EXCEL +void ScCTB::Print( FILE* fp ) +{ + Indent a; + indent_printf( fp, "[ 0x%x ] ScCTB -- dump\n", nOffSet ); + indent_printf( fp, " nViews 0x%x\n", nViews); + tb.Print( fp ); + + sal_Int32 counter = 0; + for ( auto& rItem : rVisualData ) + { + indent_printf( fp, " TBVisualData [%d]\n", counter++ ); + Indent b; + rItem.Print( fp ); + } + indent_printf( fp, " ectbid 0x%x\n", ectbid); + counter = 0; + for ( auto& rItem : rTBC ) + { + indent_printf( fp, " ScTBC [%d]\n", counter++); + Indent c; + rItem.Print( fp ); + } +} +#endif + +bool ScCTB::IsMenuToolbar() const +{ + return tb.IsMenuToolbar(); +} + +bool ScCTB::ImportMenuTB( ScCTBWrapper& rWrapper, const css::uno::Reference< css::container::XIndexContainer >& xMenuDesc, CustomToolBarImportHelper& helper ) +{ + for ( auto& rItem : rTBC ) + { + if ( !rItem.ImportToolBarControl( rWrapper, xMenuDesc, helper, IsMenuToolbar() ) ) + return false; + } + return true; +} + +bool ScCTB::ImportCustomToolBar( ScCTBWrapper& rWrapper, CustomToolBarImportHelper& helper ) +{ + + bool bRes = false; + try + { + if ( !tb.IsEnabled() ) + return true; // didn't fail, just ignoring + + // Create default setting + uno::Reference< container::XIndexContainer > xIndexContainer( helper.getCfgManager()->createSettings(), uno::UNO_SET_THROW ); + uno::Reference< container::XIndexAccess > xIndexAccess( xIndexContainer, uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xProps( xIndexContainer, uno::UNO_QUERY_THROW ); + WString& name = tb.getName(); + // set UI name for toolbar + xProps->setPropertyValue("UIName", uno::Any( name.getString() ) ); + + OUString sToolBarName = "private:resource/toolbar/custom_" + name.getString(); + for ( auto& rItem : rTBC ) + { + if ( !rItem.ImportToolBarControl( rWrapper, xIndexContainer, helper, IsMenuToolbar() ) ) + return false; + } + + helper.getCfgManager()->insertSettings( sToolBarName, xIndexAccess ); + helper.applyIcons(); + + uno::Reference< ui::XUIConfigurationPersistence > xPersistence( helper.getCfgManager()->getImageManager(), uno::UNO_QUERY_THROW ); + xPersistence->store(); + + xPersistence.set( helper.getCfgManager(), uno::UNO_QUERY_THROW ); + xPersistence->store(); + + bRes = true; + } + catch( uno::Exception& ) + { + bRes = false; + } + return bRes; +} +bool CTBS::Read( SvStream &rS ) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + rS.ReadUChar( bSignature ).ReadUChar( bVersion ).ReadUInt16( reserved1 ).ReadUInt16( reserved2 ).ReadUInt16( reserved3 ).ReadUInt16( ctb ).ReadUInt16( ctbViews ).ReadUInt16( ictbView ); + return true; +} + +#ifdef DEBUG_SC_EXCEL +void CTBS::Print( FILE* fp ) +{ + Indent a; + indent_printf( fp, "[ 0x%x ] CTBS -- dump\n", nOffSet ); + + indent_printf( fp, " bSignature 0x%x\n", bSignature); + indent_printf( fp, " bVersion 0x%x\n", bVersion); + + indent_printf( fp, " reserved1 0x%x\n", reserved1 ); + indent_printf( fp, " reserved2 0x%x\n", reserved2 ); + indent_printf( fp, " reserved3 0x%x\n", reserved3 ); + + indent_printf( fp, " ctb 0x%x\n", ctb ); + indent_printf( fp, " ctbViews 0x%x\n", ctbViews ); + indent_printf( fp, " ictbView 0x%x\n", ictbView ); +} +#endif + +ScTBC::ScTBC() +{ +} + +bool +ScTBC::Read(SvStream &rS) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + if ( !tbch.Read( rS ) ) + return false; + sal_uInt16 tcid = tbch.getTcID(); + sal_uInt8 tct = tbch.getTct(); + if ( ( tcid != 0x0001 && tcid != 0x06CC && tcid != 0x03D8 && tcid != 0x03EC && tcid != 0x1051 ) && ( ( tct > 0 && tct < 0x0B ) || ( ( tct > 0x0B && tct < 0x10 ) || tct == 0x15 ) ) ) + { + tbcCmd = std::make_shared(); + if ( ! tbcCmd->Read( rS ) ) + return false; + } + if ( tct != 0x16 ) + { + tbcd = std::make_shared( tbch ); + if ( !tbcd->Read( rS ) ) + return false; + } + return true; +} + +#ifdef DEBUG_SC_EXCEL +void +ScTBC::Print(FILE* fp) +{ + Indent a; + indent_printf( fp, "[ 0x%x ] ScTBC -- dump\n", nOffSet ); + tbch.Print( fp ); + if ( tbcCmd.get() ) + tbcCmd->Print( fp ); + if ( tbcd.get() ) + tbcd->Print( fp ); +} +#endif + +bool ScTBC::ImportToolBarControl( ScCTBWrapper& rWrapper, const css::uno::Reference< css::container::XIndexContainer >& toolbarcontainer, CustomToolBarImportHelper& helper, bool bIsMenuToolbar ) +{ + // how to identify built-in-command ? +// bool bBuiltin = false; + if ( tbcd ) + { + std::vector< css::beans::PropertyValue > props; + bool bBeginGroup = false; + tbcd->ImportToolBarControl( helper, props, bBeginGroup, bIsMenuToolbar ); + TBCMenuSpecific* pMenu = tbcd->getMenuSpecific(); + if ( pMenu ) + { + // search for ScCTB with the appropriate name ( it contains the + // menu items, although we cannot import ( or create ) a menu on + // a custom toolbar we can import the menu items in a separate + // toolbar ( better than nothing ) + ScCTB* pCustTB = rWrapper.GetCustomizationData( pMenu->Name() ); + if ( pCustTB ) + { + rtl::Reference< comphelper::IndexedPropertyValuesContainer > xMenuDesc = new comphelper::IndexedPropertyValuesContainer(); + if ( !pCustTB->ImportMenuTB( rWrapper, xMenuDesc, helper ) ) + return false; + if ( !bIsMenuToolbar ) + { + if ( !helper.createMenu( pMenu->Name(), xMenuDesc ) ) + return false; + } + else + { + beans::PropertyValue aProp; + aProp.Name = "ItemDescriptorContainer"; + aProp.Value <<= uno::Reference< container::XIndexContainer >(xMenuDesc); + props.push_back( aProp ); + } + } + } + + if ( bBeginGroup ) + { + // insert spacer + uno::Sequence sProps{ comphelper::makePropertyValue("Type", + ui::ItemType::SEPARATOR_LINE) }; + toolbarcontainer->insertByIndex( toolbarcontainer->getCount(), uno::Any( sProps ) ); + } + toolbarcontainer->insertByIndex( toolbarcontainer->getCount(), uno::Any( comphelper::containerToSequence(props) ) ); + } + return true; +} + +#ifdef DEBUG_SC_EXCEL +void +TBCCmd::Print(FILE* fp) +{ + Indent a; + indent_printf( fp, " TBCCmd -- dump\n" ); + indent_printf( fp, " cmdID 0x%x\n", cmdID ); + indent_printf( fp, " A ( fHideDrawing ) %s\n", A ? "true" : "false" ); + indent_printf( fp, " B ( reserved - ignored ) %s\n", A ? "true" : "false" ); + indent_printf( fp, " cmdType 0x%x\n", cmdType ); + indent_printf( fp, " C ( reserved - ignored ) %s\n", A ? "true" : "false" ); + indent_printf( fp, " reserved3 0x%x\n", reserved3 ); +} +#endif + +bool TBCCmd::Read( SvStream &rS ) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + rS.ReadUInt16( cmdID ); + sal_uInt16 temp; + rS.ReadUInt16( temp ); + A = (temp & 0x8000 ) == 0x8000; + B = (temp & 0x4000) == 0x4000; + cmdType = ( temp & 0x3E00 ) >> 9; + C = ( temp & 0x100 ) == 0x100; + reserved3 = ( temp & 0xFF ); + return true; +} + +ScCTBWrapper::ScCTBWrapper() +{ +} + +ScCTBWrapper::~ScCTBWrapper() +{ +} + +bool +ScCTBWrapper::Read( SvStream &rS) +{ + SAL_INFO("sc.filter", "stream pos " << rS.Tell()); + nOffSet = rS.Tell(); + if (!ctbSet.Read(rS)) + return false; + + //ScCTB is 1 TB which is min 15bytes, nViews TBVisualData which is min 20bytes + //and one 32bit number (4 bytes) + const size_t nMinRecordSize = 19 + o3tl::sanitizing_min(ctbSet.ctbViews * 20, 0); + const size_t nMaxPossibleRecords = rS.remainingSize()/nMinRecordSize; + if (ctbSet.ctb > nMaxPossibleRecords) + return false; + + for ( sal_uInt16 index = 0; index < ctbSet.ctb; ++index ) + { + ScCTB aCTB( ctbSet.ctbViews ); + if ( !aCTB.Read( rS ) ) + return false; + rCTB.push_back( aCTB ); + } + return true; +} + +#ifdef DEBUG_SC_EXCEL +void +ScCTBWrapper::Print( FILE* fp ) +{ + Indent a; + indent_printf( fp, "[ 0x%x ] ScCTBWrapper -- dump\n", nOffSet ); + ctbSet.Print( fp ); + for ( auto& rItem : rCTB ) + { + Indent b; + rItem.Print( fp ); + } +} +#endif + +ScCTB* ScCTBWrapper::GetCustomizationData( const OUString& sTBName ) +{ + ScCTB* pCTB = nullptr; + auto it = std::find_if(rCTB.begin(), rCTB.end(), [&sTBName](ScCTB& rItem) { return rItem.GetName() == sTBName; }); + if (it != rCTB.end()) + pCTB = &(*it); + return pCTB; +} + +void ScCTBWrapper::ImportCustomToolBar( SfxObjectShell& rDocSh ) +{ + if(rCTB.empty()) + return; + + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference< ui::XModuleUIConfigurationManagerSupplier > xAppCfgSupp( ui::theModuleUIConfigurationManagerSupplier::get(xContext) ); + + for ( auto& rItem : rCTB ) + { + // for each customtoolbar + CustomToolBarImportHelper helper( rDocSh, xAppCfgSupp->getUIConfigurationManager( "com.sun.star.sheet.SpreadsheetDocument" ) ); + helper.setMSOCommandMap( new MSOExcelCommandConvertor() ); + // Ignore menu toolbars, excel doesn't ( afaics ) store + // menu customizations ( but you can have menus in a customtoolbar + // such menus will be dealt with when they are encountered + // as part of importing the appropriate MenuSpecific toolbar control ) + + if ( !rItem.IsMenuToolbar() && !rItem.ImportCustomToolBar( *this, helper ) ) + return; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xltoolbar.hxx b/sc/source/filter/excel/xltoolbar.hxx new file mode 100644 index 000000000..f7da78b2f --- /dev/null +++ b/sc/source/filter/excel/xltoolbar.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 + +namespace com::sun::star::container { class XIndexContainer; } + +class ScCTBWrapper; +// hmm I don't normally use these packed structures +// but... hey always good to do something different +class TBCCmd : public TBBase +{ +public: + TBCCmd() : cmdID(0), A(false), B(false), cmdType(0), C(false), reserved3(0) + {} + sal_uInt16 cmdID; + bool A:1; + bool B:1; + sal_uInt16 cmdType:5; + bool C:1; + sal_uInt16 reserved3:8; + bool Read( SvStream& rS ) override; +#ifdef DEBUG_SC_EXCEL + virtual void Print(FILE* fp) override; +#endif +}; + +class ScTBC : public TBBase +{ + TBCHeader tbch; + std::shared_ptr tbcCmd; // optional + std::shared_ptr tbcd; +public: + ScTBC(); +#ifdef DEBUG_SC_EXCEL + virtual void Print( FILE* ) override; +#endif + bool Read(SvStream &rS) override; + bool ImportToolBarControl( ScCTBWrapper&, const css::uno::Reference< css::container::XIndexContainer >& toolbarcontainer, CustomToolBarImportHelper& helper, bool bIsMenuBar ); +}; + +class ScCTB : public TBBase +{ + sal_uInt16 nViews; + TB tb; + std::vector rVisualData; + sal_uInt32 ectbid; + std::vector< ScTBC > rTBC; +public: + explicit ScCTB(sal_uInt16); +#ifdef DEBUG_SC_EXCEL + virtual void Print( FILE* ) override; +#endif + bool Read(SvStream &rS) override; + bool IsMenuToolbar() const; + bool ImportCustomToolBar( ScCTBWrapper&, CustomToolBarImportHelper& ); + bool ImportMenuTB( ScCTBWrapper&, const css::uno::Reference< css::container::XIndexContainer >&, CustomToolBarImportHelper& ); + const OUString& GetName() { return tb.getName().getString(); } + +}; + +class CTBS : public TBBase +{ +public: + sal_uInt8 bSignature; + sal_uInt8 bVersion; + sal_uInt16 reserved1; + sal_uInt16 reserved2; + sal_uInt16 reserved3; + sal_uInt16 ctb; + sal_uInt16 ctbViews; + sal_uInt16 ictbView; + CTBS(const CTBS&); + CTBS& operator = ( const CTBS&); + CTBS(); +#ifdef DEBUG_SC_EXCEL + virtual void Print( FILE* ) override; +#endif + bool Read(SvStream &rS) override; +}; + +class ScCTBWrapper : public TBBase +{ + CTBS ctbSet; + + std::vector< ScCTB > rCTB; + +public: + ScCTBWrapper(); + virtual ~ScCTBWrapper() override; + bool Read(SvStream &rS) override; +#ifdef DEBUG_SC_EXCEL + virtual void Print( FILE* ) override; +#endif + void ImportCustomToolBar( SfxObjectShell& rDocSh ); + ScCTB* GetCustomizationData( const OUString& name ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xltools.cxx b/sc/source/filter/excel/xltools.cxx new file mode 100644 index 000000000..3a69e7da0 --- /dev/null +++ b/sc/source/filter/excel/xltools.cxx @@ -0,0 +1,744 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// GUID import/export + +XclGuid::XclGuid() + : mpnData{} +{ +} + +XclGuid::XclGuid( + sal_uInt32 nData1, sal_uInt16 nData2, sal_uInt16 nData3, + sal_uInt8 nData41, sal_uInt8 nData42, sal_uInt8 nData43, sal_uInt8 nData44, + sal_uInt8 nData45, sal_uInt8 nData46, sal_uInt8 nData47, sal_uInt8 nData48 ) +{ + // convert to little endian -> makes streaming easy + UInt32ToSVBT32( nData1, mpnData ); + ShortToSVBT16( nData2, mpnData + 4 ); + ShortToSVBT16( nData3, mpnData + 6 ); + mpnData[ 8 ] = nData41; + mpnData[ 9 ] = nData42; + mpnData[ 10 ] = nData43; + mpnData[ 11 ] = nData44; + mpnData[ 12 ] = nData45; + mpnData[ 13 ] = nData46; + mpnData[ 14 ] = nData47; + mpnData[ 15 ] = nData48; +} + +bool operator==( const XclGuid& rCmp1, const XclGuid& rCmp2 ) +{ + return ::std::equal( rCmp1.mpnData, std::end( rCmp1.mpnData ), rCmp2.mpnData ); +} + +XclImpStream& operator>>( XclImpStream& rStrm, XclGuid& rGuid ) +{ + rStrm.Read( rGuid.mpnData, 16 ); // mpnData always in little endian + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const XclGuid& rGuid ) +{ + rStrm.Write( rGuid.mpnData, 16 ); // mpnData already in little endian + return rStrm; +} + +// Excel Tools + +// GUID's +const XclGuid XclTools::maGuidStdLink( + 0x79EAC9D0, 0xBAF9, 0x11CE, 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B ); + +const XclGuid XclTools::maGuidUrlMoniker( + 0x79EAC9E0, 0xBAF9, 0x11CE, 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B ); + +const XclGuid XclTools::maGuidFileMoniker( + 0x00000303, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 ); + +// numeric conversion + +double XclTools::GetDoubleFromRK( sal_Int32 nRKValue ) +{ + union + { + double fVal; + sal_math_Double smD; + }; + fVal = 0.0; + + if( ::get_flag( nRKValue, EXC_RK_INTFLAG ) ) + { + sal_Int32 nTemp = nRKValue >> 2; + ::set_flag< sal_Int32 >( nTemp, 0xE0000000, nRKValue < 0 ); + fVal = nTemp; + } + else + { + smD.w32_parts.msw = nRKValue & EXC_RK_VALUEMASK; + } + + if( ::get_flag( nRKValue, EXC_RK_100FLAG ) ) + fVal /= 100.0; + + return fVal; +} + +bool XclTools::GetRKFromDouble( sal_Int32& rnRKValue, double fValue ) +{ + double fFrac, fInt; + + // integer + fFrac = modf( fValue, &fInt ); + if( (fFrac == 0.0) && (fInt >= -536870912.0) && (fInt <= 536870911.0) ) // 2^29 + { + rnRKValue + = static_cast( + static_cast(static_cast(fInt)) << 2) + | EXC_RK_INT; + return true; + } + + // integer/100 + fFrac = modf( fValue * 100.0, &fInt ); + if( (fFrac == 0.0) && (fInt >= -536870912.0) && (fInt <= 536870911.0) ) + { + rnRKValue + = static_cast( + static_cast(static_cast(fInt)) << 2) + | EXC_RK_INT100; + return true; + } + + // double + return false; +} + +Degree100 XclTools::GetScRotation( sal_uInt16 nXclRot, Degree100 nRotForStacked ) +{ + if( nXclRot == EXC_ROT_STACKED ) + return nRotForStacked; + OSL_ENSURE( nXclRot <= 180, "XclTools::GetScRotation - illegal rotation angle" ); + return Degree100(static_cast< sal_Int32 >( (nXclRot <= 180) ? (100 * ((nXclRot > 90) ? (450 - nXclRot) : nXclRot)) : 0 )); +} + +sal_uInt8 XclTools::GetXclRotation( Degree100 nScRot ) +{ + sal_Int32 nXclRot = nScRot.get() / 100; + if( (0 <= nXclRot) && (nXclRot <= 90) ) + return static_cast< sal_uInt8 >( nXclRot ); + if( nXclRot < 180 ) + return static_cast< sal_uInt8 >( 270 - nXclRot ); + if( nXclRot < 270 ) + return static_cast< sal_uInt8 >( nXclRot - 180 ); + if( nXclRot < 360 ) + return static_cast< sal_uInt8 >( 450 - nXclRot ); + return 0; +} + +sal_uInt8 XclTools::GetXclRotFromOrient( sal_uInt8 nXclOrient ) +{ + switch( nXclOrient ) + { + case EXC_ORIENT_NONE: return EXC_ROT_NONE; + case EXC_ORIENT_STACKED: return EXC_ROT_STACKED; + case EXC_ORIENT_90CCW: return EXC_ROT_90CCW; + case EXC_ORIENT_90CW: return EXC_ROT_90CW; + default: OSL_FAIL( "XclTools::GetXclRotFromOrient - unknown text orientation" ); + } + return EXC_ROT_NONE; +} + +sal_uInt8 XclTools::GetXclOrientFromRot( sal_uInt16 nXclRot ) +{ + if( nXclRot == EXC_ROT_STACKED ) + return EXC_ORIENT_STACKED; + OSL_ENSURE( nXclRot <= 180, "XclTools::GetXclOrientFromRot - unknown text rotation" ); + if( (45 < nXclRot) && (nXclRot <= 90) ) + return EXC_ORIENT_90CCW; + if( (135 < nXclRot) && (nXclRot <= 180) ) + return EXC_ORIENT_90CW; + return EXC_ORIENT_NONE; +} + +sal_uInt8 XclTools::GetXclErrorCode( FormulaError nScError ) +{ + switch( nScError ) + { + case FormulaError::IllegalArgument: return EXC_ERR_VALUE; + case FormulaError::IllegalFPOperation: return EXC_ERR_NUM; // maybe DIV/0 or NUM... + case FormulaError::DivisionByZero: return EXC_ERR_DIV0; + case FormulaError::IllegalParameter: return EXC_ERR_VALUE; + case FormulaError::PairExpected: return EXC_ERR_VALUE; + case FormulaError::OperatorExpected: return EXC_ERR_VALUE; + case FormulaError::VariableExpected: return EXC_ERR_VALUE; + case FormulaError::ParameterExpected: return EXC_ERR_VALUE; + case FormulaError::NoValue: return EXC_ERR_VALUE; + case FormulaError::CircularReference: return EXC_ERR_VALUE; + case FormulaError::NoCode: return EXC_ERR_NULL; + case FormulaError::NoRef: return EXC_ERR_REF; + case FormulaError::NoName: return EXC_ERR_NAME; + case FormulaError::NoAddin: return EXC_ERR_NAME; + case FormulaError::NoMacro: return EXC_ERR_NAME; + case FormulaError::NotAvailable: return EXC_ERR_NA; + default: break; + } + return EXC_ERR_NA; +} + +FormulaError XclTools::GetScErrorCode( sal_uInt8 nXclError ) +{ + switch( nXclError ) + { + case EXC_ERR_NULL: return FormulaError::NoCode; + case EXC_ERR_DIV0: return FormulaError::DivisionByZero; + case EXC_ERR_VALUE: return FormulaError::NoValue; + case EXC_ERR_REF: return FormulaError::NoRef; + case EXC_ERR_NAME: return FormulaError::NoName; + case EXC_ERR_NUM: return FormulaError::IllegalFPOperation; + case EXC_ERR_NA: return FormulaError::NotAvailable; + default: OSL_FAIL( "XclTools::GetScErrorCode - unknown error code" ); + } + return FormulaError::NotAvailable; +} + +double XclTools::ErrorToDouble( sal_uInt8 nXclError ) +{ + return CreateDoubleError(GetScErrorCode( nXclError )); +} + +XclBoolError XclTools::ErrorToEnum( double& rfDblValue, bool bErrOrBool, sal_uInt8 nValue ) +{ + XclBoolError eType; + if( bErrOrBool ) + { + // error value + switch( nValue ) + { + case EXC_ERR_NULL: eType = xlErrNull; break; + case EXC_ERR_DIV0: eType = xlErrDiv0; break; + case EXC_ERR_VALUE: eType = xlErrValue; break; + case EXC_ERR_REF: eType = xlErrRef; break; + case EXC_ERR_NAME: eType = xlErrName; break; + case EXC_ERR_NUM: eType = xlErrNum; break; + case EXC_ERR_NA: eType = xlErrNA; break; + default: eType = xlErrUnknown; + } + rfDblValue = 0.0; + } + else + { + // Boolean value + eType = nValue ? xlErrTrue : xlErrFalse; + rfDblValue = nValue ? 1.0 : 0.0; + } + return eType; +} + +template static N to(double f) { return limit_cast(f + 0.5); } + +sal_uInt16 XclTools::GetTwipsFromInch( double fInches ) +{ + return to(o3tl::convert(fInches, o3tl::Length::in, o3tl::Length::twip)); +} + +sal_uInt16 XclTools::GetTwipsFromHmm( sal_Int32 nHmm ) +{ + return limit_cast(o3tl::convert(nHmm, o3tl::Length::mm100, o3tl::Length::twip)); +} + +double XclTools::GetInchFromTwips( sal_Int32 nTwips ) +{ + return o3tl::convert(nTwips, o3tl::Length::twip, o3tl::Length::in); +} + +double XclTools::GetInchFromHmm( sal_Int32 nHmm ) +{ + return o3tl::convert(nHmm, o3tl::Length::mm100, o3tl::Length::in); +} + +sal_Int32 XclTools::GetHmmFromInch( double fInches ) +{ + return to(o3tl::convert(fInches, o3tl::Length::in, o3tl::Length::mm100)); +} + +sal_Int32 XclTools::GetHmmFromTwips( sal_Int32 nTwips ) +{ + return limit_cast(o3tl::convert(nTwips, o3tl::Length::twip, o3tl::Length::mm100)); +} + +sal_uInt16 XclTools::GetScColumnWidth( sal_uInt16 nXclWidth, tools::Long nScCharWidth ) +{ + double fScWidth = static_cast< double >( nXclWidth ) / 256.0 * nScCharWidth - 0.5; + return limit_cast< sal_uInt16 >( fScWidth ); +} + +sal_uInt16 XclTools::GetXclColumnWidth( sal_uInt16 nScWidth, tools::Long nScCharWidth ) +{ + double fXclWidth = ( static_cast< double >( nScWidth ) + 0.5 ) * 256.0 / nScCharWidth; + return limit_cast< sal_uInt16 >( fXclWidth ); +} + +// takes font height in twips (1/20 pt = 1/1440 in) +// returns correction value in 1/256th of *digit width* of default font +double XclTools::GetXclDefColWidthCorrection( tools::Long nXclDefFontHeight ) +{ + // Excel uses *max digit width of default font* (W) as cell width unit. Also it has 5-pixel + // "correction" to cell widths (ECMA-376-1:2016 18.3.1.81): each cell has 1-pixel padding, then + // 3 pixels for the border (which may be 1-pixel - hairline - then it will have 2 additional + // 1-pixel spacings from each side; or e.g. 2 hairlines with 1-pixel spacing in the middle; or + // thick 3-pixel). Obviously, correction size entirely depends on pixel size (and it is actually + // different in Excel on monitors with different resolution). Thus actual (displayed/printed) + // cell widths consist of X*W+5px; stored in file is the X (or X*256 if 1/256th of digit width + // units are used) value. + // This formula apparently converts this 5-pixel correction to 1/256th of digit width units. + // Looks like it is created from + // + // 5 * 256 * 1440 * 2.1333 / (96 * max(N-15, 60)) + 50.0 + // + // where 5 - pixel correction; 256 - used to produce 1/256th of digit width; 1440 - used to + // convert font height N (in twips) to inches; 2.1333 - an (empirical?) quotient to convert + // font *height* into digit *width*; 96 - "standard" monitor resolution (DPI). + // Additionally the formula uses 15 (of unknown origin), 60 (minimal font height 3 pt), and + // 50.0 (also of unknown origin). + // + // TODO: convert this to take font digit width directly (and possibly DPI?), to avoid guessing + // the digit width and pixel size. Or DPI might stay 96, to not follow Excel dependency on DPI + // in addition to used font, and have absolute size of the correction fixed 5/96 in. + return 40960.0 / ::std::max( nXclDefFontHeight - 15, tools::Long(60) ) + 50.0; +} + +// formatting + +Color XclTools::GetPatternColor( const Color& rPattColor, const Color& rBackColor, sal_uInt16 nXclPattern ) +{ + // 0x00 == 0% transparence (full rPattColor) + // 0x80 == 100% transparence (full rBackColor) + static const sal_uInt8 pnRatioTable[] = + { + 0x80, 0x00, 0x40, 0x20, 0x60, 0x40, 0x40, 0x40, // 00 - 07 + 0x40, 0x40, 0x20, 0x60, 0x60, 0x60, 0x60, 0x48, // 08 - 15 + 0x50, 0x70, 0x78 // 16 - 18 + }; + return (nXclPattern < SAL_N_ELEMENTS( pnRatioTable )) ? + ScfTools::GetMixedColor( rPattColor, rBackColor, pnRatioTable[ nXclPattern ] ) : rPattColor; +} + +// text encoding + +namespace { + +const struct XclCodePageEntry +{ + sal_uInt16 mnCodePage; + rtl_TextEncoding meTextEnc; +} +pCodePageTable[] = +{ + { 437, RTL_TEXTENCODING_IBM_437 }, // OEM US +// { 720, RTL_TEXTENCODING_IBM_720 }, // OEM Arabic + { 737, RTL_TEXTENCODING_IBM_737 }, // OEM Greek + { 775, RTL_TEXTENCODING_IBM_775 }, // OEM Baltic + { 850, RTL_TEXTENCODING_IBM_850 }, // OEM Latin I + { 852, RTL_TEXTENCODING_IBM_852 }, // OEM Latin II (Central European) + { 855, RTL_TEXTENCODING_IBM_855 }, // OEM Cyrillic + { 857, RTL_TEXTENCODING_IBM_857 }, // OEM Turkish +// { 858, RTL_TEXTENCODING_IBM_858 }, // OEM Multilingual Latin I with Euro + { 860, RTL_TEXTENCODING_IBM_860 }, // OEM Portuguese + { 861, RTL_TEXTENCODING_IBM_861 }, // OEM Icelandic + { 862, RTL_TEXTENCODING_IBM_862 }, // OEM Hebrew + { 863, RTL_TEXTENCODING_IBM_863 }, // OEM Canadian (French) + { 864, RTL_TEXTENCODING_IBM_864 }, // OEM Arabic + { 865, RTL_TEXTENCODING_IBM_865 }, // OEM Nordic + { 866, RTL_TEXTENCODING_IBM_866 }, // OEM Cyrillic (Russian) + { 869, RTL_TEXTENCODING_IBM_869 }, // OEM Greek (Modern) + { 874, RTL_TEXTENCODING_MS_874 }, // MS Windows Thai + { 932, RTL_TEXTENCODING_MS_932 }, // MS Windows Japanese Shift-JIS + { 936, RTL_TEXTENCODING_MS_936 }, // MS Windows Chinese Simplified GBK + { 949, RTL_TEXTENCODING_MS_949 }, // MS Windows Korean (Wansung) + { 950, RTL_TEXTENCODING_MS_950 }, // MS Windows Chinese Traditional BIG5 + { 1200, RTL_TEXTENCODING_DONTKNOW }, // Unicode (BIFF8) - return *_DONTKNOW to preserve old code page + { 1250, RTL_TEXTENCODING_MS_1250 }, // MS Windows Latin II (Central European) + { 1251, RTL_TEXTENCODING_MS_1251 }, // MS Windows Cyrillic + { 1252, RTL_TEXTENCODING_MS_1252 }, // MS Windows Latin I (BIFF4-BIFF8) + { 1253, RTL_TEXTENCODING_MS_1253 }, // MS Windows Greek + { 1254, RTL_TEXTENCODING_MS_1254 }, // MS Windows Turkish + { 1255, RTL_TEXTENCODING_MS_1255 }, // MS Windows Hebrew + { 1256, RTL_TEXTENCODING_MS_1256 }, // MS Windows Arabic + { 1257, RTL_TEXTENCODING_MS_1257 }, // MS Windows Baltic + { 1258, RTL_TEXTENCODING_MS_1258 }, // MS Windows Vietnamese + { 1361, RTL_TEXTENCODING_MS_1361 }, // MS Windows Korean (Johab) + { 10000, RTL_TEXTENCODING_APPLE_ROMAN }, // Apple Roman + { 32768, RTL_TEXTENCODING_APPLE_ROMAN }, // Apple Roman + { 32769, RTL_TEXTENCODING_MS_1252 } // MS Windows Latin I (BIFF2-BIFF3) +}; +const XclCodePageEntry* const pCodePageTableEnd = std::end(pCodePageTable); + +struct XclCodePageEntry_CPPred +{ + explicit XclCodePageEntry_CPPred( sal_uInt16 nCodePage ) : mnCodePage( nCodePage ) {} + bool operator()( const XclCodePageEntry& rEntry ) const { return rEntry.mnCodePage == mnCodePage; } + sal_uInt16 mnCodePage; +}; + +struct XclCodePageEntry_TEPred +{ + explicit XclCodePageEntry_TEPred( rtl_TextEncoding eTextEnc ) : meTextEnc( eTextEnc ) {} + bool operator()( const XclCodePageEntry& rEntry ) const { return rEntry.meTextEnc == meTextEnc; } + rtl_TextEncoding meTextEnc; +}; + +} // namespace + +rtl_TextEncoding XclTools::GetTextEncoding( sal_uInt16 nCodePage ) +{ + const XclCodePageEntry* pEntry = ::std::find_if( pCodePageTable, pCodePageTableEnd, XclCodePageEntry_CPPred( nCodePage ) ); + if( pEntry == pCodePageTableEnd ) + { + SAL_WARN("sc", "XclTools::GetTextEncoding - unknown code page: 0x" << std::hex << nCodePage ); + return RTL_TEXTENCODING_DONTKNOW; + } + return pEntry->meTextEnc; +} + +sal_uInt16 XclTools::GetXclCodePage( rtl_TextEncoding eTextEnc ) +{ + if( eTextEnc == RTL_TEXTENCODING_UNICODE ) + return 1200; // for BIFF8 + + const XclCodePageEntry* pEntry = ::std::find_if( pCodePageTable, pCodePageTableEnd, XclCodePageEntry_TEPred( eTextEnc ) ); + if( pEntry == pCodePageTableEnd ) + { + SAL_WARN("sc", "XclTools::GetXclCodePage - unsupported text encoding: 0x" << std::hex << eTextEnc ); + return 1252; + } + return pEntry->mnCodePage; +} + +OUString XclTools::GetXclFontName( const OUString& rFontName ) +{ + // substitute with MS fonts + OUString aNewName = GetSubsFontName(rFontName, SubsFontFlags::ONLYONE | SubsFontFlags::MS); + return aNewName.isEmpty() ? rFontName : aNewName; +} + +// built-in defined names +const char maDefNamePrefix[] = "Excel_BuiltIn_"; /// Prefix for built-in defined names. +const char maDefNamePrefixXml[] = "_xlnm."; /// Prefix for built-in defined names for OOX + +const char* const ppcDefNames[] = +{ + "Consolidate_Area", + "Auto_Open", + "Auto_Close", + "Extract", + "Database", + "Criteria", + "Print_Area", + "Print_Titles", + "Recorder", + "Data_Form", + "Auto_Activate", + "Auto_Deactivate", + "Sheet_Title", + "_FilterDatabase" +}; + +OUString XclTools::GetXclBuiltInDefName( sal_Unicode cBuiltIn ) +{ + OSL_ENSURE( SAL_N_ELEMENTS( ppcDefNames ) == EXC_BUILTIN_UNKNOWN, + "XclTools::GetXclBuiltInDefName - built-in defined name list modified" ); + + if( cBuiltIn < SAL_N_ELEMENTS( ppcDefNames ) ) + return OUString::createFromAscii(ppcDefNames[cBuiltIn]); + else + return OUString::number(cBuiltIn); +} + +OUString XclTools::GetBuiltInDefName( sal_Unicode cBuiltIn ) +{ + return maDefNamePrefix + GetXclBuiltInDefName(cBuiltIn); +} + +OUString XclTools::GetBuiltInDefNameXml( sal_Unicode cBuiltIn ) +{ + return maDefNamePrefixXml + GetXclBuiltInDefName(cBuiltIn); +} + +sal_Unicode XclTools::GetBuiltInDefNameIndex( const OUString& rDefName ) +{ + sal_Int32 nPrefixLen = 0; + if( rDefName.startsWithIgnoreAsciiCase( maDefNamePrefix ) ) + nPrefixLen = strlen(maDefNamePrefix); + else if( rDefName.startsWithIgnoreAsciiCase( maDefNamePrefixXml ) ) + nPrefixLen = strlen(maDefNamePrefixXml); + if( nPrefixLen > 0 ) + { + for( sal_Unicode cBuiltIn = 0; cBuiltIn < EXC_BUILTIN_UNKNOWN; ++cBuiltIn ) + { + OUString aBuiltInName(GetXclBuiltInDefName(cBuiltIn)); + sal_Int32 nBuiltInLen = aBuiltInName.getLength(); + if( rDefName.matchIgnoreAsciiCase( aBuiltInName, nPrefixLen ) ) + { + // name can be followed by underline or space character + sal_Int32 nNextCharPos = nPrefixLen + nBuiltInLen; + sal_Unicode cNextChar = (rDefName.getLength() > nNextCharPos) ? rDefName[nNextCharPos] : '\0'; + if( (cNextChar == '\0') || (cNextChar == ' ') || (cNextChar == '_') ) + return cBuiltIn; + } + } + } + return EXC_BUILTIN_UNKNOWN; +} + +// built-in style names + +const char maStyleNamePrefix1[] = "Excel_BuiltIn_"; /// Prefix for built-in cell style names. +const char maStyleNamePrefix2[] = "Excel Built-in "; /// Prefix for built-in cell style names from OOX filter. + +const char* const ppcStyleNames[] = +{ + "", // "Normal" not used directly, but localized "Default" + "RowLevel_", // outline level will be appended + "ColumnLevel_", // outline level will be appended + "Comma", + "Currency", + "Percent", + "Comma_0", + "Currency_0", + "Hyperlink", + "Followed_Hyperlink" +}; + +OUString XclTools::GetBuiltInStyleName( sal_uInt8 nStyleId, std::u16string_view rName, sal_uInt8 nLevel ) +{ + OUString aStyleName; + + if( nStyleId == EXC_STYLE_NORMAL ) // "Normal" becomes "Default" style + { + aStyleName = ScResId( STR_STYLENAME_STANDARD ); + } + else + { + OUStringBuffer aBuf(maStyleNamePrefix1); + if( nStyleId < SAL_N_ELEMENTS( ppcStyleNames ) ) + aBuf.appendAscii(ppcStyleNames[nStyleId]); + else if (!rName.empty()) + aBuf.append(rName); + else + aBuf.append(static_cast(nStyleId)); + + if( (nStyleId == EXC_STYLE_ROWLEVEL) || (nStyleId == EXC_STYLE_COLLEVEL) ) + aBuf.append(static_cast(nLevel+1)); + + aStyleName = aBuf.makeStringAndClear(); + } + + return aStyleName; +} + +bool XclTools::IsBuiltInStyleName( const OUString& rStyleName, sal_uInt8* pnStyleId, sal_Int32* pnNextChar ) +{ + // "Default" becomes "Normal" + if (rStyleName == ScResId(STR_STYLENAME_STANDARD)) + { + if( pnStyleId ) *pnStyleId = EXC_STYLE_NORMAL; + if( pnNextChar ) *pnNextChar = rStyleName.getLength(); + return true; + } + + // try the other built-in styles + sal_uInt8 nFoundId = 0; + sal_Int32 nNextChar = 0; + + sal_Int32 nPrefixLen = 0; + if( rStyleName.startsWithIgnoreAsciiCase( maStyleNamePrefix1 ) ) + nPrefixLen = strlen(maStyleNamePrefix1); + else if( rStyleName.startsWithIgnoreAsciiCase( maStyleNamePrefix2 ) ) + nPrefixLen = strlen(maStyleNamePrefix2); + if( nPrefixLen > 0 ) + { + for( sal_uInt8 nId = 0; nId < SAL_N_ELEMENTS( ppcStyleNames ); ++nId ) + { + if( nId != EXC_STYLE_NORMAL ) + { + OUString aShortName = OUString::createFromAscii(ppcStyleNames[nId]); + if( rStyleName.matchIgnoreAsciiCase( aShortName, nPrefixLen ) && + (nNextChar < nPrefixLen + aShortName.getLength())) + { + nFoundId = nId; + nNextChar = nPrefixLen + aShortName.getLength(); + } + } + } + } + + if( nNextChar > 0 ) + { + if( pnStyleId ) *pnStyleId = nFoundId; + if( pnNextChar ) *pnNextChar = nNextChar; + return true; + } + + if( pnStyleId ) *pnStyleId = EXC_STYLE_USERDEF; + if( pnNextChar ) *pnNextChar = 0; + return nPrefixLen > 0; // also return true for unknown built-in styles +} + +bool XclTools::GetBuiltInStyleId( sal_uInt8& rnStyleId, sal_uInt8& rnLevel, const OUString& rStyleName ) +{ + sal_uInt8 nStyleId; + sal_Int32 nNextChar; + if( IsBuiltInStyleName( rStyleName, &nStyleId, &nNextChar ) && (nStyleId != EXC_STYLE_USERDEF) ) + { + if( (nStyleId == EXC_STYLE_ROWLEVEL) || (nStyleId == EXC_STYLE_COLLEVEL) ) + { + std::u16string_view aLevel = rStyleName.subView(nNextChar); + sal_Int32 nLevel = o3tl::toInt32(aLevel); + if (std::u16string_view(OUString::number(nLevel)) == aLevel + && nLevel > 0 && nLevel <= EXC_STYLE_LEVELCOUNT) + { + rnStyleId = nStyleId; + rnLevel = static_cast< sal_uInt8 >( nLevel - 1 ); + return true; + } + } + else if( rStyleName.getLength() == nNextChar ) + { + rnStyleId = nStyleId; + rnLevel = EXC_STYLE_NOLEVEL; + return true; + } + } + + rnStyleId = EXC_STYLE_USERDEF; + rnLevel = EXC_STYLE_NOLEVEL; + return false; +} + +// conditional formatting style names + +const char maCFStyleNamePrefix1[] = "Excel_CondFormat_"; /// Prefix for cond. formatting style names. +const char maCFStyleNamePrefix2[] = "ConditionalStyle_"; /// Prefix for cond. formatting style names from OOX filter. +const char maCFStyleNamePrefix3[] = "ExtConditionalStyle_"; + +OUString XclTools::GetCondFormatStyleName( SCTAB nScTab, sal_Int32 nFormat, sal_uInt16 nCondition ) +{ + return maCFStyleNamePrefix1 + + OUString::number(static_cast(nScTab+1)) + + "_" + + OUString::number(static_cast(nFormat+1)) + + "_" + + OUString::number(static_cast(nCondition+1)); +} + +bool XclTools::IsCondFormatStyleName( const OUString& rStyleName ) +{ + if( rStyleName.startsWithIgnoreAsciiCase( maCFStyleNamePrefix1 ) ) + return true; + + if( rStyleName.startsWithIgnoreAsciiCase( maCFStyleNamePrefix2 ) ) + return true; + + if (rStyleName.startsWithIgnoreAsciiCase(maCFStyleNamePrefix3)) + return true; + + return false; +} + +// stream handling + +void XclTools::SkipSubStream( XclImpStream& rStrm ) +{ + bool bLoop = true; + while( bLoop && rStrm.StartNextRecord() ) + { + sal_uInt16 nRecId = rStrm.GetRecId(); + bLoop = nRecId != EXC_ID_EOF; + if( (nRecId == EXC_ID2_BOF) || (nRecId == EXC_ID3_BOF) || (nRecId == EXC_ID4_BOF) || (nRecId == EXC_ID5_BOF) ) + SkipSubStream( rStrm ); + } +} + +// Basic macro names + +const char maSbMacroPrefix[] = "vnd.sun.star.script:"; /// Prefix for StarBasic macros. +const char maSbMacroSuffix[] = "?language=Basic&location=document"; /// Suffix for StarBasic macros. + +OUString XclTools::GetSbMacroUrl( const OUString& rMacroName, SfxObjectShell* pDocShell ) +{ + OSL_ENSURE( !rMacroName.isEmpty(), "XclTools::GetSbMacroUrl - macro name is empty" ); + ::ooo::vba::MacroResolvedInfo aMacroInfo = ::ooo::vba::resolveVBAMacro( pDocShell, rMacroName ); + if( aMacroInfo.mbFound ) + return ::ooo::vba::makeMacroURL( aMacroInfo.msResolvedMacro ); + return OUString(); +} + +OUString XclTools::GetXclMacroName( const OUString& rSbMacroUrl ) +{ + sal_Int32 nSbMacroUrlLen = rSbMacroUrl.getLength(); + sal_Int32 nMacroNameLen = nSbMacroUrlLen - strlen(maSbMacroPrefix) - strlen(maSbMacroSuffix); + if( (nMacroNameLen > 0) && rSbMacroUrl.startsWithIgnoreAsciiCase( maSbMacroPrefix ) && + rSbMacroUrl.endsWithIgnoreAsciiCase( maSbMacroSuffix ) ) + { + sal_Int32 nPrjDot = rSbMacroUrl.indexOf( '.', strlen(maSbMacroPrefix) ) + 1; + return rSbMacroUrl.copy( nPrjDot, nSbMacroUrlLen - nPrjDot - strlen(maSbMacroSuffix) ); + } + return OUString(); +} + +// read/write colors + +XclImpStream& operator>>( XclImpStream& rStrm, Color& rColor ) +{ + sal_uInt8 nR = rStrm.ReaduInt8(); + sal_uInt8 nG = rStrm.ReaduInt8(); + sal_uInt8 nB = rStrm.ReaduInt8(); + rStrm.Ignore( 1 );//nD + rColor = Color( nR, nG, nB ); + return rStrm; +} + +XclExpStream& operator<<( XclExpStream& rStrm, const Color& rColor ) +{ + return rStrm << rColor.GetRed() << rColor.GetGreen() << rColor.GetBlue() << sal_uInt8( 0 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xltracer.cxx b/sc/source/filter/excel/xltracer.cxx new file mode 100644 index 000000000..c6931dbf0 --- /dev/null +++ b/sc/source/filter/excel/xltracer.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 +#include +#include +#include + +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::beans::PropertyValue; + +XclTracer::XclTracer(std::u16string_view /*rDocUrl*/) + : mbEnabled(false) + , maFirstTimes(eTraceLength, true) +{ +} + +XclTracer::~XclTracer() {} + +void XclTracer::ProcessTraceOnce(XclTracerId eProblem) +{ + if (mbEnabled && maFirstTimes[eProblem]) + { + maFirstTimes[eProblem] = false; + } +} + +void XclTracer::TraceInvalidAddress(const ScAddress& rPos, const ScAddress& rMaxPos) +{ + TraceInvalidRow(rPos.Row(), rMaxPos.Row()); + TraceInvalidTab(rPos.Tab(), rMaxPos.Tab()); +} + +void XclTracer::TraceInvalidRow(sal_uInt32 nRow, sal_uInt32 nMaxRow) +{ + if (nRow > nMaxRow) + ProcessTraceOnce(eRowLimitExceeded); +} + +void XclTracer::TraceInvalidTab(SCTAB nTab, SCTAB nMaxTab) +{ + if (nTab > nMaxTab) + ProcessTraceOnce(eTabLimitExceeded); +} + +void XclTracer::TracePrintRange() { ProcessTraceOnce(ePrintRange); } + +void XclTracer::TraceDates(sal_uInt16 nNumFmt) +{ + // Short Date = 14 and Short Date+Time = 22 + if (nNumFmt == 14 || nNumFmt == 22) + ProcessTraceOnce(eShortDate); +} + +void XclTracer::TraceBorderLineStyle(bool bBorderLineStyle) +{ + if (bBorderLineStyle) + ProcessTraceOnce(eBorderLineStyle); +} + +void XclTracer::TraceFillPattern(bool bFillPattern) +{ + if (bFillPattern) + ProcessTraceOnce(eFillPattern); +} + +void XclTracer::TraceFormulaMissingArg() +{ + // missing parameter in Formula record + ProcessTraceOnce(eFormulaMissingArg); +} + +void XclTracer::TracePivotDataSource(bool bExternal) +{ + if (bExternal) + ProcessTraceOnce(ePivotDataSource); +} + +void XclTracer::TracePivotChartExists() +{ + // Pivot Charts not currently displayed. + ProcessTraceOnce(ePivotChartExists); +} + +void XclTracer::TraceChartUnKnownType() { ProcessTraceOnce(eChartUnKnownType); } + +void XclTracer::TraceChartOnlySheet() { ProcessTraceOnce(eChartOnlySheet); } + +void XclTracer::TraceChartDataTable() +{ + // Data table is not supported. + ProcessTraceOnce(eChartDataTable); +} + +void XclTracer::TraceChartLegendPosition() +{ + // If position is set to "not docked or inside the plot area" then + // we cannot guarantee the legend position. + ProcessTraceOnce(eChartLegendPosition); +} + +void XclTracer::TraceUnsupportedObjects() +{ + // Called from Excel 5.0 - limited Graphical object support. + ProcessTraceOnce(eUnsupportedObject); +} + +void XclTracer::TraceObjectNotPrintable() { ProcessTraceOnce(eObjectNotPrintable); } + +void XclTracer::TraceDVType(bool bType) +{ + // Custom types work if 'Data->validity dialog' is not OKed. + if (bType) + ProcessTraceOnce(eDVType); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/excel/xlview.cxx b/sc/source/filter/excel/xlview.cxx new file mode 100644 index 000000000..b5768e2df --- /dev/null +++ b/sc/source/filter/excel/xlview.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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +// Structs ==================================================================== + +XclDocViewData::XclDocViewData() : + mnWinX( 0 ), + mnWinY( 0 ), + mnWinWidth( 0 ), + mnWinHeight( 0 ), + mnFlags( EXC_WIN1_HOR_SCROLLBAR | EXC_WIN1_VER_SCROLLBAR | EXC_WIN1_TABBAR ), + mnDisplXclTab( 0 ), + mnFirstVisXclTab( 0 ), + mnXclSelectCnt( 1 ), + mnTabBarWidth( 600 ) +{ +} + +XclTabViewData::XclTabViewData() : + maFirstXclPos( ScAddress::UNINITIALIZED ), + maSecondXclPos( ScAddress::UNINITIALIZED ) +{ + SetDefaults(); +} + +XclTabViewData::~XclTabViewData() +{ +} + +void XclTabViewData::SetDefaults() +{ + maSelMap.clear(); + maGridColor = COL_AUTO; + maFirstXclPos.Set( 0, 0 ); + maSecondXclPos.Set( 0, 0 ); + mnSplitX = mnSplitY = 0; + mnNormalZoom = EXC_WIN2_NORMALZOOM_DEF; + mnPageZoom = EXC_WIN2_PAGEZOOM_DEF; + mnCurrentZoom = 0; // default to mnNormalZoom or mnPageZoom + mnActivePane = EXC_PANE_TOPLEFT; + mbSelected = mbDisplayed = false; + mbMirrored = false; + mbFrozenPanes = false; + mbPageMode = false; + mbDefGridColor = true; + mbShowFormulas = false; + mbShowGrid = mbShowHeadings = mbShowZeros = mbShowOutline = true; + maTabBgColor = COL_AUTO; + mnTabBgColorId = 0; +} + +bool XclTabViewData::IsSplit() const +{ + return (mnSplitX > 0) || (mnSplitY > 0); +} + +bool XclTabViewData::HasPane( sal_uInt8 nPaneId ) const +{ + switch( nPaneId ) + { + case EXC_PANE_BOTTOMRIGHT: return (mnSplitX > 0) && (mnSplitY > 0); + case EXC_PANE_TOPRIGHT: return mnSplitX > 0; + case EXC_PANE_BOTTOMLEFT: return mnSplitY > 0; + case EXC_PANE_TOPLEFT: return true; + } + OSL_FAIL( "XclExpPane::HasPane - wrong pane ID" ); + return false; +} + +const XclSelectionData* XclTabViewData::GetSelectionData( sal_uInt8 nPane ) const +{ + XclSelectionMap::const_iterator aIt = maSelMap.find( nPane ); + return (aIt == maSelMap.end()) ? nullptr : aIt->second.get(); +} + +XclSelectionData& XclTabViewData::CreateSelectionData( sal_uInt8 nPane ) +{ + XclSelectionDataRef& rxSelData = maSelMap[ nPane ]; + if( !rxSelData ) + rxSelData = std::make_shared(); + return *rxSelData; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/ftools/fapihelper.cxx b/sc/source/filter/ftools/fapihelper.cxx new file mode 100644 index 000000000..4abfc79be --- /dev/null +++ b/sc/source/filter/ftools/fapihelper.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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Exception; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::uno::UNO_QUERY_THROW; +using ::com::sun::star::uno::XInterface; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::beans::XPropertyState; +using ::com::sun::star::lang::XServiceName; +using ::com::sun::star::lang::XMultiServiceFactory; + +using namespace ::com::sun::star; + +// Static helper functions ==================================================== + +OUString ScfApiHelper::GetServiceName( const Reference< XInterface >& xInt ) +{ + OUString aService; + Reference< XServiceName > xServiceName( xInt, UNO_QUERY ); + if( xServiceName.is() ) + aService = xServiceName->getServiceName(); + return aService; +} + +Reference< XMultiServiceFactory > ScfApiHelper::GetServiceFactory( const SfxObjectShell* pShell ) +{ + Reference< XMultiServiceFactory > xFactory; + if( pShell ) + xFactory.set( pShell->GetModel(), UNO_QUERY ); + return xFactory; +} + +Reference< XInterface > ScfApiHelper::CreateInstance( + const Reference< XMultiServiceFactory >& xFactory, const OUString& rServiceName ) +{ + Reference< XInterface > xInt; + if( xFactory.is() ) + { + try + { + xInt = xFactory->createInstance( rServiceName ); + } + catch( Exception& ) + { + OSL_FAIL( "ScfApiHelper::CreateInstance - cannot create instance" ); + } + } + return xInt; +} + +Reference< XInterface > ScfApiHelper::CreateInstance( const SfxObjectShell* pShell, const OUString& rServiceName ) +{ + return CreateInstance( GetServiceFactory( pShell ), rServiceName ); +} + +Reference< XInterface > ScfApiHelper::CreateInstance( const OUString& rServiceName ) +{ + return CreateInstance( ::comphelper::getProcessServiceFactory(), rServiceName ); +} + +uno::Sequence< beans::NamedValue > ScfApiHelper::QueryEncryptionDataForMedium( SfxMedium& rMedium, + ::comphelper::IDocPasswordVerifier& rVerifier, const ::std::vector< OUString >* pDefaultPasswords ) +{ + uno::Sequence< beans::NamedValue > aEncryptionData; + const SfxUnoAnyItem* pEncryptionDataItem = SfxItemSet::GetItem(rMedium.GetItemSet(), SID_ENCRYPTIONDATA, false); + if ( pEncryptionDataItem ) + pEncryptionDataItem->GetValue() >>= aEncryptionData; + + OUString aPassword; + const SfxStringItem* pPasswordItem = SfxItemSet::GetItem(rMedium.GetItemSet(), SID_PASSWORD, false); + if ( pPasswordItem ) + aPassword = pPasswordItem->GetValue(); + + bool bIsDefaultPassword = false; + aEncryptionData = ::comphelper::DocPasswordHelper::requestAndVerifyDocPassword( + rVerifier, aEncryptionData, aPassword, rMedium.GetInteractionHandler(), rMedium.GetOrigURL(), + ::comphelper::DocPasswordRequestType::MS, pDefaultPasswords, &bIsDefaultPassword ); + + rMedium.GetItemSet()->ClearItem( SID_PASSWORD ); + rMedium.GetItemSet()->ClearItem( SID_ENCRYPTIONDATA ); + + if( !bIsDefaultPassword && aEncryptionData.hasElements() ) + rMedium.GetItemSet()->Put( SfxUnoAnyItem( SID_ENCRYPTIONDATA, uno::Any( aEncryptionData ) ) ); + + return aEncryptionData; +} + +// Property sets ============================================================== + +ScfPropertySet::~ScfPropertySet() +{ + Reference xPropSetOpt(mxPropSet, UNO_QUERY); + if (xPropSetOpt.is()) + // Turn the property value change notification back on when finished. + xPropSetOpt->enableChangeListenerNotification(true); +} + +void ScfPropertySet::Set( Reference< XPropertySet > const & xPropSet ) +{ + mxPropSet = xPropSet; + mxMultiPropSet.set( mxPropSet, UNO_QUERY ); + Reference xPropSetOpt(mxPropSet, UNO_QUERY); + if (xPropSetOpt.is()) + // We don't want to broadcast property value changes during import to + // improve performance. + xPropSetOpt->enableChangeListenerNotification(false); +} + +OUString ScfPropertySet::GetServiceName() const +{ + return ScfApiHelper::GetServiceName( mxPropSet ); +} + +// Get properties ------------------------------------------------------------- + +bool ScfPropertySet::HasProperty( const OUString& rPropName ) const +{ + bool bHasProp = false; + try + { + Reference< XPropertyState > xPropState( mxPropSet, UNO_QUERY_THROW ); + bHasProp = xPropState->getPropertyState( rPropName ) == css::beans::PropertyState_DIRECT_VALUE; + } + catch( Exception& ) + { + } + return bHasProp; +} + +bool ScfPropertySet::GetAnyProperty( Any& rValue, const OUString& rPropName ) const +{ + bool bHasValue = false; + try + { + if( mxPropSet.is() ) + { + rValue = mxPropSet->getPropertyValue( rPropName ); + bHasValue = true; + } + } + catch( Exception& ) + { + } + return bHasValue; +} + +bool ScfPropertySet::GetBoolProperty( const OUString& rPropName ) const +{ + Any aAny; + return GetAnyProperty( aAny, rPropName ) && ScUnoHelpFunctions::GetBoolFromAny( aAny ); +} + +OUString ScfPropertySet::GetStringProperty( const OUString& rPropName ) const +{ + OUString aOUString; + GetProperty( aOUString, rPropName ); + return aOUString; +} + +bool ScfPropertySet::GetColorProperty( Color& rColor, const OUString& rPropName ) const +{ + sal_Int32 nApiColor = 0; + bool bRet = GetProperty( nApiColor, rPropName ); + rColor = Color( ColorTransparency, nApiColor ); + return bRet; +} + +void ScfPropertySet::GetProperties( Sequence< Any >& rValues, const Sequence< OUString >& rPropNames ) const +{ + try + { + OSL_ENSURE( mxMultiPropSet.is(), "ScfPropertySet::GetProperties - multi property set not available" ); + if( mxMultiPropSet.is() ) // first try the XMultiPropertySet + { + rValues = mxMultiPropSet->getPropertyValues( rPropNames ); + } + else if( mxPropSet.is() ) + { + sal_Int32 nLen = rPropNames.getLength(); + rValues.realloc( nLen ); + std::transform(rPropNames.begin(), rPropNames.end(), rValues.getArray(), + [this](const OUString& rPropName) -> Any { return mxPropSet->getPropertyValue(rPropName); }); + } + } + catch( Exception& ) + { + } +} + +// Set properties ------------------------------------------------------------- + +void ScfPropertySet::SetAnyProperty( const OUString& rPropName, const Any& rValue ) +{ + try + { + if( mxPropSet.is() ) + mxPropSet->setPropertyValue( rPropName, rValue ); + } + catch (const Exception&) + { + SAL_WARN("sc", "ScfPropertySet::SetAnyProperty - cannot set property \"" + rPropName + "\""); + } +} + +void ScfPropertySet::SetProperties( const Sequence< OUString >& rPropNames, const Sequence< Any >& rValues ) +{ + OSL_ENSURE( rPropNames.getLength() == rValues.getLength(), "ScfPropertySet::SetProperties - length of sequences different" ); + try + { + if( mxMultiPropSet.is() ) // first try the XMultiPropertySet + { + mxMultiPropSet->setPropertyValues( rPropNames, rValues ); + } + else if( mxPropSet.is() ) + { + OSL_FAIL( "ScfPropertySet::SetProperties - multi property set not available" ); + const OUString* pPropName = rPropNames.getConstArray(); + const OUString* pPropNameEnd = pPropName + rPropNames.getLength(); + const Any* pValue = rValues.getConstArray(); + for( ; pPropName != pPropNameEnd; ++pPropName, ++pValue ) + mxPropSet->setPropertyValue( *pPropName, *pValue ); + } + } + catch( Exception& ) + { + OSL_FAIL( "ScfPropertySet::SetAnyProperty - cannot set multiple properties" ); + } +} + +ScfPropSetHelper::ScfPropSetHelper( const char* const* ppcPropNames ) : + mnNextIdx( 0 ) +{ + OSL_ENSURE( ppcPropNames, "ScfPropSetHelper::ScfPropSetHelper - no strings found" ); + + // create OUStrings from ASCII property names + typedef ::std::pair< OUString, size_t > IndexedOUString; + std::vector aPropNameVec; + for( size_t nVecIdx = 0; *ppcPropNames; ++ppcPropNames, ++nVecIdx ) + { + OUString aPropName = OUString::createFromAscii( *ppcPropNames ); + aPropNameVec.emplace_back( aPropName, nVecIdx ); + } + + // sorts the pairs, which will be sorted by first component (the property name) + ::std::sort( aPropNameVec.begin(), aPropNameVec.end() ); + + // resize member sequences + size_t nSize = aPropNameVec.size(); + maNameSeq.realloc( static_cast< sal_Int32 >( nSize ) ); + auto pNameSeq = maNameSeq.getArray(); + maValueSeq.realloc( static_cast< sal_Int32 >( nSize ) ); + maNameOrder.resize( nSize ); + + // fill the property name sequence and store original sort order + sal_Int32 nSeqIdx = 0; + for( auto& aPropName : aPropNameVec ) + { + pNameSeq[ nSeqIdx ] = aPropName.first; + maNameOrder[ aPropName.second ] = nSeqIdx; + ++nSeqIdx; + } +} + +// read properties ------------------------------------------------------------ + +void ScfPropSetHelper::ReadFromPropertySet( const ScfPropertySet& rPropSet ) +{ + rPropSet.GetProperties( maValueSeq, maNameSeq ); + mnNextIdx = 0; +} + +void ScfPropSetHelper::ReadValue( Any& rAny ) +{ + Any* pAny = GetNextAny(); + if( pAny ) + rAny = *pAny; +} + +void ScfPropSetHelper::ReadValue( Color& rColor ) +{ + sal_Int32 nApiColor(0); + ReadValue( nApiColor ); + rColor = Color( ColorTransparency, nApiColor ); +} + +void ScfPropSetHelper::ReadValue( bool& rbValue ) +{ + Any aAny; + ReadValue( aAny ); + rbValue = ScUnoHelpFunctions::GetBoolFromAny( aAny ); +} + +// write properties ----------------------------------------------------------- + +void ScfPropSetHelper::InitializeWrite() +{ + mnNextIdx = 0; +} + +void ScfPropSetHelper::WriteValue( const Any& rAny ) +{ + if( Any* pAny = GetNextAny() ) + *pAny = rAny; +} + +void ScfPropSetHelper::WriteValue( bool rbValue ) +{ + if( Any* pAny = GetNextAny() ) + *pAny <<= rbValue; +} + +void ScfPropSetHelper::WriteToPropertySet( ScfPropertySet& rPropSet ) const +{ + rPropSet.SetProperties( maNameSeq, maValueSeq ); +} + +// private -------------------------------------------------------------------- + +Any* ScfPropSetHelper::GetNextAny() +{ + OSL_ENSURE( mnNextIdx < maNameOrder.size(), "ScfPropSetHelper::GetNextAny - sequence overflow" ); + Any* pAny = nullptr; + if( mnNextIdx < maNameOrder.size() ) + pAny = &maValueSeq.getArray()[ maNameOrder[ mnNextIdx++ ] ]; + return pAny; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/ftools/fprogressbar.cxx b/sc/source/filter/ftools/fprogressbar.cxx new file mode 100644 index 000000000..3b84e8492 --- /dev/null +++ b/sc/source/filter/ftools/fprogressbar.cxx @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include + +ScfProgressBar::ScfProgressSegment::ScfProgressSegment( std::size_t nSize ) : + mnSize( nSize ), + mnPos( 0 ) +{ +} + +ScfProgressBar::ScfProgressSegment::~ScfProgressSegment() +{ +} + +ScfProgressBar::ScfProgressBar( SfxObjectShell* pDocShell, const OUString& rText ) : + maText( rText ) +{ + Init( pDocShell ); +} + +ScfProgressBar::ScfProgressBar(SfxObjectShell* pDocShell, TranslateId pResId) + : maText(ScResId(pResId)) +{ + Init( pDocShell ); +} + +ScfProgressBar::ScfProgressBar( ScfProgressBar& rParProgress, ScfProgressSegment* pParSegment ) +{ + Init( rParProgress.mpDocShell ); + mpParentProgress = &rParProgress; + mpParentSegment = pParSegment; +} + +ScfProgressBar::~ScfProgressBar() +{ +} + +void ScfProgressBar::Init( SfxObjectShell* pDocShell ) +{ + mpDocShell = pDocShell; + mpParentProgress = nullptr; + mpParentSegment = mpCurrSegment = nullptr; + mnTotalSize = mnTotalPos = mnUnitSize = mnNextUnitPos = 0; + mnSysProgressScale = 1; // used to workaround the SfxProgress sal_uInt32 limit + mbInProgress = false; +} + +ScfProgressBar::ScfProgressSegment* ScfProgressBar::GetSegment( sal_Int32 nSegment ) +{ + if( nSegment < 0 ) + return nullptr; + return maSegments.at( nSegment ).get(); +} + +void ScfProgressBar::SetCurrSegment( ScfProgressSegment* pSegment ) +{ + if( mpCurrSegment == pSegment ) + return; + + mpCurrSegment = pSegment; + + if( mpParentProgress && mpParentSegment ) + { + mpParentProgress->SetCurrSegment( mpParentSegment ); + } + else if( !mxSysProgress && (mnTotalSize > 0) ) + { + // SfxProgress has a limit of sal_uInt32. + mnSysProgressScale = 1; + std::size_t nSysTotalSize = mnTotalSize; + while( nSysTotalSize > std::numeric_limits::max() ) + { + nSysTotalSize /= 2; + mnSysProgressScale *= 2; + } + mxSysProgress.reset( new ScProgress( mpDocShell, maText, nSysTotalSize, true ) ); + } + + if( !mbInProgress && mpCurrSegment && (mnTotalSize > 0) ) + { + mnUnitSize = mnTotalSize / 256 + 1; // at most 256 calls of system progress + mnNextUnitPos = 0; + mbInProgress = true; + } +} + +void ScfProgressBar::IncreaseProgressBar( std::size_t nDelta ) +{ + std::size_t nNewPos = mnTotalPos + nDelta; + + // call back to parent progress bar + if( mpParentProgress && mpParentSegment ) + { + // calculate new position of parent progress bar + std::size_t nParentPos = static_cast< std::size_t >( + static_cast< double >( nNewPos ) * mpParentSegment->mnSize / mnTotalSize ); + mpParentProgress->ProgressAbs( nParentPos ); + } + // modify system progress bar + else if( mxSysProgress ) + { + if( nNewPos >= mnNextUnitPos ) + { + mnNextUnitPos = nNewPos + mnUnitSize; + mxSysProgress->SetState( static_cast< sal_uLong >( nNewPos / mnSysProgressScale ) ); + } + } + else + { + OSL_FAIL( "ScfProgressBar::IncreaseProgressBar - no progress bar found" ); + } + + mnTotalPos = nNewPos; +} + +sal_Int32 ScfProgressBar::AddSegment( std::size_t nSize ) +{ + OSL_ENSURE( !mbInProgress, "ScfProgressBar::AddSegment - already in progress mode" ); + if( nSize == 0 ) + return SCF_INV_SEGMENT; + + maSegments.push_back( std::make_unique( nSize ) ); + mnTotalSize += nSize; + return static_cast< sal_Int32 >( maSegments.size() - 1 ); +} + +ScfProgressBar& ScfProgressBar::GetSegmentProgressBar( sal_Int32 nSegment ) +{ + ScfProgressSegment* pSegment = GetSegment( nSegment ); + OSL_ENSURE( !pSegment || (pSegment->mnPos == 0), "ScfProgressBar::GetSegmentProgressBar - segment already started" ); + if( pSegment && (pSegment->mnPos == 0) ) + { + if( !pSegment->mxProgress ) + pSegment->mxProgress.reset( new ScfProgressBar( *this, pSegment ) ); + return *pSegment->mxProgress; + } + return *this; +} + +bool ScfProgressBar::IsFull() const +{ + OSL_ENSURE( mbInProgress && mpCurrSegment, "ScfProgressBar::IsFull - no segment started" ); + return mpCurrSegment && (mpCurrSegment->mnPos >= mpCurrSegment->mnSize); +} + +void ScfProgressBar::ActivateSegment( sal_Int32 nSegment ) +{ + OSL_ENSURE( mnTotalSize > 0, "ScfProgressBar::ActivateSegment - progress range is zero" ); + if( mnTotalSize > 0 ) + SetCurrSegment( GetSegment( nSegment ) ); +} + +void ScfProgressBar::ProgressAbs( std::size_t nPos ) +{ + OSL_ENSURE( mbInProgress && mpCurrSegment, "ScfProgressBar::ProgressAbs - no segment started" ); + if( mpCurrSegment ) + { + OSL_ENSURE( mpCurrSegment->mnPos <= nPos, "ScfProgressBar::ProgressAbs - delta pos < 0" ); + OSL_ENSURE( nPos <= mpCurrSegment->mnSize, "ScfProgressBar::ProgressAbs - segment overflow" ); + if( (mpCurrSegment->mnPos < nPos) && (nPos <= mpCurrSegment->mnSize) ) + { + IncreaseProgressBar( nPos - mpCurrSegment->mnPos ); + mpCurrSegment->mnPos = nPos; + } + } +} + +void ScfProgressBar::Progress( std::size_t nDelta ) +{ + ProgressAbs( mpCurrSegment ? (mpCurrSegment->mnPos + nDelta) : 0 ); +} + +ScfSimpleProgressBar::ScfSimpleProgressBar( std::size_t nSize, SfxObjectShell* pDocShell, const OUString& rText ) : + maProgress( pDocShell, rText ) +{ + Init( nSize ); +} + +ScfSimpleProgressBar::ScfSimpleProgressBar(std::size_t nSize, SfxObjectShell* pDocShell, TranslateId pResId) + : maProgress(pDocShell, pResId) +{ + Init( nSize ); +} + +void ScfSimpleProgressBar::Init( std::size_t nSize ) +{ + sal_Int32 nSegment = maProgress.AddSegment( nSize ); + if( nSegment >= 0 ) + maProgress.ActivateSegment( nSegment ); +} + +ScfStreamProgressBar::ScfStreamProgressBar( SvStream& rStrm, SfxObjectShell* pDocShell ) : + mrStrm( rStrm ) +{ + Init( pDocShell, ScResId( STR_LOAD_DOC ) ); +} + +void ScfStreamProgressBar::Progress() +{ + mxProgress->ProgressAbs( mrStrm.Tell() ); +} + +void ScfStreamProgressBar::Init( SfxObjectShell* pDocShell, const OUString& rText ) +{ + sal_uInt64 const nSize = mrStrm.TellEnd(); + mxProgress.reset( new ScfSimpleProgressBar( nSize, pDocShell, rText ) ); + Progress(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/ftools/ftools.cxx b/sc/source/filter/ftools/ftools.cxx new file mode 100644 index 000000000..5c219e3c3 --- /dev/null +++ b/sc/source/filter/ftools/ftools.cxx @@ -0,0 +1,365 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + + +// ScFilterTools::ReadLongDouble() + +void ScfTools::ReadLongDouble(SvStream& rStrm, double& fResult) + +#ifdef __SIMPLE_FUNC // for <=VC 1.5 +{ + long double fRet; + bool bOk = 10 == rStrm.Read(&fRet, 10); + if (!bOk) + return; + fResult = static_cast(fRet); +} +#undef __SIMPLE_FUNC + +#else // detailed for all others +{ + +/* +"Mapping - Guide" 10-Byte Intel + +77777777 77666666 66665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000 x10 +98765432 10987654 32109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210 Bit-# total +9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0 Byte-# +76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210 76543210 Bit-# in Byte +SEEEEEEE EEEEEEEE IMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM MMMMMMMM Group +01111110 00000000 06665555 55555544 44444444 33333333 33222222 22221111 11111100 00000000 x10 +14321098 76543210 02109876 54321098 76543210 98765432 10987654 32109876 54321098 76543210 Bit in Group +*/ + + long double lfDouble; + long double lfFactor = 256.0; + sal_uInt8 pDouble10[ 10 ]; + + bool bOk = 10 == rStrm.ReadBytes(pDouble10, 10); // Intel-10 in pDouble10 + if (!bOk) + return; + + lfDouble = static_cast< long double >( pDouble10[ 7 ] ); // Byte 7 + lfDouble *= lfFactor; + lfDouble += static_cast< long double >( pDouble10[ 6 ] ); // Byte 6 + lfDouble *= lfFactor; + lfDouble += static_cast< long double >( pDouble10[ 5 ] ); // Byte 5 + lfDouble *= lfFactor; + lfDouble += static_cast< long double >( pDouble10[ 4 ] ); // Byte 4 + lfDouble *= lfFactor; + lfDouble += static_cast< long double >( pDouble10[ 3 ] ); // Byte 3 + lfDouble *= lfFactor; + lfDouble += static_cast< long double >( pDouble10[ 2 ] ); // Byte 2 + lfDouble *= lfFactor; + lfDouble += static_cast< long double >( pDouble10[ 1 ] ); // Byte 1 + lfDouble *= lfFactor; + lfDouble += static_cast< long double >( pDouble10[ 0 ] ); // Byte 0 + + // For value 0.0 all bits are zero; pow(2.0,-16446) does not work with CSet compilers + if( lfDouble != 0.0 ) + { + // exponent + sal_Int32 nExp; + nExp = pDouble10[ 9 ] & 0x7F; + nExp <<= 8; + nExp += pDouble10[ 8 ]; + nExp -= 16446; + + lfDouble *= pow( 2.0, static_cast< double >( nExp ) ); + } + + // sign + if( pDouble10[ 9 ] & 0x80 ) + lfDouble *= static_cast< long double >( -1.0 ); + + fResult = static_cast(lfDouble); +} +#endif + +// *** common methods *** ----------------------------------------------------- + +rtl_TextEncoding ScfTools::GetSystemTextEncoding() +{ + return osl_getThreadTextEncoding(); +} + +OUString ScfTools::GetHexStr( sal_uInt16 nValue ) +{ + const char pHex[] = "0123456789ABCDEF"; + OUString aStr = OUStringChar( pHex[ nValue >> 12 ] ) + + OUStringChar( pHex[ (nValue >> 8) & 0x000F ] ) + + OUStringChar( pHex[ (nValue >> 4) & 0x000F ] ) + + OUStringChar( pHex[ nValue & 0x000F ] ); + return aStr; +} + +sal_uInt8 ScfTools::GetMixedColorComp( sal_uInt8 nFore, sal_uInt8 nBack, sal_uInt8 nTrans ) +{ + sal_Int32 nTemp = ((static_cast< sal_Int32 >( nBack ) - nFore) * nTrans) / 0x80 + nFore; + return static_cast< sal_uInt8 >( nTemp ); +} + +Color ScfTools::GetMixedColor( const Color& rFore, const Color& rBack, sal_uInt8 nTrans ) +{ + return Color( + GetMixedColorComp( rFore.GetRed(), rBack.GetRed(), nTrans ), + GetMixedColorComp( rFore.GetGreen(), rBack.GetGreen(), nTrans ), + GetMixedColorComp( rFore.GetBlue(), rBack.GetBlue(), nTrans ) ); +} + +// *** conversion of names *** ------------------------------------------------ + +/* XXX As in sc/source/core/tool/rangenam.cxx ScRangeData::IsValidName() */ + +OUString ScfTools::ConvertToScDefinedName(const OUString& rName ) +{ + //fdo#37872: we don't allow points in range names any more + OUString sName = rName.replace(u'.', + u'_'); + sal_Int32 nLen = sName.getLength(); + if( nLen && !ScCompiler::IsCharFlagAllConventions( sName, 0, ScCharFlags::CharName ) ) + sName = sName.replaceAt( 0, 1, u"_" ); + for( sal_Int32 nPos = 1; nPos < nLen; ++nPos ) + if( !ScCompiler::IsCharFlagAllConventions( sName, nPos, ScCharFlags::Name ) ) + sName = sName.replaceAt( nPos, 1, u"_" ); + return sName; +} + +// *** streams and storages *** ----------------------------------------------- + +tools::SvRef ScfTools::OpenStorageRead( tools::SvRef const & xStrg, const OUString& rStrgName ) +{ + tools::SvRef xSubStrg; + if( xStrg.is() && xStrg->IsContained( rStrgName ) ) + xSubStrg = xStrg->OpenSotStorage( rStrgName, StreamMode::STD_READ ); + return xSubStrg; +} + +tools::SvRef ScfTools::OpenStorageWrite( tools::SvRef const & xStrg, const OUString& rStrgName ) +{ + tools::SvRef xSubStrg; + if( xStrg.is() ) + xSubStrg = xStrg->OpenSotStorage( rStrgName, StreamMode::STD_WRITE ); + return xSubStrg; +} + +tools::SvRef ScfTools::OpenStorageStreamRead( tools::SvRef const & xStrg, const OUString& rStrmName ) +{ + tools::SvRef xStrm; + if( xStrg.is() && xStrg->IsContained( rStrmName ) && xStrg->IsStream( rStrmName ) ) + xStrm = xStrg->OpenSotStream( rStrmName, StreamMode::STD_READ ); + return xStrm; +} + +tools::SvRef ScfTools::OpenStorageStreamWrite( tools::SvRef const & xStrg, const OUString& rStrmName ) +{ + OSL_ENSURE( !xStrg.is() || !xStrg->IsContained( rStrmName ), "ScfTools::OpenStorageStreamWrite - stream exists already" ); + tools::SvRef xStrm; + if( xStrg.is() ) + xStrm = xStrg->OpenSotStream( rStrmName, StreamMode::STD_WRITE | StreamMode::TRUNC ); + return xStrm; +} + +// *** item handling *** ------------------------------------------------------ + +bool ScfTools::CheckItem( const SfxItemSet& rItemSet, sal_uInt16 nWhichId, bool bDeep ) +{ + return rItemSet.GetItemState( nWhichId, bDeep ) == SfxItemState::SET; +} + +bool ScfTools::CheckItems( const SfxItemSet& rItemSet, const sal_uInt16* pnWhichIds, bool bDeep ) +{ + OSL_ENSURE( pnWhichIds, "ScfTools::CheckItems - no which id list" ); + for( const sal_uInt16* pnWhichId = pnWhichIds; *pnWhichId != 0; ++pnWhichId ) + if( CheckItem( rItemSet, *pnWhichId, bDeep ) ) + return true; + return false; +} + +void ScfTools::PutItem( SfxItemSet& rItemSet, const SfxPoolItem& rItem, sal_uInt16 nWhichId, bool bSkipPoolDef ) +{ + if( !bSkipPoolDef || (rItem != rItemSet.GetPool()->GetDefaultItem( nWhichId )) ) + { + rItemSet.Put( rItem.CloneSetWhich(nWhichId) ); + } +} + +void ScfTools::PutItem( SfxItemSet& rItemSet, const SfxPoolItem& rItem, bool bSkipPoolDef ) +{ + PutItem( rItemSet, rItem, rItem.Which(), bSkipPoolDef ); +} + +// *** style sheet handling *** ----------------------------------------------- + +namespace { + +ScStyleSheet& lclMakeStyleSheet( ScStyleSheetPool& rPool, const OUString& rStyleName, SfxStyleFamily eFamily, bool bForceName ) +{ + // find an unused name + OUString aNewName( rStyleName ); + sal_Int32 nIndex = 0; + SfxStyleSheetBase* pOldStyleSheet = nullptr; + while( SfxStyleSheetBase* pStyleSheet = rPool.Find( aNewName, eFamily ) ) + { + if( !pOldStyleSheet ) + pOldStyleSheet = pStyleSheet; + aNewName = rStyleName + " " + OUString::number( ++nIndex ); + } + + // rename existing style + if( pOldStyleSheet && bForceName ) + { + pOldStyleSheet->SetName( aNewName ); + aNewName = rStyleName; + } + + // create new style sheet + return static_cast< ScStyleSheet& >( rPool.Make( aNewName, eFamily, SfxStyleSearchBits::UserDefined ) ); +} + +} // namespace + +ScStyleSheet& ScfTools::MakeCellStyleSheet( ScStyleSheetPool& rPool, const OUString& rStyleName, bool bForceName ) +{ + return lclMakeStyleSheet( rPool, rStyleName, SfxStyleFamily::Para, bForceName ); +} + +ScStyleSheet& ScfTools::MakePageStyleSheet( ScStyleSheetPool& rPool, const OUString& rStyleName, bool bForceName ) +{ + return lclMakeStyleSheet( rPool, rStyleName, SfxStyleFamily::Page, bForceName ); +} + +// *** byte string import operations *** -------------------------------------- + +OString ScfTools::read_zeroTerminated_uInt8s_ToOString(SvStream& rStrm, sal_Int32& rnBytesLeft) +{ + OString aRet(::read_zeroTerminated_uInt8s_ToOString(rStrm)); + rnBytesLeft -= aRet.getLength(); //we read this number of bytes anyway + if (rStrm.good()) //if the stream is happy we read the null terminator as well + --rnBytesLeft; + return aRet; +} + +void ScfTools::AppendCString( SvStream& rStrm, OUString& rString, rtl_TextEncoding eTextEnc ) +{ + rString += ::read_zeroTerminated_uInt8s_ToOUString(rStrm, eTextEnc); +} + +// *** HTML table names <-> named range names *** ----------------------------- + +const OUString& ScfTools::GetHTMLDocName() +{ + static const OUString saHTMLDoc( "HTML_all" ); + return saHTMLDoc; +} + +const OUString& ScfTools::GetHTMLTablesName() +{ + static const OUString saHTMLTables( "HTML_tables" ); + return saHTMLTables; +} + +const OUString& ScfTools::GetHTMLIndexPrefix() +{ + static const OUString saHTMLIndexPrefix( "HTML_" ); + return saHTMLIndexPrefix; + +} + +const OUString& ScfTools::GetHTMLNamePrefix() +{ + static const OUString saHTMLNamePrefix( "HTML__" ); + return saHTMLNamePrefix; +} + +OUString ScfTools::GetNameFromHTMLIndex( sal_uInt32 nIndex ) +{ + OUString aName = GetHTMLIndexPrefix() + + OUString::number( static_cast< sal_Int32 >( nIndex ) ); + return aName; +} + +OUString ScfTools::GetNameFromHTMLName( std::u16string_view rTabName ) +{ + return GetHTMLNamePrefix() + rTabName; +} + +bool ScfTools::IsHTMLDocName( std::u16string_view rSource ) +{ + return o3tl::equalsIgnoreAsciiCase( rSource, GetHTMLDocName() ); +} + +bool ScfTools::IsHTMLTablesName( std::u16string_view rSource ) +{ + return o3tl::equalsIgnoreAsciiCase( rSource, GetHTMLTablesName() ); +} + +bool ScfTools::GetHTMLNameFromName( const OUString& rSource, OUString& rName ) +{ + rName.clear(); + if( rSource.startsWithIgnoreAsciiCase( GetHTMLNamePrefix() ) ) + { + rName = rSource.copy( GetHTMLNamePrefix().getLength() ); + ScGlobal::AddQuotes( rName, '"', false ); + } + else if( rSource.startsWithIgnoreAsciiCase( GetHTMLIndexPrefix() ) ) + { + OUString aIndex( rSource.copy( GetHTMLIndexPrefix().getLength() ) ); + if( CharClass::isAsciiNumeric( aIndex ) && (aIndex.toInt32() > 0) ) + rName = aIndex; + } + return !rName.isEmpty(); +} + +ScFormatFilterPluginImpl::ScFormatFilterPluginImpl() {} +ScFormatFilterPluginImpl::~ScFormatFilterPluginImpl() {} + +ScOrcusFilters* ScFormatFilterPluginImpl::GetOrcusFilters() +{ + static ScOrcusFiltersImpl aImpl; + return &aImpl; +} + +ScFormatFilterPlugin * ScFilterCreate() +{ + return new ScFormatFilterPluginImpl(); +} + +// implementation class inside the filters + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/ftools/sharedformulagroups.cxx b/sc/source/filter/ftools/sharedformulagroups.cxx new file mode 100644 index 000000000..9b028d9eb --- /dev/null +++ b/sc/source/filter/ftools/sharedformulagroups.cxx @@ -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/. + */ + +#include +#include +#include + +namespace sc { + +void SharedFormulaGroups::set( size_t nSharedId, std::unique_ptr pArray ) +{ + m_Store.try_emplace(nSharedId, std::move(pArray), ScAddress(ScAddress::INITIALIZE_INVALID)); +} + +void SharedFormulaGroups::set( size_t nSharedId, std::unique_ptr pArray, const ScAddress& rOrigin ) +{ + m_Store.try_emplace(nSharedId, std::move(pArray), rOrigin); +} + +const ScTokenArray* SharedFormulaGroups::get( size_t nSharedId ) const +{ + StoreType::const_iterator const it = m_Store.find(nSharedId); + return it == m_Store.end() ? nullptr : it->second.getTokenArray(); +} + +const SharedFormulaGroupEntry* SharedFormulaGroups::getEntry( size_t nSharedId ) const +{ + StoreType::const_iterator const it = m_Store.find(nSharedId); + return it == m_Store.end() ? nullptr : &(it->second); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/html/htmlexp.cxx b/sc/source/filter/html/htmlexp.cxx new file mode 100644 index 000000000..9c9f8745f --- /dev/null +++ b/sc/source/filter/html/htmlexp.cxx @@ -0,0 +1,1378 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// Without strings.hrc: error C2679: binary '=' : no operator defined which takes a +// right-hand operand of type 'const class String (__stdcall *)(class ScResId)' +// at +// const String aStrTable( ScResId( SCSTR_TABLE ) ); aStrOut = aStrTable; +// ?!??? +#include +#include + +#include +#include +#include +#include +#include +#include + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +const char sMyBegComment[] = ""; +const char sDisplay[] = "display:"; +const char sBorder[] = "border:"; +const char sBackground[] = "background:"; + +const sal_uInt16 ScHTMLExport::nDefaultFontSize[SC_HTML_FONTSIZES] = +{ + HTMLFONTSZ1_DFLT, HTMLFONTSZ2_DFLT, HTMLFONTSZ3_DFLT, HTMLFONTSZ4_DFLT, + HTMLFONTSZ5_DFLT, HTMLFONTSZ6_DFLT, HTMLFONTSZ7_DFLT +}; + +sal_uInt16 ScHTMLExport::nFontSize[SC_HTML_FONTSIZES] = { 0 }; + +const char* ScHTMLExport::pFontSizeCss[SC_HTML_FONTSIZES] = +{ + "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large" +}; + +const sal_uInt16 ScHTMLExport::nCellSpacing = 0; +const char ScHTMLExport::sIndentSource[nIndentMax+1] = + "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + +// Macros for HTML export + +#define TAG_ON( tag ) HTMLOutFuncs::Out_AsciiTag( rStrm, tag ) +#define TAG_OFF( tag ) HTMLOutFuncs::Out_AsciiTag( rStrm, tag, false ) +#define OUT_STR( str ) HTMLOutFuncs::Out_String( rStrm, str, &aNonConvertibleChars ) +#define OUT_LF() rStrm.WriteCharPtr( SAL_NEWLINE_STRING ).WriteCharPtr( GetIndentStr() ) +#define TAG_ON_LF( tag ) (TAG_ON( tag ).WriteCharPtr( SAL_NEWLINE_STRING ).WriteCharPtr( GetIndentStr() )) +#define TAG_OFF_LF( tag ) (TAG_OFF( tag ).WriteCharPtr( SAL_NEWLINE_STRING ).WriteCharPtr( GetIndentStr() )) +#define OUT_HR() TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_horzrule ) +#define OUT_COMMENT( comment ) (rStrm.WriteCharPtr( sMyBegComment ), OUT_STR( comment ) \ + .WriteCharPtr( sMyEndComment ).WriteCharPtr( SAL_NEWLINE_STRING ) \ + .WriteCharPtr( GetIndentStr() )) + +#define OUT_SP_CSTR_ASS( s ) rStrm.WriteChar( ' ').WriteCharPtr( s ).WriteChar( '=' ) + +#define GLOBSTR(id) ScResId( id ) + +void ScFormatFilterPluginImpl::ScExportHTML( SvStream& rStrm, const OUString& rBaseURL, ScDocument* pDoc, + const ScRange& rRange, const rtl_TextEncoding /*eNach*/, bool bAll, + const OUString& rStreamPath, OUString& rNonConvertibleChars, const OUString& rFilterOptions ) +{ + ScHTMLExport aEx( rStrm, rBaseURL, pDoc, rRange, bAll, rStreamPath, rFilterOptions ); + aEx.Write(); + rNonConvertibleChars = aEx.GetNonConvertibleChars(); +} + +static OString lcl_getColGroupString(sal_Int32 nSpan, sal_Int32 nWidth) +{ + OStringBuffer aByteStr(OOO_STRING_SVTOOLS_HTML_colgroup); + aByteStr.append(' '); + if( nSpan > 1 ) + { + aByteStr.append(OOO_STRING_SVTOOLS_HTML_O_span); + aByteStr.append("=\""); + aByteStr.append(nSpan); + aByteStr.append("\" "); + } + aByteStr.append(OOO_STRING_SVTOOLS_HTML_O_width); + aByteStr.append("=\""); + aByteStr.append(nWidth); + aByteStr.append('"'); + return aByteStr.makeStringAndClear(); +} + +static void lcl_AddStamp( OUString& rStr, std::u16string_view rName, + const css::util::DateTime& rDateTime, + const LocaleDataWrapper& rLoc ) +{ + Date aD(rDateTime.Day, rDateTime.Month, rDateTime.Year); + tools::Time aT(rDateTime.Hours, rDateTime.Minutes, rDateTime.Seconds, + rDateTime.NanoSeconds); + DateTime aDateTime(aD,aT); + + OUString aStrDate = rLoc.getDate( aDateTime ); + OUString aStrTime = rLoc.getTime( aDateTime ); + + rStr += GLOBSTR( STR_BY ) + " "; + if (!rName.empty()) + rStr += rName; + else + rStr += "???"; + rStr += " " + GLOBSTR( STR_ON ) + " "; + if (!aStrDate.isEmpty()) + rStr += aStrDate; + else + rStr += "???"; + rStr += ", "; + if (!aStrTime.isEmpty()) + rStr += aStrTime; + else + rStr += "???"; +} + +static OString lcl_makeHTMLColorTriplet(const Color& rColor) +{ + char buf[24]; + + // hello + snprintf( buf, 24, "\"#%02X%02X%02X\"", rColor.GetRed(), rColor.GetGreen(), rColor.GetBlue() ); + + return OString(buf); +} + +ScHTMLExport::ScHTMLExport( SvStream& rStrmP, const OUString& rBaseURL, ScDocument* pDocP, + const ScRange& rRangeP, bool bAllP, + const OUString& rStreamPathP, std::u16string_view rFilterOptions ) : + ScExportBase( rStrmP, pDocP, rRangeP ), + aBaseURL( rBaseURL ), + aStreamPath( rStreamPathP ), + pAppWin( Application::GetDefaultDevice() ), + nUsedTables( 0 ), + nIndent( 0 ), + bAll( bAllP ), + bTabHasGraphics( false ), + bTabAlignedLeft( false ), + bCalcAsShown( pDocP->GetDocOptions().IsCalcAsShown() ), + bTableDataHeight( true ), + mbSkipImages ( false ), + mbSkipHeaderFooter( false ) +{ + strcpy( sIndent, sIndentSource ); + sIndent[0] = 0; + + // set HTML configuration + bCopyLocalFileToINet = officecfg::Office::Common::Filter::HTML::Export::LocalGraphic::get(); + + if (rFilterOptions == u"SkipImages") + { + mbSkipImages = true; + } + else if (rFilterOptions == u"SkipHeaderFooter") + { + mbSkipHeaderFooter = true; + } + + for ( sal_uInt16 j=0; j < SC_HTML_FONTSIZES; j++ ) + { + sal_uInt16 nSize = SvxHtmlOptions::GetFontSize( j ); + // remember in Twips, like our SvxFontHeightItem + if ( nSize ) + nFontSize[j] = nSize * 20; + else + nFontSize[j] = nDefaultFontSize[j] * 20; + } + + const SCTAB nCount = pDoc->GetTableCount(); + for ( SCTAB nTab = 0; nTab < nCount; nTab++ ) + { + if ( !IsEmptyTable( nTab ) ) + nUsedTables++; + } +} + +ScHTMLExport::~ScHTMLExport() +{ + aGraphList.clear(); +} + +sal_uInt16 ScHTMLExport::GetFontSizeNumber( sal_uInt16 nHeight ) +{ + sal_uInt16 nSize = 1; + for ( sal_uInt16 j=SC_HTML_FONTSIZES-1; j>0; j-- ) + { + if( nHeight > (nFontSize[j] + nFontSize[j-1]) / 2 ) + { // The one next to it + nSize = j+1; + break; + } + } + return nSize; +} + +const char* ScHTMLExport::GetFontSizeCss( sal_uInt16 nHeight ) +{ + sal_uInt16 nSize = GetFontSizeNumber( nHeight ); + return pFontSizeCss[ nSize-1 ]; +} + +sal_uInt16 ScHTMLExport::ToPixel( sal_uInt16 nVal ) +{ + if( nVal ) + { + nVal = static_cast(pAppWin->LogicToPixel( + Size( nVal, nVal ), MapMode( MapUnit::MapTwip ) ).Width()); + if( !nVal ) // If there's a Twip there should also be a Pixel + nVal = 1; + } + return nVal; +} + +Size ScHTMLExport::MMToPixel( const Size& rSize ) +{ + Size aSize = pAppWin->LogicToPixel( rSize, MapMode( MapUnit::Map100thMM ) ); + // If there's something there should also be a Pixel + if ( !aSize.Width() && rSize.Width() ) + aSize.setWidth( 1 ); + if ( !aSize.Height() && rSize.Height() ) + aSize.setHeight( 1 ); + return aSize; +} + +void ScHTMLExport::Write() +{ + if (!mbSkipHeaderFooter) + { + rStrm.WriteChar( '<' ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_doctype ).WriteChar( ' ' ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_doctype5 ).WriteChar( '>' ) + .WriteCharPtr( SAL_NEWLINE_STRING ).WriteCharPtr( SAL_NEWLINE_STRING ); + TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_html ); + WriteHeader(); + OUT_LF(); + } + WriteBody(); + OUT_LF(); + if (!mbSkipHeaderFooter) + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_html ); +} + +void ScHTMLExport::WriteHeader() +{ + IncIndent(1); TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_head ); + + if ( pDoc->IsClipOrUndo() ) + { // no real DocInfo available, but some META information like charset needed + SfxFrameHTMLWriter::Out_DocInfo( rStrm, aBaseURL, nullptr, sIndent, &aNonConvertibleChars ); + } + else + { + uno::Reference xDPS( + pDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW); + uno::Reference xDocProps + = xDPS->getDocumentProperties(); + SfxFrameHTMLWriter::Out_DocInfo( rStrm, aBaseURL, xDocProps, + sIndent, &aNonConvertibleChars ); + OUT_LF(); + + if (!xDocProps->getPrintedBy().isEmpty()) + { + OUT_COMMENT( GLOBSTR( STR_DOC_INFO ) ); + OUString aStrOut = GLOBSTR( STR_DOC_PRINTED ) + ": "; + lcl_AddStamp( aStrOut, xDocProps->getPrintedBy(), + xDocProps->getPrintDate(), ScGlobal::getLocaleData() ); + OUT_COMMENT( aStrOut ); + } + + } + OUT_LF(); + + // CSS1 StyleSheet + PageDefaults( bAll ? 0 : aRange.aStart.Tab() ); + IncIndent(1); + rStrm.WriteCharPtr( "<" ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_style ).WriteCharPtr( " " ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_O_type ).WriteCharPtr( "=\"text/css\">" ); + + OUT_LF(); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_body); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_division); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_table); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_thead); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_tbody); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_tfoot); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_tablerow); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_tableheader); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_tabledata); + rStrm.WriteCharPtr(","); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_parabreak); + rStrm.WriteCharPtr(" { "); + rStrm.WriteCharPtr("font-family:"); + + if (!aHTMLStyle.aFontFamilyName.isEmpty()) + { + const OUString& rList = aHTMLStyle.aFontFamilyName; + for(sal_Int32 nPos {0};;) + { + rStrm.WriteChar( '\"' ); + OUT_STR( rList.getToken( 0, ';', nPos ) ); + rStrm.WriteChar( '\"' ); + if (nPos<0) + break; + rStrm.WriteCharPtr( ", " ); + } + } + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr("font-size:"); + rStrm.WriteCharPtr(GetFontSizeCss(static_cast(aHTMLStyle.nFontHeight))); + rStrm.WriteCharPtr(" }"); + + OUT_LF(); + + // write the style for the comments to make them stand out from normal cell content + // this is done through only showing the cell contents when the custom indicator is hovered + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_anchor); + rStrm.WriteCharPtr(".comment-indicator:hover"); + rStrm.WriteCharPtr(" + "); + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_comment2); + rStrm.WriteCharPtr(" { "); + rStrm.WriteCharPtr(sBackground); + rStrm.WriteCharPtr("#ffd"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr("position:"); + rStrm.WriteCharPtr("absolute"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr(sDisplay); + rStrm.WriteCharPtr("block"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr(sBorder); + rStrm.WriteCharPtr("1px solid black"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr("padding:"); + rStrm.WriteCharPtr("0.5em"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr(" } "); + + OUT_LF(); + + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_anchor); + rStrm.WriteCharPtr(".comment-indicator"); + rStrm.WriteCharPtr(" { "); + rStrm.WriteCharPtr(sBackground); + rStrm.WriteCharPtr("red"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr(sDisplay); + rStrm.WriteCharPtr("inline-block"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr(sBorder); + rStrm.WriteCharPtr("1px solid black"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr("width:"); + rStrm.WriteCharPtr("0.5em"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr("height:"); + rStrm.WriteCharPtr("0.5em"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr(" } "); + + OUT_LF(); + + rStrm.WriteCharPtr(OOO_STRING_SVTOOLS_HTML_comment2); + rStrm.WriteCharPtr(" { "); + rStrm.WriteCharPtr(sDisplay); + rStrm.WriteCharPtr("none"); + rStrm.WriteCharPtr("; "); + rStrm.WriteCharPtr(" } "); + + + IncIndent(-1); + OUT_LF(); + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_style ); + + IncIndent(-1); + OUT_LF(); + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_head ); +} + +void ScHTMLExport::WriteOverview() +{ + if ( nUsedTables <= 1 ) + return; + + IncIndent(1); + OUT_HR(); + IncIndent(1); TAG_ON( OOO_STRING_SVTOOLS_HTML_parabreak ); TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_center ); + TAG_ON( OOO_STRING_SVTOOLS_HTML_head1 ); + OUT_STR( ScResId( STR_OVERVIEW ) ); + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_head1 ); + + OUString aStr; + + const SCTAB nCount = pDoc->GetTableCount(); + for ( SCTAB nTab = 0; nTab < nCount; nTab++ ) + { + if ( !IsEmptyTable( nTab ) ) + { + pDoc->GetName( nTab, aStr ); + rStrm.WriteCharPtr( "" ); + OUT_STR( aStr ); + rStrm.WriteCharPtr( "" ); + TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_linebreak ); + } + } + + IncIndent(-1); OUT_LF(); + IncIndent(-1); TAG_OFF( OOO_STRING_SVTOOLS_HTML_center ); TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_parabreak ); +} + +const SfxItemSet& ScHTMLExport::PageDefaults( SCTAB nTab ) +{ + SfxStyleSheetBasePool* pStylePool = pDoc->GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = nullptr; + OSL_ENSURE( pStylePool, "StylePool not found! :-(" ); + + // remember defaults for compare in WriteCell + if ( !aHTMLStyle.bInitialized ) + { + pStyleSheet = pStylePool->Find( + ScResId(STR_STYLENAME_STANDARD), + SfxStyleFamily::Para ); + OSL_ENSURE( pStyleSheet, "ParaStyle not found! :-(" ); + if (!pStyleSheet) + pStyleSheet = pStylePool->First(SfxStyleFamily::Para); + const SfxItemSet& rSetPara = pStyleSheet->GetItemSet(); + + aHTMLStyle.nDefaultScriptType = ScGlobal::GetDefaultScriptType(); + aHTMLStyle.aFontFamilyName = static_cast((rSetPara.Get( + ScGlobal::GetScriptedWhichID( + aHTMLStyle.nDefaultScriptType, ATTR_FONT + )))).GetFamilyName(); + aHTMLStyle.nFontHeight = static_cast((rSetPara.Get( + ScGlobal::GetScriptedWhichID( + aHTMLStyle.nDefaultScriptType, ATTR_FONT_HEIGHT + )))).GetHeight(); + aHTMLStyle.nFontSizeNumber = GetFontSizeNumber( static_cast< sal_uInt16 >( aHTMLStyle.nFontHeight ) ); + } + + // Page style sheet printer settings, e.g. for background graphics. + // There's only one background graphic in HTML! + pStyleSheet = pStylePool->Find( pDoc->GetPageStyle( nTab ), SfxStyleFamily::Page ); + OSL_ENSURE( pStyleSheet, "PageStyle not found! :-(" ); + if (!pStyleSheet) + pStyleSheet = pStylePool->First(SfxStyleFamily::Page); + const SfxItemSet& rSet = pStyleSheet->GetItemSet(); + if ( !aHTMLStyle.bInitialized ) + { + const SvxBrushItem* pBrushItem = &rSet.Get( ATTR_BACKGROUND ); + aHTMLStyle.aBackgroundColor = pBrushItem->GetColor(); + aHTMLStyle.bInitialized = true; + } + return rSet; +} + +OString ScHTMLExport::BorderToStyle(const char* pBorderName, + const SvxBorderLine* pLine, bool& bInsertSemicolon) +{ + OStringBuffer aOut; + + if ( pLine ) + { + if ( bInsertSemicolon ) + aOut.append("; "); + + // which border + aOut.append(OString::Concat("border-") + pBorderName + ": "); + + // thickness + int nWidth = pLine->GetWidth(); + int nPxWidth = (nWidth > 0) ? + std::max(o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::px), sal_Int64(1)) : 0; + aOut.append(OString::number(nPxWidth) + "px "); + switch (pLine->GetBorderLineStyle()) + { + case SvxBorderLineStyle::SOLID: + aOut.append("solid"); + break; + case SvxBorderLineStyle::DOTTED: + aOut.append("dotted"); + break; + case SvxBorderLineStyle::DASHED: + case SvxBorderLineStyle::DASH_DOT: + case SvxBorderLineStyle::DASH_DOT_DOT: + aOut.append("dashed"); + break; + case SvxBorderLineStyle::DOUBLE: + case SvxBorderLineStyle::DOUBLE_THIN: + case SvxBorderLineStyle::THINTHICK_SMALLGAP: + case SvxBorderLineStyle::THINTHICK_MEDIUMGAP: + case SvxBorderLineStyle::THINTHICK_LARGEGAP: + case SvxBorderLineStyle::THICKTHIN_SMALLGAP: + case SvxBorderLineStyle::THICKTHIN_MEDIUMGAP: + case SvxBorderLineStyle::THICKTHIN_LARGEGAP: + aOut.append("double"); + break; + case SvxBorderLineStyle::EMBOSSED: + aOut.append("ridge"); + break; + case SvxBorderLineStyle::ENGRAVED: + aOut.append("groove"); + break; + case SvxBorderLineStyle::OUTSET: + aOut.append("outset"); + break; + case SvxBorderLineStyle::INSET: + aOut.append("inset"); + break; + default: + aOut.append("hidden"); + } + aOut.append(" #"); + + // color + char hex[7]; + snprintf( hex, 7, "%06" SAL_PRIxUINT32, static_cast( pLine->GetColor().GetRGBColor() ) ); + hex[6] = 0; + + aOut.append(hex); + + bInsertSemicolon = true; + } + + return aOut.makeStringAndClear(); +} + +void ScHTMLExport::WriteBody() +{ + const SfxItemSet& rSet = PageDefaults( bAll ? 0 : aRange.aStart.Tab() ); + const SvxBrushItem* pBrushItem = &rSet.Get( ATTR_BACKGROUND ); + + // default text color black + if (!mbSkipHeaderFooter) + { + rStrm.WriteChar( '<' ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_body ); + + if (!mbSkipImages) + { + if ( bAll && GPOS_NONE != pBrushItem->GetGraphicPos() ) + { + OUString aLink = pBrushItem->GetGraphicLink(); + OUString aGrfNm; + + // Embedded graphic -> write using WriteGraphic + if( aLink.isEmpty() ) + { + const Graphic* pGrf = pBrushItem->GetGraphic(); + if( pGrf ) + { + // Save graphic as (JPG) file + aGrfNm = aStreamPath; + ErrCode nErr = XOutBitmap::WriteGraphic( *pGrf, aGrfNm, + "JPG", XOutFlags::UseNativeIfPossible ); + if( !nErr ) // Contains errors, as we have nothing to output + { + aGrfNm = URIHelper::SmartRel2Abs( + INetURLObject(aBaseURL), + aGrfNm, URIHelper::GetMaybeFileHdl()); + aLink = aGrfNm; + } + } + } + else + { + aGrfNm = aLink; + if( bCopyLocalFileToINet ) + { + CopyLocalFileToINet( aGrfNm, aStreamPath ); + } + else + aGrfNm = URIHelper::SmartRel2Abs( + INetURLObject(aBaseURL), + aGrfNm, URIHelper::GetMaybeFileHdl()); + aLink = aGrfNm; + } + if( !aLink.isEmpty() ) + { + rStrm.WriteChar( ' ' ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_O_background ).WriteCharPtr( "=\"" ); + OUT_STR( URIHelper::simpleNormalizedMakeRelative( + aBaseURL, + aLink ) ).WriteChar( '\"' ); + } + } + } + if ( !aHTMLStyle.aBackgroundColor.IsTransparent() ) + { // A transparent background color should always result in default + // background of the browser. Also, HTMLOutFuncs::Out_Color() writes + // black #000000 for COL_AUTO which is the same as white #ffffff with + // transparency set to 0xff, our default background. + OUT_SP_CSTR_ASS( OOO_STRING_SVTOOLS_HTML_O_bgcolor ); + HTMLOutFuncs::Out_Color( rStrm, aHTMLStyle.aBackgroundColor ); + } + + rStrm.WriteChar( '>' ); OUT_LF(); + } + + if ( bAll ) + WriteOverview(); + + WriteTables(); + + if (!mbSkipHeaderFooter) + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_body ); +} + +void ScHTMLExport::WriteTables() +{ + const SCTAB nTabCount = pDoc->GetTableCount(); + const OUString aStrTable( ScResId( SCSTR_TABLE ) ); + OUString aStr; + OUString aStrOut; + SCCOL nStartCol; + SCROW nStartRow; + SCTAB nStartTab; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nEndTab; + SCCOL nStartColFix = 0; + SCROW nStartRowFix = 0; + SCCOL nEndColFix = 0; + SCROW nEndRowFix = 0; + ScDrawLayer* pDrawLayer = pDoc->GetDrawLayer(); + if ( bAll ) + { + nStartTab = 0; + nEndTab = nTabCount - 1; + } + else + { + nStartCol = nStartColFix = aRange.aStart.Col(); + nStartRow = nStartRowFix = aRange.aStart.Row(); + nStartTab = aRange.aStart.Tab(); + nEndCol = nEndColFix = aRange.aEnd.Col(); + nEndRow = nEndRowFix = aRange.aEnd.Row(); + nEndTab = aRange.aEnd.Tab(); + } + SCTAB nTableStrNum = 1; + for ( SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++ ) + { + if ( !pDoc->IsVisible( nTab ) ) + continue; // for + + if ( bAll ) + { + if ( !GetDataArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow ) ) + continue; // for + + if ( nUsedTables > 1 ) + { + aStrOut = aStrTable + " " + OUString::number( nTableStrNum++ ) + ": "; + + OUT_HR(); + + // Write anchor + rStrm.WriteCharPtr( "" ); + TAG_ON( OOO_STRING_SVTOOLS_HTML_head1 ); + OUT_STR( aStrOut ); + TAG_ON( OOO_STRING_SVTOOLS_HTML_emphasis ); + + pDoc->GetName( nTab, aStr ); + OUT_STR( aStr ); + + TAG_OFF( OOO_STRING_SVTOOLS_HTML_emphasis ); + TAG_OFF( OOO_STRING_SVTOOLS_HTML_head1 ); + rStrm.WriteCharPtr( "" ); OUT_LF(); + } + } + else + { + nStartCol = nStartColFix; + nStartRow = nStartRowFix; + nEndCol = nEndColFix; + nEndRow = nEndRowFix; + if ( !TrimDataArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow ) ) + continue; // for + } + + // + OStringBuffer aByteStrOut(OOO_STRING_SVTOOLS_HTML_table); + + bTabHasGraphics = bTabAlignedLeft = false; + if ( bAll && pDrawLayer ) + PrepareGraphics( pDrawLayer, nTab, nStartCol, nStartRow, + nEndCol, nEndRow ); + + // more
+ if ( bTabAlignedLeft ) + { + aByteStrOut.append(" " OOO_STRING_SVTOOLS_HTML_O_align + "=\"" + OOO_STRING_SVTOOLS_HTML_AL_left "\""); + } + // ALIGN=LEFT allow text and graphics to flow around + // CELLSPACING + aByteStrOut.append(" " OOO_STRING_SVTOOLS_HTML_O_cellspacing + "=\"" + + OString::number(nCellSpacing) + "\""); + + // BORDER=0, we do the styling of the cells in ---- + { + SCCOL nCol = nStartCol; + sal_Int32 nWidth = 0; + sal_Int32 nSpan = 0; + while( nCol <= nEndCol ) + { + if( pDoc->ColHidden(nCol, nTab) ) + { + ++nCol; + continue; + } + + if( nWidth != ToPixel( pDoc->GetColWidth( nCol, nTab ) ) ) + { + if( nSpan != 0 ) + { + TAG_ON(lcl_getColGroupString(nSpan, nWidth).getStr()); + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_colgroup ); + } + nWidth = ToPixel( pDoc->GetColWidth( nCol, nTab ) ); + nSpan = 1; + } + else + nSpan++; + nCol++; + } + if( nSpan ) + { + TAG_ON(lcl_getColGroupString(nSpan, nWidth).getStr()); + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_colgroup ); + } + } + + // // Re-enable only when THEAD and TFOOT are exported + // IncIndent(1); TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_tbody ); + // At least old (3.x, 4.x?) Netscape doesn't follow
+ aByteStrOut.append(" " OOO_STRING_SVTOOLS_HTML_O_border "=\"0\""); + IncIndent(1); TAG_ON_LF( aByteStrOut.makeStringAndClear().getStr() ); + + // ---
and + // specified, but needs a width at every column. + bool bHasHiddenRows = pDoc->HasHiddenRows(nStartRow, nEndRow, nTab); + // We need to cache sc::ColumnBlockPosition per each column. + std::vector< sc::ColumnBlockPosition > blockPos( nEndCol - nStartCol + 1 ); + for( SCCOL i = nStartCol; i <= nEndCol; ++i ) + pDoc->InitColumnBlockPosition( blockPos[ i - nStartCol ], nTab, i ); + for ( SCROW nRow=nStartRow; nRow<=nEndRow; nRow++ ) + { + if ( bHasHiddenRows && pDoc->RowHidden(nRow, nTab) ) + { + nRow = pDoc->FirstVisibleRow(nRow+1, nEndRow, nTab); + --nRow; + continue; // for + } + + IncIndent(1); TAG_ON_LF( OOO_STRING_SVTOOLS_HTML_tablerow ); + bTableDataHeight = true; // height at every first cell of each row + for ( SCCOL nCol2=nStartCol; nCol2<=nEndCol; nCol2++ ) + { + if ( pDoc->ColHidden(nCol2, nTab) ) + continue; // for + + if ( nCol2 == nEndCol ) + IncIndent(-1); + WriteCell( blockPos[ nCol2 - nStartCol ], nCol2, nRow, nTab ); + bTableDataHeight = false; + } + + if ( nRow == nEndRow ) + IncIndent(-1); + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_tablerow ); + } + // TODO: Uncomment later + // IncIndent(-1); TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_tbody ); + + IncIndent(-1); TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_table ); + + if ( bTabHasGraphics && !mbSkipImages ) + { + // the rest that is not in a cell + size_t ListSize = aGraphList.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + ScHTMLGraphEntry* pE = &aGraphList[ i ]; + if ( !pE->bWritten ) + WriteGraphEntry( pE ); + } + aGraphList.clear(); + if ( bTabAlignedLeft ) + { + // clear
with
+ aByteStrOut.append(OOO_STRING_SVTOOLS_HTML_linebreak); + aByteStrOut.append(' '). + append(OOO_STRING_SVTOOLS_HTML_O_clear).append('='). + append(OOO_STRING_SVTOOLS_HTML_AL_left); + TAG_ON_LF( aByteStrOut.makeStringAndClear().getStr() ); + } + } + + if ( bAll ) + OUT_COMMENT( "**************************************************************************" ); + } +} + +void ScHTMLExport::WriteCell( sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + ScAddress aPos( nCol, nRow, nTab ); + ScRefCellValue aCell(*pDoc, aPos, rBlockPos); + const ScPatternAttr* pAttr = pDoc->GetPattern( nCol, nRow, nTab ); + const SfxItemSet* pCondItemSet = pDoc->GetCondResult( nCol, nRow, nTab, &aCell ); + + const ScMergeFlagAttr& rMergeFlagAttr = pAttr->GetItem( ATTR_MERGE_FLAG, pCondItemSet ); + if ( rMergeFlagAttr.IsOverlapped() ) + return ; + + ScHTMLGraphEntry* pGraphEntry = nullptr; + if ( bTabHasGraphics && !mbSkipImages ) + { + size_t ListSize = aGraphList.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + ScHTMLGraphEntry* pE = &aGraphList[ i ]; + if ( pE->bInCell && pE->aRange.Contains( aPos ) ) + { + if ( pE->aRange.aStart == aPos ) + { + pGraphEntry = pE; + break; // for + } + else + return ; // Is a Col/RowSpan, Overlapped + } + } + } + + sal_uInt32 nFormat = pAttr->GetNumberFormat( pFormatter ); + bool bValueData = aCell.hasNumeric(); + SvtScriptType nScriptType = SvtScriptType::NONE; + if (!aCell.isEmpty()) + nScriptType = pDoc->GetScriptType(nCol, nRow, nTab, &aCell); + + if ( nScriptType == SvtScriptType::NONE ) + nScriptType = aHTMLStyle.nDefaultScriptType; + + OStringBuffer aStrTD(OOO_STRING_SVTOOLS_HTML_tabledata); + + // border of the cells + const SvxBoxItem* pBorder = pDoc->GetAttr( nCol, nRow, nTab, ATTR_BORDER ); + if ( pBorder && (pBorder->GetTop() || pBorder->GetBottom() || pBorder->GetLeft() || pBorder->GetRight()) ) + { + aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_style "=\""); + + bool bInsertSemicolon = false; + aStrTD.append(BorderToStyle("top", pBorder->GetTop(), + bInsertSemicolon)); + aStrTD.append(BorderToStyle("bottom", pBorder->GetBottom(), + bInsertSemicolon)); + aStrTD.append(BorderToStyle("left", pBorder->GetLeft(), + bInsertSemicolon)); + aStrTD.append(BorderToStyle("right", pBorder->GetRight(), + bInsertSemicolon)); + + aStrTD.append('"'); + } + + const char* pChar; + sal_uInt16 nHeightPixel; + + const ScMergeAttr& rMergeAttr = pAttr->GetItem( ATTR_MERGE, pCondItemSet ); + if ( pGraphEntry || rMergeAttr.IsMerged() ) + { + SCCOL nC, jC; + SCROW nR; + tools::Long v; + if ( pGraphEntry ) + nC = std::max( SCCOL(pGraphEntry->aRange.aEnd.Col() - nCol + 1), + rMergeAttr.GetColMerge() ); + else + nC = rMergeAttr.GetColMerge(); + if ( nC > 1 ) + { + aStrTD.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_colspan). + append('=').append(static_cast(nC)); + nC = nC + nCol; + for ( jC=nCol, v=0; jCGetColWidth( jC, nTab ); + } + + if ( pGraphEntry ) + nR = std::max( SCROW(pGraphEntry->aRange.aEnd.Row() - nRow + 1), + rMergeAttr.GetRowMerge() ); + else + nR = rMergeAttr.GetRowMerge(); + if ( nR > 1 ) + { + aStrTD.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_rowspan). + append('=').append(static_cast(nR)); + nR += nRow; + v = pDoc->GetRowHeight( nRow, nR-1, nTab ); + nHeightPixel = ToPixel( static_cast< sal_uInt16 >( v ) ); + } + else + nHeightPixel = ToPixel( pDoc->GetRowHeight( nRow, nTab ) ); + } + else + nHeightPixel = ToPixel( pDoc->GetRowHeight( nRow, nTab ) ); + + if ( bTableDataHeight ) + { + aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_height "=\"" + + OString::number(nHeightPixel) + "\""); + } + + const SvxFontItem& rFontItem = static_cast( pAttr->GetItem( + ScGlobal::GetScriptedWhichID( nScriptType, ATTR_FONT), + pCondItemSet) ); + + const SvxFontHeightItem& rFontHeightItem = static_cast( + pAttr->GetItem( ScGlobal::GetScriptedWhichID( nScriptType, + ATTR_FONT_HEIGHT), pCondItemSet) ); + + const SvxWeightItem& rWeightItem = static_cast( pAttr->GetItem( + ScGlobal::GetScriptedWhichID( nScriptType, ATTR_FONT_WEIGHT), + pCondItemSet) ); + + const SvxPostureItem& rPostureItem = static_cast( + pAttr->GetItem( ScGlobal::GetScriptedWhichID( nScriptType, + ATTR_FONT_POSTURE), pCondItemSet) ); + + const SvxUnderlineItem& rUnderlineItem = + pAttr->GetItem( ATTR_FONT_UNDERLINE, pCondItemSet ); + + const SvxCrossedOutItem& rCrossedOutItem = + pAttr->GetItem( ATTR_FONT_CROSSEDOUT, pCondItemSet ); + + const SvxColorItem& rColorItem = pAttr->GetItem( + ATTR_FONT_COLOR, pCondItemSet ); + + const SvxHorJustifyItem& rHorJustifyItem = + pAttr->GetItem( ATTR_HOR_JUSTIFY, pCondItemSet ); + + const SvxVerJustifyItem& rVerJustifyItem = + pAttr->GetItem( ATTR_VER_JUSTIFY, pCondItemSet ); + + const SvxBrushItem& rBrushItem = pAttr->GetItem( + ATTR_BACKGROUND, pCondItemSet ); + + Color aBgColor; + if ( rBrushItem.GetColor().GetAlpha() == 0 ) + aBgColor = aHTMLStyle.aBackgroundColor; // No unwanted background color + else + aBgColor = rBrushItem.GetColor(); + + bool bBold = ( WEIGHT_BOLD <= rWeightItem.GetWeight() ); + bool bItalic = ( ITALIC_NONE != rPostureItem.GetPosture() ); + bool bUnderline = ( LINESTYLE_NONE != rUnderlineItem.GetLineStyle() ); + bool bCrossedOut = ( STRIKEOUT_SINGLE <= rCrossedOutItem.GetStrikeout() ); + bool bSetFontColor = ( COL_AUTO != rColorItem.GetValue() ); // default is AUTO now + bool bSetFontName = ( aHTMLStyle.aFontFamilyName != rFontItem.GetFamilyName() ); + sal_uInt16 nSetFontSizeNumber = 0; + sal_uInt32 nFontHeight = rFontHeightItem.GetHeight(); + if ( nFontHeight != aHTMLStyle.nFontHeight ) + { + nSetFontSizeNumber = GetFontSizeNumber( static_cast(nFontHeight) ); + if ( nSetFontSizeNumber == aHTMLStyle.nFontSizeNumber ) + nSetFontSizeNumber = 0; // no difference, don't set + } + + bool bSetFont = (bSetFontColor || bSetFontName || nSetFontSizeNumber); + + //! TODO: we could entirely use CSS1 here instead, but that would exclude + //! Netscape 3.0 and Netscape 4.x without JavaScript enabled. + //! Do we want that? + + switch( rHorJustifyItem.GetValue() ) + { + case SvxCellHorJustify::Standard: + pChar = (bValueData ? OOO_STRING_SVTOOLS_HTML_AL_right : OOO_STRING_SVTOOLS_HTML_AL_left); + break; + case SvxCellHorJustify::Center: pChar = OOO_STRING_SVTOOLS_HTML_AL_center; break; + case SvxCellHorJustify::Block: pChar = OOO_STRING_SVTOOLS_HTML_AL_justify; break; + case SvxCellHorJustify::Right: pChar = OOO_STRING_SVTOOLS_HTML_AL_right; break; + case SvxCellHorJustify::Left: + case SvxCellHorJustify::Repeat: + default: pChar = OOO_STRING_SVTOOLS_HTML_AL_left; break; + } + + aStrTD.append(" " OOO_STRING_SVTOOLS_HTML_O_align "=\"" + + OString::Concat(pChar) + "\""); + + switch( rVerJustifyItem.GetValue() ) + { + case SvxCellVerJustify::Top: pChar = OOO_STRING_SVTOOLS_HTML_VA_top; break; + case SvxCellVerJustify::Center: pChar = OOO_STRING_SVTOOLS_HTML_VA_middle; break; + case SvxCellVerJustify::Bottom: pChar = OOO_STRING_SVTOOLS_HTML_VA_bottom; break; + case SvxCellVerJustify::Standard: + default: pChar = nullptr; + } + if ( pChar ) + { + aStrTD.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_valign). + append('=').append(pChar); + } + + if ( aHTMLStyle.aBackgroundColor != aBgColor ) + { + aStrTD.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_bgcolor). + append('='); + aStrTD.append(lcl_makeHTMLColorTriplet(aBgColor)); + } + + double fVal = 0.0; + if ( bValueData ) + { + switch (aCell.meType) + { + case CELLTYPE_VALUE: + fVal = aCell.mfValue; + if ( bCalcAsShown && fVal != 0.0 ) + fVal = pDoc->RoundValueAsShown( fVal, nFormat ); + break; + case CELLTYPE_FORMULA: + fVal = aCell.mpFormula->GetValue(); + break; + default: + OSL_FAIL( "value data with unsupported cell type" ); + } + } + + aStrTD.append(HTMLOutFuncs::CreateTableDataOptionsValNum(bValueData, fVal, + nFormat, *pFormatter, &aNonConvertibleChars)); + + TAG_ON(aStrTD.makeStringAndClear().getStr()); + + //write the note for this as the first thing in the tag + ScPostIt* pNote = pDoc->HasNote(aPos) ? pDoc->GetNote(aPos) : nullptr; + if (pNote) + { + //create the comment indicator + OString aStr = OOO_STRING_SVTOOLS_HTML_anchor " " + OOO_STRING_SVTOOLS_HTML_O_class "=\"comment-indicator\""; + TAG_ON(aStr.getStr()); + TAG_OFF(OOO_STRING_SVTOOLS_HTML_anchor); + OUT_LF(); + + //create the element holding the contents + //this is a bit naive, since it doesn't separate + //lines into html breaklines yet + TAG_ON(OOO_STRING_SVTOOLS_HTML_comment2); + OUT_STR( pNote->GetText() ); + TAG_OFF(OOO_STRING_SVTOOLS_HTML_comment2); + OUT_LF(); + } + + if ( bBold ) TAG_ON( OOO_STRING_SVTOOLS_HTML_bold ); + if ( bItalic ) TAG_ON( OOO_STRING_SVTOOLS_HTML_italic ); + if ( bUnderline ) TAG_ON( OOO_STRING_SVTOOLS_HTML_underline ); + if ( bCrossedOut ) TAG_ON( OOO_STRING_SVTOOLS_HTML_strikethrough ); + + if ( bSetFont ) + { + OStringBuffer aStr(OOO_STRING_SVTOOLS_HTML_font); + if ( bSetFontName ) + { + aStr.append(" " OOO_STRING_SVTOOLS_HTML_O_face "=\""); + + if (!rFontItem.GetFamilyName().isEmpty()) + { + const OUString& rList = rFontItem.GetFamilyName(); + for (sal_Int32 nPos {0};;) + { + OString aTmpStr = HTMLOutFuncs::ConvertStringToHTML( + rList.getToken( 0, ';', nPos ), + &aNonConvertibleChars); + aStr.append(aTmpStr); + if (nPos<0) + break; + aStr.append(','); + } + } + + aStr.append('\"'); + } + if ( nSetFontSizeNumber ) + { + aStr.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_size). + append('=').append(static_cast(nSetFontSizeNumber)); + } + if ( bSetFontColor ) + { + Color aColor = rColorItem.GetValue(); + + // always export automatic text color as black + if ( aColor == COL_AUTO ) + aColor = COL_BLACK; + + aStr.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_color). + append('=').append(lcl_makeHTMLColorTriplet(aColor)); + } + TAG_ON(aStr.makeStringAndClear().getStr()); + } + + OUString aURL; + bool bWriteHyperLink(false); + if (aCell.meType == CELLTYPE_FORMULA) + { + ScFormulaCell* pFCell = aCell.mpFormula; + if (pFCell->IsHyperLinkCell()) + { + OUString aCellText; + pFCell->GetURLResult(aURL, aCellText); + bWriteHyperLink = true; + } + } + + if (bWriteHyperLink) + { + OString aURLStr = HTMLOutFuncs::ConvertStringToHTML(aURL, &aNonConvertibleChars); + OString aStr = OOO_STRING_SVTOOLS_HTML_anchor " " OOO_STRING_SVTOOLS_HTML_O_href "=\"" + aURLStr + "\""; + TAG_ON(aStr.getStr()); + } + + OUString aStrOut; + bool bFieldText = false; + + const Color* pColor; + switch (aCell.meType) + { + case CELLTYPE_EDIT : + bFieldText = WriteFieldText(aCell.mpEditText); + if ( bFieldText ) + break; + [[fallthrough]]; + default: + aStrOut = ScCellFormat::GetString(aCell, nFormat, &pColor, *pFormatter, *pDoc); + } + + if ( !bFieldText ) + { + if ( aStrOut.isEmpty() ) + { + TAG_ON( OOO_STRING_SVTOOLS_HTML_linebreak ); // No completely empty line + } + else + { + sal_Int32 nPos = aStrOut.indexOf( '\n' ); + if ( nPos == -1 ) + { + OUT_STR( aStrOut ); + } + else + { + sal_Int32 nStartPos = 0; + do + { + OUString aSingleLine = aStrOut.copy( nStartPos, nPos - nStartPos ); + OUT_STR( aSingleLine ); + TAG_ON( OOO_STRING_SVTOOLS_HTML_linebreak ); + nStartPos = nPos + 1; + } + while( ( nPos = aStrOut.indexOf( '\n', nStartPos ) ) != -1 ); + OUString aSingleLine = aStrOut.copy( nStartPos ); + OUT_STR( aSingleLine ); + } + } + } + if ( pGraphEntry ) + WriteGraphEntry( pGraphEntry ); + + if (bWriteHyperLink) { TAG_OFF(OOO_STRING_SVTOOLS_HTML_anchor); } + + if ( bSetFont ) TAG_OFF( OOO_STRING_SVTOOLS_HTML_font ); + if ( bCrossedOut ) TAG_OFF( OOO_STRING_SVTOOLS_HTML_strikethrough ); + if ( bUnderline ) TAG_OFF( OOO_STRING_SVTOOLS_HTML_underline ); + if ( bItalic ) TAG_OFF( OOO_STRING_SVTOOLS_HTML_italic ); + if ( bBold ) TAG_OFF( OOO_STRING_SVTOOLS_HTML_bold ); + + TAG_OFF_LF( OOO_STRING_SVTOOLS_HTML_tabledata ); +} + +bool ScHTMLExport::WriteFieldText( const EditTextObject* pData ) +{ + bool bFields = false; + // text and anchor of URL fields, Doc-Engine is a ScFieldEditEngine + EditEngine& rEngine = pDoc->GetEditEngine(); + rEngine.SetText( *pData ); + sal_Int32 nParas = rEngine.GetParagraphCount(); + if ( nParas ) + { + ESelection aSel( 0, 0, nParas-1, rEngine.GetTextLen( nParas-1 ) ); + SfxItemSet aSet( rEngine.GetAttribs( aSel ) ); + SfxItemState eFieldState = aSet.GetItemState( EE_FEATURE_FIELD, false ); + if ( eFieldState == SfxItemState::DONTCARE || eFieldState == SfxItemState::SET ) + bFields = true; + } + if ( bFields ) + { + bool bOldUpdateMode = rEngine.SetUpdateLayout( true ); // no portions if not formatted + for ( sal_Int32 nPar=0; nPar < nParas; nPar++ ) + { + if ( nPar > 0 ) + TAG_ON( OOO_STRING_SVTOOLS_HTML_linebreak ); + std::vector aPortions; + rEngine.GetPortions( nPar, aPortions ); + sal_Int32 nStart = 0; + for ( const sal_Int32 nEnd : aPortions ) + { + ESelection aSel( nPar, nStart, nPar, nEnd ); + bool bUrl = false; + // fields are single characters + if ( nEnd == nStart+1 ) + { + SfxItemSet aSet = rEngine.GetAttribs( aSel ); + if ( const SvxFieldItem* pFieldItem = aSet.GetItemIfSet( EE_FEATURE_FIELD, false ) ) + { + const SvxFieldData* pField = pFieldItem->GetField(); + if (const SvxURLField* pURLField = dynamic_cast(pField)) + { + bUrl = true; + rStrm.WriteChar( '<' ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_anchor ).WriteChar( ' ' ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_O_href ).WriteCharPtr( "=\"" ); + OUT_STR( pURLField->GetURL() ); + rStrm.WriteCharPtr( "\">" ); + OUT_STR( pURLField->GetRepresentation() ); + rStrm.WriteCharPtr( "' ); + } + } + } + if ( !bUrl ) + OUT_STR( rEngine.GetText( aSel ) ); + nStart = nEnd; + } + } + rEngine.SetUpdateLayout( bOldUpdateMode ); + } + return bFields; +} + +void ScHTMLExport::CopyLocalFileToINet( OUString& rFileNm, + std::u16string_view rTargetNm ) +{ + INetURLObject aFileUrl, aTargetUrl; + aFileUrl.SetSmartURL( rFileNm ); + aTargetUrl.SetSmartURL( rTargetNm ); + if( !(INetProtocol::File == aFileUrl.GetProtocol() && + ( INetProtocol::File != aTargetUrl.GetProtocol() && + INetProtocol::Ftp <= aTargetUrl.GetProtocol() && + INetProtocol::Javascript >= aTargetUrl.GetProtocol())) ) + return; + + if( pFileNameMap ) + { + // Did we already move the file? + std::map::iterator it = pFileNameMap->find( rFileNm ); + if( it != pFileNameMap->end() ) + { + rFileNm = it->second; + return; + } + } + else + { + pFileNameMap.reset( new std::map ); + } + + bool bRet = false; + SvFileStream aTmp( aFileUrl.PathToFileName(), StreamMode::READ ); + + OUString aSrc = rFileNm; + OUString aDest = aTargetUrl.GetPartBeforeLastName() + aFileUrl.GetLastName(); + + SfxMedium aMedium( aDest, StreamMode::WRITE | StreamMode::SHARE_DENYNONE ); + + { + SvFileStream aCpy( aMedium.GetPhysicalName(), StreamMode::WRITE ); + aCpy.WriteStream( aTmp ); + } + + // Take over + aMedium.Close(); + aMedium.Commit(); + + bRet = ERRCODE_NONE == aMedium.GetError(); + + if( bRet ) + { + pFileNameMap->insert( std::make_pair( aSrc, aDest ) ); + rFileNm = aDest; + } +} + +void ScHTMLExport::IncIndent( short nVal ) +{ + sIndent[nIndent] = '\t'; + nIndent = nIndent + nVal; + if ( nIndent < 0 ) + nIndent = 0; + else if ( nIndent > nIndentMax ) + nIndent = nIndentMax; + sIndent[nIndent] = 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/html/htmlexp2.cxx b/sc/source/filter/html/htmlexp2.cxx new file mode 100644 index 000000000..475fea96c --- /dev/null +++ b/sc/source/filter/html/htmlexp2.cxx @@ -0,0 +1,223 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using namespace com::sun::star; + +void ScHTMLExport::PrepareGraphics( ScDrawLayer* pDrawLayer, SCTAB nTab, + SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow ) +{ + if ( !pDrawLayer->HasObjectsInRows( nTab, nStartRow, nEndRow ) ) + return; + + SdrPage* pDrawPage = pDrawLayer->GetPage( static_cast(nTab) ); + if ( !pDrawPage ) + return; + + bTabHasGraphics = true; + FillGraphList( pDrawPage, nTab, nStartCol, nStartRow, nEndCol, nEndRow ); + size_t ListSize = aGraphList.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + ScHTMLGraphEntry* pE = &aGraphList[ i ]; + if ( !pE->bInCell ) + { // not all cells: table next to some + bTabAlignedLeft = true; + break; + } + } +} + +void ScHTMLExport::FillGraphList( const SdrPage* pPage, SCTAB nTab, + SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow ) +{ + if ( !pPage->GetObjCount() ) + return; + + tools::Rectangle aRect; + if ( !bAll ) + aRect = pDoc->GetMMRect( nStartCol, nStartRow, nEndCol, nEndRow, nTab ); + SdrObjListIter aIter( pPage, SdrIterMode::Flat ); + SdrObject* pObject = aIter.Next(); + while ( pObject ) + { + tools::Rectangle aObjRect = pObject->GetCurrentBoundRect(); + if ( (bAll || aRect.Contains( aObjRect )) && !ScDrawLayer::IsNoteCaption(pObject) ) + { + Size aSpace; + ScRange aR = pDoc->GetRange( nTab, aObjRect ); + // Rectangle in mm/100 + Size aSize( MMToPixel( aObjRect.GetSize() ) ); + // If the image is somewhere in a merged range we must + // move the anchor to the upper left (THE span cell). + pDoc->ExtendOverlapped( aR ); + SCCOL nCol1 = aR.aStart.Col(); + SCROW nRow1 = aR.aStart.Row(); + SCCOL nCol2 = aR.aEnd.Col(); + SCROW nRow2 = aR.aEnd.Row(); + // All cells empty under object? + bool bInCell = pDoc->IsEmptyData( nCol1, nRow1, nCol2, nRow2, nTab ); + if ( bInCell ) + { // Spacing in spanning cell + tools::Rectangle aCellRect = pDoc->GetMMRect( + nCol1, nRow1, nCol2, nRow2, nTab ); + aSpace = MMToPixel( Size( + aCellRect.GetWidth() - aObjRect.GetWidth(), + aCellRect.GetHeight() - aObjRect.GetHeight() )); + aSpace.AdjustWidth((nCol2-nCol1) * (nCellSpacing+1) ); + aSpace.AdjustHeight((nRow2-nRow1) * (nCellSpacing+1) ); + aSpace.setWidth( aSpace.Width() / 2 ); + aSpace.setHeight( aSpace.Height() / 2 ); + } + aGraphList.emplace_back( pObject, + aR, aSize, bInCell, aSpace ); + } + pObject = aIter.Next(); + } +} + +void ScHTMLExport::WriteGraphEntry( ScHTMLGraphEntry* pE ) +{ + SdrObject* pObject = pE->pObject; + OStringBuffer aBuf; + aBuf.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_width).append('='). + append(static_cast(pE->aSize.Width())); + aBuf.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_height).append('='). + append(static_cast(pE->aSize.Height())); + if ( pE->bInCell ) + { + aBuf.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_hspace).append('='). + append(static_cast(pE->aSpace.Width())); + aBuf.append(' ').append(OOO_STRING_SVTOOLS_HTML_O_vspace).append('='). + append(static_cast(pE->aSpace.Height())); + } + OString aOpt = aBuf.makeStringAndClear(); + switch ( pObject->GetObjIdentifier() ) + { + case SdrObjKind::Graphic: + { + const SdrGrafObj* pSGO = static_cast(pObject); + std::unique_ptr pGeo(static_cast(pSGO->GetGeoData().release())); + sal_uInt16 nMirrorCase = (pGeo->maGeo.nRotationAngle == 18000_deg100 ? + ( pGeo->bMirrored ? 3 : 4 ) : ( pGeo->bMirrored ? 2 : 1 )); + bool bHMirr = ( ( nMirrorCase == 2 ) || ( nMirrorCase == 4 ) ); + bool bVMirr = ( ( nMirrorCase == 3 ) || ( nMirrorCase == 4 ) ); + XOutFlags nXOutFlags = XOutFlags::NONE; + if ( bHMirr ) + nXOutFlags |= XOutFlags::MirrorHorz; + if ( bVMirr ) + nXOutFlags |= XOutFlags::MirrorVert; + OUString aLinkName; + if ( pSGO->IsLinkedGraphic() ) + aLinkName = pSGO->GetFileName(); + WriteImage( aLinkName, pSGO->GetGraphic(), aOpt, nXOutFlags ); + pE->bWritten = true; + } + break; + case SdrObjKind::OLE2: + { + const Graphic* pGraphic = static_cast(pObject)->GetGraphic(); + if ( pGraphic ) + { + OUString aLinkName; + WriteImage( aLinkName, *pGraphic, aOpt ); + pE->bWritten = true; + } + } + break; + default: + { + Graphic aGraph(SdrExchangeView::GetObjGraphic(*pObject)); + OUString aLinkName; + WriteImage( aLinkName, aGraph, aOpt ); + pE->bWritten = true; + } + } +} + +void ScHTMLExport::WriteImage( OUString& rLinkName, const Graphic& rGrf, + std::string_view rImgOptions, XOutFlags nXOutFlags ) +{ + // Embedded graphic -> create an image file + if( rLinkName.isEmpty() ) + { + if( !aStreamPath.isEmpty() ) + { + // Save as a PNG + OUString aGrfNm( aStreamPath ); + nXOutFlags |= XOutFlags::UseNativeIfPossible; + ErrCode nErr = XOutBitmap::WriteGraphic( rGrf, aGrfNm, + "PNG", nXOutFlags ); + + // If it worked, create a URL for the IMG tag + if( !nErr ) + { + rLinkName = URIHelper::SmartRel2Abs( + INetURLObject(aBaseURL), + aGrfNm, + URIHelper::GetMaybeFileHdl()); + } + } + } + else + { + // Linked graphic - figure out the URL for the IMG tag + if( bCopyLocalFileToINet ) + { + CopyLocalFileToINet( rLinkName, aStreamPath ); + } + else + rLinkName = URIHelper::SmartRel2Abs( + INetURLObject(aBaseURL), + rLinkName, + URIHelper::GetMaybeFileHdl()); + } + + // If a URL was set, output the IMG tag. + // + if( !rLinkName.isEmpty() ) + { + rStrm.WriteChar( '<' ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_image ).WriteChar( ' ' ).WriteCharPtr( OOO_STRING_SVTOOLS_HTML_O_src ).WriteCharPtr( "=\"" ); + HTMLOutFuncs::Out_String( rStrm, URIHelper::simpleNormalizedMakeRelative( + aBaseURL, + rLinkName ) ).WriteChar( '\"' ); + if ( !rImgOptions.empty() ) + rStrm.WriteOString( rImgOptions ); + rStrm.WriteChar( '>' ).WriteCharPtr( SAL_NEWLINE_STRING ).WriteCharPtr( GetIndentStr() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/html/htmlimp.cxx b/sc/source/filter/html/htmlimp.cxx new file mode 100644 index 000000000..12e98a9ef --- /dev/null +++ b/sc/source/filter/html/htmlimp.cxx @@ -0,0 +1,239 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ErrCode ScFormatFilterPluginImpl::ScImportHTML( SvStream &rStream, const OUString& rBaseURL, ScDocument *pDoc, + ScRange& rRange, double nOutputFactor, bool bCalcWidthHeight, SvNumberFormatter* pFormatter, + bool bConvertDate ) +{ + ScHTMLImport aImp( pDoc, rBaseURL, rRange, bCalcWidthHeight ); + ErrCode nErr = aImp.Read( rStream, rBaseURL ); + ScRange aR = aImp.GetRange(); + rRange.aEnd = aR.aEnd; + aImp.WriteToDocument( true, nOutputFactor, pFormatter, bConvertDate ); + return nErr; +} + +std::unique_ptr ScFormatFilterPluginImpl::CreateHTMLImport( ScDocument* pDocP, const OUString& rBaseURL, const ScRange& rRange ) +{ + return std::make_unique( pDocP, rBaseURL, rRange, true/*bCalcWidthHeight*/ ); +} + +ScHTMLImport::ScHTMLImport( ScDocument* pDocP, const OUString& rBaseURL, const ScRange& rRange, bool bCalcWidthHeight ) : + ScEEImport( pDocP, rRange ) +{ + Size aPageSize; + OutputDevice* pDefaultDev = Application::GetDefaultDevice(); + const OUString& aPageStyle = mpDoc->GetPageStyle( rRange.aStart.Tab() ); + ScStyleSheet* pStyleSheet = static_cast(mpDoc-> + GetStyleSheetPool()->Find( aPageStyle, SfxStyleFamily::Page )); + if ( pStyleSheet ) + { + const SfxItemSet& rSet = pStyleSheet->GetItemSet(); + const SvxLRSpaceItem* pLRItem = &rSet.Get( ATTR_LRSPACE ); + tools::Long nLeftMargin = pLRItem->GetLeft(); + tools::Long nRightMargin = pLRItem->GetRight(); + const SvxULSpaceItem* pULItem = &rSet.Get( ATTR_ULSPACE ); + tools::Long nTopMargin = pULItem->GetUpper(); + tools::Long nBottomMargin = pULItem->GetLower(); + aPageSize = rSet.Get(ATTR_PAGE_SIZE).GetSize(); + if ( !aPageSize.Width() || !aPageSize.Height() ) + { + OSL_FAIL("PageSize Null ?!?!?"); + aPageSize = SvxPaperInfo::GetPaperSize( PAPER_A4 ); + } + aPageSize.AdjustWidth( -(nLeftMargin + nRightMargin) ); + aPageSize.AdjustHeight( -(nTopMargin + nBottomMargin) ); + aPageSize = pDefaultDev->LogicToPixel( aPageSize, MapMode( MapUnit::MapTwip ) ); + } + else + { + OSL_FAIL("no StyleSheet?!?"); + aPageSize = pDefaultDev->LogicToPixel( + SvxPaperInfo::GetPaperSize( PAPER_A4 ), MapMode( MapUnit::MapTwip ) ); + } + if( bCalcWidthHeight ) + mpParser.reset( new ScHTMLLayoutParser( mpEngine.get(), rBaseURL, aPageSize, pDocP )); + else + mpParser.reset( new ScHTMLQueryParser( mpEngine.get(), pDocP )); +} + +void ScHTMLImport::InsertRangeName( ScDocument& rDoc, const OUString& rName, const ScRange& rRange ) +{ + ScComplexRefData aRefData; + aRefData.InitRange( rRange ); + aRefData.Ref1.SetFlag3D( true ); + aRefData.Ref2.SetFlag3D( aRefData.Ref2.Tab() != aRefData.Ref1.Tab() ); + ScTokenArray aTokArray(rDoc); + aTokArray.AddDoubleReference( aRefData ); + ScRangeData* pRangeData = new ScRangeData( rDoc, rName, aTokArray ); + rDoc.GetRangeName()->insert( pRangeData ); +} + +void ScHTMLImport::WriteToDocument( + bool bSizeColsRows, double nOutputFactor, SvNumberFormatter* pFormatter, bool bConvertDate ) +{ + ScEEImport::WriteToDocument( bSizeColsRows, nOutputFactor, pFormatter, bConvertDate ); + + const ScHTMLParser* pParser = static_cast(mpParser.get()); + const ScHTMLTable* pGlobTable = pParser->GetGlobalTable(); + if( !pGlobTable ) + return; + + // set cell borders for HTML table cells + pGlobTable->ApplyCellBorders( mpDoc, maRange.aStart ); + + // correct cell borders for merged cells + for ( size_t i = 0, n = pParser->ListSize(); i < n; ++i ) + { + const ScEEParseEntry* pEntry = pParser->ListEntry( i ); + if( (pEntry->nColOverlap > 1) || (pEntry->nRowOverlap > 1) ) + { + SCTAB nTab = maRange.aStart.Tab(); + const ScMergeAttr* pItem = mpDoc->GetAttr( pEntry->nCol, pEntry->nRow, nTab, ATTR_MERGE ); + if( pItem->IsMerged() ) + { + SCCOL nColMerge = pItem->GetColMerge(); + SCROW nRowMerge = pItem->GetRowMerge(); + + const SvxBoxItem* pToItem = mpDoc->GetAttr( pEntry->nCol, pEntry->nRow, nTab, ATTR_BORDER ); + SvxBoxItem aNewItem( *pToItem ); + if( nColMerge > 1 ) + { + const SvxBoxItem* pFromItem = + mpDoc->GetAttr( pEntry->nCol + nColMerge - 1, pEntry->nRow, nTab, ATTR_BORDER ); + aNewItem.SetLine( pFromItem->GetLine( SvxBoxItemLine::RIGHT ), SvxBoxItemLine::RIGHT ); + } + if( nRowMerge > 1 ) + { + const SvxBoxItem* pFromItem = + mpDoc->GetAttr( pEntry->nCol, pEntry->nRow + nRowMerge - 1, nTab, ATTR_BORDER ); + aNewItem.SetLine( pFromItem->GetLine( SvxBoxItemLine::BOTTOM ), SvxBoxItemLine::BOTTOM ); + } + mpDoc->ApplyAttr( pEntry->nCol, pEntry->nRow, nTab, aNewItem ); + } + } + } + + // create ranges for HTML tables + // 1 - entire document + ScRange aNewRange( maRange.aStart ); + aNewRange.aEnd.IncCol( static_cast(pGlobTable->GetDocSize( tdCol )) - 1 ); + aNewRange.aEnd.IncRow( pGlobTable->GetDocSize( tdRow ) - 1 ); + InsertRangeName( *mpDoc, ScfTools::GetHTMLDocName(), aNewRange ); + + // 2 - all tables + InsertRangeName( *mpDoc, ScfTools::GetHTMLTablesName(), ScRange( maRange.aStart ) ); + + // 3 - single tables + SCCOL nColDiff = maRange.aStart.Col(); + SCROW nRowDiff = maRange.aStart.Row(); + SCTAB nTabDiff = maRange.aStart.Tab(); + + ScHTMLTable* pTable = nullptr; + ScHTMLTableId nTableId = SC_HTML_GLOBAL_TABLE; + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + while( (pTable = pGlobTable->FindNestedTable( ++nTableId )) != nullptr ) + { + pTable->GetDocRange( aNewRange ); + if (!aNewRange.Move( nColDiff, nRowDiff, nTabDiff, aErrorRange, *mpDoc )) + { + assert(!"can't move"); + } + // insert table number as name + OUStringBuffer aName(ScfTools::GetNameFromHTMLIndex(nTableId)); + // insert table id as name + if (!pTable->GetTableName().isEmpty()) + aName.append(" - " + pTable->GetTableName()); + // insert table caption as name + if (!pTable->GetTableCaption().isEmpty()) + aName.append(" - " + pTable->GetTableCaption()); + const OUString sName(aName.makeStringAndClear()); + if (!mpDoc->GetRangeName()->findByUpperName(ScGlobal::getCharClass().uppercase(sName))) + InsertRangeName(*mpDoc, sName, aNewRange); + } +} + +OUString ScFormatFilterPluginImpl::GetHTMLRangeNameList( ScDocument& rDoc, const OUString& rOrigName ) +{ + return ScHTMLImport::GetHTMLRangeNameList( rDoc, rOrigName ); +} + +OUString ScHTMLImport::GetHTMLRangeNameList( const ScDocument& rDoc, std::u16string_view rOrigName ) +{ + if (rOrigName.empty()) + return OUString(); + + OUString aNewName; + ScRangeName* pRangeNames = rDoc.GetRangeName(); + ScRangeList aRangeList; + sal_Int32 nStringIx = 0; + do + { + OUString aToken( o3tl::getToken(rOrigName, 0, ';', nStringIx ) ); + if( pRangeNames && ScfTools::IsHTMLTablesName( aToken ) ) + { // build list with all HTML tables + sal_uLong nIndex = 1; + for(;;) + { + aToken = ScfTools::GetNameFromHTMLIndex( nIndex++ ); + const ScRangeData* pRangeData = pRangeNames->findByUpperName(ScGlobal::getCharClass().uppercase(aToken)); + if (!pRangeData) + break; + ScRange aRange; + if( pRangeData->IsReference( aRange ) && !aRangeList.Contains( aRange ) ) + { + aNewName = ScGlobal::addToken(aNewName, aToken, ';'); + aRangeList.push_back( aRange ); + } + } + } + else + aNewName = ScGlobal::addToken(aNewName, aToken, ';'); + } + while (nStringIx>0); + return aNewName; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/html/htmlpars.cxx b/sc/source/filter/html/htmlpars.cxx new file mode 100644 index 000000000..d6dbb6f8f --- /dev/null +++ b/sc/source/filter/html/htmlpars.cxx @@ -0,0 +1,3168 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +using ::editeng::SvxBorderLine; +using namespace ::com::sun::star; + +ScHTMLStyles::ScHTMLStyles() : maEmpty() {} + +void ScHTMLStyles::add(const char* pElemName, size_t nElemName, const char* pClassName, size_t nClassName, + const OUString& aProp, const OUString& aValue) +{ + if (pElemName) + { + OUString aElem(pElemName, nElemName, RTL_TEXTENCODING_UTF8); + aElem = aElem.toAsciiLowerCase(); + if (pClassName) + { + // Both element and class names given. + ElemsType::iterator itrElem = m_ElemProps.find(aElem); + if (itrElem == m_ElemProps.end()) + { + // new element + std::pair r = + m_ElemProps.insert(std::make_pair(aElem, NamePropsType())); + if (!r.second) + // insertion failed. + return; + itrElem = r.first; + } + + NamePropsType& rClsProps = itrElem->second; + OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8); + aClass = aClass.toAsciiLowerCase(); + insertProp(rClsProps, aClass, aProp, aValue); + } + else + { + // Element name only. Add it to the element global. + insertProp(m_ElemGlobalProps, aElem, aProp, aValue); + } + } + else + { + if (pClassName) + { + // Class name only. Add it to the global. + OUString aClass(pClassName, nClassName, RTL_TEXTENCODING_UTF8); + aClass = aClass.toAsciiLowerCase(); + insertProp(m_GlobalProps, aClass, aProp, aValue); + } + } +} + +const OUString& ScHTMLStyles::getPropertyValue( + const OUString& rElem, const OUString& rClass, const OUString& rPropName) const +{ + // First, look into the element-class storage. + { + auto const itr = m_ElemProps.find(rElem); + if (itr != m_ElemProps.end()) + { + const NamePropsType& rClasses = itr->second; + NamePropsType::const_iterator itr2 = rClasses.find(rClass); + if (itr2 != rClasses.end()) + { + const PropsType& rProps = itr2->second; + PropsType::const_iterator itr3 = rProps.find(rPropName); + if (itr3 != rProps.end()) + return itr3->second; + } + } + } + // Next, look into the class global storage. + { + auto const itr = m_GlobalProps.find(rClass); + if (itr != m_GlobalProps.end()) + { + const PropsType& rProps = itr->second; + PropsType::const_iterator itr2 = rProps.find(rPropName); + if (itr2 != rProps.end()) + return itr2->second; + } + } + // As the last resort, look into the element global storage. + { + auto const itr = m_ElemGlobalProps.find(rClass); + if (itr != m_ElemGlobalProps.end()) + { + const PropsType& rProps = itr->second; + PropsType::const_iterator itr2 = rProps.find(rPropName); + if (itr2 != rProps.end()) + return itr2->second; + } + } + + return maEmpty; // nothing found. +} + +void ScHTMLStyles::insertProp( + NamePropsType& rStore, const OUString& aName, + const OUString& aProp, const OUString& aValue) +{ + NamePropsType::iterator itr = rStore.find(aName); + if (itr == rStore.end()) + { + // new element + std::pair r = + rStore.insert(std::make_pair(aName, PropsType())); + if (!r.second) + // insertion failed. + return; + + itr = r.first; + } + + PropsType& rProps = itr->second; + rProps.emplace(aProp, aValue); +} + +// BASE class for HTML parser classes + +ScHTMLParser::ScHTMLParser( EditEngine* pEditEngine, ScDocument* pDoc ) : + ScEEParser( pEditEngine ), + mpDoc( pDoc ) +{ + maFontHeights[0] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_1::get() * 20; + maFontHeights[1] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_2::get() * 20; + maFontHeights[2] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_3::get() * 20; + maFontHeights[3] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_4::get() * 20; + maFontHeights[4] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_5::get() * 20; + maFontHeights[5] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_6::get() * 20; + maFontHeights[6] = officecfg::Office::Common::Filter::HTML::Import::FontSize::Size_7::get() * 20; +} + +ScHTMLParser::~ScHTMLParser() +{ +} + +ScHTMLLayoutParser::ScHTMLLayoutParser( + EditEngine* pEditP, const OUString& rBaseURL, const Size& aPageSizeP, + ScDocument* pDocP ) : + ScHTMLParser( pEditP, pDocP ), + aPageSize( aPageSizeP ), + aBaseURL( rBaseURL ), + xLockedList( new ScRangeList ), + pLocalColOffset( new ScHTMLColOffset ), + nFirstTableCell(0), + nTableLevel(0), + nTable(0), + nMaxTable(0), + nColCntStart(0), + nMaxCol(0), + nTableWidth(0), + nColOffset(0), + nColOffsetStart(0), + nOffsetTolerance( SC_HTML_OFFSET_TOLERANCE_SMALL ), + bFirstRow( true ), + bTabInTabCell( false ), + bInCell( false ), + bInTitle( false ) +{ + MakeColNoRef( pLocalColOffset, 0, 0, 0, 0 ); + MakeColNoRef( &maColOffset, 0, 0, 0, 0 ); +} + +ScHTMLLayoutParser::~ScHTMLLayoutParser() +{ + while ( !aTableStack.empty() ) + { + ScHTMLTableStackEntry * pS = aTableStack.top().get(); + if ( pS->pLocalColOffset != pLocalColOffset ) + delete pS->pLocalColOffset; + aTableStack.pop(); + } + delete pLocalColOffset; + if ( pTables ) + { + for( const auto& rEntry : *pTables) + delete rEntry.second; + pTables.reset(); + } +} + +ErrCode ScHTMLLayoutParser::Read( SvStream& rStream, const OUString& rBaseURL ) +{ + Link aOldLink = pEdit->GetHtmlImportHdl(); + pEdit->SetHtmlImportHdl( LINK( this, ScHTMLLayoutParser, HTMLImportHdl ) ); + + SfxObjectShell* pObjSh = mpDoc->GetDocumentShell(); + bool bLoading = pObjSh && pObjSh->IsLoading(); + + SvKeyValueIteratorRef xValues; + SvKeyValueIterator* pAttributes = nullptr; + if ( bLoading ) + pAttributes = pObjSh->GetHeaderAttributes(); + else + { + // When not loading, set up fake http headers to force the SfxHTMLParser to use UTF8 + // (used when pasting from clipboard) + const char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 ); + if( pCharSet ) + { + OUString aContentType = "text/html; charset=" + + OUString::createFromAscii( pCharSet ); + + xValues = new SvKeyValueIterator; + xValues->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type, aContentType ) ); + pAttributes = xValues.get(); + } + } + + ErrCode nErr = pEdit->Read( rStream, rBaseURL, EETextFormat::Html, pAttributes ); + + pEdit->SetHtmlImportHdl( aOldLink ); + // Create column width + Adjust(); + OutputDevice* pDefaultDev = Application::GetDefaultDevice(); + sal_uInt16 nCount = maColOffset.size(); + sal_uLong nOff = maColOffset[0]; + Size aSize; + for ( sal_uInt16 j = 1; j < nCount; j++ ) + { + aSize.setWidth( maColOffset[j] - nOff ); + aSize = pDefaultDev->PixelToLogic( aSize, MapMode( MapUnit::MapTwip ) ); + maColWidths[ j-1 ] = aSize.Width(); + nOff = maColOffset[j]; + } + return nErr; +} + +const ScHTMLTable* ScHTMLLayoutParser::GetGlobalTable() const +{ + return nullptr; +} + +void ScHTMLLayoutParser::NewActEntry( const ScEEParseEntry* pE ) +{ + ScEEParser::NewActEntry( pE ); + if ( pE ) + { + if ( !pE->aSel.HasRange() ) + { // Completely empty, following text ends up in the same paragraph! + mxActEntry->aSel.nStartPara = pE->aSel.nEndPara; + mxActEntry->aSel.nStartPos = pE->aSel.nEndPos; + } + } + mxActEntry->aSel.nEndPara = mxActEntry->aSel.nStartPara; + mxActEntry->aSel.nEndPos = mxActEntry->aSel.nStartPos; +} + +void ScHTMLLayoutParser::EntryEnd( ScEEParseEntry* pE, const ESelection& rSel ) +{ + if ( rSel.nEndPara >= pE->aSel.nStartPara ) + { + pE->aSel.nEndPara = rSel.nEndPara; + pE->aSel.nEndPos = rSel.nEndPos; + } + else if ( rSel.nStartPara == pE->aSel.nStartPara - 1 && !pE->aSel.HasRange() ) + { // Did not attach a paragraph, but empty, do nothing + } + else + { + OSL_FAIL( "EntryEnd: EditEngine ESelection End < Start" ); + } +} + +void ScHTMLLayoutParser::NextRow( const HtmlImportInfo* pInfo ) +{ + if ( bInCell ) + CloseEntry( pInfo ); + if ( nRowMax < ++nRowCnt ) + nRowMax = nRowCnt; + nColCnt = nColCntStart; + nColOffset = nColOffsetStart; + bFirstRow = false; +} + +bool ScHTMLLayoutParser::SeekOffset( const ScHTMLColOffset* pOffset, sal_uInt16 nOffset, + SCCOL* pCol, sal_uInt16 nOffsetTol ) +{ + OSL_ENSURE( pOffset, "ScHTMLLayoutParser::SeekOffset - illegal call" ); + ScHTMLColOffset::const_iterator it = pOffset->find( nOffset ); + bool bFound = it != pOffset->end(); + sal_uInt16 nPos = it - pOffset->begin(); + *pCol = static_cast(nPos); + if ( bFound ) + return true; + sal_uInt16 nCount = pOffset->size(); + if ( !nCount ) + return false; + // nPos is the position of insertion, that's where the next higher one is (or isn't) + if ( nPos < nCount && (((*pOffset)[nPos] - nOffsetTol) <= nOffset) ) + return true; + // Not smaller than everything else? Then compare with the next lower one + else if ( nPos && (((*pOffset)[nPos-1] + nOffsetTol) >= nOffset) ) + { + (*pCol)--; + return true; + } + return false; +} + +void ScHTMLLayoutParser::MakeCol( ScHTMLColOffset* pOffset, sal_uInt16& nOffset, + sal_uInt16& nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol ) +{ + OSL_ENSURE( pOffset, "ScHTMLLayoutParser::MakeCol - illegal call" ); + SCCOL nPos; + if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) ) + nOffset = static_cast((*pOffset)[nPos]); + else + pOffset->insert( nOffset ); + if ( nWidth ) + { + if ( SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) ) + nWidth = static_cast((*pOffset)[nPos]) - nOffset; + else + pOffset->insert( nOffset + nWidth ); + } +} + +void ScHTMLLayoutParser::MakeColNoRef( ScHTMLColOffset* pOffset, sal_uInt16 nOffset, + sal_uInt16 nWidth, sal_uInt16 nOffsetTol, sal_uInt16 nWidthTol ) +{ + OSL_ENSURE( pOffset, "ScHTMLLayoutParser::MakeColNoRef - illegal call" ); + SCCOL nPos; + if ( SeekOffset( pOffset, nOffset, &nPos, nOffsetTol ) ) + nOffset = static_cast((*pOffset)[nPos]); + else + pOffset->insert( nOffset ); + if ( nWidth ) + { + if ( !SeekOffset( pOffset, nOffset + nWidth, &nPos, nWidthTol ) ) + pOffset->insert( nOffset + nWidth ); + } +} + +void ScHTMLLayoutParser::ModifyOffset( ScHTMLColOffset* pOffset, sal_uInt16& nOldOffset, + sal_uInt16& nNewOffset, sal_uInt16 nOffsetTol ) +{ + OSL_ENSURE( pOffset, "ScHTMLLayoutParser::ModifyOffset - illegal call" ); + SCCOL nPos; + if ( !SeekOffset( pOffset, nOldOffset, &nPos, nOffsetTol ) ) + { + if ( SeekOffset( pOffset, nNewOffset, &nPos, nOffsetTol ) ) + nNewOffset = static_cast((*pOffset)[nPos]); + else + pOffset->insert( nNewOffset ); + return ; + } + nOldOffset = static_cast((*pOffset)[nPos]); + SCCOL nPos2; + if ( SeekOffset( pOffset, nNewOffset, &nPos2, nOffsetTol ) ) + { + nNewOffset = static_cast((*pOffset)[nPos2]); + return ; + } + tools::Long nDiff = nNewOffset - nOldOffset; + if ( nDiff < 0 ) + { + do + { + const_cast((*pOffset)[nPos]) += nDiff; + } while ( nPos-- ); + } + else + { + do + { + const_cast((*pOffset)[nPos]) += nDiff; + } while ( ++nPos < static_cast(pOffset->size()) ); + } +} + +void ScHTMLLayoutParser::SkipLocked( ScEEParseEntry* pE, bool bJoin ) +{ + if ( !mpDoc->ValidCol(pE->nCol) ) + return; + +// Or else this would create a wrong value at ScAddress (chance for an infinite loop)! + bool bBadCol = false; + bool bAgain; + ScRange aRange( pE->nCol, pE->nRow, 0, + pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 ); + do + { + bAgain = false; + for ( size_t i = 0, nRanges = xLockedList->size(); i < nRanges; ++i ) + { + ScRange & rR = (*xLockedList)[i]; + if ( rR.Intersects( aRange ) ) + { + pE->nCol = rR.aEnd.Col() + 1; + SCCOL nTmp = pE->nCol + pE->nColOverlap - 1; + if ( pE->nCol > mpDoc->MaxCol() || nTmp > mpDoc->MaxCol() ) + bBadCol = true; + else + { + bAgain = true; + aRange.aStart.SetCol( pE->nCol ); + aRange.aEnd.SetCol( nTmp ); + } + break; + } + } + } while ( bAgain ); + if ( bJoin && !bBadCol ) + xLockedList->Join( aRange ); +} + +void ScHTMLLayoutParser::Adjust() +{ + xLockedList->RemoveAll(); + + std::stack< std::unique_ptr > aStack; + sal_uInt16 nTab = 0; + SCCOL nLastCol = SCCOL_MAX; + SCROW nNextRow = 0; + SCROW nCurRow = 0; + sal_uInt16 nPageWidth = static_cast(aPageSize.Width()); + InnerMap* pTab = nullptr; + for (auto& pE : maList) + { + if ( pE->nTab < nTab ) + { // Table finished + if ( !aStack.empty() ) + { + std::unique_ptr pS = std::move(aStack.top()); + aStack.pop(); + + nLastCol = pS->nLastCol; + nNextRow = pS->nNextRow; + nCurRow = pS->nCurRow; + } + nTab = pE->nTab; + if (pTables) + { + OuterMap::const_iterator it = pTables->find( nTab ); + if ( it != pTables->end() ) + pTab = it->second; + } + + } + SCROW nRow = pE->nRow; + if ( pE->nCol <= nLastCol ) + { // Next row + if ( pE->nRow < nNextRow ) + pE->nRow = nCurRow = nNextRow; + else + nCurRow = nNextRow = pE->nRow; + SCROW nR = 0; + if ( pTab ) + { + InnerMap::const_iterator it = pTab->find( nCurRow ); + if ( it != pTab->end() ) + nR = it->second; + } + if ( nR ) + nNextRow += nR; + else + nNextRow++; + } + else + pE->nRow = nCurRow; + nLastCol = pE->nCol; // Read column + if ( pE->nTab > nTab ) + { // New table + aStack.push( std::make_unique( + nLastCol, nNextRow, nCurRow ) ); + nTab = pE->nTab; + if ( pTables ) + { + OuterMap::const_iterator it = pTables->find( nTab ); + if ( it != pTables->end() ) + pTab = it->second; + } + // New line spacing + SCROW nR = 0; + if ( pTab ) + { + InnerMap::const_iterator it = pTab->find( nCurRow ); + if ( it != pTab->end() ) + nR = it->second; + } + if ( nR ) + nNextRow = nCurRow + nR; + else + nNextRow = nCurRow + 1; + } + if ( nTab == 0 ) + pE->nWidth = nPageWidth; + else + { // Real table, no paragraphs on the field + if ( pTab ) + { + SCROW nRowSpan = pE->nRowOverlap; + for ( SCROW j=0; j < nRowSpan; j++ ) + { // RowSpan resulting from merged rows + SCROW nRows = 0; + InnerMap::const_iterator it = pTab->find( nRow+j ); + if ( it != pTab->end() ) + nRows = it->second; + if ( nRows > 1 ) + { + pE->nRowOverlap += nRows - 1; + if ( j == 0 ) + { // Merged rows move the next row + SCROW nTmp = nCurRow + nRows; + if ( nNextRow < nTmp ) + nNextRow = nTmp; + } + } + } + } + } + // Real column + (void)SeekOffset( &maColOffset, pE->nOffset, &pE->nCol, nOffsetTolerance ); + SCCOL nColBeforeSkip = pE->nCol; + SkipLocked(pE.get(), false); + if ( pE->nCol != nColBeforeSkip ) + { + SCCOL nCount = static_cast(maColOffset.size()); + if ( nCount <= pE->nCol ) + { + pE->nOffset = static_cast(maColOffset[nCount-1]); + MakeCol( &maColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance ); + } + else + { + pE->nOffset = static_cast(maColOffset[pE->nCol]); + } + } + SCCOL nPos; + if ( pE->nWidth && SeekOffset( &maColOffset, pE->nOffset + pE->nWidth, &nPos, nOffsetTolerance ) ) + pE->nColOverlap = (nPos > pE->nCol ? nPos - pE->nCol : 1); + else + { + //FIXME: This may not be correct, but works anyway ... + pE->nColOverlap = 1; + } + xLockedList->Join( ScRange( pE->nCol, pE->nRow, 0, + pE->nCol + pE->nColOverlap - 1, pE->nRow + pE->nRowOverlap - 1, 0 ) ); + // Take over MaxDimensions + SCCOL nColTmp = pE->nCol + pE->nColOverlap; + if ( nColMax < nColTmp ) + nColMax = nColTmp; + SCROW nRowTmp = pE->nRow + pE->nRowOverlap; + if ( nRowMax < nRowTmp ) + nRowMax = nRowTmp; + } +} + +sal_uInt16 ScHTMLLayoutParser::GetWidth( const ScEEParseEntry* pE ) +{ + if ( pE->nWidth ) + return pE->nWidth; + sal_Int32 nTmp = std::min( static_cast( pE->nCol - + nColCntStart + pE->nColOverlap), + static_cast( pLocalColOffset->size() - 1)); + SCCOL nPos = (nTmp < 0 ? 0 : static_cast(nTmp)); + sal_uInt16 nOff2 = static_cast((*pLocalColOffset)[nPos]); + if ( pE->nOffset < nOff2 ) + return nOff2 - pE->nOffset; + return 0; +} + +void ScHTMLLayoutParser::SetWidths() +{ + SCCOL nCol; + if ( !nTableWidth ) + nTableWidth = static_cast(aPageSize.Width()); + SCCOL nColsPerRow = nMaxCol - nColCntStart; + if ( nColsPerRow <= 0 ) + nColsPerRow = 1; + if ( pLocalColOffset->size() <= 2 ) + { // Only PageSize, there was no width setting + sal_uInt16 nWidth = nTableWidth / static_cast(nColsPerRow); + sal_uInt16 nOff = nColOffsetStart; + pLocalColOffset->clear(); + for ( nCol = 0; nCol <= nColsPerRow; ++nCol, nOff = nOff + nWidth ) + { + MakeColNoRef( pLocalColOffset, nOff, 0, 0, 0 ); + } + nTableWidth = static_cast(pLocalColOffset->back() - pLocalColOffset->front()); + for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i ) + { + auto& pE = maList[ i ]; + if ( pE->nTab == nTable ) + { + pE->nOffset = static_cast((*pLocalColOffset)[pE->nCol - nColCntStart]); + pE->nWidth = 0; // to be recalculated later + } + } + } + else + { // Some without width + // Why actually no pE? + if ( nFirstTableCell < maList.size() ) + { + std::unique_ptr pOffsets(new sal_uInt16[ nColsPerRow+1 ]); + memset( pOffsets.get(), 0, (nColsPerRow+1) * sizeof(sal_uInt16) ); + std::unique_ptr pWidths(new sal_uInt16[ nColsPerRow ]); + memset( pWidths.get(), 0, nColsPerRow * sizeof(sal_uInt16) ); + pOffsets[0] = nColOffsetStart; + for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i ) + { + auto& pE = maList[ i ]; + if ( pE->nTab == nTable && pE->nWidth ) + { + nCol = pE->nCol - nColCntStart; + if ( nCol < nColsPerRow ) + { + if ( pE->nColOverlap == 1 ) + { + if ( pWidths[nCol] < pE->nWidth ) + pWidths[nCol] = pE->nWidth; + } + else + { // try to find a single undefined width + sal_uInt16 nTotal = 0; + bool bFound = false; + SCCOL nHere = 0; + SCCOL nStop = std::min( static_cast(nCol + pE->nColOverlap), nColsPerRow ); + for ( ; nCol < nStop; nCol++ ) + { + if ( pWidths[nCol] ) + nTotal = nTotal + pWidths[nCol]; + else + { + if ( bFound ) + { + bFound = false; + break; // for + } + bFound = true; + nHere = nCol; + } + } + if ( bFound && pE->nWidth > nTotal ) + pWidths[nHere] = pE->nWidth - nTotal; + } + } + } + } + sal_uInt16 nWidths = 0; + sal_uInt16 nUnknown = 0; + for ( nCol = 0; nCol < nColsPerRow; nCol++ ) + { + if ( pWidths[nCol] ) + nWidths = nWidths + pWidths[nCol]; + else + nUnknown++; + } + if ( nUnknown ) + { + sal_uInt16 nW = ((nWidths < nTableWidth) ? + ((nTableWidth - nWidths) / nUnknown) : + (nTableWidth / nUnknown)); + for ( nCol = 0; nCol < nColsPerRow; nCol++ ) + { + if ( !pWidths[nCol] ) + pWidths[nCol] = nW; + } + } + for ( nCol = 1; nCol <= nColsPerRow; nCol++ ) + { + pOffsets[nCol] = pOffsets[nCol-1] + pWidths[nCol-1]; + } + pLocalColOffset->clear(); + for ( nCol = 0; nCol <= nColsPerRow; nCol++ ) + { + MakeColNoRef( pLocalColOffset, pOffsets[nCol], 0, 0, 0 ); + } + nTableWidth = pOffsets[nColsPerRow] - pOffsets[0]; + + for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i ) + { + auto& pE = maList[ i ]; + if ( pE->nTab == nTable ) + { + nCol = pE->nCol - nColCntStart; + OSL_ENSURE( nCol < nColsPerRow, "ScHTMLLayoutParser::SetWidths: column overflow" ); + if ( nCol < nColsPerRow ) + { + pE->nOffset = pOffsets[nCol]; + nCol = nCol + pE->nColOverlap; + if ( nCol > nColsPerRow ) + nCol = nColsPerRow; + pE->nWidth = pOffsets[nCol] - pE->nOffset; + } + } + } + } + } + if ( !pLocalColOffset->empty() ) + { + sal_uInt16 nMax = static_cast(pLocalColOffset->back()); + if ( aPageSize.Width() < nMax ) + aPageSize.setWidth( nMax ); + if (nTableLevel == 0) + { + // Local table is very outer table, create missing offsets. + for (auto it = pLocalColOffset->begin(); it != pLocalColOffset->end(); ++it) + { + // Only exact offsets, do not use MakeColNoRef(). + if (maColOffset.find(*it) == maColOffset.end()) + maColOffset.insert(*it); + } + } + } + for ( size_t i = nFirstTableCell, nListSize = maList.size(); i < nListSize; ++i ) + { + auto& pE = maList[ i ]; + if ( pE->nTab == nTable ) + { + if ( !pE->nWidth ) + { + pE->nWidth = GetWidth(pE.get()); + OSL_ENSURE( pE->nWidth, "SetWidths: pE->nWidth == 0" ); + } + MakeCol( &maColOffset, pE->nOffset, pE->nWidth, nOffsetTolerance, nOffsetTolerance ); + } + } +} + +void ScHTMLLayoutParser::Colonize( ScEEParseEntry* pE ) +{ + if ( pE->nCol == SCCOL_MAX ) + pE->nCol = nColCnt; + if ( pE->nRow == SCROW_MAX ) + pE->nRow = nRowCnt; + SCCOL nCol = pE->nCol; + SkipLocked( pE ); // Change of columns to the right + + if ( nCol < pE->nCol ) + { // Replaced + nCol = pE->nCol - nColCntStart; + SCCOL nCount = static_cast(pLocalColOffset->size()); + if ( nCol < nCount ) + nColOffset = static_cast((*pLocalColOffset)[nCol]); + else + nColOffset = static_cast((*pLocalColOffset)[nCount - 1]); + } + pE->nOffset = nColOffset; + sal_uInt16 nWidth = GetWidth( pE ); + MakeCol( pLocalColOffset, pE->nOffset, nWidth, nOffsetTolerance, nOffsetTolerance ); + if ( pE->nWidth ) + pE->nWidth = nWidth; + nColOffset = pE->nOffset + nWidth; + if ( nTableWidth < nColOffset - nColOffsetStart ) + nTableWidth = nColOffset - nColOffsetStart; +} + +void ScHTMLLayoutParser::CloseEntry( const HtmlImportInfo* pInfo ) +{ + bInCell = false; + if ( bTabInTabCell ) + { // From the stack in TableOff + bTabInTabCell = false; + NewActEntry(maList.back().get()); // New free flying mxActEntry + return ; + } + if (mxActEntry->nTab == 0) + mxActEntry->nWidth = static_cast(aPageSize.Width()); + Colonize(mxActEntry.get()); + nColCnt = mxActEntry->nCol + mxActEntry->nColOverlap; + if ( nMaxCol < nColCnt ) + nMaxCol = nColCnt; // TableStack MaxCol + if ( nColMax < nColCnt ) + nColMax = nColCnt; // Global MaxCol for ScEEParser GetDimensions! + EntryEnd(mxActEntry.get(), pInfo->aSelection); + ESelection& rSel = mxActEntry->aSel; + while ( rSel.nStartPara < rSel.nEndPara + && pEdit->GetTextLen( rSel.nStartPara ) == 0 ) + { // Strip preceding empty paragraphs + rSel.nStartPara++; + } + while ( rSel.nEndPos == 0 && rSel.nEndPara > rSel.nStartPara ) + { // Strip successive empty paragraphs + rSel.nEndPara--; + rSel.nEndPos = pEdit->GetTextLen( rSel.nEndPara ); + } + if ( rSel.nStartPara > rSel.nEndPara ) + { // Gives GPF in CreateTextObject + OSL_FAIL( "CloseEntry: EditEngine ESelection Start > End" ); + rSel.nEndPara = rSel.nStartPara; + } + if ( rSel.HasRange() ) + mxActEntry->aItemSet.Put( ScLineBreakCell(true) ); + maList.push_back(mxActEntry); + NewActEntry(mxActEntry.get()); // New free flying mxActEntry +} + +IMPL_LINK( ScHTMLLayoutParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void ) +{ + switch ( rInfo.eState ) + { + case HtmlImportState::NextToken: + ProcToken( &rInfo ); + break; + case HtmlImportState::Start: + break; + case HtmlImportState::End: + if ( rInfo.aSelection.nEndPos ) + { + // If text remains: create paragraph, without calling CloseEntry(). + if( bInCell ) // ...but only in opened table cells. + { + bInCell = false; + NextRow( &rInfo ); + bInCell = true; + } + CloseEntry( &rInfo ); + } + while ( nTableLevel > 0 ) + TableOff( &rInfo ); // close tables, if
missing + break; + case HtmlImportState::SetAttr: + break; + case HtmlImportState::InsertText: + break; + case HtmlImportState::InsertPara: + if ( nTableLevel < 1 ) + { + CloseEntry( &rInfo ); + NextRow( &rInfo ); + } + break; + case HtmlImportState::InsertField: + break; + default: + OSL_FAIL("HTMLImportHdl: unknown ImportInfo.eState"); + } +} + +void ScHTMLLayoutParser::TableDataOn( HtmlImportInfo* pInfo ) +{ + if ( bInCell ) + CloseEntry( pInfo ); + if ( !nTableLevel ) + { + OSL_FAIL( "dumbo doc! or without previous " ); + TableOn( pInfo ); + } + bInCell = true; + bool bHorJustifyCenterTH = (pInfo->nToken == HtmlTokenId::TABLEHEADER_ON); + const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); + for (const auto & rOption : rOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::COLSPAN: + { + mxActEntry->nColOverlap = static_cast(rOption.GetString().toInt32()); + } + break; + case HtmlOptionId::ROWSPAN: + { + mxActEntry->nRowOverlap = static_cast(rOption.GetString().toInt32()); + } + break; + case HtmlOptionId::ALIGN: + { + bHorJustifyCenterTH = false; + SvxCellHorJustify eVal; + const OUString& rOptVal = rOption.GetString(); + if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) ) + eVal = SvxCellHorJustify::Right; + else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center ) ) + eVal = SvxCellHorJustify::Center; + else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) ) + eVal = SvxCellHorJustify::Left; + else + eVal = SvxCellHorJustify::Standard; + if ( eVal != SvxCellHorJustify::Standard ) + mxActEntry->aItemSet.Put(SvxHorJustifyItem(eVal, ATTR_HOR_JUSTIFY)); + } + break; + case HtmlOptionId::VALIGN: + { + SvxCellVerJustify eVal; + const OUString& rOptVal = rOption.GetString(); + if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top ) ) + eVal = SvxCellVerJustify::Top; + else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle ) ) + eVal = SvxCellVerJustify::Center; + else if ( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom ) ) + eVal = SvxCellVerJustify::Bottom; + else + eVal = SvxCellVerJustify::Standard; + mxActEntry->aItemSet.Put(SvxVerJustifyItem(eVal, ATTR_VER_JUSTIFY)); + } + break; + case HtmlOptionId::WIDTH: + { + mxActEntry->nWidth = GetWidthPixel(rOption); + } + break; + case HtmlOptionId::BGCOLOR: + { + Color aColor; + rOption.GetColor( aColor ); + mxActEntry->aItemSet.Put(SvxBrushItem(aColor, ATTR_BACKGROUND)); + } + break; + case HtmlOptionId::SDVAL: + { + mxActEntry->pValStr = rOption.GetString(); + } + break; + case HtmlOptionId::SDNUM: + { + mxActEntry->pNumStr = rOption.GetString(); + } + break; + default: break; + } + } + + mxActEntry->nCol = nColCnt; + mxActEntry->nRow = nRowCnt; + mxActEntry->nTab = nTable; + + if ( bHorJustifyCenterTH ) + mxActEntry->aItemSet.Put( + SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY) ); +} + +void ScHTMLLayoutParser::TableRowOn( const HtmlImportInfo* pInfo ) +{ + if ( nColCnt > nColCntStart ) + NextRow( pInfo ); // The optional TableRowOff wasn't there + nColOffset = nColOffsetStart; +} + +void ScHTMLLayoutParser::TableRowOff( const HtmlImportInfo* pInfo ) +{ + NextRow( pInfo ); +} + +void ScHTMLLayoutParser::TableDataOff( const HtmlImportInfo* pInfo ) +{ + if ( bInCell ) + CloseEntry( pInfo ); // Only if it really was one +} + +void ScHTMLLayoutParser::TableOn( HtmlImportInfo* pInfo ) +{ + if ( ++nTableLevel > 1 ) + { // Table in Table + sal_uInt16 nTmpColOffset = nColOffset; // Will be changed in Colonize() + Colonize(mxActEntry.get()); + aTableStack.push( std::make_unique( + mxActEntry, xLockedList, pLocalColOffset, nFirstTableCell, + nRowCnt, nColCntStart, nMaxCol, nTable, + nTableWidth, nColOffset, nColOffsetStart, + bFirstRow ) ); + sal_uInt16 nLastWidth = nTableWidth; + nTableWidth = GetWidth(mxActEntry.get()); + if ( nTableWidth == nLastWidth && nMaxCol - nColCntStart > 1 ) + { // There must be more than one, so this one cannot be enough + nTableWidth = nLastWidth / static_cast((nMaxCol - nColCntStart)); + } + nLastWidth = nTableWidth; + if ( pInfo->nToken == HtmlTokenId::TABLE_ON ) + { // It can still be TD or TH, if we didn't have a TABLE earlier + const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); + for (const auto & rOption : rOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::WIDTH: + { // Percent: of document width or outer cell + nTableWidth = GetWidthPixel( rOption ); + } + break; + case HtmlOptionId::BORDER: + // Border is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0)); + break; + default: break; + } + } + } + bInCell = false; + if ( bTabInTabCell && (nTableWidth >= nLastWidth) ) + { // Multiple tables in one cell, underneath each other + bTabInTabCell = false; + NextRow( pInfo ); + } + else + { // It start's in this cell or next to each other + bTabInTabCell = false; + nColCntStart = nColCnt; + nColOffset = nTmpColOffset; + nColOffsetStart = nColOffset; + } + + NewActEntry(!maList.empty() ? maList.back().get() : nullptr); // New free flying mxActEntry + xLockedList = new ScRangeList; + } + else + { // Simple table at the document level + EntryEnd(mxActEntry.get(), pInfo->aSelection); + if (mxActEntry->aSel.HasRange()) + { // Flying text left + CloseEntry( pInfo ); + NextRow( pInfo ); + } + aTableStack.push( std::make_unique( + mxActEntry, xLockedList, pLocalColOffset, nFirstTableCell, + nRowCnt, nColCntStart, nMaxCol, nTable, + nTableWidth, nColOffset, nColOffsetStart, + bFirstRow ) ); + // As soon as we have multiple tables we need to be tolerant with the offsets. + if (nMaxTable > 0) + nOffsetTolerance = SC_HTML_OFFSET_TOLERANCE_LARGE; + nTableWidth = 0; + if ( pInfo->nToken == HtmlTokenId::TABLE_ON ) + { + // It can still be TD or TH, if we didn't have a TABLE earlier + const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); + for (const auto & rOption : rOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::WIDTH: + { // Percent: of document width or outer cell + nTableWidth = GetWidthPixel( rOption ); + } + break; + case HtmlOptionId::BORDER: + //BorderOn is: ((pOption->GetString().Len() == 0) || (pOption->GetNumber() != 0)); + break; + default: break; + } + } + } + } + nTable = ++nMaxTable; + bFirstRow = true; + nFirstTableCell = maList.size(); + + pLocalColOffset = new ScHTMLColOffset; + MakeColNoRef( pLocalColOffset, nColOffsetStart, 0, 0, 0 ); +} + +void ScHTMLLayoutParser::TableOff( const HtmlImportInfo* pInfo ) +{ + if ( bInCell ) + CloseEntry( pInfo ); + if ( nColCnt > nColCntStart ) + TableRowOff( pInfo ); // The optional TableRowOff wasn't + if ( !nTableLevel ) + { + OSL_FAIL( "dumbo doc!
without opening " ); + return ; + } + if ( --nTableLevel > 0 ) + { // Table in Table done + if ( !aTableStack.empty() ) + { + std::unique_ptr pS = std::move(aTableStack.top()); + aTableStack.pop(); + + auto& pE = pS->xCellEntry; + SCROW nRows = nRowCnt - pS->nRowCnt; + if ( nRows > 1 ) + { // Insert size of table at this position + SCROW nRow = pS->nRowCnt; + sal_uInt16 nTab = pS->nTable; + if ( !pTables ) + pTables.reset( new OuterMap ); + // Height of outer table + OuterMap::const_iterator it = pTables->find( nTab ); + InnerMap* pTab1; + if ( it == pTables->end() ) + { + pTab1 = new InnerMap; + (*pTables)[ nTab ] = pTab1; + } + else + pTab1 = it->second; + SCROW nRowSpan = pE->nRowOverlap; + SCROW nRowKGV; + SCROW nRowsPerRow1; // Outer table + SCROW nRowsPerRow2; // Inner table + if ( nRowSpan > 1 ) + { // LCM to which we can map the inner and outer rows + nRowKGV = std::lcm( nRowSpan, nRows ); + nRowsPerRow1 = nRowKGV / nRowSpan; + nRowsPerRow2 = nRowKGV / nRows; + } + else + { + nRowKGV = nRowsPerRow1 = nRows; + nRowsPerRow2 = 1; + } + InnerMap* pTab2 = nullptr; + if ( nRowsPerRow2 > 1 ) + { // Height of the inner table + pTab2 = new InnerMap; + (*pTables)[ nTable ] = pTab2; + } + // Abuse void* Data entry of the Table class for height mapping + if ( nRowKGV > 1 ) + { + if ( nRowsPerRow1 > 1 ) + { // Outer + for ( SCROW j=0; j < nRowSpan; j++ ) + { + sal_uLong nRowKey = nRow + j; + SCROW nR = (*pTab1)[ nRowKey ]; + if ( !nR ) + (*pTab1)[ nRowKey ] = nRowsPerRow1; + else if ( nRowsPerRow1 > nR ) + (*pTab1)[ nRowKey ] = nRowsPerRow1; + //TODO: How can we improve on this? + else if ( nRowsPerRow1 < nR && nRowSpan == 1 + && nTable == nMaxTable ) + { // Still some space left, merge in a better way (if possible) + SCROW nAdd = nRowsPerRow1 - (nR % nRowsPerRow1); + nR += nAdd; + if ( (nR % nRows) == 0 ) + { // Only if representable + SCROW nR2 = (*pTab1)[ nRowKey+1 ]; + if ( nR2 > nAdd ) + { // Only if we really have enough space + (*pTab1)[ nRowKey ] = nR; + (*pTab1)[ nRowKey+1 ] = nR2 - nAdd; + nRowsPerRow2 = nR / nRows; + } + } + } + } + } + if ( nRowsPerRow2 > 1 ) + { // Inner + if ( !pTab2 ) + { // nRowsPerRow2 could be've been incremented + pTab2 = new InnerMap; + (*pTables)[ nTable ] = pTab2; + } + for ( SCROW j=0; j < nRows; j++ ) + { + sal_uLong nRowKey = nRow + j; + (*pTab2)[ nRowKey ] = nRowsPerRow2; + } + } + } + } + + SetWidths(); + + if ( !pE->nWidth ) + pE->nWidth = nTableWidth; + else if ( pE->nWidth < nTableWidth ) + { + sal_uInt16 nOldOffset = pE->nOffset + pE->nWidth; + sal_uInt16 nNewOffset = pE->nOffset + nTableWidth; + ModifyOffset( pS->pLocalColOffset, nOldOffset, nNewOffset, nOffsetTolerance ); + sal_uInt16 nTmp = nNewOffset - pE->nOffset - pE->nWidth; + pE->nWidth = nNewOffset - pE->nOffset; + pS->nTableWidth = pS->nTableWidth + nTmp; + if ( pS->nColOffset >= nOldOffset ) + pS->nColOffset = pS->nColOffset + nTmp; + } + + nColCnt = pE->nCol + pE->nColOverlap; + nRowCnt = pS->nRowCnt; + nColCntStart = pS->nColCntStart; + nMaxCol = pS->nMaxCol; + nTable = pS->nTable; + nTableWidth = pS->nTableWidth; + nFirstTableCell = pS->nFirstTableCell; + nColOffset = pS->nColOffset; + nColOffsetStart = pS->nColOffsetStart; + bFirstRow = pS->bFirstRow; + xLockedList = pS->xLockedList; + pLocalColOffset = pS->pLocalColOffset; + // mxActEntry is kept around if a table is started in the same row + // (anything's possible in HTML); will be deleted by CloseEntry + mxActEntry = pE; + } + bTabInTabCell = true; + bInCell = true; + } + else + { // Simple table finished + SetWidths(); + nMaxCol = 0; + nTable = 0; + if ( !aTableStack.empty() ) + { + ScHTMLTableStackEntry* pS = aTableStack.top().get(); + delete pLocalColOffset; + pLocalColOffset = pS->pLocalColOffset; + aTableStack.pop(); + } + } +} + +void ScHTMLLayoutParser::Image( HtmlImportInfo* pInfo ) +{ + mxActEntry->maImageList.push_back(std::make_unique()); + ScHTMLImage* pImage = mxActEntry->maImageList.back().get(); + const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); + for (const auto & rOption : rOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::SRC: + { + pImage->aURL = INetURLObject::GetAbsURL( aBaseURL, rOption.GetString() ); + } + break; + case HtmlOptionId::ALT: + { + if (!mxActEntry->bHasGraphic) + { // ALT text only if not any image loaded + if (!mxActEntry->aAltText.isEmpty()) + mxActEntry->aAltText += "; "; + + mxActEntry->aAltText += rOption.GetString(); + } + } + break; + case HtmlOptionId::WIDTH: + { + pImage->aSize.setWidth( static_cast(rOption.GetNumber()) ); + } + break; + case HtmlOptionId::HEIGHT: + { + pImage->aSize.setHeight( static_cast(rOption.GetNumber()) ); + } + break; + case HtmlOptionId::HSPACE: + { + pImage->aSpace.setX( static_cast(rOption.GetNumber()) ); + } + break; + case HtmlOptionId::VSPACE: + { + pImage->aSpace.setY( static_cast(rOption.GetNumber()) ); + } + break; + default: break; + } + } + if (pImage->aURL.isEmpty()) + { + OSL_FAIL( "Image: graphic without URL ?!?" ); + return ; + } + + sal_uInt16 nFormat; + std::unique_ptr pGraphic(new Graphic); + GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); + if ( ERRCODE_NONE != GraphicFilter::LoadGraphic( pImage->aURL, pImage->aFilterName, + *pGraphic, &rFilter, &nFormat ) ) + { + return ; // Bad luck + } + if (!mxActEntry->bHasGraphic) + { // discard any ALT text in this cell if we have any image + mxActEntry->bHasGraphic = true; + mxActEntry->aAltText.clear(); + } + pImage->aFilterName = rFilter.GetImportFormatName( nFormat ); + pImage->pGraphic = std::move( pGraphic ); + if ( !(pImage->aSize.Width() && pImage->aSize.Height()) ) + { + OutputDevice* pDefaultDev = Application::GetDefaultDevice(); + pImage->aSize = pDefaultDev->LogicToPixel( pImage->pGraphic->GetPrefSize(), + pImage->pGraphic->GetPrefMapMode() ); + } + if (mxActEntry->maImageList.empty()) + return; + + tools::Long nWidth = 0; + for (const std::unique_ptr & pI : mxActEntry->maImageList) + { + if ( pI->nDir & nHorizontal ) + nWidth += pI->aSize.Width() + 2 * pI->aSpace.X(); + else + nWidth = 0; + } + if ( mxActEntry->nWidth + && (nWidth + pImage->aSize.Width() + 2 * pImage->aSpace.X() + >= mxActEntry->nWidth) ) + mxActEntry->maImageList.back()->nDir = nVertical; +} + +void ScHTMLLayoutParser::ColOn( HtmlImportInfo* pInfo ) +{ + const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); + for (const auto & rOption : rOptions) + { + if( rOption.GetToken() == HtmlOptionId::WIDTH ) + { + sal_uInt16 nVal = GetWidthPixel( rOption ); + MakeCol( pLocalColOffset, nColOffset, nVal, 0, 0 ); + nColOffset = nColOffset + nVal; + } + } +} + +sal_uInt16 ScHTMLLayoutParser::GetWidthPixel( const HTMLOption& rOption ) +{ + const OUString& rOptVal = rOption.GetString(); + if ( rOptVal.indexOf('%') != -1 ) + { // Percent + sal_uInt16 nW = (nTableWidth ? nTableWidth : static_cast(aPageSize.Width())); + return static_cast((rOption.GetNumber() * nW) / 100); + } + else + { + if ( rOptVal.indexOf('*') != -1 ) + { // Relative to what? + // TODO: Collect all relative values in ColArray and then MakeCol + return 0; + } + else + return static_cast(rOption.GetNumber()); // Pixel + } +} + +void ScHTMLLayoutParser::AnchorOn( HtmlImportInfo* pInfo ) +{ + const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); + for (const auto & rOption : rOptions) + { + if( rOption.GetToken() == HtmlOptionId::NAME ) + mxActEntry->pName = rOption.GetString(); + } +} + +bool ScHTMLLayoutParser::IsAtBeginningOfText( const HtmlImportInfo* pInfo ) +{ + ESelection& rSel = mxActEntry->aSel; + return rSel.nStartPara == rSel.nEndPara && + rSel.nStartPara <= pInfo->aSelection.nEndPara && + pEdit->GetTextLen( rSel.nStartPara ) == 0; +} + +void ScHTMLLayoutParser::FontOn( HtmlImportInfo* pInfo ) +{ + if ( !IsAtBeginningOfText( pInfo ) ) + return; + +// Only at the start of the text; applies to whole line + const HTMLOptions& rOptions = static_cast(pInfo->pParser)->GetOptions(); + for (const auto & rOption : rOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::FACE : + { + const OUString& rFace = rOption.GetString(); + OUStringBuffer aFontName; + sal_Int32 nPos = 0; + while( nPos != -1 ) + { + // Font list, VCL uses the semicolon as separator + // HTML uses the comma + std::u16string_view aFName = o3tl::getToken(rFace, 0, ',', nPos ); + aFName = comphelper::string::strip(aFName, ' '); + if( !aFontName.isEmpty() ) + aFontName.append(";"); + aFontName.append(aFName); + } + if ( !aFontName.isEmpty() ) + mxActEntry->aItemSet.Put( SvxFontItem( FAMILY_DONTKNOW, + aFontName.makeStringAndClear(), OUString(), PITCH_DONTKNOW, + RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) ); + } + break; + case HtmlOptionId::SIZE : + { + sal_uInt16 nSize = static_cast(rOption.GetNumber()); + if ( nSize == 0 ) + nSize = 1; + else if ( nSize > SC_HTML_FONTSIZES ) + nSize = SC_HTML_FONTSIZES; + mxActEntry->aItemSet.Put( SvxFontHeightItem( + maFontHeights[nSize-1], 100, ATTR_FONT_HEIGHT ) ); + } + break; + case HtmlOptionId::COLOR : + { + Color aColor; + rOption.GetColor( aColor ); + mxActEntry->aItemSet.Put( SvxColorItem( aColor, ATTR_FONT_COLOR ) ); + } + break; + default: break; + } + } +} + +void ScHTMLLayoutParser::ProcToken( HtmlImportInfo* pInfo ) +{ + switch ( pInfo->nToken ) + { + case HtmlTokenId::META: + { + HTMLParser* pParser = static_cast(pInfo->pParser); + uno::Reference xDPS( + mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW); + pParser->ParseMetaOptions( + xDPS->getDocumentProperties(), + mpDoc->GetDocumentShell()->GetHeaderAttributes() ); + } + break; + case HtmlTokenId::TITLE_ON: + { + bInTitle = true; + aString.clear(); + } + break; + case HtmlTokenId::TITLE_OFF: + { + if ( bInTitle && !aString.isEmpty() ) + { + // Remove blanks from line breaks + aString = aString.trim(); + uno::Reference xDPS( + mpDoc->GetDocumentShell()->GetModel(), + uno::UNO_QUERY_THROW); + xDPS->getDocumentProperties()->setTitle(aString); + } + bInTitle = false; + } + break; + case HtmlTokenId::TABLE_ON: + { + TableOn( pInfo ); + } + break; + case HtmlTokenId::COL_ON: + { + ColOn( pInfo ); + } + break; + case HtmlTokenId::TABLEHEADER_ON: // Opens row + { + if ( bInCell ) + CloseEntry( pInfo ); + // Do not set bInCell to true, TableDataOn does that + mxActEntry->aItemSet.Put( + SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT) ); + [[fallthrough]]; + } + case HtmlTokenId::TABLEDATA_ON: // Opens cell + { + TableDataOn( pInfo ); + } + break; + case HtmlTokenId::TABLEHEADER_OFF: + case HtmlTokenId::TABLEDATA_OFF: // Closes cell + { + TableDataOff( pInfo ); + } + break; + case HtmlTokenId::TABLEROW_ON: // Before first cell in row + { + TableRowOn( pInfo ); + } + break; + case HtmlTokenId::TABLEROW_OFF: // After last cell in row + { + TableRowOff( pInfo ); + } + break; + case HtmlTokenId::TABLE_OFF: + { + TableOff( pInfo ); + } + break; + case HtmlTokenId::IMAGE: + { + Image( pInfo ); + } + break; + case HtmlTokenId::PARABREAK_OFF: + { // We continue vertically after an image + if (!mxActEntry->maImageList.empty()) + mxActEntry->maImageList.back()->nDir = nVertical; + } + break; + case HtmlTokenId::ANCHOR_ON: + { + AnchorOn( pInfo ); + } + break; + case HtmlTokenId::FONT_ON : + { + FontOn( pInfo ); + } + break; + case HtmlTokenId::BIGPRINT_ON : + { + // TODO: Remember current font size and increase by 1 + if ( IsAtBeginningOfText( pInfo ) ) + mxActEntry->aItemSet.Put( SvxFontHeightItem( + maFontHeights[3], 100, ATTR_FONT_HEIGHT ) ); + } + break; + case HtmlTokenId::SMALLPRINT_ON : + { + // TODO: Remember current font size and decrease by 1 + if ( IsAtBeginningOfText( pInfo ) ) + mxActEntry->aItemSet.Put( SvxFontHeightItem( + maFontHeights[0], 100, ATTR_FONT_HEIGHT ) ); + } + break; + case HtmlTokenId::BOLD_ON : + case HtmlTokenId::STRONG_ON : + { + if ( IsAtBeginningOfText( pInfo ) ) + mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD, + ATTR_FONT_WEIGHT ) ); + } + break; + case HtmlTokenId::ITALIC_ON : + case HtmlTokenId::EMPHASIS_ON : + case HtmlTokenId::ADDRESS_ON : + case HtmlTokenId::BLOCKQUOTE_ON : + case HtmlTokenId::BLOCKQUOTE30_ON : + case HtmlTokenId::CITATION_ON : + case HtmlTokenId::VARIABLE_ON : + { + if ( IsAtBeginningOfText( pInfo ) ) + mxActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL, + ATTR_FONT_POSTURE ) ); + } + break; + case HtmlTokenId::DEFINSTANCE_ON : + { + if ( IsAtBeginningOfText( pInfo ) ) + { + mxActEntry->aItemSet.Put( SvxWeightItem( WEIGHT_BOLD, + ATTR_FONT_WEIGHT ) ); + mxActEntry->aItemSet.Put( SvxPostureItem( ITALIC_NORMAL, + ATTR_FONT_POSTURE ) ); + } + } + break; + case HtmlTokenId::UNDERLINE_ON : + { + if ( IsAtBeginningOfText( pInfo ) ) + mxActEntry->aItemSet.Put( SvxUnderlineItem( LINESTYLE_SINGLE, + ATTR_FONT_UNDERLINE ) ); + } + break; + case HtmlTokenId::TEXTTOKEN: + { + if ( bInTitle ) + aString += pInfo->aText; + } + break; + default: ; + } +} + +// HTML DATA QUERY PARSER + +template< typename Type > +static Type getLimitedValue( const Type& rValue, const Type& rMin, const Type& rMax ) +{ return std::clamp( rValue, rMin, rMax ); } + +ScHTMLEntry::ScHTMLEntry( const SfxItemSet& rItemSet, ScHTMLTableId nTableId ) : + ScEEParseEntry( rItemSet ), + mbImportAlways( false ) +{ + nTab = nTableId; + bEntirePara = false; +} + +bool ScHTMLEntry::HasContents() const +{ + return mbImportAlways || aSel.HasRange() || !aAltText.isEmpty() || IsTable(); +} + +void ScHTMLEntry::AdjustStart( const HtmlImportInfo& rInfo ) +{ + // set start position + aSel.nStartPara = rInfo.aSelection.nStartPara; + aSel.nStartPos = rInfo.aSelection.nStartPos; + // adjust end position + if( (aSel.nEndPara < aSel.nStartPara) || ((aSel.nEndPara == aSel.nStartPara) && (aSel.nEndPos < aSel.nStartPos)) ) + { + aSel.nEndPara = aSel.nStartPara; + aSel.nEndPos = aSel.nStartPos; + } +} + +void ScHTMLEntry::AdjustEnd( const HtmlImportInfo& rInfo ) +{ + OSL_ENSURE( (aSel.nEndPara < rInfo.aSelection.nEndPara) || + ((aSel.nEndPara == rInfo.aSelection.nEndPara) && (aSel.nEndPos <= rInfo.aSelection.nEndPos)), + "ScHTMLQueryParser::AdjustEntryEnd - invalid end position" ); + // set end position + aSel.nEndPara = rInfo.aSelection.nEndPara; + aSel.nEndPos = rInfo.aSelection.nEndPos; +} + +void ScHTMLEntry::Strip( const EditEngine& rEditEngine ) +{ + // strip leading empty paragraphs + while( (aSel.nStartPara < aSel.nEndPara) && (rEditEngine.GetTextLen( aSel.nStartPara ) <= aSel.nStartPos) ) + { + ++aSel.nStartPara; + aSel.nStartPos = 0; + } + // strip trailing empty paragraphs + while( (aSel.nStartPara < aSel.nEndPara) && (aSel.nEndPos == 0) ) + { + --aSel.nEndPara; + aSel.nEndPos = rEditEngine.GetTextLen( aSel.nEndPara ); + } +} + +/** A map of ScHTMLTable objects. + + Organizes the tables with a unique table key. Stores nested tables inside + the parent table and forms in this way a tree structure of tables. An + instance of this class owns the contained table objects and deletes them + on destruction. + */ +class ScHTMLTableMap final +{ +private: + typedef std::shared_ptr< ScHTMLTable > ScHTMLTablePtr; + typedef std::map< ScHTMLTableId, ScHTMLTablePtr > ScHTMLTableStdMap; + +public: + typedef ScHTMLTableStdMap::iterator iterator; + typedef ScHTMLTableStdMap::const_iterator const_iterator; + +private: + ScHTMLTable& mrParentTable; /// Reference to parent table. + ScHTMLTableStdMap maTables; /// Container for all table objects. + mutable ScHTMLTable* mpCurrTable; /// Current table, used for fast search. + +public: + explicit ScHTMLTableMap( ScHTMLTable& rParentTable ); + + const_iterator begin() const { return maTables.begin(); } + const_iterator end() const { return maTables.end(); } + + /** Returns the specified table. + @param nTableId Unique identifier of the table. + @param bDeep true = searches deep in all nested table; false = only in this container. */ + ScHTMLTable* FindTable( ScHTMLTableId nTableId, bool bDeep = true ) const; + + /** Inserts a new table into the container. This container owns the created table. + @param bPreFormText true = New table is based on preformatted text (
 tag). */
+    ScHTMLTable*        CreateTable( const HtmlImportInfo& rInfo, bool bPreFormText, const ScDocument& rDoc );
+
+private:
+    /** Sets a working table with its index for search optimization. */
+    void         SetCurrTable( ScHTMLTable* pTable ) const
+                            { if( pTable ) mpCurrTable = pTable; }
+};
+
+ScHTMLTableMap::ScHTMLTableMap( ScHTMLTable& rParentTable ) :
+    mrParentTable(rParentTable),
+    mpCurrTable(nullptr)
+{
+}
+
+ScHTMLTable* ScHTMLTableMap::FindTable( ScHTMLTableId nTableId, bool bDeep ) const
+{
+    ScHTMLTable* pResult = nullptr;
+    if( mpCurrTable && (nTableId == mpCurrTable->GetTableId()) )
+        pResult = mpCurrTable;              // cached table
+    else
+    {
+        const_iterator aFind = maTables.find( nTableId );
+        if( aFind != maTables.end() )
+            pResult = aFind->second.get();  // table from this container
+    }
+
+    // not found -> search deep in nested tables
+    if( !pResult && bDeep )
+        for( const_iterator aIter = begin(), aEnd = end(); !pResult && (aIter != aEnd); ++aIter )
+            pResult = aIter->second->FindNestedTable( nTableId );
+
+    SetCurrTable( pResult );
+    return pResult;
+}
+
+ScHTMLTable* ScHTMLTableMap::CreateTable( const HtmlImportInfo& rInfo, bool bPreFormText, const ScDocument& rDoc )
+{
+    ScHTMLTable* pTable = new ScHTMLTable( mrParentTable, rInfo, bPreFormText, rDoc );
+    maTables[ pTable->GetTableId() ].reset( pTable );
+    SetCurrTable( pTable );
+    return pTable;
+}
+
+namespace {
+
+/** Simplified forward iterator for convenience.
+
+    Before the iterator can be dereferenced, it must be tested with the is()
+    method. The iterator may be invalid directly after construction (e.g. empty
+    container).
+ */
+class ScHTMLTableIterator
+{
+public:
+    /** Constructs the iterator for the passed table map.
+        @param pTableMap  Pointer to the table map (is allowed to be NULL). */
+    explicit            ScHTMLTableIterator( const ScHTMLTableMap* pTableMap );
+
+    bool         is() const { return mpTableMap && maIter != maEnd; }
+    ScHTMLTable* operator->() { return maIter->second.get(); }
+    ScHTMLTableIterator& operator++() { ++maIter; return *this; }
+
+private:
+    ScHTMLTableMap::const_iterator maIter;
+    ScHTMLTableMap::const_iterator maEnd;
+    const ScHTMLTableMap* mpTableMap;
+};
+
+}
+
+ScHTMLTableIterator::ScHTMLTableIterator( const ScHTMLTableMap* pTableMap ) :
+    mpTableMap(pTableMap)
+{
+    if( pTableMap )
+    {
+        maIter = pTableMap->begin();
+        maEnd = pTableMap->end();
+    }
+}
+
+ScHTMLTableAutoId::ScHTMLTableAutoId( ScHTMLTableId& rnUnusedId ) :
+    mnTableId( rnUnusedId ),
+    mrnUnusedId( rnUnusedId )
+{
+    ++mrnUnusedId;
+}
+
+ScHTMLTable::ScHTMLTable( ScHTMLTable& rParentTable, const HtmlImportInfo& rInfo, bool bPreFormText, const ScDocument& rDoc ) :
+    mpParentTable( &rParentTable ),
+    maTableId( rParentTable.maTableId.mrnUnusedId ),
+    maTableItemSet( rParentTable.GetCurrItemSet() ),
+    mrEditEngine( rParentTable.mrEditEngine ),
+    mrEEParseList( rParentTable.mrEEParseList ),
+    mpCurrEntryVector( nullptr ),
+    maSize( 1, 1 ),
+    mpParser(rParentTable.mpParser),
+    mrDoc(rDoc),
+    mbBorderOn( false ),
+    mbPreFormText( bPreFormText ),
+    mbRowOn( false ),
+    mbDataOn( false ),
+    mbPushEmptyLine( false ),
+    mbCaptionOn ( false )
+{
+    if( mbPreFormText )
+    {
+        ImplRowOn();
+        ImplDataOn( ScHTMLSize( 1, 1 ) );
+    }
+    else
+    {
+        ProcessFormatOptions( maTableItemSet, rInfo );
+        const HTMLOptions& rOptions = static_cast(rInfo.pParser)->GetOptions();
+        for (const auto& rOption : rOptions)
+        {
+            switch( rOption.GetToken() )
+            {
+                case HtmlOptionId::BORDER:
+                    mbBorderOn = rOption.GetString().isEmpty() || (rOption.GetNumber() != 0);
+                break;
+                case HtmlOptionId::ID:
+                    maTableName = rOption.GetString();
+                break;
+                default: break;
+            }
+        }
+    }
+
+    CreateNewEntry( rInfo );
+}
+
+ScHTMLTable::ScHTMLTable(
+    SfxItemPool& rPool,
+    EditEngine& rEditEngine,
+    std::vector>& rEEParseList,
+    ScHTMLTableId& rnUnusedId, ScHTMLParser* pParser, const ScDocument& rDoc
+) :
+    mpParentTable( nullptr ),
+    maTableId( rnUnusedId ),
+    maTableItemSet( rPool ),
+    mrEditEngine( rEditEngine ),
+    mrEEParseList( rEEParseList ),
+    mpCurrEntryVector( nullptr ),
+    maSize( 1, 1 ),
+    mpParser(pParser),
+    mrDoc(rDoc),
+    mbBorderOn( false ),
+    mbPreFormText( false ),
+    mbRowOn( false ),
+    mbDataOn( false ),
+    mbPushEmptyLine( false ),
+    mbCaptionOn ( false )
+{
+    // open the first "cell" of the document
+    ImplRowOn();
+    ImplDataOn( ScHTMLSize( 1, 1 ) );
+    mxCurrEntry = CreateEntry();
+}
+
+ScHTMLTable::~ScHTMLTable()
+{
+}
+
+const SfxItemSet& ScHTMLTable::GetCurrItemSet() const
+{
+    // first try cell item set, then row item set, then table item set
+    return moDataItemSet ? *moDataItemSet : (moRowItemSet ? *moRowItemSet : maTableItemSet);
+}
+
+ScHTMLSize ScHTMLTable::GetSpan( const ScHTMLPos& rCellPos ) const
+{
+    ScHTMLSize aSpan( 1, 1 );
+    const ScRange* pRange = maVMergedCells.Find( rCellPos.MakeAddr() );
+    if (!pRange)
+        pRange = maHMergedCells.Find( rCellPos.MakeAddr() );
+    if (pRange)
+        aSpan.Set( pRange->aEnd.Col() - pRange->aStart.Col() + 1, pRange->aEnd.Row() - pRange->aStart.Row() + 1 );
+    return aSpan;
+}
+
+ScHTMLTable* ScHTMLTable::FindNestedTable( ScHTMLTableId nTableId ) const
+{
+    return mxNestedTables ? mxNestedTables->FindTable( nTableId ) : nullptr;
+}
+
+void ScHTMLTable::PutItem( const SfxPoolItem& rItem )
+{
+    OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PutItem - no current entry" );
+    if( mxCurrEntry && mxCurrEntry->IsEmpty() )
+        mxCurrEntry->GetItemSet().Put( rItem );
+}
+
+void ScHTMLTable::PutText( const HtmlImportInfo& rInfo )
+{
+    OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PutText - no current entry" );
+    if( mxCurrEntry )
+    {
+        if( !mxCurrEntry->HasContents() && IsSpaceCharInfo( rInfo ) )
+            mxCurrEntry->AdjustStart( rInfo );
+        else
+            mxCurrEntry->AdjustEnd( rInfo );
+        if (mbCaptionOn)
+            maCaptionBuffer.append(rInfo.aText);
+
+    }
+}
+
+void ScHTMLTable::InsertPara( const HtmlImportInfo& rInfo )
+{
+    if( mxCurrEntry && mbDataOn && !IsEmptyCell() )
+        mxCurrEntry->SetImportAlways();
+    PushEntry( rInfo );
+    CreateNewEntry( rInfo );
+    InsertLeadingEmptyLine();
+}
+
+void ScHTMLTable::BreakOn()
+{
+    // empty line, if 
is at start of cell + mbPushEmptyLine = !mbPreFormText && mbDataOn && IsEmptyCell(); +} + +void ScHTMLTable::HeadingOn() +{ + // call directly, InsertPara() has not been called before + InsertLeadingEmptyLine(); +} + +void ScHTMLTable::InsertLeadingEmptyLine() +{ + // empty line, if

,

, , or are not at start of cell + mbPushEmptyLine = !mbPreFormText && mbDataOn && !IsEmptyCell(); +} + +void ScHTMLTable::AnchorOn() +{ + OSL_ENSURE( mxCurrEntry, "ScHTMLTable::AnchorOn - no current entry" ); + // don't skip entries with single hyperlinks + if( mxCurrEntry ) + mxCurrEntry->SetImportAlways(); +} + +ScHTMLTable* ScHTMLTable::TableOn( const HtmlImportInfo& rInfo ) +{ + PushEntry( rInfo ); + return InsertNestedTable( rInfo, false ); +} + +ScHTMLTable* ScHTMLTable::TableOff( const HtmlImportInfo& rInfo ) +{ + return mbPreFormText ? this : CloseTable( rInfo ); +} + +void ScHTMLTable::CaptionOn() +{ + mbCaptionOn = true; + maCaptionBuffer.setLength(0); +} + +void ScHTMLTable::CaptionOff() +{ + if (!mbCaptionOn) + return; + maCaption = maCaptionBuffer.makeStringAndClear().trim(); + mbCaptionOn = false; +} + +ScHTMLTable* ScHTMLTable::PreOn( const HtmlImportInfo& rInfo ) +{ + PushEntry( rInfo ); + return InsertNestedTable( rInfo, true ); +} + +ScHTMLTable* ScHTMLTable::PreOff( const HtmlImportInfo& rInfo ) +{ + return mbPreFormText ? CloseTable( rInfo ) : this; +} + +void ScHTMLTable::RowOn( const HtmlImportInfo& rInfo ) +{ + PushEntry( rInfo, true ); + if( mpParentTable && !mbPreFormText ) // no rows allowed in global and preformatted tables + { + ImplRowOn(); + ProcessFormatOptions( *moRowItemSet, rInfo ); + } + CreateNewEntry( rInfo ); +} + +void ScHTMLTable::RowOff( const HtmlImportInfo& rInfo ) +{ + PushEntry( rInfo, true ); + if( mpParentTable && !mbPreFormText ) // no rows allowed in global and preformatted tables + ImplRowOff(); + CreateNewEntry( rInfo ); +} + +namespace { + +/** + * Decode a number format string stored in Excel-generated HTML's CSS + * region. + */ +OUString decodeNumberFormat(const OUString& rFmt) +{ + OUStringBuffer aBuf; + const sal_Unicode* p = rFmt.getStr(); + sal_Int32 n = rFmt.getLength(); + for (sal_Int32 i = 0; i < n; ++i, ++p) + { + if (*p == '\\') + { + // Skip '\'. + ++i; + ++p; + + // Parse all subsequent digits until first non-digit is found. + sal_Int32 nDigitCount = 0; + const sal_Unicode* p1 = p; + for (; i < n; ++i, ++p, ++nDigitCount) + { + if (*p < '0' || '9' < *p) + { + --i; + --p; + break; + } + + } + if (nDigitCount) + { + // Hex-encoded character found. Decode it back into its + // original character. An example of number format with + // hex-encoded chars: "\0022$\0022\#\,\#\#0\.00" + sal_uInt32 nVal = OUString(p1, nDigitCount).toUInt32(16); + aBuf.append(static_cast(nVal)); + } + } + else + aBuf.append(*p); + } + return aBuf.makeStringAndClear(); +} + +} + +void ScHTMLTable::DataOn( const HtmlImportInfo& rInfo ) +{ + PushEntry( rInfo, true ); + if( mpParentTable && !mbPreFormText ) // no cells allowed in global and preformatted tables + { + // read needed options from the
+ case HtmlTokenId::TABLEROW_ON: mpCurrTable->RowOn( rInfo ); break; // + case HtmlTokenId::TABLEROW_OFF: mpCurrTable->RowOff( rInfo ); break; // + case HtmlTokenId::TABLEHEADER_ON: // + case HtmlTokenId::PREFORMTXT_ON: PreOn( rInfo ); break; //
+        case HtmlTokenId::PREFORMTXT_OFF:   PreOff( rInfo );                break;  // 
+ +// --- formatting --- + case HtmlTokenId::FONT_ON: FontOn( rInfo ); break; // + + case HtmlTokenId::BIGPRINT_ON: // + //! TODO: store current font size, use following size + mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 3 ], 100, ATTR_FONT_HEIGHT ) ); + break; + case HtmlTokenId::SMALLPRINT_ON: // + //! TODO: store current font size, use preceding size + mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ 0 ], 100, ATTR_FONT_HEIGHT ) ); + break; + + case HtmlTokenId::BOLD_ON: // + case HtmlTokenId::STRONG_ON: // + mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); + break; + + case HtmlTokenId::ITALIC_ON: // + case HtmlTokenId::EMPHASIS_ON: // + case HtmlTokenId::ADDRESS_ON: //
+ case HtmlTokenId::BLOCKQUOTE_ON: //
+ case HtmlTokenId::BLOCKQUOTE30_ON: // + case HtmlTokenId::CITATION_ON: // + case HtmlTokenId::VARIABLE_ON: // + mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) ); + break; + + case HtmlTokenId::DEFINSTANCE_ON: // + mpCurrTable->PutItem( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); + mpCurrTable->PutItem( SvxPostureItem( ITALIC_NORMAL, ATTR_FONT_POSTURE ) ); + break; + + case HtmlTokenId::UNDERLINE_ON: // + mpCurrTable->PutItem( SvxUnderlineItem( LINESTYLE_SINGLE, ATTR_FONT_UNDERLINE ) ); + break; + default: break; + } +} + +void ScHTMLQueryParser::InsertText( const HtmlImportInfo& rInfo ) +{ + mpCurrTable->PutText( rInfo ); + if( mbTitleOn ) + maTitle.append(rInfo.aText); +} + +void ScHTMLQueryParser::FontOn( const HtmlImportInfo& rInfo ) +{ + const HTMLOptions& rOptions = static_cast(rInfo.pParser)->GetOptions(); + for (const auto& rOption : rOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::FACE : + { + const OUString& rFace = rOption.GetString(); + OUString aFontName; + sal_Int32 nPos = 0; + while( nPos != -1 ) + { + // font list separator: VCL = ';' HTML = ',' + std::u16string_view aFName = comphelper::string::strip(o3tl::getToken(rFace, 0, ',', nPos), ' '); + aFontName = ScGlobal::addToken(aFontName, aFName, ';'); + } + if ( !aFontName.isEmpty() ) + mpCurrTable->PutItem( SvxFontItem( FAMILY_DONTKNOW, + aFontName, OUString(), PITCH_DONTKNOW, + RTL_TEXTENCODING_DONTKNOW, ATTR_FONT ) ); + } + break; + case HtmlOptionId::SIZE : + { + sal_uInt32 nSize = getLimitedValue< sal_uInt32 >( rOption.GetNumber(), 1, SC_HTML_FONTSIZES ); + mpCurrTable->PutItem( SvxFontHeightItem( maFontHeights[ nSize - 1 ], 100, ATTR_FONT_HEIGHT ) ); + } + break; + case HtmlOptionId::COLOR : + { + Color aColor; + rOption.GetColor( aColor ); + mpCurrTable->PutItem( SvxColorItem( aColor, ATTR_FONT_COLOR ) ); + } + break; + default: break; + } + } +} + +void ScHTMLQueryParser::MetaOn( const HtmlImportInfo& rInfo ) +{ + if( mpDoc->GetDocumentShell() ) + { + HTMLParser* pParser = static_cast< HTMLParser* >( rInfo.pParser ); + + uno::Reference xDPS( + mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW); + pParser->ParseMetaOptions( + xDPS->getDocumentProperties(), + mpDoc->GetDocumentShell()->GetHeaderAttributes() ); + } +} + +void ScHTMLQueryParser::TitleOn() +{ + mbTitleOn = true; + maTitle.setLength(0); +} + +void ScHTMLQueryParser::TitleOff( const HtmlImportInfo& rInfo ) +{ + if( !mbTitleOn ) + return; + + OUString aTitle = maTitle.makeStringAndClear().trim(); + if (!aTitle.isEmpty() && mpDoc->GetDocumentShell()) + { + uno::Reference xDPS( + mpDoc->GetDocumentShell()->GetModel(), uno::UNO_QUERY_THROW); + + xDPS->getDocumentProperties()->setTitle(aTitle); + } + InsertText( rInfo ); + mbTitleOn = false; +} + +void ScHTMLQueryParser::TableOn( const HtmlImportInfo& rInfo ) +{ + mpCurrTable = mpCurrTable->TableOn( rInfo ); +} + +void ScHTMLQueryParser::TableOff( const HtmlImportInfo& rInfo ) +{ + mpCurrTable = mpCurrTable->TableOff( rInfo ); +} + +void ScHTMLQueryParser::PreOn( const HtmlImportInfo& rInfo ) +{ + mpCurrTable = mpCurrTable->PreOn( rInfo ); +} + +void ScHTMLQueryParser::PreOff( const HtmlImportInfo& rInfo ) +{ + mpCurrTable = mpCurrTable->PreOff( rInfo ); +} + +void ScHTMLQueryParser::CloseTable( const HtmlImportInfo& rInfo ) +{ + mpCurrTable = mpCurrTable->CloseTable( rInfo ); +} + +namespace { + +/** + * Handler class for the CSS parser. + */ +class CSSHandler: public orcus::css_handler +{ + struct MemStr + { + const char* mp; + size_t mn; + + MemStr() : mp(nullptr), mn(0) {} + MemStr(const char* p, size_t n) : mp(p), mn(n) {} + MemStr(const MemStr& r) : mp(r.mp), mn(r.mn) {} + MemStr& operator=(const MemStr& r) = default; + }; + + typedef std::pair SelectorName; // element : class + typedef std::vector SelectorNames; + + SelectorNames maSelectorNames; // current selector names + MemStr maPropName; // current property name. + MemStr maPropValue; // current property value. + ScHTMLStyles& mrStyles; + +public: + explicit CSSHandler(ScHTMLStyles& rStyles): + maPropName(), + maPropValue(), + mrStyles(rStyles) + {} + + // selector name starting with "@" + static void at_rule_name(const char* /*p*/, size_t /*n*/) + { + // TODO: For now, we ignore at-rule properties + } + + // selector name not starting with "." or "#" (i.e. element selectors) + void simple_selector_type(const char* pElem, size_t nElem) + { + MemStr aElem(pElem, nElem); // element given + MemStr aClass(nullptr, 0); // class name not given - to be added in the "element global" storage + SelectorName aName(aElem, aClass); + + maSelectorNames.push_back(aName); + } + + // selector names starting with a "." (i.e. class selector) + void simple_selector_class(const char* pClassName, size_t nClassName) + { + MemStr aElem(nullptr, 0); // no element given - should be added in the "global" storage + MemStr aClass(pClassName, nClassName); + SelectorName aName(aElem, aClass); + + maSelectorNames.push_back(aName); + } + + // TODO: Add other selectors + + void property_name(const char* p, size_t n) + { + maPropName = MemStr(p, n); + } + + void value(const char* p, size_t n) + { + maPropValue = MemStr(p, n); + } + + void end_block() { + maSelectorNames.clear(); + } + + void end_property() + { + SelectorNames::const_iterator itr = maSelectorNames.begin(), itrEnd = maSelectorNames.end(); + for (; itr != itrEnd; ++itr) + { + // Add this property to the collection for each selector. + const SelectorName& rSelName = *itr; + const MemStr& rElem = rSelName.first; + const MemStr& rClass = rSelName.second; + OUString aName(maPropName.mp, maPropName.mn, RTL_TEXTENCODING_UTF8); + OUString aValue(maPropValue.mp, maPropValue.mn, RTL_TEXTENCODING_UTF8); + mrStyles.add(rElem.mp, rElem.mn, rClass.mp, rClass.mn, aName, aValue); + } + maPropName = MemStr(); + maPropValue = MemStr(); + } + +}; + +} + +void ScHTMLQueryParser::ParseStyle(std::u16string_view rStrm) +{ + OString aStr = OUStringToOString(rStrm, RTL_TEXTENCODING_UTF8); + CSSHandler aHdl(GetStyles()); + orcus::css_parser aParser(aStr.getStr(), aStr.getLength(), aHdl); + try + { + aParser.parse(); + } + catch (const orcus::css::parse_error& rOrcusParseError) + { + SAL_WARN("sc", "ScHTMLQueryParser::ParseStyle: " << rOrcusParseError.what()); + // TODO: Parsing of CSS failed. Do nothing for now. + } +} + +IMPL_LINK( ScHTMLQueryParser, HTMLImportHdl, HtmlImportInfo&, rInfo, void ) +{ + switch( rInfo.eState ) + { + case HtmlImportState::Start: + break; + + case HtmlImportState::NextToken: + ProcessToken( rInfo ); + break; + + case HtmlImportState::InsertPara: + mpCurrTable->InsertPara( rInfo ); + break; + + case HtmlImportState::SetAttr: + case HtmlImportState::InsertText: + case HtmlImportState::InsertField: + break; + + case HtmlImportState::End: + while( mpCurrTable->GetTableId() != SC_HTML_GLOBAL_TABLE ) + CloseTable( rInfo ); + break; + + default: + OSL_FAIL( "ScHTMLQueryParser::HTMLImportHdl - unknown ImportInfo::eState" ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/importfilterdata.cxx b/sc/source/filter/importfilterdata.cxx new file mode 100644 index 000000000..100188928 --- /dev/null +++ b/sc/source/filter/importfilterdata.cxx @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +namespace sc { + +ImportPostProcessData::DataStream::DataStream() : + mbRefreshOnEmpty(false), meInsertPos(InsertBottom) {} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/SparklineFragment.hxx b/sc/source/filter/inc/SparklineFragment.hxx new file mode 100644 index 000000000..0d4e76e6b --- /dev/null +++ b/sc/source/filter/inc/SparklineFragment.hxx @@ -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/. + */ + +#pragma once + +#include "excelhandlers.hxx" +#include +#include + +#include +#include + +namespace oox +{ +class AttributeList; +} + +namespace oox::xls +{ +/** Transitional sparkline data */ +class Sparkline +{ +public: + ScRangeList m_aInputRange; + ScRangeList m_aTargetRange; + Sparkline() {} +}; + +/** Transitional sparkline group data */ +class SparklineGroup +{ +private: + std::vector m_aSparklines; + + std::shared_ptr m_pSparklineGroup; + +public: + SparklineGroup() + : m_pSparklineGroup(new sc::SparklineGroup) + { + } + + const std::shared_ptr& getSparklineGroup() { return m_pSparklineGroup; } + + std::vector& getSparklines() { return m_aSparklines; } +}; + +/** Handle import of the sparkline, sparkline group and attributes */ +class SparklineGroupsContext : public WorksheetContextBase +{ +private: + std::vector m_aSparklineGroups; + +public: + explicit SparklineGroupsContext(WorksheetContextBase& rFragment); + + oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement, + const AttributeList& rAttribs) override; + void onStartElement(const AttributeList& rAttribs) override; + void onCharacters(const OUString& rCharacters) override; + void onEndElement() override; + + void insertSparkline(SparklineGroup& rSparklineGroup, Sparkline& rSparkline); +}; + +} //namespace oox::xls + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/XclExpChangeTrack.hxx b/sc/source/filter/inc/XclExpChangeTrack.hxx new file mode 100644 index 000000000..48e34e2e3 --- /dev/null +++ b/sc/source/filter/inc/XclExpChangeTrack.hxx @@ -0,0 +1,610 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 +#include +#include +#include +#include +#include "xelink.hxx" +#include "xestring.hxx" +#include "excrecds.hxx" +#include "xlformula.hxx" +#include "xeformula.hxx" + +class ExcXmlRecord : public ExcRecord +{ +public: + virtual std::size_t GetLen() const override; + virtual sal_uInt16 GetNum() const override; + virtual void Save( XclExpStream& rStrm ) override; +}; + +// XclExpUserBView - one UserBView record for each user + +class XclExpUserBView : public ExcRecord +{ +private: + XclExpString sUsername; + sal_uInt8 aGUID[ 16 ]; + + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + XclExpUserBView( const OUString& rUsername, const sal_uInt8* pGUID ); + + const sal_uInt8* GetGUID() const { return aGUID; } + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// XclExpUserBViewList - list of UserBView records + +class XclExpUserBViewList : public ExcEmptyRec +{ +private: + std::vector aViews; + +public: + + typedef std::vector::const_iterator const_iterator; + + XclExpUserBViewList( const ScChangeTrack& rChangeTrack ); + virtual ~XclExpUserBViewList() override; + + const_iterator cbegin () { return aViews.cbegin(); } + const_iterator cend () { return aViews.cend(); } + + virtual void Save( XclExpStream& rStrm ) override; +}; + +// XclExpUsersViewBegin - begin of view block (one per sheet) + +class XclExpUsersViewBegin : public ExcRecord +{ +private: + sal_uInt8 aGUID[ 16 ]; + sal_uInt32 nCurrTab; + + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + XclExpUsersViewBegin( const sal_uInt8* pGUID, sal_uInt32 nTab ); + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// XclExpUsersViewEnd - end of view block (one per sheet) + +class XclExpUsersViewEnd : public ExcRecord +{ +private: + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// dummy record for "User Names" stream + +class XclExpChTr0x0191 : public ExcRecord +{ +private: + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// dummy record for "User Names" stream + +class XclExpChTr0x0198 : public ExcRecord +{ +private: + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// dummy record for "User Names" stream + +class XclExpChTr0x0192 : public ExcRecord +{ +private: + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// dummy record for "User Names" stream + +class XclExpChTr0x0197 : public ExcRecord +{ +private: + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// dummy record without content + +class XclExpChTrEmpty : public ExcRecord +{ +private: + sal_uInt16 nRecNum; + +public: + XclExpChTrEmpty( sal_uInt16 nNum ) : nRecNum( nNum ) {} + virtual ~XclExpChTrEmpty() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// dummy record for "Revision Log" stream + +class XclExpChTr0x0195 : public ExcRecord +{ +private: + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + virtual ~XclExpChTr0x0195() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// dummy record for "Revision Log" stream + +class XclExpChTr0x0194 : public ExcRecord +{ +private: + XclExpString sUsername; + DateTime aDateTime; + + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + inline XclExpChTr0x0194( const ScChangeTrack& rChangeTrack ); + virtual ~XclExpChTr0x0194() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +inline XclExpChTr0x0194::XclExpChTr0x0194( const ScChangeTrack& rChangeTrack ) : + sUsername( rChangeTrack.GetUser() ), + aDateTime( rChangeTrack.GetFixDateTime() ) +{ +} + +// XclExpChTrHeader - header record, includes action count + +class XclExpChTrHeader : public ExcRecord +{ +private: + sal_uInt8 aGUID[ 16 ]; + sal_uInt32 nCount; + + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + XclExpChTrHeader() : nCount( 0 ) {} + virtual ~XclExpChTrHeader() override; + + void SetGUID( const sal_uInt8* pGUID ) { memcpy( aGUID, pGUID, 16 ); } + void SetCount( sal_uInt32 nNew ) { nCount = nNew; } + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +class XclExpXmlChTrHeaders : public ExcXmlRecord +{ + sal_uInt8 maGUID[16]; +public: + void SetGUID( const sal_uInt8* pGUID ); + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +class XclExpChTrTabIdBuffer; +class XclExpChTrAction; + +class XclExpXmlChTrHeader : public ExcXmlRecord +{ + OUString maUserName; + DateTime maDateTime; + sal_uInt8 maGUID[16]; + sal_Int32 mnLogNumber; + sal_uInt32 mnMinAction; + sal_uInt32 mnMaxAction; + + std::vector maTabBuffer; + std::vector> maActions; + +public: + XclExpXmlChTrHeader( + const OUString& rUserName, const DateTime& rDateTime, const sal_uInt8* pGUID, + sal_Int32 nLogNumber, const XclExpChTrTabIdBuffer& rBuf ); + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; + + void AppendAction( std::unique_ptr pAction ); +}; + +// XclExpChTrInfo - header of action group of a user + +class XclExpChTrInfo : public ExcRecord +{ +private: + XclExpString sUsername; + DateTime aDateTime; + sal_uInt8 aGUID[ 16 ]; + + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + XclExpChTrInfo( const OUString& rUsername, const DateTime& rDateTime, + const sal_uInt8* pGUID ); + + virtual ~XclExpChTrInfo() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// XclExpChTrTabIdBuffer - buffer for tab id's + +class XclExpChTrTabIdBuffer +{ +private: + std::unique_ptr + pBuffer; + sal_uInt16* pLast; + sal_uInt16 nBufSize; + sal_uInt16 nLastId; + +public: + XclExpChTrTabIdBuffer( sal_uInt16 nCount ); + XclExpChTrTabIdBuffer( const XclExpChTrTabIdBuffer& rCopy ); + ~XclExpChTrTabIdBuffer(); + + void InitFill( sal_uInt16 nIndex ); + void InitFillup(); + + sal_uInt16 GetId( sal_uInt16 nIndex ) const; + void Remove(); + + sal_uInt16 GetBufferCount() const + { return static_cast< sal_uInt16 >( (pLast - pBuffer.get()) + 1 ); } + void GetBufferCopy( sal_uInt16* pDest ) const + { memcpy( pDest, pBuffer.get(), sizeof(sal_uInt16) * GetBufferCount() ); } +}; + +// XclExpChTrTabId - tab id record + +class XclExpChTrTabId : public ExcRecord +{ +private: + std::unique_ptr pBuffer; + sal_uInt16 nTabCount; + + void Clear() { pBuffer.reset(); } + + virtual void SaveCont( XclExpStream& rStrm ) override; + +public: + XclExpChTrTabId( sal_uInt16 nCount ) : nTabCount( nCount ) {} + XclExpChTrTabId( const XclExpChTrTabIdBuffer& rBuffer ); + virtual ~XclExpChTrTabId() override; + + void Copy( const XclExpChTrTabIdBuffer& rBuffer ); + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetLen() const override; +}; + +// XclExpChTrAction - base class for action records + +class XclExpChTrAction : public ExcRecord +{ +private: + OUString sUsername; + DateTime aDateTime; + sal_uInt32 nIndex; // action number + std::unique_ptr + pAddAction; // additional record for this action + bool bAccepted; + +protected: + const XclExpTabInfo& rTabInfo; // for table num export (sc num -> xcl num) + const XclExpChTrTabIdBuffer& rIdBuffer; // for table num export (xcl num -> tab id) + sal_uInt32 nLength; // this is not the record size + sal_uInt16 nOpCode; // EXC_CHTR_OP_*** + bool bForceInfo; + + XclExpChTrAction( const XclExpChTrAction& rCopy ); + + void SetAddAction( XclExpChTrAction* pAction ); + void AddDependentContents( + const ScChangeAction& rAction, + const XclExpRoot& rRoot, + const ScChangeTrack& rChangeTrack ); + + static inline void Write2DAddress( XclExpStream& rStrm, const ScAddress& rAddress ); + static inline void Write2DRange( XclExpStream& rStrm, const ScRange& rRange ); + inline sal_uInt16 GetTabId( SCTAB nTabId ) const; + inline void WriteTabId( XclExpStream& rStrm, SCTAB nTabId ) const; + + // save header data, call SaveActionData() + virtual void SaveCont( XclExpStream& rStrm ) override; + static std::size_t GetHeaderByteCount() { return 12; } + + // override to save action data without header, called by SaveCont() + virtual void SaveActionData( XclExpStream& rStrm ) const = 0; + // override to get action size without header, called by GetLen() + virtual std::size_t GetActionByteCount() const = 0; + + // do something before writing the record + virtual void PrepareSaveAction( XclExpStream& rStrm ) const; + // do something after writing the record + virtual void CompleteSaveAction( XclExpStream& rStrm ) const; + + bool GetAccepted() const { return bAccepted; } + +public: + XclExpChTrAction( + const ScChangeAction& rAction, + const XclExpRoot& rRoot, + const XclExpChTrTabIdBuffer& rTabIdBuffer, + sal_uInt16 nNewOpCode = EXC_CHTR_OP_UNKNOWN ); + virtual ~XclExpChTrAction() override; + + const OUString& GetUsername() const { return sUsername; } + const DateTime& GetDateTime() const { return aDateTime; } + const XclExpChTrTabIdBuffer& GetTabIdBuffer() const { return rIdBuffer; } + bool ForceInfoRecord() const { return bForceInfo; } + + // set own index & return new index + // could override to use more indexes per action + void SetIndex( sal_uInt32& rIndex ); + + virtual void Save( XclExpStream& rStrm ) override; + virtual std::size_t GetLen() const override; + + XclExpChTrAction* GetAddAction() { return pAddAction.get(); } + sal_uInt32 GetActionNumber() const { return nIndex; } +}; + +inline void XclExpChTrAction::Write2DAddress( XclExpStream& rStrm, const ScAddress& rAddress ) +{ + rStrm << static_cast(rAddress.Row()) + << static_cast(rAddress.Col()); +} + +inline void XclExpChTrAction::Write2DRange( XclExpStream& rStrm, const ScRange& rRange ) +{ + rStrm << static_cast(rRange.aStart.Row()) + << static_cast(rRange.aEnd.Row()) + << static_cast(rRange.aStart.Col()) + << static_cast(rRange.aEnd.Col()); +} + +inline sal_uInt16 XclExpChTrAction::GetTabId( SCTAB nTab ) const +{ + return rIdBuffer.GetId( rTabInfo.GetXclTab( nTab ) ); +} + +inline void XclExpChTrAction::WriteTabId( XclExpStream& rStrm, SCTAB nTab ) const +{ + rStrm << GetTabId( nTab ); +} + +// XclExpChTrData - cell content itself + +struct XclExpChTrData +{ + std::unique_ptr pString; + XclExpStringRef mpFormattedString; + const ScFormulaCell* mpFormulaCell; + XclTokenArrayRef mxTokArr; + XclExpRefLog maRefLog; + double fValue; + sal_Int32 nRKValue; + sal_uInt16 nType; + std::size_t nSize; + + XclExpChTrData(); + ~XclExpChTrData(); + void Clear(); + + void WriteFormula( + XclExpStream& rStrm, + const XclExpChTrTabIdBuffer& rTabIdBuffer ); + void Write( + XclExpStream& rStrm, + const XclExpChTrTabIdBuffer& rTabIdBuffer ); +}; + +// XclExpChTrCellContent - changed cell content + +class XclExpChTrCellContent final : public XclExpChTrAction, protected XclExpRoot +{ + std::unique_ptr pOldData; + std::unique_ptr pNewData; + sal_uInt16 nOldLength; // this is not the record size + ScAddress aPosition; + + static void MakeEmptyChTrData( std::unique_ptr& rpData ); + + void GetCellData( + const XclExpRoot& rRoot, const ScCellValue& rScCell, std::unique_ptr& rpData, + sal_uInt32& rXclLength1, sal_uInt16& rXclLength2 ); + + virtual void SaveActionData( XclExpStream& rStrm ) const override; + +public: + XclExpChTrCellContent( + const ScChangeActionContent& rAction, + const XclExpRoot& rRoot, + const XclExpChTrTabIdBuffer& rTabIdBuffer ); + virtual ~XclExpChTrCellContent() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetActionByteCount() const override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +// XclExpChTrInsert - insert/delete columns/rows + +class XclExpChTrInsert : public XclExpChTrAction +{ + bool mbEndOfList; + +protected: + ScRange aRange; + + XclExpChTrInsert( const XclExpChTrInsert& rCopy ); + + virtual void SaveActionData( XclExpStream& rStrm ) const override; + virtual void PrepareSaveAction( XclExpStream& rStrm ) const override; + virtual void CompleteSaveAction( XclExpStream& rStrm ) const override; + +public: + XclExpChTrInsert( + const ScChangeAction& rAction, + const XclExpRoot& rRoot, + const XclExpChTrTabIdBuffer& rTabIdBuffer, + const ScChangeTrack& rChangeTrack ); + virtual ~XclExpChTrInsert() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetActionByteCount() const override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +// XclExpChTrInsertTab - insert table + +class XclExpChTrInsertTab : public XclExpChTrAction, protected XclExpRoot +{ +private: + SCTAB nTab; + +protected: + virtual void SaveActionData( XclExpStream& rStrm ) const override; + +public: + XclExpChTrInsertTab( + const ScChangeAction& rAction, + const XclExpRoot& rRoot, + const XclExpChTrTabIdBuffer& rTabIdBuffer ); + virtual ~XclExpChTrInsertTab() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetActionByteCount() const override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +// XclExpChTrMoveRange - move cell range + +class XclExpChTrMoveRange final : public XclExpChTrAction +{ + ScRange aSourceRange; + ScRange aDestRange; + + virtual void SaveActionData( XclExpStream& rStrm ) const override; + virtual void PrepareSaveAction( XclExpStream& rStrm ) const override; + virtual void CompleteSaveAction( XclExpStream& rStrm ) const override; + +public: + XclExpChTrMoveRange( + const ScChangeActionMove& rAction, + const XclExpRoot& rRoot, + const XclExpChTrTabIdBuffer& rTabIdBuffer, + const ScChangeTrack& rChangeTrack ); + virtual ~XclExpChTrMoveRange() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetActionByteCount() const override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +// XclExpChTr0x019A - additional data for delete action + +class XclExpChTr0x014A : public XclExpChTrInsert +{ +protected: + virtual void SaveActionData( XclExpStream& rStrm ) const override; + +public: + XclExpChTr0x014A( const XclExpChTrInsert& rAction ); + virtual ~XclExpChTr0x014A() override; + + virtual sal_uInt16 GetNum() const override; + virtual std::size_t GetActionByteCount() const override; + + virtual void SaveXml( XclExpXmlStream& rStrm ) override; +}; + +// XclExpChangeTrack - exports the "Revision Log" stream + +class XclExpChangeTrack : protected XclExpRoot +{ + typedef std::vector> RecListType; + RecListType maRecList; // list of "Revision Log" stream records + std::stack aActionStack; + XclExpChTrTabIdBuffer* pTabIdBuffer; + std::vector> + maBuffers; + + ScDocumentUniquePtr xTempDoc; // empty document + + ScChangeTrack* CreateTempChangeTrack(); + void PushActionRecord( const ScChangeAction& rAction ); + + bool WriteUserNamesStream(); + +public: + XclExpChangeTrack( const XclExpRoot& rRoot ); + virtual ~XclExpChangeTrack() override; + + void Write(); + void WriteXml( XclExpXmlStream& rStrm ); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/XclImpChangeTrack.hxx b/sc/source/filter/inc/XclImpChangeTrack.hxx new file mode 100644 index 000000000..532cc9e32 --- /dev/null +++ b/sc/source/filter/inc/XclImpChangeTrack.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 "xiroot.hxx" +#include "xistream.hxx" +#include "excform.hxx" + +struct ScCellValue; +class ScChangeAction; +class ScChangeTrack; +class DateTime; + +struct XclImpChTrRecHeader +{ + sal_uInt32 nSize; + sal_uInt32 nIndex; + sal_uInt16 nOpCode; + sal_uInt16 nAccept; +}; + +inline XclImpStream& operator>>( XclImpStream& rStrm, XclImpChTrRecHeader& rRecHeader ) +{ + rRecHeader.nSize = rStrm.ReaduInt32(); + rRecHeader.nIndex = rStrm.ReaduInt32(); + rRecHeader.nOpCode = rStrm.ReaduInt16(); + rRecHeader.nAccept = rStrm.ReaduInt16(); + return rStrm; +} + +class XclImpChangeTrack : protected XclImpRoot +{ +private: + XclImpChTrRecHeader aRecHeader; + OUString sOldUsername; + + std::unique_ptr pChangeTrack; + tools::SvRef xInStrm; // input stream + std::unique_ptr pStrm; // stream import class + sal_uInt16 nTabIdCount; + bool bGlobExit; // global exit loop + + enum { nmBase, nmFound, nmNested } + eNestedMode; // action with nested content actions + + bool FoundNestedMode() { return eNestedMode == nmFound; } + + void DoAcceptRejectAction( ScChangeAction* pAction ); + void DoAcceptRejectAction( sal_uInt32 nFirst, sal_uInt32 nLast ); + + void DoInsertRange( const ScRange& rRange, bool bEndOfList ); + void DoDeleteRange( const ScRange& rRange ); + + inline sal_uInt8 LookAtuInt8(); + inline void Read2DAddress( ScAddress& rAddress ); + inline void Read2DRange( ScRange& rRange ); + SCTAB ReadTabNum(); + void ReadDateTime( DateTime& rDateTime ); + + bool CheckRecord( sal_uInt16 nOpCode ); + + void ReadFormula( + std::unique_ptr& rpTokenArray, + const ScAddress& rPosition ); + void ReadCell( ScCellValue& rCell, sal_uInt32& rFormat, sal_uInt16 nFlags, const ScAddress& rPosition ); + + void ReadChTrInsert(); // 0x0137 + void ReadChTrInfo(); // 0x0138 + void ReadChTrCellContent(); // 0x013B + void ReadChTrTabId(); // 0x013D + void ReadChTrMoveRange(); // 0x0140 + void ReadChTrInsertTab(); // 0x014D + void InitNestedMode(); // 0x014E, 0x0150 + void ReadNestedRecords(); + bool EndNestedMode(); // 0x014F, 0x0151 + + void ReadRecords(); + +public: + XclImpChangeTrack( const XclImpRoot& rRoot, const XclImpStream& rBookStrm ); + virtual ~XclImpChangeTrack() override; + + // reads extended 3D ref info following the formulas, returns sc tab nums + // ( called by XclImpChTrFmlConverter::Read3DTabReference() ) + void Read3DTabRefInfo( SCTAB& rFirstTab, SCTAB& rLastTab, ExcelToSc8::ExternalTabInfo& rExtInfo ); + + void Apply(); +}; + +inline sal_uInt8 XclImpChangeTrack::LookAtuInt8() +{ + pStrm->PushPosition(); + sal_uInt8 nValue; + nValue = pStrm->ReaduInt8(); + pStrm->PopPosition(); + return nValue; +} + +inline void XclImpChangeTrack::Read2DAddress( ScAddress& rAddress ) +{ + rAddress.SetRow( static_cast(pStrm->ReaduInt16()) ); + rAddress.SetCol( static_cast(pStrm->ReaduInt16()) ); +} + +inline void XclImpChangeTrack::Read2DRange( ScRange& rRange ) +{ + rRange.aStart.SetRow( static_cast(pStrm->ReaduInt16()) ); + rRange.aEnd.SetRow( static_cast(pStrm->ReaduInt16()) ); + rRange.aStart.SetCol( static_cast(pStrm->ReaduInt16()) ); + rRange.aEnd.SetCol( static_cast(pStrm->ReaduInt16()) ); +} + +// derived class for special 3D ref handling + +class XclImpChTrFmlConverter : public ExcelToSc8 +{ +private: + XclImpChangeTrack& rChangeTrack; + + virtual bool Read3DTabReference( sal_uInt16 nIxti, SCTAB& rFirstTab, SCTAB& rLastTab, ExternalTabInfo& rExtInfo ) override; + +public: + XclImpChTrFmlConverter( XclImpRoot& rRoot, XclImpChangeTrack& rXclChTr ); + virtual ~XclImpChTrFmlConverter() override; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/addressconverter.hxx b/sc/source/filter/inc/addressconverter.hxx new file mode 100644 index 000000000..af1bc340c --- /dev/null +++ b/sc/source/filter/inc/addressconverter.hxx @@ -0,0 +1,505 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 +#include +#include "workbookhelper.hxx" +#include + +namespace com::sun::star::table { struct CellRangeAddress; } +namespace oox { class SequenceInputStream; } + +namespace oox { +namespace xls { + +/** A 2D cell address struct for binary filters. */ +struct BinAddress +{ + sal_Int32 mnCol; + sal_Int32 mnRow; + + explicit BinAddress() : mnCol( 0 ), mnRow( 0 ) {} + explicit BinAddress( sal_Int32 nCol, sal_Int32 nRow ) : mnCol( nCol ), mnRow( nRow ) {} + explicit BinAddress( const ScAddress& rAddr ) : mnCol( rAddr.Col() ), mnRow( rAddr.Row() ) {} + + void read( SequenceInputStream& rStrm ); +}; + +inline bool operator<( const BinAddress& rL, const BinAddress& rR ) +{ + return (rL.mnCol < rR.mnCol) || ((rL.mnCol == rR.mnCol) && (rL.mnRow < rR.mnRow)); +} + +inline SequenceInputStream& operator>>( SequenceInputStream& rStrm, BinAddress& orPos ) +{ + orPos.read( rStrm ); + return rStrm; +} + +/** A 2D cell range address struct for binary filters. */ +struct BinRange +{ + BinAddress maFirst; + BinAddress maLast; + + void read( SequenceInputStream& rStrm ); +}; + +inline SequenceInputStream& operator>>( SequenceInputStream& rStrm, BinRange& orRange ) +{ + orRange.read( rStrm ); + return rStrm; +} + +/** A 2D cell range address list for binary filters. */ +class BinRangeList +{ +public: + explicit BinRangeList() : mvRanges() {} + + ::std::vector< BinRange >::const_iterator begin() const { return mvRanges.begin(); } + ::std::vector< BinRange >::const_iterator end() const { return mvRanges.end(); } + + void read( SequenceInputStream& rStrm ); + +private: + ::std::vector< BinRange > mvRanges; +}; + +inline SequenceInputStream& operator>>( SequenceInputStream& rStrm, BinRangeList& orRanges ) +{ + orRanges.read( rStrm ); + return rStrm; +} + +/** Converter for cell addresses and cell ranges for OOXML and BIFF filters. + */ +class AddressConverter final : public WorkbookHelper +{ +public: + explicit AddressConverter( const WorkbookHelper& rHelper ); + + /** Tries to parse the passed string for a 2d cell address in A1 notation. + + This function accepts all strings that match the regular expression + "[a-zA-Z]{1,6}0*[1-9][0-9]{0,8}" (without quotes), i.e. 1 to 6 letters + for the column index (translated to 0-based column indexes from 0 to + 321,272,405), and 1 to 9 digits for the 1-based row index (translated + to 0-based row indexes from 0 to 999,999,998). The row number part may + contain leading zeros, they will be ignored. It is up to the caller to + handle cell addresses outside of a specific valid range (e.g. the + entire spreadsheet). + + @param ornColumn (out-parameter) Returns the converted column index. + @param ornRow (out-parameter) returns the converted row index. + @param rString The string containing the cell address. + @param nStart Start index of string part in rString to be parsed. + @param nLength Length of string part in rString to be parsed. + + @return true = Parsed string was valid, returned values can be used. + */ + static bool parseOoxAddress2d( + sal_Int32& ornColumn, sal_Int32& ornRow, + const OUString& rString, + sal_Int32 nStart = 0, + sal_Int32 nLength = SAL_MAX_INT32 ); + + static bool parseOoxAddress2d( + sal_Int32& ornColumn, sal_Int32& ornRow, const char* pStr ); + + /** Tries to parse the passed string for a 2d cell range in A1 notation. + + This function accepts all strings that match the regular expression + "ADDR(:ADDR)?" (without quotes), where ADDR is a cell address accepted + by the parseOoxAddress2d() function of this class. It is up to the + caller to handle cell ranges outside of a specific valid range (e.g. + the entire spreadsheet). + + @param ornStartColumn (out-parameter) Returns the converted start column index. + @param ornStartRow (out-parameter) returns the converted start row index. + @param ornEndColumn (out-parameter) Returns the converted end column index. + @param ornEndRow (out-parameter) returns the converted end row index. + @param rString The string containing the cell address. + @param nStart Start index of string part in rString to be parsed. + + @return true = Parsed string was valid, returned values can be used. + */ + static bool parseOoxRange2d( + sal_Int32& ornStartColumn, sal_Int32& ornStartRow, + sal_Int32& ornEndColumn, sal_Int32& ornEndRow, + const OUString& rString, + sal_Int32 nStart = 0 ); + + /** Returns the biggest valid cell address in the own Calc document. */ + const ScAddress& + getMaxApiAddress() const { return maMaxApiPos; } + + /** Returns the biggest valid cell address in the imported/exported + Excel document. */ + const ScAddress& + getMaxXlsAddress() const { return maMaxXlsPos; } + + /** Returns the biggest valid cell address in both Calc and the + imported/exported Excel document. */ + const ScAddress& + getMaxAddress() const { return maMaxPos; } + + /** Checks if the passed column index is valid. + + @param nCol The column index to check. + @param bTrackOverflow true = Update the internal overflow flag, if the + column index is outside of the supported limits. + @return true = Passed column index is valid (no index overflow). + */ + bool checkCol( sal_Int32 nCol, bool bTrackOverflow ); + + /** Checks if the passed row index is valid. + + @param nRow The row index to check. + @param bTrackOverflow true = Update the internal overflow flag, if the + row index is outside of the supported limits. + @return true = Passed row index is valid (no index overflow). + */ + bool checkRow( sal_Int32 nRow, bool bTrackOverflow ); + + /** Checks if the passed sheet index is valid. + + @param nSheet The sheet index to check. + @param bTrackOverflow true = Update the internal overflow flag, if the + sheet index is outside of the supported limits. + @return true = Passed sheet index is valid (no index overflow). + */ + bool checkTab( sal_Int16 nSheet, bool bTrackOverflow ); + + /** Checks the passed cell address if it fits into the spreadsheet limits. + + @param rAddress The cell address to be checked. + @param bTrackOverflow true = Update the internal overflow flags, if + the address is outside of the supported sheet limits. + @return true = Passed address is valid (no index overflow). + */ + bool checkCellAddress( + const ScAddress& rAddress, + bool bTrackOverflow ); + + /** Converts the passed string to a single cell address, without checking + any sheet limits. + + @param orAddress (out-parameter) Returns the converted cell address. + @param rString Cell address string in A1 notation. + @param nSheet Sheet index to be inserted into orAddress. + @return true = Cell address could be parsed from the passed string. + */ + static bool convertToCellAddressUnchecked( + ScAddress& orAddress, + const OUString& rString, + sal_Int16 nSheet ); + + static bool convertToCellAddressUnchecked( + ScAddress& orAddress, const char* pStr, sal_Int16 nSheet ); + + /** Tries to convert the passed string to a single cell address. + + @param orAddress (out-parameter) Returns the converted cell address. + @param rString Cell address string in A1 notation. + @param nSheet Sheet index to be inserted into orAddress (will be checked). + @param bTrackOverflow true = Update the internal overflow flags, if + the address is outside of the supported sheet limits. + @return true = Converted address is valid (no index overflow). + */ + bool convertToCellAddress( + ScAddress& orAddress, + const OUString& rString, + sal_Int16 nSheet, + bool bTrackOverflow ); + + bool convertToCellAddress( + ScAddress& rAddress, + const char* pStr, sal_Int16 nSheet, bool bTrackOverflow ); + + /** Returns a valid cell address by moving it into allowed dimensions. + + @param rString Cell address string in A1 notation. + @param nSheet Sheet index for the returned address (will be checked). + @param bTrackOverflow true = Update the internal overflow flags, if + the address is outside of the supported sheet limits. + @return A valid cell address struct. */ + ScAddress createValidCellAddress( + const OUString& rString, + sal_Int16 nSheet, + bool bTrackOverflow ); + + /** Converts the passed address to a single cell address, without checking + any sheet limits. + + @param orAddress (out-parameter) Returns the converted cell address. + @param rBinAddress Binary cell address struct. + @param nSheet Sheet index to be inserted into orAddress. + */ + static void convertToCellAddressUnchecked( + ScAddress& orAddress, + const BinAddress& rBinAddress, + sal_Int16 nSheet ); + + /** Tries to convert the passed address to a single cell address. + + @param orAddress (out-parameter) Returns the converted cell address. + @param rBinAddress Binary cell address struct. + @param nSheet Sheet index to be inserted into orAddress (will be checked). + @param bTrackOverflow true = Update the internal overflow flags, if + the address is outside of the supported sheet limits. + @return true = Converted address is valid (no index overflow). + */ + bool convertToCellAddress( + ScAddress& orAddress, + const BinAddress& rBinAddress, + sal_Int16 nSheet, + bool bTrackOverflow ); + + /** Returns a valid cell address by moving it into allowed dimensions. + + @param rBinAddress Binary cell address struct. + @param nSheet Sheet index for the returned address (will be checked). + @param bTrackOverflow true = Update the internal overflow flags, if + the address is outside of the supported sheet limits. + @return A valid cell address struct. */ + ScAddress createValidCellAddress( + const BinAddress& rBinAddress, + sal_Int16 nSheet, + bool bTrackOverflow ); + + /** Checks the passed cell range if it fits into the spreadsheet limits. + + @param rRange The cell range address to be checked. + @param bAllowOverflow true = Allow ranges that start inside the + supported sheet limits but may end outside of these limits. + false = Do not allow ranges that overflow the supported limits. + @param bTrackOverflow true = Update the internal overflow flags, if + the passed range contains cells outside of the supported sheet + limits. + @return true = Cell range is valid. This function returns also true, + if only parts of the range are outside the current sheet limits and + such an overflow is allowed via parameter bAllowOverflow. Returns + false, if the entire range is outside the sheet limits, or if + overflow is not allowed via parameter bAllowOverflow. + */ + bool checkCellRange( + const ScRange& rRange, + bool bAllowOverflow, bool bTrackOverflow ); + + /** Checks the passed cell range, may try to fit it to current sheet limits. + + First, this function reorders the column and row indexes so that the + starting indexes are less than or equal to the end indexes. Then, + depending on the parameter bAllowOverflow, the range is just checked or + cropped to the current sheet limits. + + @param orRange (in-out-parameter) Converts the passed cell range + into a valid cell range address. If the passed range contains cells + outside the currently supported spreadsheet limits, it will be + cropped to these limits. + @param bAllowOverflow true = Allow ranges that start inside the + supported sheet limits but may end outside of these limits. The + cell range returned in orRange will be cropped to these limits. + false = Do not allow ranges that overflow the supported limits. The + function will return false when the range overflows the sheet limits. + @param bTrackOverflow true = Update the internal overflow flags, if + the original range contains cells outside of the supported sheet + limits. + @return true = Converted range address is valid. This function + returns also true, if overflowing ranges are allowed via parameter + bAllowOverflow and the range has been cropped, but still contains + cells inside the current sheet limits. Returns false, if the entire + range is outside the sheet limits or overflowing ranges are not + allowed via parameter bAllowOverflow. + */ + bool validateCellRange( + ScRange& orRange, + bool bAllowOverflow, bool bTrackOverflow ); + + /** Converts the passed string to a cell range address, without checking + any sheet limits. + + @param orRange (out-parameter) Returns the converted range address. + @param rString Cell range string in A1 notation. + @param nSheet Sheet index to be inserted into orRange. + @return true = Range address could be parsed from the passed string. + */ + static bool convertToCellRangeUnchecked( + ScRange& orRange, + const OUString& rString, + sal_Int16 nSheet ); + + /** Tries to convert the passed string to a cell range address. + + @param orRange (out-parameter) Returns the converted cell range + address. If the original range in the passed string contains cells + outside the currently supported spreadsheet limits, and parameter + bAllowOverflow is set to true, the range will be cropped to these + limits. Example: the range string "A1:ZZ100000" may be converted to + the range A1:IV65536. + @param rString Cell range string in A1 notation. + @param nSheet Sheet index to be inserted into orRange (will be checked). + @param bAllowOverflow true = Allow ranges that start inside the + supported sheet limits but may end outside of these limits. The + cell range returned in orRange will be cropped to these limits. + false = Do not allow ranges that overflow the supported limits. + @param bTrackOverflow true = Update the internal overflow flags, if + the original range contains cells outside of the supported sheet + limits. + @return true = Converted and returned range is valid. This function + returns also true, if overflowing ranges are allowed via parameter + bAllowOverflow and the range has been cropped, but still contains + cells inside the current sheet limits. Returns false, if the entire + range is outside the sheet limits or overflowing ranges are not + allowed via parameter bAllowOverflow. + */ + bool convertToCellRange( + ScRange& orRange, + const OUString& rString, + sal_Int16 nSheet, + bool bAllowOverflow, bool bTrackOverflow ); + + /** Converts the passed range to a cell range address, without checking any + sheet limits. + + @param orRange (out-parameter) Returns the converted range address. + @param rBinRange Binary cell range struct. + @param nSheet Sheet index to be inserted into orRange. + */ + static void convertToCellRangeUnchecked( + ScRange& orRange, + const BinRange& rBinRange, + sal_Int16 nSheet ); + + /** Tries to convert the passed range to a cell range address. + + @param orRange (out-parameter) Returns the converted cell range + address. If the passed original range contains cells outside the + currently supported spreadsheet limits, and parameter bAllowOverflow + is set to true, the range will be cropped to these limits. + @param rBinRange Binary cell range struct. + @param nSheet Sheet index to be inserted into orRange (will be checked). + @param bAllowOverflow true = Allow ranges that start inside the + supported sheet limits but may end outside of these limits. The + cell range returned in orRange will be cropped to these limits. + false = Do not allow ranges that overflow the supported limits. + @param bTrackOverflow true = Update the internal overflow flags, if + the original range contains cells outside of the supported sheet + limits. + @return true = Converted and returned range is valid. This function + returns also true, if overflowing ranges are allowed via parameter + bAllowOverflow and the range has been cropped, but still contains + cells inside the current sheet limits. Returns false, if the entire + range is outside the sheet limits or if overflowing ranges are not + allowed via parameter bAllowOverflow. + */ + bool convertToCellRange( + ScRange& orRange, + const BinRange& rBinRange, + sal_Int16 nSheet, + bool bAllowOverflow, bool bTrackOverflow ); + + + /** Tries to restrict the passed cell range list to current sheet limits. + + @param orRanges (in-out-parameter) Restricts the cell range addresses + in the passed list to the current sheet limits and removes invalid + ranges from the list. + @param bTrackOverflow true = Update the internal overflow flags, if + the original ranges contain cells outside of the supported sheet + limits. + */ + void validateCellRangeList( + ScRangeList& orRanges, + bool bTrackOverflow ); + + /** Tries to convert the passed string to a cell range list. + + @param orRanges (out-parameter) Returns the converted cell range + addresses. If a range in the passed string contains cells outside + the currently supported spreadsheet limits, it will be cropped to + these limits. Example: the range string "A1:ZZ100000" may be + converted to the range A1:IV65536. If a range is completely outside + the limits, it will be omitted. + @param rString Cell range list string in A1 notation, space separated. + @param nSheet Sheet index to be inserted into orRanges (will be checked). + @param bTrackOverflow true = Update the internal overflow flags, if + the original ranges contain cells outside of the supported sheet + limits. + */ + void convertToCellRangeList( + ScRangeList& orRanges, + const OUString& rString, + sal_Int16 nSheet, + bool bTrackOverflow ); + + /** Tries to convert the passed range list to a cell range list. + + @param orRanges (out-parameter) Returns the converted cell range + addresses. If a range in the passed string contains cells outside + the currently supported spreadsheet limits, it will be cropped to + these limits. Example: the range string "A1:ZZ100000" may be + converted to the range A1:IV65536. If a range is completely outside + the limits, it will be omitted. + @param rBinRanges List of binary cell range objects. + @param nSheet Sheet index to be inserted into orRanges (will be checked). + @param bTrackOverflow true = Update the internal overflow flags, if + the original ranges contain cells outside of the supported sheet + limits. + */ + void convertToCellRangeList( + ScRangeList& orRanges, + const BinRangeList& rBinRanges, + sal_Int16 nSheet, + bool bTrackOverflow ); + + /** Converts the passed range list to a sequence of cell range addresses. + + @param orRanges List of range objects. + @return A uno sequence of cell range addresses as used in API calls. + Does not check ranges for supported sheet limits. + */ + static css::uno::Sequence + toApiSequence(const ScRangeList& orRanges); + + bool isColOverflow() const { return mbColOverflow; } + bool isRowOverflow() const { return mbRowOverflow; } + bool isTabOverflow() const { return mbTabOverflow; } + +private: + void initializeMaxPos( + sal_Int16 nMaxXlsTab, sal_Int32 nMaxXlsCol, sal_Int32 nMaxXlsRow ); + +private: + ScAddress maMaxApiPos; /// Maximum valid cell address in Calc. + ScAddress maMaxXlsPos; /// Maximum valid cell address in Excel. + ScAddress maMaxPos; /// Maximum valid cell address in Calc/Excel. + bool mbColOverflow; /// Flag for "columns overflow". + bool mbRowOverflow; /// Flag for "rows overflow". + bool mbTabOverflow; /// Flag for "tables overflow". +}; + +} // namespace xls +} // namespace oox + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/autofilterbuffer.hxx b/sc/source/filter/inc/autofilterbuffer.hxx new file mode 100644 index 000000000..788be9561 --- /dev/null +++ b/sc/source/filter/inc/autofilterbuffer.hxx @@ -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/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 +#include +#include "workbookhelper.hxx" +#include +#include + +namespace com::sun::star { + namespace sheet { class XDatabaseRange; } + namespace sheet { class XSheetFilterDescriptor3; } +} + +namespace oox { class AttributeList; } +namespace oox { class SequenceInputStream; } + +namespace oox { +namespace xls { + +/** Contains UNO API filter settings for a column in a filtered range. */ +struct ApiFilterSettings +{ + typedef ::std::vector FilterFieldVector; + + FilterFieldVector maFilterFields; /// List of UNO API filter settings. + OptValue< bool > mobNeedsRegExp; /// If set, requires regular expressions to be enabled/disabled. + + explicit ApiFilterSettings(); + + void appendField( bool bAnd, sal_Int32 nOperator, double fValue ); + void appendField( bool bAnd, sal_Int32 nOperator, const OUString& rValue ); + void appendField( bool bAnd, css::util::Color aColor, bool bIsBackgroundColor ); + void appendField( bool bAnd, const std::vector>& rValues ); +}; + +/** Base class for specific filter settings for a column in a filtered range. + */ +class FilterSettingsBase : public WorkbookHelper +{ +public: + explicit FilterSettingsBase( const WorkbookHelper& rHelper ); + + /** Derived classes import filter settings from the passed attribute list. */ + virtual void importAttribs( sal_Int32 nElement, const AttributeList& rAttribs ); + /** Derived classes import filter settings from the passed record. */ + virtual void importRecord( sal_Int32 nRecId, SequenceInputStream& rStrm ); + + /** Derived classes return converted UNO API filter settings representing all filter settings. */ + virtual ApiFilterSettings finalizeImport(); +}; + + +/** Settings for a discrete filter, specifying a list of values to be shown in + the filtered range. + */ +class DiscreteFilter final : public FilterSettingsBase +{ +public: + explicit DiscreteFilter( const WorkbookHelper& rHelper ); + + /** Imports filter settings from the filters and filter elements. */ + virtual void importAttribs( sal_Int32 nElement, const AttributeList& rAttribs ) override; + /** Imports filter settings from the FILTERS and FILTER records. */ + virtual void importRecord( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + + /** Returns converted UNO API filter settings representing all filter settings. */ + virtual ApiFilterSettings finalizeImport() override; + +private: + + std::vector> maValues; // first->values, second->bDatefFormat + sal_Int32 mnCalendarType; + bool mbShowBlank; +}; + +/** Settings for a top-10 filter. */ +class Top10Filter final : public FilterSettingsBase +{ +public: + explicit Top10Filter( const WorkbookHelper& rHelper ); + + /** Imports filter settings from the filters and filter elements. */ + virtual void importAttribs( sal_Int32 nElement, const AttributeList& rAttribs ) override; + /** Imports filter settings from the FILTERS and FILTER records. */ + virtual void importRecord( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + + /** Returns converted UNO API filter settings representing all filter settings. */ + virtual ApiFilterSettings finalizeImport() override; + +private: + double mfValue; /// Number of items or percentage. + bool mbTop; /// True = show top (greatest) items/percentage. + bool mbPercent; /// True = percentage, false = number of items. +}; + +/** Settings for a color filter. */ +class ColorFilter final : public FilterSettingsBase +{ +public: + explicit ColorFilter(const WorkbookHelper& rHelper); + + /** Imports filter settings from the filters and filter elements. */ + virtual void importAttribs(sal_Int32 nElement, const AttributeList& rAttribs) override; + /** Imports filter settings from the FILTERS and FILTER records. */ + virtual void importRecord(sal_Int32 nRecId, SequenceInputStream& rStrm) override; + + /** Returns converted UNO API filter settings representing all filter settings. */ + virtual ApiFilterSettings finalizeImport() override; + +private: + /// Whether we are dealing with the background color (vs. text color) + bool mbIsBackgroundColor; + /// Style name to retrieve the color from + OUString msStyleName; +}; + +/** A filter criterion for a custom filter. */ +struct FilterCriterionModel +{ + css::uno::Any maValue; /// Comparison operand. + sal_Int32 mnOperator; /// Comparison operator. + sal_uInt8 mnDataType; /// Operand data type (BIFF only). + + explicit FilterCriterionModel(); + + /** Sets the passed BIFF operator constant. */ + void setBiffOperator( sal_uInt8 nOperator ); + + /** Imports the criterion model from the passed BIFF12 stream. */ + void readBiffData( SequenceInputStream& rStrm ); +}; + +/** Settings for a custom filter, specifying one or two comparison operators + associated with some values. + */ +class CustomFilter final : public FilterSettingsBase +{ +public: + explicit CustomFilter( const WorkbookHelper& rHelper ); + + /** Imports filter settings from the filters and filter elements. */ + virtual void importAttribs( sal_Int32 nElement, const AttributeList& rAttribs ) override; + /** Imports filter settings from the FILTERS and FILTER records. */ + virtual void importRecord( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + + /** Returns converted UNO API filter settings representing all filter settings. */ + virtual ApiFilterSettings finalizeImport() override; + +private: + /** Appends the passed filter criterion, if it contains valid settings. */ + void appendCriterion( const FilterCriterionModel& rCriterion ); + +private: + typedef ::std::vector< FilterCriterionModel > FilterCriterionVector; + + FilterCriterionVector maCriteria; + bool mbAnd; +}; + +/** A column in a filtered range. Contains an object with specific filter + settings for the cells in the column. + */ +class FilterColumn final : public WorkbookHelper +{ +public: + explicit FilterColumn( const WorkbookHelper& rHelper ); + + /** Imports auto filter column settings from the filterColumn element. */ + void importFilterColumn( const AttributeList& rAttribs ); + /** Imports auto filter column settings from the FILTERCOLUMN record. */ + void importFilterColumn( SequenceInputStream& rStrm ); + + /** Creates and returns the specified filter settings object. */ + template< typename FilterSettingsType > + FilterSettingsBase& createFilterSettings() + { mxSettings = std::make_shared( *this ); return *mxSettings; } + + /** Returns converted UNO API filter settings representing all filter + settings of this column. */ + ApiFilterSettings finalizeImport(); + +private: + std::shared_ptr< FilterSettingsBase > + mxSettings; + sal_Int32 mnColId; + bool mbHiddenButton; + bool mbShowButton; +}; + +// class SortCondition + +class SortCondition final : public WorkbookHelper +{ +public: + explicit SortCondition( const WorkbookHelper& rHelper ); + + void importSortCondition( const AttributeList& rAttribs, sal_Int16 nSheet ); + + ScRange maRange; // Column/Row that this sort condition applies to. + OUString maSortCustomList; // Sort by a custom list. + bool mbDescending; +}; + +// class AutoFilter + +class AutoFilter final : public WorkbookHelper +{ +public: + explicit AutoFilter( const WorkbookHelper& rHelper ); + + /** Imports auto filter settings from the autoFilter element. */ + void importAutoFilter( const AttributeList& rAttribs, sal_Int16 nSheet ); + /** Imports auto filter settings from the AUTOFILTER record. */ + void importAutoFilter( SequenceInputStream& rStrm, sal_Int16 nSheet ); + + void importSortState( const AttributeList& rAttribs, sal_Int16 nSheet ); + + /** Creates a new auto filter column and stores it internally. */ + FilterColumn& createFilterColumn(); + + SortCondition& createSortCondition(); + + /** Applies the filter to the passed filter descriptor. */ + void finalizeImport( const css::uno::Reference< css::sheet::XDatabaseRange >& rxDatabaseRange, + sal_Int16 nSheet ); + +private: + typedef RefVector< FilterColumn > FilterColumnVector; + + FilterColumnVector maFilterColumns; + ScRange maRange; + + ScRange maSortRange; // The whole range of data to sort (not just the sort-by column). + typedef RefVector< SortCondition > SortConditionVector; + SortConditionVector maSortConditions; +}; + +class AutoFilterBuffer final : public WorkbookHelper +{ +public: + explicit AutoFilterBuffer( const WorkbookHelper& rHelper ); + + /** Creates a new auto filter and stores it internally. */ + AutoFilter& createAutoFilter(); + + /** Applies filter settings to a new database range object (used for sheet + autofilter or advanced filter as specified by built-in defined names). */ + void finalizeImport( sal_Int16 nSheet ); + + /** Applies the filters to the passed database range object. + @return True = this buffer contains valid auto filter settings. */ + bool finalizeImport( const css::uno::Reference< css::sheet::XDatabaseRange >& rxDatabaseRange, + sal_Int16 nSheet ); + +private: + /** Returns the auto filter object used to perform auto filtering. */ + AutoFilter* getActiveAutoFilter(); + +private: + typedef RefVector< AutoFilter > AutoFilterVector; + AutoFilterVector maAutoFilters; +}; + +} // namespace xls +} // namespace oox + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/autofiltercontext.hxx b/sc/source/filter/inc/autofiltercontext.hxx new file mode 100644 index 000000000..f6eadab80 --- /dev/null +++ b/sc/source/filter/inc/autofiltercontext.hxx @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "excelhandlers.hxx" +#include "autofilterbuffer.hxx" + +namespace oox::xls { + +class AutoFilter; +class FilterColumn; +class FilterSettingsBase; + +class FilterSettingsContext final : public WorksheetContextBase +{ +public: + explicit FilterSettingsContext( WorksheetContextBase& rParent, FilterSettingsBase& rFilterSettings ); + +private: + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + + virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + virtual void onStartRecord( SequenceInputStream& rStrm ) override; + + FilterSettingsBase& mrFilterSettings; +}; + +class FilterColumnContext final : public WorksheetContextBase +{ +public: + explicit FilterColumnContext( WorksheetContextBase& rParent, FilterColumn& rFilterColumn ); + +private: + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + + virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + virtual void onStartRecord( SequenceInputStream& rStrm ) override; + + FilterColumn& mrFilterColumn; +}; + +// class SortConditionContext + +class SortConditionContext final : public WorksheetContextBase +{ +public: + explicit SortConditionContext( WorksheetContextBase& rFragment, SortCondition& rSortCondition ); + +private: + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + + virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + virtual void onStartRecord( SequenceInputStream& rStrm ) override; + + SortCondition& mrSortCondition; +}; + +// class SortStateContext + +class SortStateContext final : public WorksheetContextBase +{ +public: + explicit SortStateContext( WorksheetContextBase& rFragment, AutoFilter& rAutoFilter ); + +private: + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + + virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + virtual void onStartRecord( SequenceInputStream& rStrm ) override; + + AutoFilter& mrAutoFilter; +}; + +// class AutoFilterContext + +class AutoFilterContext final : public WorksheetContextBase +{ +public: + explicit AutoFilterContext( WorksheetFragmentBase& rFragment, AutoFilter& rAutoFilter ); + +private: + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + + virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + virtual void onStartRecord( SequenceInputStream& rStrm ) override; + + AutoFilter& mrAutoFilter; +}; + +} // namespace oox::xls + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/biffhelper.hxx b/sc/source/filter/inc/biffhelper.hxx new file mode 100644 index 000000000..1cf3ed9c6 --- /dev/null +++ b/sc/source/filter/inc/biffhelper.hxx @@ -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 . + */ + +#pragma once + +#include +#include + +namespace oox { class SequenceInputStream; } + +namespace oox::xls { + +// BIFF12 record identifiers ================================================== + +const sal_Int32 BIFF12_ID_ARRAY = 0x01AA; +const sal_Int32 BIFF12_ID_AUTOFILTER = 0x00A1; +const sal_Int32 BIFF12_ID_AUTOSORTSCOPE = 0x01CB; +const sal_Int32 BIFF12_ID_BINARYINDEXBLOCK = 0x002A; +const sal_Int32 BIFF12_ID_BINARYINDEXROWS = 0x0028; +const sal_Int32 BIFF12_ID_BOOKVIEWS = 0x0087; +const sal_Int32 BIFF12_ID_BORDER = 0x002E; +const sal_Int32 BIFF12_ID_BORDERS = 0x0265; +const sal_Int32 BIFF12_ID_BRK = 0x018C; +const sal_Int32 BIFF12_ID_CALCPR = 0x009D; +const sal_Int32 BIFF12_ID_CELL_BLANK = 0x0001; +const sal_Int32 BIFF12_ID_CELL_BOOL = 0x0004; +const sal_Int32 BIFF12_ID_CELL_DOUBLE = 0x0005; +const sal_Int32 BIFF12_ID_CELL_ERROR = 0x0003; +const sal_Int32 BIFF12_ID_CELL_RK = 0x0002; +const sal_Int32 BIFF12_ID_CELL_RSTRING = 0x003E; +const sal_Int32 BIFF12_ID_CELL_SI = 0x0007; +const sal_Int32 BIFF12_ID_CELL_STRING = 0x0006; +const sal_Int32 BIFF12_ID_CELLSTYLE = 0x0030; +const sal_Int32 BIFF12_ID_CELLSTYLES = 0x026B; +const sal_Int32 BIFF12_ID_CELLSTYLEXFS = 0x0272; +const sal_Int32 BIFF12_ID_CELLXFS = 0x0269; +const sal_Int32 BIFF12_ID_CFCOLOR = 0x0234; +const sal_Int32 BIFF12_ID_CFRULE = 0x01CF; +const sal_Int32 BIFF12_ID_CHARTPAGESETUP = 0x028C; +const sal_Int32 BIFF12_ID_CHARTPROTECTION = 0x029D; +const sal_Int32 BIFF12_ID_CHARTSHEETPR = 0x028B; +const sal_Int32 BIFF12_ID_CHARTSHEETVIEW = 0x008D; +const sal_Int32 BIFF12_ID_CHARTSHEETVIEWS = 0x008B; +const sal_Int32 BIFF12_ID_COL = 0x003C; +const sal_Int32 BIFF12_ID_COLBREAKS = 0x018A; +const sal_Int32 BIFF12_ID_COLOR = 0x023C; +const sal_Int32 BIFF12_ID_COLORS = 0x01D9; +const sal_Int32 BIFF12_ID_COLORSCALE = 0x01D5; +const sal_Int32 BIFF12_ID_COLS = 0x0186; +const sal_Int32 BIFF12_ID_COMMENT = 0x027B; +const sal_Int32 BIFF12_ID_COMMENTAUTHOR = 0x0278; +const sal_Int32 BIFF12_ID_COMMENTAUTHORS = 0x0276; +const sal_Int32 BIFF12_ID_COMMENTLIST = 0x0279; +const sal_Int32 BIFF12_ID_COMMENTS = 0x0274; +const sal_Int32 BIFF12_ID_COMMENTTEXT = 0x027D; +const sal_Int32 BIFF12_ID_CONDFORMATTING = 0x01CD; +const sal_Int32 BIFF12_ID_CONNECTION = 0x00C9; +const sal_Int32 BIFF12_ID_CONNECTIONS = 0x01AD; +const sal_Int32 BIFF12_ID_CONTROL = 0x0284; +const sal_Int32 BIFF12_ID_CONTROLS = 0x0283; +const sal_Int32 BIFF12_ID_CUSTOMCHARTVIEW = 0x028F; +const sal_Int32 BIFF12_ID_CUSTOMCHARTVIEWS = 0x028D; +const sal_Int32 BIFF12_ID_CUSTOMFILTER = 0x00AE; +const sal_Int32 BIFF12_ID_CUSTOMFILTERS = 0x00AC; +const sal_Int32 BIFF12_ID_CUSTOMSHEETVIEW = 0x01A7; +const sal_Int32 BIFF12_ID_CUSTOMSHEETVIEWS = 0x01A6; +const sal_Int32 BIFF12_ID_CUSTOMWORKBOOKVIEW= 0x018D; +const sal_Int32 BIFF12_ID_DATABAR = 0x01D3; +const sal_Int32 BIFF12_ID_DATATABLE = 0x01AC; +const sal_Int32 BIFF12_ID_DATAVALIDATION = 0x0040; +const sal_Int32 BIFF12_ID_DATAVALIDATIONS = 0x023D; +const sal_Int32 BIFF12_ID_DDEITEMVALUES = 0x0242; +const sal_Int32 BIFF12_ID_DDEITEM_BOOL = 0x0248; +const sal_Int32 BIFF12_ID_DDEITEM_DOUBLE = 0x0244; +const sal_Int32 BIFF12_ID_DDEITEM_ERROR = 0x0245; +const sal_Int32 BIFF12_ID_DDEITEM_STRING = 0x0246; +const sal_Int32 BIFF12_ID_DEFINEDNAME = 0x0027; +const sal_Int32 BIFF12_ID_DIMENSION = 0x0094; +const sal_Int32 BIFF12_ID_DISCRETEFILTER = 0x00A7; +const sal_Int32 BIFF12_ID_DISCRETEFILTERS = 0x00A5; +const sal_Int32 BIFF12_ID_DRAWING = 0x0226; +const sal_Int32 BIFF12_ID_DXF = 0x01FB; +const sal_Int32 BIFF12_ID_DXFS = 0x01F9; +const sal_Int32 BIFF12_ID_EXTCELL_BLANK = 0x016F; +const sal_Int32 BIFF12_ID_EXTCELL_BOOL = 0x0171; +const sal_Int32 BIFF12_ID_EXTCELL_DOUBLE = 0x0170; +const sal_Int32 BIFF12_ID_EXTCELL_ERROR = 0x0172; +const sal_Int32 BIFF12_ID_EXTCELL_STRING = 0x0173; +const sal_Int32 BIFF12_ID_EXTERNALADDIN = 0x029B; +const sal_Int32 BIFF12_ID_EXTERNALBOOK = 0x0168; +const sal_Int32 BIFF12_ID_EXTERNALNAME = 0x0241; +const sal_Int32 BIFF12_ID_EXTERNALREF = 0x0163; +const sal_Int32 BIFF12_ID_EXTERNALREFS = 0x0161; +const sal_Int32 BIFF12_ID_EXTERNALSELF = 0x0165; +const sal_Int32 BIFF12_ID_EXTERNALSAME = 0x0166; +const sal_Int32 BIFF12_ID_EXTERNALSHEETS = 0x016A; +const sal_Int32 BIFF12_ID_EXTROW = 0x016E; +const sal_Int32 BIFF12_ID_EXTSHEETDATA = 0x016B; +const sal_Int32 BIFF12_ID_EXTERNALNAMEFLAGS = 0x024A; +const sal_Int32 BIFF12_ID_EXTSHEETNAMES = 0x0167; +const sal_Int32 BIFF12_ID_FILESHARING = 0x0224; +const sal_Int32 BIFF12_ID_FILEVERSION = 0x0080; +const sal_Int32 BIFF12_ID_FILL = 0x002D; +const sal_Int32 BIFF12_ID_FILLS = 0x025B; +const sal_Int32 BIFF12_ID_FILTERCOLUMN = 0x00A3; +const sal_Int32 BIFF12_ID_FONT = 0x002B; +const sal_Int32 BIFF12_ID_FONTS = 0x0263; +const sal_Int32 BIFF12_ID_FORMULA_STRING = 0x0008; +const sal_Int32 BIFF12_ID_FORMULA_DOUBLE = 0x0009; +const sal_Int32 BIFF12_ID_FORMULA_BOOL = 0x000A; +const sal_Int32 BIFF12_ID_FORMULA_ERROR = 0x000B; +const sal_Int32 BIFF12_ID_FUNCTIONGROUP = 0x0299; +const sal_Int32 BIFF12_ID_FUNCTIONGROUPS = 0x0298; +const sal_Int32 BIFF12_ID_HEADERFOOTER = 0x01DF; +const sal_Int32 BIFF12_ID_HYPERLINK = 0x01EE; +const sal_Int32 BIFF12_ID_ICONSET = 0x01D1; +const sal_Int32 BIFF12_ID_INDEXEDCOLORS = 0x0235; +const sal_Int32 BIFF12_ID_INPUTCELLS = 0x01F8; +const sal_Int32 BIFF12_ID_LEGACYDRAWING = 0x0227; +const sal_Int32 BIFF12_ID_MERGECELL = 0x00B0; +const sal_Int32 BIFF12_ID_MERGECELLS = 0x00B1; +const sal_Int32 BIFF12_ID_MRUCOLORS = 0x0239; +const sal_Int32 BIFF12_ID_MULTCELL_BLANK = 0x000C; +const sal_Int32 BIFF12_ID_MULTCELL_BOOL = 0x000F; +const sal_Int32 BIFF12_ID_MULTCELL_DOUBLE = 0x0010; +const sal_Int32 BIFF12_ID_MULTCELL_ERROR = 0x000E; +const sal_Int32 BIFF12_ID_MULTCELL_RK = 0x000D; +const sal_Int32 BIFF12_ID_MULTCELL_RSTRING = 0x003D; +const sal_Int32 BIFF12_ID_MULTCELL_SI = 0x0012; +const sal_Int32 BIFF12_ID_MULTCELL_STRING = 0x0011; +const sal_Int32 BIFF12_ID_NUMFMT = 0x002C; +const sal_Int32 BIFF12_ID_NUMFMTS = 0x0267; +const sal_Int32 BIFF12_ID_OLEOBJECT = 0x027F; +const sal_Int32 BIFF12_ID_OLEOBJECTS = 0x027E; +const sal_Int32 BIFF12_ID_OLESIZE = 0x0225; +const sal_Int32 BIFF12_ID_PAGEMARGINS = 0x01DC; +const sal_Int32 BIFF12_ID_PAGESETUP = 0x01DE; +const sal_Int32 BIFF12_ID_PANE = 0x0097; +const sal_Int32 BIFF12_ID_PCDEFINITION = 0x00B3; +const sal_Int32 BIFF12_ID_PCDFDISCRETEPR = 0x00E1; +const sal_Int32 BIFF12_ID_PCDFGROUPITEMS = 0x00DD; +const sal_Int32 BIFF12_ID_PCDFIELD = 0x00B7; +const sal_Int32 BIFF12_ID_PCDFIELDGROUP = 0x00DB; +const sal_Int32 BIFF12_ID_PCDFIELDS = 0x00B5; +const sal_Int32 BIFF12_ID_PCDFRANGEPR = 0x00DF; +const sal_Int32 BIFF12_ID_PCDFSHAREDITEMS = 0x00BD; +const sal_Int32 BIFF12_ID_PCDSHEETSOURCE = 0x00BB; +const sal_Int32 BIFF12_ID_PCDSOURCE = 0x00B9; +const sal_Int32 BIFF12_ID_PCITEM_ARRAY = 0x00BF; +const sal_Int32 BIFF12_ID_PCITEM_BOOL = 0x0016; +const sal_Int32 BIFF12_ID_PCITEM_DATE = 0x0019; +const sal_Int32 BIFF12_ID_PCITEM_DOUBLE = 0x0015; +const sal_Int32 BIFF12_ID_PCITEM_ERROR = 0x0017; +const sal_Int32 BIFF12_ID_PCITEM_INDEX = 0x001A; +const sal_Int32 BIFF12_ID_PCITEM_MISSING = 0x0014; +const sal_Int32 BIFF12_ID_PCITEM_STRING = 0x0018; +const sal_Int32 BIFF12_ID_PCITEMA_BOOL = 0x001D; +const sal_Int32 BIFF12_ID_PCITEMA_DATE = 0x0020; +const sal_Int32 BIFF12_ID_PCITEMA_DOUBLE = 0x001C; +const sal_Int32 BIFF12_ID_PCITEMA_ERROR = 0x001E; +const sal_Int32 BIFF12_ID_PCITEMA_MISSING = 0x001B; +const sal_Int32 BIFF12_ID_PCITEMA_STRING = 0x001F; +const sal_Int32 BIFF12_ID_PCRECORD = 0x0021; +const sal_Int32 BIFF12_ID_PCRECORDDT = 0x0022; +const sal_Int32 BIFF12_ID_PCRECORDS = 0x00C1; +const sal_Int32 BIFF12_ID_PHONETICPR = 0x0219; +const sal_Int32 BIFF12_ID_PICTURE = 0x0232; +const sal_Int32 BIFF12_ID_PIVOTAREA = 0x00F7; +const sal_Int32 BIFF12_ID_PIVOTCACHE = 0x0182; +const sal_Int32 BIFF12_ID_PIVOTCACHES = 0x0180; +const sal_Int32 BIFF12_ID_PRINTOPTIONS = 0x01DD; +const sal_Int32 BIFF12_ID_PTCOLFIELDS = 0x0137; +const sal_Int32 BIFF12_ID_PTDATAFIELD = 0x0125; +const sal_Int32 BIFF12_ID_PTDATAFIELDS = 0x0127; +const sal_Int32 BIFF12_ID_PTDEFINITION = 0x0118; +const sal_Int32 BIFF12_ID_PTFIELD = 0x011D; +const sal_Int32 BIFF12_ID_PTFIELDS = 0x011F; +const sal_Int32 BIFF12_ID_PTFILTER = 0x0259; +const sal_Int32 BIFF12_ID_PTFILTERS = 0x0257; +const sal_Int32 BIFF12_ID_PTFITEM = 0x011A; +const sal_Int32 BIFF12_ID_PTFITEMS = 0x011B; +const sal_Int32 BIFF12_ID_PTLOCATION = 0x013A; +const sal_Int32 BIFF12_ID_PTPAGEFIELD = 0x0121; +const sal_Int32 BIFF12_ID_PTPAGEFIELDS = 0x0123; +const sal_Int32 BIFF12_ID_PTREFERENCE = 0x00FB; +const sal_Int32 BIFF12_ID_PTREFERENCEITEM = 0x017E; +const sal_Int32 BIFF12_ID_PTREFERENCES = 0x00F9; +const sal_Int32 BIFF12_ID_PTROWFIELDS = 0x0135; +const sal_Int32 BIFF12_ID_QUERYTABLE = 0x01BF; +const sal_Int32 BIFF12_ID_QUERYTABLEREFRESH = 0x01C1; +const sal_Int32 BIFF12_ID_RGBCOLOR = 0x01DB; +const sal_Int32 BIFF12_ID_ROW = 0x0000; +const sal_Int32 BIFF12_ID_ROWBREAKS = 0x0188; +const sal_Int32 BIFF12_ID_SCENARIO = 0x01F6; +const sal_Int32 BIFF12_ID_SCENARIOS = 0x01F4; +const sal_Int32 BIFF12_ID_SELECTION = 0x0098; +const sal_Int32 BIFF12_ID_SHAREDFMLA = 0x01AB; +const sal_Int32 BIFF12_ID_SHEET = 0x009C; +const sal_Int32 BIFF12_ID_SHEETDATA = 0x0091; +const sal_Int32 BIFF12_ID_SHEETFORMATPR = 0x01E5; +const sal_Int32 BIFF12_ID_SHEETPR = 0x0093; +const sal_Int32 BIFF12_ID_SHEETPROTECTION = 0x0217; +const sal_Int32 BIFF12_ID_SHEETS = 0x008F; +const sal_Int32 BIFF12_ID_SHEETVIEW = 0x0089; +const sal_Int32 BIFF12_ID_SHEETVIEWS = 0x0085; +const sal_Int32 BIFF12_ID_SI = 0x0013; +const sal_Int32 BIFF12_ID_SST = 0x009F; +const sal_Int32 BIFF12_ID_STYLESHEET = 0x0116; +const sal_Int32 BIFF12_ID_TABLE = 0x0157; +const sal_Int32 BIFF12_ID_TABLEPART = 0x0295; +const sal_Int32 BIFF12_ID_TABLEPARTS = 0x0294; +const sal_Int32 BIFF12_ID_TABLESTYLEINFO = 0x0201; +const sal_Int32 BIFF12_ID_TABLESTYLES = 0x01FC; +const sal_Int32 BIFF12_ID_TOP10FILTER = 0x00AA; +const sal_Int32 BIFF12_ID_VOLTYPE = 0x0204; +const sal_Int32 BIFF12_ID_VOLTYPEMAIN = 0x0206; +const sal_Int32 BIFF12_ID_VOLTYPES = 0x0202; +const sal_Int32 BIFF12_ID_VOLTYPESTP = 0x020A; +const sal_Int32 BIFF12_ID_VOLTYPETR = 0x020B; +const sal_Int32 BIFF12_ID_WEBPR = 0x0105; +const sal_Int32 BIFF12_ID_WEBPRTABLES = 0x0107; +const sal_Int32 BIFF12_ID_WORKBOOK = 0x0083; +const sal_Int32 BIFF12_ID_WORKBOOKPR = 0x0099; +const sal_Int32 BIFF12_ID_WORKBOOKVIEW = 0x009E; +const sal_Int32 BIFF12_ID_WORKSHEET = 0x0081; +const sal_Int32 BIFF12_ID_XF = 0x002F; + +// BIFF2-BIFF8 record identifiers ============================================= + +/** all binary Excel file format types (BIFF types). + BIFF2 /// MS Excel 2.1. + BIFF3 /// MS Excel 3.0. + BIFF4 /// MS Excel 4.0. + BIFF5 /// MS Excel 5.0, MS Excel 7.0 (95). + BIFF8 /// MS Excel 8.0 (97), 9.0 (2000), 10.0 (XP), 11.0 (2003). + BIFF_UNKNOWN /// Unknown BIFF version. +*/ + +//const sal_uInt16 BIFF2_MAXRECSIZE = 2080; +//const sal_uInt16 BIFF8_MAXRECSIZE = 8224; + +// record identifiers --------------------------------------------------------- + +const sal_uInt16 BIFF2_ID_BOF = 0x0009; +const sal_uInt16 BIFF3_ID_BOF = 0x0209; +const sal_uInt16 BIFF4_ID_BOF = 0x0409; +const sal_uInt16 BIFF5_ID_BOF = 0x0809; +const sal_uInt16 BIFF_ID_CONT = 0x003C; +const sal_uInt16 BIFF_ID_EOF = 0x000A; +const sal_uInt16 BIFF_ID_PCDEFINITION = 0x00C6; +const sal_uInt16 BIFF_ID_PCDEFINITION2 = 0x0122; +const sal_uInt16 BIFF_ID_PCDFDISCRETEPR = 0x00D9; +const sal_uInt16 BIFF_ID_PCDFIELD = 0x00C7; +const sal_uInt16 BIFF_ID_PCDFRANGEPR = 0x00D8; +const sal_uInt16 BIFF_ID_PCDFSQLTYPE = 0x01BB; +const sal_uInt16 BIFF_ID_PCITEM_BOOL = 0x00CA; +const sal_uInt16 BIFF_ID_PCITEM_DATE = 0x00CE; +const sal_uInt16 BIFF_ID_PCITEM_DOUBLE = 0x00C9; +const sal_uInt16 BIFF_ID_PCITEM_ERROR = 0x00CB; +const sal_uInt16 BIFF_ID_PCITEM_INDEXLIST = 0x00C8; +const sal_uInt16 BIFF_ID_PCITEM_INTEGER = 0x00CC; +const sal_uInt16 BIFF_ID_PCITEM_MISSING = 0x00CF; +const sal_uInt16 BIFF_ID_PCITEM_STRING = 0x00CD; + +const sal_uInt16 BIFF_ID_UNKNOWN = SAL_MAX_UINT16; + +/* Many of these constants might be unused, but please keep for documentation. If you notice + * hardcoded numbers in the code that actually correspond in meaning in the context (not just value) + * to one of the named constants, feel free to change it to use the constant instead, of course. + */ +const sal_uInt16 BIFF2_ID_ARRAY = 0x0021; +const sal_uInt16 BIFF3_ID_ARRAY = 0x0221; +const sal_uInt16 BIFF_ID_AUTOFILTER = 0x009D; +const sal_uInt16 BIFF2_ID_BLANK = 0x0001; +const sal_uInt16 BIFF3_ID_BLANK = 0x0201; +const sal_uInt16 BIFF_ID_BOOKBOOL = 0x00DA; +const sal_uInt16 BIFF_ID_BOOKEXT = 0x0863; +const sal_uInt16 BIFF2_ID_BOOLERR = 0x0005; +const sal_uInt16 BIFF3_ID_BOOLERR = 0x0205; +const sal_uInt16 BIFF_ID_BOTTOMMARGIN = 0x0029; +const sal_uInt16 BIFF_ID_CALCCOUNT = 0x000C; +const sal_uInt16 BIFF_ID_CALCMODE = 0x000D; +const sal_uInt16 BIFF_ID_CFHEADER = 0x01B0; +const sal_uInt16 BIFF_ID_CFRULE = 0x01B1; +const sal_uInt16 BIFF_ID_CFRULE12 = 0x087A; +const sal_uInt16 BIFF_ID_CFRULEEXT = 0x087B; +const sal_uInt16 BIFF_ID_CH3DDATAFORMAT = 0x105F; +const sal_uInt16 BIFF_ID_CHAREA = 0x101A; +const sal_uInt16 BIFF_ID_CHAREAFORMAT = 0x100A; +const sal_uInt16 BIFF_ID_CHATTACHEDLABEL = 0x100C; +const sal_uInt16 BIFF_ID_CHAXESSET = 0x1041; +const sal_uInt16 BIFF_ID_CHAXIS = 0x101D; +const sal_uInt16 BIFF_ID_CHAXISLINE = 0x1021; +const sal_uInt16 BIFF_ID_CHBAR = 0x1017; +const sal_uInt16 BIFF_ID_CHBEGIN = 0x1033; +const sal_uInt16 BIFF_ID_CHCHART = 0x1002; +const sal_uInt16 BIFF_ID_CHCHART3D = 0x103A; +const sal_uInt16 BIFF_ID_CHCHARTLINE = 0x101C; +const sal_uInt16 BIFF_ID_CHDATAFORMAT = 0x1006; +const sal_uInt16 BIFF_ID_CHDATERANGE = 0x1062; +const sal_uInt16 BIFF_ID_CHDEFAULTTEXT = 0x1024; +const sal_uInt16 BIFF_ID_CHDROPBAR = 0x103D; +const sal_uInt16 BIFF_ID_CHECKCOMPAT = 0x088C; +const sal_uInt16 BIFF_ID_CHEND = 0x1034; +const sal_uInt16 BIFF_ID_CHESCHERFORMAT = 0x1066; +const sal_uInt16 BIFF_ID_CHFONT = 0x1026; +const sal_uInt16 BIFF_ID_CHFORMAT = 0x104E; +const sal_uInt16 BIFF_ID_CHFORMATRUNS = 0x1050; +const sal_uInt16 BIFF_ID_CHFRAME = 0x1032; +const sal_uInt16 BIFF_ID_CHFRAMEPOS = 0x104F; +const sal_uInt16 BIFF_ID_CHFRBLOCKBEGIN = 0x0852; +const sal_uInt16 BIFF_ID_CHFRBLOCKEND = 0x0853; +const sal_uInt16 BIFF_ID_CHFRCATEGORYPROPS = 0x0856; +const sal_uInt16 BIFF_ID_CHFREXTPROPS = 0x089E; +const sal_uInt16 BIFF_ID_CHFREXTPROPSCONT = 0x089F; +const sal_uInt16 BIFF_ID_CHFRINFO = 0x0850; +const sal_uInt16 BIFF_ID_CHFRLABELPROPS = 0x086B; +const sal_uInt16 BIFF_ID_CHFRLAYOUT = 0x089D; +const sal_uInt16 BIFF_ID_CHFRPLOTAREALAYOUT = 0x08A7; +const sal_uInt16 BIFF_ID_CHFRSHAPEPROPS = 0x08A4; +const sal_uInt16 BIFF_ID_CHFRTEXTPROPS = 0x08A5; +const sal_uInt16 BIFF_ID_CHFRUNITPROPS = 0x0857; +const sal_uInt16 BIFF_ID_CHFRWRAPPER = 0x0851; +const sal_uInt16 BIFF_ID_CHLABELRANGE = 0x1020; +const sal_uInt16 BIFF_ID_CHLEGEND = 0x1015; +const sal_uInt16 BIFF_ID_CHLINE = 0x1018; +const sal_uInt16 BIFF_ID_CHLINEFORMAT = 0x1007; +const sal_uInt16 BIFF_ID_CHMARKERFORMAT = 0x1009; +const sal_uInt16 BIFF_ID_CHOBJECTLINK = 0x1027; +const sal_uInt16 BIFF_ID_CHPICFORMAT = 0x103C; +const sal_uInt16 BIFF_ID_CHPIE = 0x1019; +const sal_uInt16 BIFF_ID_CHPIEEXT = 0x1061; +const sal_uInt16 BIFF_ID_CHPIEFORMAT = 0x100B; +const sal_uInt16 BIFF_ID_CHPIVOTFLAGS = 0x0859; +const sal_uInt16 BIFF5_ID_CHPIVOTREF = 0x1048; +const sal_uInt16 BIFF8_ID_CHPIVOTREF = 0x0858; +const sal_uInt16 BIFF_ID_CHPLOTFRAME = 0x1035; +const sal_uInt16 BIFF_ID_CHPLOTGROWTH = 0x1064; +const sal_uInt16 BIFF_ID_CHPROPERTIES = 0x1044; +const sal_uInt16 BIFF_ID_CHRADARLINE = 0x103E; +const sal_uInt16 BIFF_ID_CHRADARAREA = 0x1040; +const sal_uInt16 BIFF_ID_CHSCATTER = 0x101B; +const sal_uInt16 BIFF_ID_CHSERERRORBAR = 0x105B; +const sal_uInt16 BIFF_ID_CHSERGROUP = 0x1045; +const sal_uInt16 BIFF_ID_CHSERIES = 0x1003; +const sal_uInt16 BIFF_ID_CHSERIESFORMAT = 0x105D; +const sal_uInt16 BIFF_ID_CHSERPARENT = 0x104A; +const sal_uInt16 BIFF_ID_CHSERTRENDLINE = 0x104B; +const sal_uInt16 BIFF_ID_CHSOURCELINK = 0x1051; +const sal_uInt16 BIFF_ID_CHSTRING = 0x100D; +const sal_uInt16 BIFF_ID_CHSURFACE = 0x103F; +const sal_uInt16 BIFF_ID_CHTEXT = 0x1025; +const sal_uInt16 BIFF_ID_CHTICK = 0x101E; +const sal_uInt16 BIFF_ID_CHTYPEGROUP = 0x1014; +const sal_uInt16 BIFF_ID_CHVALUERANGE = 0x101F; +const sal_uInt16 BIFF_ID_CODENAME = 0x01BA; +const sal_uInt16 BIFF_ID_CODEPAGE = 0x0042; +const sal_uInt16 BIFF_ID_COLINFO = 0x007D; +const sal_uInt16 BIFF_ID_COLUMNDEFAULT = 0x0020; +const sal_uInt16 BIFF_ID_COLWIDTH = 0x0024; +const sal_uInt16 BIFF_ID_COMPRESSPICS = 0x089B; +const sal_uInt16 BIFF_ID_CONNECTION = 0x0876; +const sal_uInt16 BIFF_ID_COORDLIST = 0x00A9; +const sal_uInt16 BIFF_ID_COUNTRY = 0x008C; +const sal_uInt16 BIFF_ID_CRN = 0x005A; +const sal_uInt16 BIFF2_ID_DATATABLE = 0x0036; +const sal_uInt16 BIFF3_ID_DATATABLE = 0x0236; +const sal_uInt16 BIFF2_ID_DATATABLE2 = 0x0037; +const sal_uInt16 BIFF_ID_DATAVALIDATION = 0x01BE; +const sal_uInt16 BIFF_ID_DATAVALIDATIONS = 0x01B2; +const sal_uInt16 BIFF_ID_DATEMODE = 0x0022; +const sal_uInt16 BIFF_ID_DBCELL = 0x00D7; +const sal_uInt16 BIFF_ID_DBQUERY = 0x00DC; +const sal_uInt16 BIFF_ID_DCONBINAME = 0x01B5; +const sal_uInt16 BIFF_ID_DCONNAME = 0x0052; +const sal_uInt16 BIFF_ID_DCONREF = 0x0051; +const sal_uInt16 BIFF_ID_DEFCOLWIDTH = 0x0055; +const sal_uInt16 BIFF2_ID_DEFINEDNAME = 0x0018; +const sal_uInt16 BIFF3_ID_DEFINEDNAME = 0x0218; +const sal_uInt16 BIFF5_ID_DEFINEDNAME = 0x0018; +const sal_uInt16 BIFF2_ID_DEFROWHEIGHT = 0x0025; +const sal_uInt16 BIFF3_ID_DEFROWHEIGHT = 0x0225; +const sal_uInt16 BIFF_ID_DELTA = 0x0010; +const sal_uInt16 BIFF2_ID_DIMENSION = 0x0000; +const sal_uInt16 BIFF3_ID_DIMENSION = 0x0200; +const sal_uInt16 BIFF_ID_DXF = 0x088D; +const sal_uInt16 BIFF_ID_EXTERNALBOOK = 0x01AE; +const sal_uInt16 BIFF2_ID_EXTERNALNAME = 0x0023; +const sal_uInt16 BIFF3_ID_EXTERNALNAME = 0x0223; +const sal_uInt16 BIFF5_ID_EXTERNALNAME = 0x0023; +const sal_uInt16 BIFF_ID_EXTERNSHEET = 0x0017; +const sal_uInt16 BIFF_ID_EXTSST = 0x00FF; +const sal_uInt16 BIFF_ID_FILEPASS = 0x002F; +const sal_uInt16 BIFF_ID_FILESHARING = 0x005B; +const sal_uInt16 BIFF_ID_FILTERCOLUMN = 0x009E; +const sal_uInt16 BIFF_ID_FILTERMODE = 0x009B; +const sal_uInt16 BIFF2_ID_FONT = 0x0031; +const sal_uInt16 BIFF3_ID_FONT = 0x0231; +const sal_uInt16 BIFF5_ID_FONT = 0x0031; +const sal_uInt16 BIFF_ID_FONTCOLOR = 0x0045; +const sal_uInt16 BIFF_ID_FOOTER = 0x0015; +const sal_uInt16 BIFF_ID_FORCEFULLCALC = 0x08A3; +const sal_uInt16 BIFF2_ID_FORMAT = 0x001E; +const sal_uInt16 BIFF4_ID_FORMAT = 0x041E; +const sal_uInt16 BIFF2_ID_FORMULA = 0x0006; +const sal_uInt16 BIFF3_ID_FORMULA = 0x0206; +const sal_uInt16 BIFF4_ID_FORMULA = 0x0406; +const sal_uInt16 BIFF5_ID_FORMULA = 0x0006; +const sal_uInt16 BIFF_ID_GUTS = 0x0080; +const sal_uInt16 BIFF_ID_HCENTER = 0x0083; +const sal_uInt16 BIFF_ID_HEADER = 0x0014; +const sal_uInt16 BIFF_ID_HEADERFOOTER = 0x089C; +const sal_uInt16 BIFF_ID_HIDEOBJ = 0x008D; +const sal_uInt16 BIFF_ID_HORPAGEBREAKS = 0x001B; +const sal_uInt16 BIFF_ID_HYPERLINK = 0x01B8; +const sal_uInt16 BIFF3_ID_IMGDATA = 0x007F; +const sal_uInt16 BIFF8_ID_IMGDATA = 0x00E9; +const sal_uInt16 BIFF2_ID_INDEX = 0x000B; +const sal_uInt16 BIFF3_ID_INDEX = 0x020B; +const sal_uInt16 BIFF2_ID_INTEGER = 0x0002; +const sal_uInt16 BIFF_ID_INTERFACEHDR = 0x00E1; +const sal_uInt16 BIFF_ID_ITERATION = 0x0011; +const sal_uInt16 BIFF_ID_IXFE = 0x0044; +const sal_uInt16 BIFF2_ID_LABEL = 0x0004; +const sal_uInt16 BIFF3_ID_LABEL = 0x0204; +const sal_uInt16 BIFF_ID_LABELRANGES = 0x015F; +const sal_uInt16 BIFF_ID_LABELSST = 0x00FD; +const sal_uInt16 BIFF_ID_LEFTMARGIN = 0x0026; +const sal_uInt16 BIFF_ID_MERGEDCELLS = 0x00E5; +const sal_uInt16 BIFF_ID_MSODRAWING = 0x00EC; +const sal_uInt16 BIFF_ID_MSODRAWINGGROUP = 0x00EB; +const sal_uInt16 BIFF_ID_MSODRAWINGSEL = 0x00ED; +const sal_uInt16 BIFF_ID_MTHREADSETTINGS = 0x089A; +const sal_uInt16 BIFF_ID_MULTBLANK = 0x00BE; +const sal_uInt16 BIFF_ID_MULTRK = 0x00BD; +const sal_uInt16 BIFF_ID_NOTE = 0x001C; +const sal_uInt16 BIFF_ID_NOTESOUND = 0x0096; +const sal_uInt16 BIFF2_ID_NUMBER = 0x0003; +const sal_uInt16 BIFF3_ID_NUMBER = 0x0203; +const sal_uInt16 BIFF_ID_OBJ = 0x005D; +const sal_uInt16 BIFF_ID_OBJECTPROTECT = 0x0063; +const sal_uInt16 BIFF_ID_OLESIZE = 0x00DE; +const sal_uInt16 BIFF_ID_PAGELAYOUTVIEW = 0x088B; +const sal_uInt16 BIFF_ID_PAGESETUP = 0x00A1; +const sal_uInt16 BIFF_ID_PALETTE = 0x0092; +const sal_uInt16 BIFF_ID_PANE = 0x0041; +const sal_uInt16 BIFF_ID_PARAMQUERY = 0x00DC; +const sal_uInt16 BIFF_ID_PASSWORD = 0x0013; +const sal_uInt16 BIFF_ID_PCDFIELDINDEX = 0x0103; +const sal_uInt16 BIFF_ID_PCDFORMULAFIELD = 0x00F9; +const sal_uInt16 BIFF_ID_PCDSOURCE = 0x00E3; +const sal_uInt16 BIFF_ID_PHONETICPR = 0x00EF; +const sal_uInt16 BIFF_ID_PICTURE = 0x00E9; +const sal_uInt16 BIFF_ID_PIVOTCACHE = 0x00D5; +const sal_uInt16 BIFF_ID_PRECISION = 0x000E; +const sal_uInt16 BIFF_ID_PRINTGRIDLINES = 0x002B; +const sal_uInt16 BIFF_ID_PRINTHEADERS = 0x002A; +const sal_uInt16 BIFF_ID_PROJEXTSHEET = 0x00A3; +const sal_uInt16 BIFF_ID_PROTECT = 0x0012; +const sal_uInt16 BIFF_ID_PTDATAFIELD = 0x00C5; +const sal_uInt16 BIFF_ID_PTDEFINITION = 0x00B0; +const sal_uInt16 BIFF_ID_PTDEFINITION2 = 0x00F1; +const sal_uInt16 BIFF_ID_PTFIELD = 0x00B1; +const sal_uInt16 BIFF_ID_PTFIELD2 = 0x0100; +const sal_uInt16 BIFF_ID_PTFITEM = 0x00B2; +const sal_uInt16 BIFF_ID_PTPAGEFIELDS = 0x00B6; +const sal_uInt16 BIFF_ID_PTROWCOLFIELDS = 0x00B4; +const sal_uInt16 BIFF_ID_PTROWCOLITEMS = 0x00B5; +const sal_uInt16 BIFF_ID_QUERYTABLE = 0x01AD; +const sal_uInt16 BIFF_ID_QUERYTABLEREFRESH = 0x0802; +const sal_uInt16 BIFF_ID_QUERYTABLESETTINGS = 0x0803; +const sal_uInt16 BIFF_ID_QUERYTABLESTRING = 0x0804; +const sal_uInt16 BIFF_ID_RECALCID = 0x01C1; +const sal_uInt16 BIFF_ID_REFMODE = 0x000F; +const sal_uInt16 BIFF_ID_RIGHTMARGIN = 0x0027; +const sal_uInt16 BIFF_ID_RK = 0x027E; +const sal_uInt16 BIFF2_ID_ROW = 0x0008; +const sal_uInt16 BIFF3_ID_ROW = 0x0208; +const sal_uInt16 BIFF_ID_RSTRING = 0x00D6; +const sal_uInt16 BIFF_ID_SAVERECALC = 0x005F; +const sal_uInt16 BIFF_ID_SCENARIO = 0x00AF; +const sal_uInt16 BIFF_ID_SCENARIOS = 0x00AE; +const sal_uInt16 BIFF_ID_SCL = 0x00A0; +const sal_uInt16 BIFF_ID_SCENPROTECT = 0x00DD; +const sal_uInt16 BIFF_ID_SCREENTIP = 0x0800; +const sal_uInt16 BIFF_ID_SELECTION = 0x001D; +const sal_uInt16 BIFF_ID_SHAREDFEATHEAD = 0x0867; +const sal_uInt16 BIFF_ID_SHAREDFMLA = 0x04BC; +const sal_uInt16 BIFF_ID_SHEET = 0x0085; +const sal_uInt16 BIFF_ID_SHEETEXT = 0x0862; +const sal_uInt16 BIFF_ID_SHEETHEADER = 0x008F; +const sal_uInt16 BIFF_ID_SHEETPR = 0x0081; +const sal_uInt16 BIFF_ID_SST = 0x00FC; +const sal_uInt16 BIFF_ID_STANDARDWIDTH = 0x0099; +const sal_uInt16 BIFF2_ID_STRING = 0x0007; +const sal_uInt16 BIFF3_ID_STRING = 0x0207; +const sal_uInt16 BIFF_ID_STYLE = 0x0293; +const sal_uInt16 BIFF_ID_STYLEEXT = 0x0892; +const sal_uInt16 BIFF_ID_TABLESTYLES = 0x088E; +const sal_uInt16 BIFF_ID_THEME = 0x0896; +const sal_uInt16 BIFF_ID_TOPMARGIN = 0x0028; +const sal_uInt16 BIFF_ID_TXO = 0x01B6; +const sal_uInt16 BIFF_ID_UNCALCED = 0x005E; +const sal_uInt16 BIFF_ID_USESELFS = 0x0160; +const sal_uInt16 BIFF_ID_VBAPROJECT = 0x00D3; +const sal_uInt16 BIFF_ID_VBAPROJECTEMPTY = 0x01BD; +const sal_uInt16 BIFF_ID_VCENTER = 0x0084; +const sal_uInt16 BIFF_ID_VERPAGEBREAKS = 0x001A; +const sal_uInt16 BIFF_ID_WINDOW1 = 0x003D; +const sal_uInt16 BIFF2_ID_WINDOW2 = 0x003E; +const sal_uInt16 BIFF3_ID_WINDOW2 = 0x023E; +const sal_uInt16 BIFF_ID_WRITEACCESS = 0x005C; +const sal_uInt16 BIFF_ID_XCT = 0x0059; +const sal_uInt16 BIFF2_ID_XF = 0x0043; +const sal_uInt16 BIFF3_ID_XF = 0x0243; +const sal_uInt16 BIFF4_ID_XF = 0x0443; +const sal_uInt16 BIFF5_ID_XF = 0x00E0; +const sal_uInt16 BIFF_ID_XFCRC = 0x087C; +const sal_uInt16 BIFF_ID_XFEXT = 0x087D; + +// OBJ subrecord identifiers -------------------------------------------------- +const sal_uInt16 BIFF_ID_OBJEND = 0x0000; /// End of OBJ. +const sal_uInt16 BIFF_ID_OBJMACRO = 0x0004; /// Macro link. +const sal_uInt16 BIFF_ID_OBJBUTTON = 0x0005; /// Button data. +const sal_uInt16 BIFF_ID_OBJGMO = 0x0006; /// Group marker. +const sal_uInt16 BIFF_ID_OBJCF = 0x0007; /// Clipboard format. +const sal_uInt16 BIFF_ID_OBJFLAGS = 0x0008; /// Option flags. +const sal_uInt16 BIFF_ID_OBJPICTFMLA = 0x0009; /// OLE link formula. +const sal_uInt16 BIFF_ID_OBJCBLS = 0x000A; /// Check box/radio button data. +const sal_uInt16 BIFF_ID_OBJRBO = 0x000B; /// Radio button group data. +const sal_uInt16 BIFF_ID_OBJSBS = 0x000C; /// Scroll bar data. +const sal_uInt16 BIFF_ID_OBJNTS = 0x000C; /// Note data. +const sal_uInt16 BIFF_ID_OBJSBSFMLA = 0x000E; /// Scroll bar/list box/combo box cell link. +const sal_uInt16 BIFF_ID_OBJGBODATA = 0x000F; /// Group box data. +const sal_uInt16 BIFF_ID_OBJEDODATA = 0x0010; /// Edit box data. +const sal_uInt16 BIFF_ID_OBJRBODATA = 0x0011; /// Radio button group data. +const sal_uInt16 BIFF_ID_OBJCBLSDATA = 0x0012; /// Check box/radio button data. +const sal_uInt16 BIFF_ID_OBJLBSDATA = 0x0013; /// List box/combo box data. +const sal_uInt16 BIFF_ID_OBJCBLSFMLA = 0x0014; /// Check box/radio button cell link. +const sal_uInt16 BIFF_ID_OBJCMO = 0x0015; /// Common object settings. + +// record constants ----------------------------------------------------------- + +const sal_uInt8 BIFF_ERR_NULL = 0x00; +const sal_uInt8 BIFF_ERR_DIV0 = 0x07; +const sal_uInt8 BIFF_ERR_VALUE = 0x0F; +const sal_uInt8 BIFF_ERR_REF = 0x17; +const sal_uInt8 BIFF_ERR_NAME = 0x1D; +const sal_uInt8 BIFF_ERR_NUM = 0x24; +const sal_uInt8 BIFF_ERR_NA = 0x2A; + +const sal_uInt16 BIFF_BOF_BIFF2 = 0x0200; +const sal_uInt16 BIFF_BOF_BIFF3 = 0x0300; +const sal_uInt16 BIFF_BOF_BIFF4 = 0x0400; +const sal_uInt16 BIFF_BOF_BIFF5 = 0x0500; +const sal_uInt16 BIFF_BOF_BIFF8 = 0x0600; + +const sal_uInt8 BIFF_DATATYPE_EMPTY = 0; +const sal_uInt8 BIFF_DATATYPE_DOUBLE = 1; +const sal_uInt8 BIFF_DATATYPE_STRING = 2; +const sal_uInt8 BIFF_DATATYPE_BOOL = 4; +const sal_uInt8 BIFF_DATATYPE_ERROR = 16; + +const sal_uInt8 BIFF_BOOLERR_BOOL = 0; +const sal_uInt8 BIFF_BOOLERR_ERROR = 1; + +// BIFF8 unicode strings ------------------------------------------------------ + +const sal_uInt8 BIFF_STRF_16BIT = 0x01; +const sal_uInt8 BIFF_STRF_PHONETIC = 0x04; +const sal_uInt8 BIFF_STRF_RICH = 0x08; +const sal_uInt8 BIFF_STRF_UNKNOWN = 0xF2; + +/** Static helper functions for BIFF filters. */ +class BiffHelper +{ +public: + // conversion ------------------------------------------------------------- + + /** Converts the passed packed number to a double. */ + static double calcDoubleFromRk( sal_Int32 nRkValue ); + + /** Converts the passed BIFF error to a double containing the respective Calc error code. */ + static double calcDoubleFromError( sal_uInt8 nErrorCode ); + + // BIFF12 import ---------------------------------------------------------- + + /** Reads a BIFF12 string with leading 16-bit or 32-bit length field. */ + static OUString readString( SequenceInputStream& rStrm, bool b32BitLen = true ); + +private: + BiffHelper() = delete; + ~BiffHelper() = delete; +}; + +/** BIFF12 stream operator for an OUString, reads 32-bit string length and Unicode array. */ +inline SequenceInputStream& operator>>( SequenceInputStream& rStrm, OUString& orString ) +{ + orString = BiffHelper::readString( rStrm ); + return rStrm; +} + +} // namespace oox::xls + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/chartsheetfragment.hxx b/sc/source/filter/inc/chartsheetfragment.hxx new file mode 100644 index 000000000..c2cce27c2 --- /dev/null +++ b/sc/source/filter/inc/chartsheetfragment.hxx @@ -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 . + */ + +#pragma once + +#include "excelhandlers.hxx" + +namespace oox::xls { + +class ChartsheetFragment final : public WorksheetFragmentBase +{ +public: + explicit ChartsheetFragment( + const WorksheetHelper& rHelper, + const OUString& rFragmentPath ); + +private: + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onCharacters( const OUString& rChars ) override; + + virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + + virtual const ::oox::core::RecordInfo* getRecordInfos() const override; + virtual void initializeImport() override; + virtual void finalizeImport() override; + + /** Imports the relation identifier for the DrawingML part. */ + void importDrawing( const AttributeList& rAttribs ); + /** Imports the DRAWING record containing the relation identifier for the DrawingML part. */ + void importDrawing( SequenceInputStream& rStrm ); +}; + +} // namespace oox::xls + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/colrowst.hxx b/sc/source/filter/inc/colrowst.hxx new file mode 100644 index 000000000..963ea9682 --- /dev/null +++ b/sc/source/filter/inc/colrowst.hxx @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "xiroot.hxx" +#include +#include + +enum class ExcColRowFlags : sal_uInt8 { + NONE = 0x00, + Used = 0x01, + Default = 0x02, + Hidden = 0x04, + Man = 0x08, +}; +namespace o3tl { + template<> struct typed_flags : is_typed_flags {}; +} + + +class XclImpColRowSettings final : protected XclImpRoot +{ +public: + explicit XclImpColRowSettings( const XclImpRoot& rRoot ); + virtual ~XclImpColRowSettings() override; + + void SetDefWidth( sal_uInt16 nDefWidth, bool bStdWidthRec = false ); + void SetWidthRange( SCCOL nCol1, SCCOL nCol2, sal_uInt16 nWidth ); + void HideCol( SCCOL nCol ); + void HideColRange( SCCOL nCol1, SCCOL nCol2 ); + + void SetDefHeight( sal_uInt16 nDefHeight, sal_uInt16 nFlags ); + void SetHeight( SCROW nRow, sal_uInt16 nHeight ); + void SetRowSettings( SCROW nRow, sal_uInt16 nHeight, sal_uInt16 nFlags ); + void SetManualRowHeight( SCROW nScRow ); + + void SetDefaultXF( SCCOL nScCol1, SCCOL nScCol2, sal_uInt16 nXFIndex ); + /** Inserts all column and row settings of the specified sheet, except the hidden flags. */ + void Convert( SCTAB nScTab ); + /** Sets the HIDDEN flags at all hidden columns and rows in the specified sheet. */ + void ConvertHiddenFlags( SCTAB nScTab ); + +private: + void ApplyColFlag(SCCOL nCol, ExcColRowFlags nNewVal); + bool GetColFlag(SCCOL nCol, ExcColRowFlags nMask) const; + +private: + typedef ::mdds::flat_segment_tree WidthHeightStoreType; + typedef ::mdds::flat_segment_tree ColRowFlagsType; + typedef ::mdds::flat_segment_tree RowHiddenType; + + WidthHeightStoreType maColWidths; + ColRowFlagsType maColFlags; + WidthHeightStoreType maRowHeights; + ColRowFlagsType maRowFlags; + RowHiddenType maHiddenRows; + + SCROW mnLastScRow; + + sal_uInt16 mnDefWidth; /// Default width from DEFCOLWIDTH or STANDARDWIDTH record. + sal_uInt16 mnDefHeight; /// Default height from DEFAULTROWHEIGHT record. + sal_uInt16 mnDefRowFlags; /// Default row flags from DEFAULTROWHEIGHT record. + + bool mbHasStdWidthRec; /// true = Width from STANDARDWIDTH (overrides DEFCOLWIDTH record). + bool mbHasDefHeight; /// true = mnDefHeight and mnDefRowFlags are valid. + bool mbDirty; +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/commentsbuffer.hxx b/sc/source/filter/inc/commentsbuffer.hxx new file mode 100644 index 000000000..c30d6765d --- /dev/null +++ b/sc/source/filter/inc/commentsbuffer.hxx @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "richstring.hxx" +#include "worksheethelper.hxx" +#include + +namespace oox::xls { + +struct CommentModel +{ + ScRange maRange; /// Position of the comment in the worksheet. + RichStringRef mxText; /// Formatted text of the comment (not used in BIFF8). + sal_Int32 mnAuthorId; /// Identifier of the comment's author (OOXML and BIFF12 only). + bool mbAutoFill; /// Auto Selection of comment object's fill style + bool mbAutoScale; /// Auto Scale comment text + bool mbColHidden; /// Comment cell's Column is Hidden + bool mbLocked; /// Comment changes Locked + bool mbRowHidden; /// Comment cell's Row is Hidden + sal_Int32 mnTHA; /// Horizontal Alignment + sal_Int32 mnTVA; /// Vertical Alignment + css::awt::Rectangle maAnchor; /// Anchor parameters + + explicit CommentModel(); +}; + +class Comment final : public WorksheetHelper +{ +public: + explicit Comment( const WorksheetHelper& rHelper ); + + /** Imports a cell comment from the passed attributes of the comment element. */ + void importComment( const AttributeList& rAttribs ); + /** Imports a cell comment Properties from the passed attributes of the comment element. */ + void importCommentPr( const AttributeList& rAttribs ); + /** Imports a cell comment from the passed stream of a COMMENT record. */ + void importComment( SequenceInputStream& rStrm ); + + /** Creates and returns a new rich-string object for the comment text. */ + RichStringRef const & createText(); + + /** Finalizes the formatted string of the comment. */ + void finalizeImport(); + +private: + CommentModel maModel; +}; + +typedef std::shared_ptr< Comment > CommentRef; + +class CommentsBuffer final : public WorksheetHelper +{ +public: + explicit CommentsBuffer( const WorksheetHelper& rHelper ); + + /** Appends a new author to the list of comment authors. */ + void appendAuthor( const OUString& rAuthor ); + /** Creates and returns a new comment. */ + CommentRef createComment(); + + /** Finalizes the formatted string of all comments. */ + void finalizeImport(); + +private: + typedef RefVector< Comment > CommentVector; + + std::vector< OUString > maAuthors; + CommentVector maComments; +}; + +} // namespace oox::xls + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/commentsfragment.hxx b/sc/source/filter/inc/commentsfragment.hxx new file mode 100644 index 000000000..4acf4b0d8 --- /dev/null +++ b/sc/source/filter/inc/commentsfragment.hxx @@ -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 . + */ + +#pragma once + +#include "commentsbuffer.hxx" +#include "excelhandlers.hxx" + +namespace oox::xls { + +class CommentsFragment final : public WorksheetFragmentBase +{ +public: + explicit CommentsFragment( + const WorksheetHelper& rHelper, + const OUString& rFragmentPath ); +private: + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onCharacters( const OUString& rChars ) override; + virtual void onEndElement() override; + + virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + virtual void onEndRecord() override; + + virtual const ::oox::core::RecordInfo* getRecordInfos() const override; + + /** Imports comment data from the comment element. */ + void importComment( const AttributeList& rAttribs ); + /** Imports comment data from the COMMENT record. */ + void importComment( SequenceInputStream& rStrm ); + + CommentRef mxComment; +}; + +} // namespace oox::xls + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/condformatbuffer.hxx b/sc/source/filter/inc/condformatbuffer.hxx new file mode 100644 index 000000000..92c7463a5 --- /dev/null +++ b/sc/source/filter/inc/condformatbuffer.hxx @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "worksheethelper.hxx" +#include +#include +#include +#include + +#include +#include + +class ScColorScaleFormat; +class ScDataBarFormat; +struct ScDataBarFormatData; +class ScIconSetFormat; +struct ScIconSetFormatData; + +namespace oox { class AttributeList; } + +namespace oox::xls { + +class CondFormat; + +/** Model for a single rule in a conditional formatting. */ +struct CondFormatRuleModel +{ + typedef ::std::vector< ApiTokenSequence > ApiTokenSequenceVector; + + ApiTokenSequenceVector maFormulas; /// Formulas for rule conditions. + OUString maText; /// Text for 'contains' rules. + sal_Int32 mnPriority; /// Priority of this rule. + sal_Int32 mnType; /// Type of the rule. + sal_Int32 mnOperator; /// In cell-is rules: Comparison operator. + sal_Int32 mnTimePeriod; /// In time-period rules: Type of time period. + sal_Int32 mnRank; /// In top-10 rules: True = bottom, false = top. + sal_Int32 mnStdDev; /// In average rules: Number of std deviations. + sal_Int32 mnDxfId; /// Differential formatting identifier. + bool mbStopIfTrue; /// True = stop evaluating rules, if this rule is true. + bool mbBottom; /// In top-10 rules: True = bottom, false = top. + bool mbPercent; /// In top-10 rules: True = percent, false = rank. + bool mbAboveAverage; /// In average rules: True = above average, false = below. + bool mbEqualAverage; /// In average rules: True = include average, false = exclude. + + explicit CondFormatRuleModel(); + + /** Sets the passed BIFF operator for condition type cellIs. */ + void setBiffOperator( sal_Int32 nOperator ); + + /** Sets the passed BIFF12 text comparison type and operator. */ + void setBiff12TextType( sal_Int32 nOperator ); +}; + +struct ColorScaleRuleModelEntry +{ + ::Color maColor; + double mnVal; + + bool mbMin; + bool mbMax; + bool mbPercent; + bool mbPercentile; + bool mbNum; + OUString maFormula; + + ColorScaleRuleModelEntry(): + maColor(), + mnVal(0), + mbMin(false), + mbMax(false), + mbPercent(false), + mbPercentile(false), + mbNum(false) {} +}; + +class ColorScaleRule final : public WorksheetHelper +{ +public: + ColorScaleRule( const CondFormat& rFormat ); + + void importCfvo( const AttributeList& rAttribs ); + void importColor( const AttributeList& rAttribs ); + + void AddEntries( ScColorScaleFormat* pFormat, ScDocument* pDoc, const ScAddress& rAddr ); + +private: + std::vector< ColorScaleRuleModelEntry > maColorScaleRuleEntries; + + sal_uInt32 mnCfvo; + sal_uInt32 mnCol; +}; + +class DataBarRule final : public WorksheetHelper +{ +public: + DataBarRule( const CondFormat& rFormat ); + void importCfvo( const AttributeList& rAttribs ); + void importColor( const AttributeList& rAttribs ); + void importAttribs( const AttributeList& rAttribs ); + + void SetData( ScDataBarFormat* pFormat, ScDocument* pDoc, const ScAddress& rAddr ); + + ScDataBarFormatData* getDataBarFormatData() { return mxFormat.get(); } + +private: + std::unique_ptr mxFormat; + + std::unique_ptr mpUpperLimit; + std::unique_ptr mpLowerLimit; +}; + +class IconSetRule final : public WorksheetHelper +{ +public: + IconSetRule( const WorksheetHelper& rParent ); + void importCfvo( const AttributeList& rAttribs ); + void importAttribs( const AttributeList& rAttribs ); + void importFormula(const OUString& rFormula); + void importIcon(const AttributeList& rAttribs); + + void SetData( ScIconSetFormat* pFormat, ScDocument* pDoc, const ScAddress& rAddr ); + +private: + std::vector< ColorScaleRuleModelEntry > maEntries; + std::unique_ptr mxFormatData; + OUString maIconSetType; + bool mbCustom; +}; + +/** Represents a single rule in a conditional formatting. */ +class CondFormatRule final : public WorksheetHelper +{ +public: + explicit CondFormatRule( const CondFormat& rCondFormat, ScConditionalFormat* pFormat ); + + /** Imports rule settings from the cfRule element. */ + void importCfRule( const AttributeList& rAttribs ); + /** Appends a new condition formula string. */ + void appendFormula( const OUString& rFormula ); + + /** Imports rule settings from a CFRULE record. */ + void importCfRule( SequenceInputStream& rStrm ); + + /** Directly set a ScFormatEntry with a priority ready for finalizeImport(). */ + void setFormatEntry(sal_Int32 nPriority, ScFormatEntry* pEntry); + + /** Creates a conditional formatting rule in the Calc document. */ + void finalizeImport(); + + /** Returns the priority of this rule. */ + sal_Int32 getPriority() const { return maModel.mnPriority; } + + ColorScaleRule* getColorScale(); + DataBarRule* getDataBar(); + IconSetRule* getIconSet(); + +private: + const CondFormat& mrCondFormat; + CondFormatRuleModel maModel; + ScConditionalFormat* mpFormat; + ScFormatEntry* mpFormatEntry; + std::unique_ptr mpColor; + std::unique_ptr mpDataBar; + std::unique_ptr mpIconSet; +}; + +typedef std::shared_ptr< CondFormatRule > CondFormatRuleRef; + +/** Model for a conditional formatting object. */ +struct CondFormatModel +{ + ScRangeList maRanges; /// Cell ranges for this conditional format. + bool mbPivot; /// Conditional formatting belongs to pivot table. + + explicit CondFormatModel(); +}; + +class CondFormatBuffer; + +/** Represents a conditional formatting object with a list of affected cell ranges. */ +class CondFormat final : public WorksheetHelper +{ +friend class CondFormatBuffer; +public: + explicit CondFormat( const WorksheetHelper& rHelper ); + + /** Imports settings from the conditionalFormatting element. */ + void importConditionalFormatting( const AttributeList& rAttribs ); + /** Imports a conditional formatting rule from the cfRule element. */ + CondFormatRuleRef importCfRule( const AttributeList& rAttribs ); + + /** Imports settings from the CONDFORMATTING record. */ + void importCondFormatting( SequenceInputStream& rStrm ); + /** Imports a conditional formatting rule from the CFRULE record. */ + void importCfRule( SequenceInputStream& rStrm ); + + /** Creates the conditional formatting in the Calc document. */ + void finalizeImport(); + + /** Returns the cell ranges this conditional formatting belongs to. */ + const ScRangeList& getRanges() const { return maModel.maRanges; } + + void setReadyForFinalize() { mbReadyForFinalize = true; } +private: + CondFormatRuleRef createRule(); + void insertRule( CondFormatRuleRef const & xRule ); + +private: + typedef RefMap< sal_Int32, CondFormatRule > CondFormatRuleMap; + + CondFormatModel maModel; /// Model of this conditional formatting. + CondFormatRuleMap maRules; /// Maps formatting rules by priority. + ScConditionalFormat* mpFormat; + bool mbReadyForFinalize; +}; + +struct ExCfRuleModel +{ + ExCfRuleModel() : mnAxisColor( ColorTransparency, UNSIGNED_RGB_TRANSPARENT ), mnNegativeColor( ColorTransparency, UNSIGNED_RGB_TRANSPARENT ), mbGradient( false ), mbIsLower( true ) {} + // AxisColor + ::Color mnAxisColor; + // NegativeFillColor + ::Color mnNegativeColor; + OUString maAxisPosition; // DataBar + OUString maColorScaleType; // Cfvo + bool mbGradient; // DataBar + bool mbIsLower; // Cfvo +}; + +class ExtCfDataBarRule : public WorksheetHelper +{ + enum RuleType + { + DATABAR, + NEGATIVEFILLCOLOR, + AXISCOLOR, + CFVO, + UNKNOWN, + }; + ExCfRuleModel maModel; + RuleType mnRuleType; + ScDataBarFormatData* mpTarget; +public: + + ExtCfDataBarRule(ScDataBarFormatData* pTarget, const WorksheetHelper& rParent); + void finalizeImport(); + void importDataBar( const AttributeList& rAttribs ); + void importNegativeFillColor( const AttributeList& rAttribs ); + void importAxisColor( const AttributeList& rAttribs ); + void importCfvo( const AttributeList& rAttribs ); + ExCfRuleModel& getModel() { return maModel; } +}; + +class ExtCfCondFormat +{ +public: + ExtCfCondFormat(const ScRangeList& aRange, std::vector< std::unique_ptr >& rEntries, + const std::vector* pPriorities = nullptr); + ~ExtCfCondFormat(); + + const ScRangeList& getRange() const; + const std::vector< std::unique_ptr >& getEntries() const; + const std::vector& getPriorities() const { return maPriorities; } + +private: + std::vector< std::unique_ptr > maEntries; + std::vector maPriorities; + ScRangeList maRange; +}; + +typedef std::shared_ptr< CondFormat > CondFormatRef; +typedef std::shared_ptr< ExtCfDataBarRule > ExtCfDataBarRuleRef; + +class CondFormatBuffer final : public WorksheetHelper +{ +public: + explicit CondFormatBuffer( const WorksheetHelper& rHelper ); + + /** Imports settings from the conditionalFormatting element. */ + CondFormatRef importConditionalFormatting( const AttributeList& rAttribs ); + /** Imports settings from the CONDFORMATTING record. */ + CondFormatRef importCondFormatting( SequenceInputStream& rStrm ); + ExtCfDataBarRuleRef createExtCfDataBarRule(ScDataBarFormatData* pTarget); + std::vector< std::unique_ptr >& importExtCondFormat(); + + /** Converts an OOXML condition operator token to the API constant. */ + static sal_Int32 convertToApiOperator( sal_Int32 nToken ); + static ScConditionMode convertToInternalOperator( sal_Int32 nToken ); + void finalizeImport(); +private: + CondFormatRef createCondFormat(); + +private: + typedef RefVector< CondFormat > CondFormatVec; + typedef RefVector< ExtCfDataBarRule > ExtCfDataBarRuleVec; + CondFormatVec maCondFormats; /// All conditional formatting in a sheet. + ExtCfDataBarRuleVec maCfRules; /// All external conditional formatting rules in a sheet. + std::vector< std::unique_ptr > maExtCondFormats; + sal_Int32 mnNonPrioritizedRuleNextPriority = 1048576; +}; + +} // namespace oox::xls + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/condformatcontext.hxx b/sc/source/filter/inc/condformatcontext.hxx new file mode 100644 index 000000000..4da08a5bb --- /dev/null +++ b/sc/source/filter/inc/condformatcontext.hxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 "condformatbuffer.hxx" +#include "excelhandlers.hxx" + +namespace oox::xls { + +class CondFormatContext; + +class ColorScaleContext final : public WorksheetContextBase +{ +public: + explicit ColorScaleContext( CondFormatContext& rFragment, CondFormatRuleRef const & xRule ); + + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + +private: + CondFormatRuleRef mxRule; +}; + +class DataBarContext final : public WorksheetContextBase +{ +public: + explicit DataBarContext( CondFormatContext& rFormat, CondFormatRuleRef const & xRule ); + + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + +private: + CondFormatRuleRef mxRule; +}; + +class IconSetContext final : public WorksheetContextBase +{ +public: + explicit IconSetContext( WorksheetContextBase& rParent, IconSetRule* pIconSet ); + + virtual oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + virtual void onCharacters(const OUString& rChars) override; + virtual void onEndElement() override; + +private: + IconSetRule* mpIconSet; + OUString maChars; +}; + +class CondFormatContext final : public WorksheetContextBase +{ +public: + explicit CondFormatContext( WorksheetFragmentBase& rFragment ); + +private: + virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override; + virtual void onStartElement( const AttributeList& rAttribs ) override; + virtual void onCharacters( const OUString& rChars ) override; + virtual void onEndElement() override; + + virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override; + virtual void onStartRecord( SequenceInputStream& rStrm ) override; + virtual void onEndRecord() override; + + CondFormatRef mxCondFmt; + CondFormatRuleRef mxRule; +}; + +} // namespace oox::xls + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/filter/inc/connectionsbuffer.hxx b/sc/source/filter/inc/connectionsbuffer.hxx new file mode 100644 index 000000000..9308da5a3 --- /dev/null +++ b/sc/source/filter/inc/connectionsbuffer.hxx @@ -0,0 +1,160 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this 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 +#include +#include "workbookhelper.hxx" + +namespace oox { class AttributeList; } +namespace oox { class SequenceInputStream; } + +namespace oox::xls { + +const sal_Int32 BIFF12_CONNECTION_UNKNOWN = 0; +const sal_Int32 BIFF12_CONNECTION_ODBC = 1; +const sal_Int32 BIFF12_CONNECTION_DAO = 2; +const sal_Int32 BIFF12_CONNECTION_FILE = 3; +const sal_Int32 BIFF12_CONNECTION_HTML = 4; +const sal_Int32 BIFF12_CONNECTION_OLEDB = 5; +const sal_Int32 BIFF12_CONNECTION_TEXT = 6; +const sal_Int32 BIFF12_CONNECTION_ADO = 7; +const sal_Int32 BIFF12_CONNECTION_DSP = 8; + +/** Special properties for data connections representing web queries. */ +struct WebPrModel +{ + typedef ::std::vector< css::uno::Any > TablesVector; + + TablesVector maTables; /// Names or indexes of the web query tables. + OUString maUrl; /// Source URL to refresh the data. + OUString maPostMethod; /// POST method to query data. + OUString maEditPage; /// Web page showing query data (for XML queries). + sal_Int32 mnHtmlFormat; /// Plain text, rich text, or HTML. + bool mbXml; /// True = XML query, false = HTML query. + bool mbSourceData; /// True = import XML source data referred by HTML table. + bool mbParsePre; /// True = parse preformatted sections (
 tag).
+    bool                mbConsecutive;      /// True = join consecutive delimiters.
+    bool                mbFirstRow;         /// True = use column widths of first row for entire 
 tag.
+    bool                mbXl97Created;      /// True = web query created with Excel 97.
+    bool                mbTextDates;        /// True = read date values as text, false = parse dates.
+    bool                mbXl2000Refreshed;  /// True = refreshed with Excel 2000 or newer.
+    bool                mbHtmlTables;       /// True = HTML tables, false = entire document.
+
+    explicit            WebPrModel();
+};
+
+/** Common properties of an external data connection. */
+struct ConnectionModel
+{
+    typedef ::std::unique_ptr< WebPrModel > WebPrModelPtr;
+
+    WebPrModelPtr       mxWebPr;            /// Special settings for web queries.
+    OUString     maName;             /// Unique name of this connection.
+    OUString     maDescription;      /// User description of this connection.
+    OUString     maSourceFile;       /// URL of a source data file.
+    OUString     maSourceConnFile;   /// URL of a source connection file.
+    OUString     maSsoId;            /// Single sign-on identifier.
+    sal_Int32           mnId;               /// Unique connection identifier.
+    sal_Int32           mnType;             /// Data source type.
+    sal_Int32           mnReconnectMethod;  /// Reconnection method.
+    sal_Int32           mnCredentials;      /// Credentials method.
+    sal_Int32           mnInterval;         /// Refresh interval in minutes.
+    bool                mbKeepAlive;        /// True = keep connection open after import.
+    bool                mbNew;              /// True = new connection, never updated.
+    bool                mbDeleted;          /// True = connection has been deleted.
+    bool                mbOnlyUseConnFile;  /// True = use maSourceConnFile, ignore mnReconnectMethod.
+    bool                mbBackground;       /// True = background refresh enabled.
+    bool                mbRefreshOnLoad;    /// True = refresh connection on import.
+    bool                mbSaveData;         /// True = save cached data with connection.
+    bool                mbSavePassword;     /// True = save password in connection string.
+
+    explicit            ConnectionModel();
+
+    WebPrModel&         createWebPr();
+};
+
+/** An external data connection (database, web query, etc.). */
+class Connection final : public WorkbookHelper
+{
+public:
+    explicit            Connection( const WorkbookHelper& rHelper );
+
+    /** Imports connection settings from the connection element. */
+    void                importConnection( const AttributeList& rAttribs );
+    /** Imports web query settings from the webPr element. */
+    void                importWebPr( const AttributeList& rAttribs );
+    /** Imports web query table settings from the tables element. */
+    void                importTables();
+    /** Imports a web query table identifier from the m, s, or x element. */
+    void                importTable( const AttributeList& rAttribs, sal_Int32 nElement );
+
+    /** Imports connection settings from the CONNECTION record. */
+    void                importConnection( SequenceInputStream& rStrm );
+    /** Imports web query settings from the WEBPR record. */
+    void                importWebPr( SequenceInputStream& rStrm );
+    /** Imports web query table settings from the WEBPRTABLES record. */
+    void                importWebPrTables( SequenceInputStream& rStrm );
+    /** Imports a web query table identifier from the PCITEM_MISSING, PCITEM_STRING, or PCITEM_INDEX record. */
+    void                importWebPrTable( SequenceInputStream& rStrm, sal_Int32 nRecId );
+
+    /** Returns the unique connection identifier. */
+    sal_Int32    getConnectionId() const { return maModel.mnId; }
+    /** Returns the source data type of the connection. */
+    sal_Int32    getConnectionType() const { return maModel.mnType; }
+    /** Returns read-only access to the connection model data. */
+    const ConnectionModel& getModel() const { return maModel; }
+
+private:
+    ConnectionModel     maModel;
+};
+
+typedef std::shared_ptr< Connection > ConnectionRef;
+
+class ConnectionsBuffer final : public WorkbookHelper
+{
+public:
+    explicit            ConnectionsBuffer( const WorkbookHelper& rHelper );
+
+    /** Creates a new empty connection. */
+    Connection&         createConnection();
+
+    /** Maps all connections by their identifier. */
+    void                finalizeImport();
+
+    /** Returns a data connection by its unique identifier. */
+    ConnectionRef       getConnection( sal_Int32 nConnId ) const;
+
+private:
+    /** Inserts the passed connection into the map according to its identifier. */
+    void                insertConnectionToMap( const ConnectionRef& rxConnection );
+
+private:
+    typedef RefVector< Connection >         ConnectionVector;
+    typedef RefMap< sal_Int32, Connection > ConnectionMap;
+
+    ConnectionVector    maConnections;
+    ConnectionMap       maConnectionsById;
+    sal_Int32           mnUnusedId;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/connectionsfragment.hxx b/sc/source/filter/inc/connectionsfragment.hxx
new file mode 100644
index 000000000..8eaec1c41
--- /dev/null
+++ b/sc/source/filter/inc/connectionsfragment.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include "excelhandlers.hxx"
+
+namespace oox::xls {
+
+class Connection;
+
+class ConnectionContext final : public WorkbookContextBase
+{
+public:
+    explicit            ConnectionContext( WorkbookFragmentBase& rParent, Connection& rConnection );
+
+private:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void        onStartElement( const AttributeList& rAttribs ) override;
+
+    virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override;
+    virtual void        onStartRecord( SequenceInputStream& rStrm ) override;
+
+    Connection&         mrConnection;
+};
+
+class ConnectionsFragment final : public WorkbookFragmentBase
+{
+public:
+    explicit            ConnectionsFragment(
+                            const WorkbookHelper& rHelper,
+                            const OUString& rFragmentPath );
+
+private:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override;
+
+    virtual const ::oox::core::RecordInfo* getRecordInfos() const override;
+    virtual void        finalizeImport() override;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/decl.h b/sc/source/filter/inc/decl.h
new file mode 100644
index 000000000..0cfbc239c
--- /dev/null
+++ b/sc/source/filter/inc/decl.h
@@ -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 .
+ */
+
+
+#pragma once
+
+enum WKTYP { eWK_UNKNOWN = -2, eWK_1 = 0, eWK_2, eWK3, eWK4, eWK_Error, eWK123 };
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/defnamesbuffer.hxx b/sc/source/filter/inc/defnamesbuffer.hxx
new file mode 100644
index 000000000..ade123682
--- /dev/null
+++ b/sc/source/filter/inc/defnamesbuffer.hxx
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 "workbookhelper.hxx"
+#include 
+#include 
+
+#include 
+
+class ScTokenArray;
+
+namespace oox { class AttributeList; }
+namespace oox { class SequenceInputStream; }
+
+namespace oox::xls {
+
+// codes for built-in names
+const sal_Unicode BIFF_DEFNAME_CONSOLIDATEAREA  = '\x00';
+const sal_Unicode BIFF_DEFNAME_AUTOOPEN         = '\x01';   // Sheet macro executed when workbook is opened.
+const sal_Unicode BIFF_DEFNAME_AUTOCLOSE        = '\x02';   // Sheet macro executed when workbook is closed.
+const sal_Unicode BIFF_DEFNAME_EXTRACT          = '\x03';   // Filter output destination for advanced filter.
+const sal_Unicode BIFF_DEFNAME_DATABASE         = '\x04';
+const sal_Unicode BIFF_DEFNAME_CRITERIA         = '\x05';   // Filter criteria source range for advanced filter.
+const sal_Unicode BIFF_DEFNAME_PRINTAREA        = '\x06';   // Print ranges.
+const sal_Unicode BIFF_DEFNAME_PRINTTITLES      = '\x07';   // Rows/columns repeated on each page when printing.
+const sal_Unicode BIFF_DEFNAME_RECORDER         = '\x08';
+const sal_Unicode BIFF_DEFNAME_DATAFORM         = '\x09';
+const sal_Unicode BIFF_DEFNAME_AUTOACTIVATE     = '\x0A';   // Sheet macro executed when workbook is activated.
+const sal_Unicode BIFF_DEFNAME_AUTODEACTIVATE   = '\x0B';   // Sheet macro executed when workbook is deactivated.
+const sal_Unicode BIFF_DEFNAME_SHEETTITLE       = '\x0C';
+const sal_Unicode BIFF_DEFNAME_FILTERDATABASE   = '\x0D';   // Sheet range autofilter or advanced filter works on.
+const sal_Unicode BIFF_DEFNAME_UNKNOWN          = '\x0E';
+
+struct DefinedNameModel
+{
+    OUString     maName;         /// The original name.
+    OUString     maFormula;      /// The formula string.
+    sal_Int32           mnSheet;        /// Sheet index for local names.
+    sal_Int32           mnFuncGroupId;  /// Function group identifier.
+    bool                mbMacro;        /// True = Macro name (VBA or sheet macro).
+    bool                mbFunction;     /// True = function, false = command.
+    bool                mbVBName;       /// True = VBA macro, false = sheet macro.
+    bool                mbHidden;       /// True = name hidden in UI.
+
+    explicit            DefinedNameModel();
+};
+
+/** Base class for defined names and external names. */
+class DefinedNameBase : public WorkbookHelper
+{
+public:
+    explicit            DefinedNameBase( const WorkbookHelper& rHelper );
+
+    /** Returns the original name as imported from or exported to the file. */
+    const OUString& getModelName() const { return maModel.maName; }
+    /** Returns the name as used in the Calc document. */
+    const OUString& getCalcName() const { return maCalcName; }
+
+    /** Returns the original name as imported from or exported to the file. */
+    const OUString& getUpcaseModelName() const;
+
+protected:
+    DefinedNameModel    maModel;        /// Model data for this defined name.
+    mutable OUString    maUpModelName;  /// Model name converted to uppercase ASCII.
+    OUString            maCalcName;     /// Final name used in the Calc document.
+};
+
+class DefinedName final : public DefinedNameBase
+{
+public:
+    explicit            DefinedName( const WorkbookHelper& rHelper );
+    virtual ~DefinedName() override;
+
+    /** Sets the attributes for this defined name from the passed attribute set. */
+    void                importDefinedName( const AttributeList& rAttribs );
+    /** Sets the formula string from the body of the definedName element. */
+    void                setFormula( const OUString& rFormula );
+    /** Imports the defined name from a DEFINEDNAME record in the passed stream. */
+    void                importDefinedName( SequenceInputStream& rStrm );
+
+    /** Creates a defined name in the Calc document. */
+    void                createNameObject( sal_Int32 nIndex );
+    /** Converts the formula string or BIFF token array for this defined name. */
+    void                convertFormula( const css::uno::Sequence& rExternalLinks );
+    std::unique_ptr getScTokens( const css::uno::Sequence& rExternalLinks );
+    /** Returns true, if this defined name is global in the document. */
+    bool         isGlobalName() const { return mnCalcSheet < 0; }
+    /** Returns true, if this defined name is a special builtin name. */
+    bool         isBuiltinName() const { return mcBuiltinId != BIFF_DEFNAME_UNKNOWN; }
+    /** Returns true, if this defined name is a macro function call. */
+    bool         isMacroFunction() const { return maModel.mbMacro && maModel.mbFunction; }
+    /** Returns true, if this defined name is a reference to a VBA macro. */
+    bool         isVBName() const { return maModel.mbMacro && maModel.mbVBName; }
+
+    /** Returns the 0-based sheet index for local names, or -1 for global names. */
+    sal_Int16    getLocalCalcSheet() const { return mnCalcSheet; }
+    /** Returns the built-in identifier of the defined name. */
+    sal_Unicode  getBuiltinId() const { return mcBuiltinId; }
+    /** Returns the token index used in API token arrays (com.sun.star.sheet.FormulaToken). */
+    sal_Int32    getTokenIndex() const { return mnTokenIndex; }
+    /** Tries to resolve the defined name to an absolute cell range. */
+    bool                getAbsoluteRange( ScRange& orRange ) const;
+    bool isValid(const css::uno::Sequence& rExternalLinks) const;
+
+private:
+    typedef ::std::unique_ptr< StreamDataSequence >   StreamDataSeqPtr;
+
+    RangeDataRet        maScRangeData;      /// ScRangeData of the defined name.
+    sal_Int32           mnTokenIndex;       /// Name index used in API token array.
+    sal_Int16           mnCalcSheet;        /// Calc sheet index for sheet-local names.
+    sal_Unicode         mcBuiltinId;        /// Identifier for built-in defined names.
+    StreamDataSeqPtr    mxFormula;          /// Formula data for BIFF12 import.
+};
+
+typedef std::shared_ptr< DefinedName > DefinedNameRef;
+
+class DefinedNamesBuffer final : public WorkbookHelper
+{
+public:
+    explicit            DefinedNamesBuffer( const WorkbookHelper& rHelper );
+
+    /** Imports a defined name from the passed attribute set. */
+    DefinedNameRef      importDefinedName( const AttributeList& rAttribs );
+    /** Imports a defined name from a DEFINEDNAME record in the passed stream. */
+    void                importDefinedName( SequenceInputStream& rStrm );
+
+    /** Creates all defined names in the document. */
+    void                finalizeImport();
+
+    /** Returns a defined name by zero-based index (order of appearance). */
+    DefinedNameRef      getByIndex( sal_Int32 nIndex ) const;
+    /** Returns a defined name by token index (index in XDefinedNames container). */
+    DefinedNameRef      getByTokenIndex( sal_Int32 nIndex ) const;
+    /** Returns a defined name by its model name.
+        @param nSheet  The sheet index for local names or -1 for global names.
+            If no local name is found, tries to find a matching global name.
+        @return  Reference to the defined name or empty reference. */
+    DefinedNameRef      getByModelName( const OUString& rModelName, sal_Int16 nCalcSheet = -1 ) const;
+    /** Returns a built-in defined name by its built-in identifier.
+        @param nSheet  The sheet index of the built-in name.
+        @return  Reference to the defined name or empty reference. */
+    DefinedNameRef      getByBuiltinId( sal_Unicode cBuiltinId, sal_Int16 nCalcSheet ) const;
+
+private:
+    DefinedNameRef      createDefinedName();
+
+private:
+    typedef ::std::pair< sal_Int16, OUString >   SheetNameKey;
+    typedef ::std::pair< sal_Int16, sal_Unicode >       BuiltinKey;
+
+    typedef RefVector< DefinedName >            DefNameVector;
+    typedef RefMap< SheetNameKey, DefinedName > DefNameNameMap;
+    typedef RefMap< BuiltinKey, DefinedName >   DefNameBuiltinMap;
+    typedef RefMap< sal_Int32, DefinedName >    DefNameTokenIdMap;
+
+    DefNameVector       maDefNames;         /// List of all defined names in insertion order.
+    DefNameNameMap      maModelNameMap;     /// Maps all defined names by sheet index and model name.
+    DefNameBuiltinMap   maBuiltinMap;       /// Maps all defined names by sheet index and built-in identifier.
+    DefNameTokenIdMap   maTokenIdMap;       /// Maps all defined names by API token index.
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/dif.hxx b/sc/source/filter/inc/dif.hxx
new file mode 100644
index 000000000..594327058
--- /dev/null
+++ b/sc/source/filter/inc/dif.hxx
@@ -0,0 +1,152 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+
+class SvStream;
+class SvNumberFormatter;
+class ScDocument;
+
+extern const std::u16string_view pKeyTABLE;
+extern const std::u16string_view pKeyVECTORS;
+extern const std::u16string_view pKeyTUPLES;
+extern const std::u16string_view pKeyDATA;
+extern const std::u16string_view pKeyBOT;
+extern const std::u16string_view pKeyEOD;
+
+enum TOPIC
+{
+    T_UNKNOWN,
+    T_TABLE, T_VECTORS, T_TUPLES, T_DATA, T_LABEL, T_COMMENT, T_SIZE,
+    T_PERIODICITY, T_MAJORSTART, T_MINORSTART, T_TRUELENGTH, T_UINITS,
+    T_DISPLAYUNITS,
+    T_END
+};
+
+enum DATASET { D_BOT, D_EOD, D_NUMERIC, D_STRING, D_UNKNOWN, D_SYNT_ERROR };
+
+class DifParser
+{
+public:
+    OUStringBuffer      m_aData;
+    double              fVal;
+    sal_uInt32          nVector;
+    sal_uInt32          nVal;
+    sal_uInt32          nNumFormat;
+private:
+    SvNumberFormatter*  pNumFormatter;
+    SvStream&           rIn;
+    OUString       aLookAheadLine;
+
+    bool                ReadNextLine( OUString& rStr );
+    bool                LookAhead();
+    DATASET             GetNumberDataset( const sal_Unicode* pPossibleNumericData );
+    static inline bool  IsBOT( const sal_Unicode* pRef );
+    static inline bool  IsEOD( const sal_Unicode* pRef );
+    static inline bool  Is1_0( const sal_Unicode* pRef );
+public:
+                        DifParser( SvStream&, const ScDocument&, rtl_TextEncoding );
+
+    TOPIC               GetNextTopic();
+
+    DATASET             GetNextDataset();
+
+    static const sal_Unicode* ScanIntVal( const sal_Unicode* pStart, sal_uInt32& rRet );
+
+    static inline bool  IsNumber( const sal_Unicode cChar );
+
+    static inline bool  IsV( const sal_Unicode* pRef );
+};
+
+inline bool DifParser::IsBOT( const sal_Unicode* pRef )
+{
+    return  pRef == pKeyBOT;
+}
+
+inline bool DifParser::IsEOD( const sal_Unicode* pRef )
+{
+    return  pRef == pKeyEOD;
+}
+
+inline bool DifParser::Is1_0( const sal_Unicode* pRef )
+{
+    return  pRef == std::u16string_view(u"1,0");
+}
+
+inline bool DifParser::IsV( const sal_Unicode* pRef )
+{
+    return  pRef == std::u16string_view(u"V");
+}
+
+inline bool DifParser::IsNumber( const sal_Unicode cChar )
+{
+    return ( cChar >= '0' && cChar <= '9' );
+}
+
+class DifColumn
+{
+    friend class DifAttrCache;
+
+    struct ENTRY
+    {
+        sal_uInt32 nNumFormat;
+        SCROW nStart;
+        SCROW nEnd;
+    };
+
+    ENTRY *mpCurrent;
+    std::vector maEntries;
+
+    DifColumn();
+
+    void SetNumFormat( const ScDocument* pDoc, SCROW nRow, const sal_uInt32 nNumFormat );
+
+    void NewEntry( const SCROW nPos, const sal_uInt32 nNumFormat );
+
+    void Apply( ScDocument &rDoc, const SCCOL nCol, const SCTAB nTab );
+};
+
+class DifAttrCache
+{
+public:
+
+    DifAttrCache();
+
+    ~DifAttrCache();
+
+    void SetNumFormat( const ScDocument* pDoc, const SCCOL nCol, const SCROW nRow, const sal_uInt32 nNumFormat );
+
+    void Apply( ScDocument&, SCTAB nTab );
+
+private:
+
+    std::map> maColMap;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/drawingbase.hxx b/sc/source/filter/inc/drawingbase.hxx
new file mode 100644
index 000000000..576a3619d
--- /dev/null
+++ b/sc/source/filter/inc/drawingbase.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include 
+#include "worksheethelper.hxx"
+
+namespace oox { class AttributeList; }
+
+namespace oox::xls {
+
+/** Absolute position in a spreadsheet (in EMUs) independent from cells. */
+struct AnchorPointModel : public ::oox::drawingml::EmuPoint
+{
+    explicit     AnchorPointModel() : ::oox::drawingml::EmuPoint( -1, -1 ) {}
+    bool         isValid() const { return (X >= 0) && (Y >= 0); }
+};
+
+/** Absolute size in a spreadsheet (in EMUs). */
+struct AnchorSizeModel : public ::oox::drawingml::EmuSize
+{
+    explicit     AnchorSizeModel() : ::oox::drawingml::EmuSize( -1, -1 ) {}
+    bool         isValid() const { return (Width >= 0) && (Height >= 0); }
+};
+
+/** Position in spreadsheet (cell position and offset inside cell). */
+struct CellAnchorModel
+{
+    sal_Int32           mnCol;              /// Column index.
+    sal_Int32           mnRow;              /// Row index.
+    sal_Int64           mnColOffset;        /// X offset inside the column.
+    sal_Int64           mnRowOffset;        /// Y offset inside the row.
+
+    explicit            CellAnchorModel();
+    bool         isValid() const { return (mnCol >= 0) && (mnRow >= 0); }
+};
+
+/** Application-specific client data of a shape. */
+struct AnchorClientDataModel
+{
+    bool                mbLocksWithSheet;
+    bool                mbPrintsWithSheet;
+
+    explicit            AnchorClientDataModel();
+};
+
+/** Contains the position of a shape in the spreadsheet. Supports different
+    shape anchor modes (absolute, one-cell, two-cell). */
+class ShapeAnchor final : public WorksheetHelper
+{
+public:
+    enum AnchorType
+    {
+        ANCHOR_INVALID,         /// Anchor type is unknown.
+        ANCHOR_ABSOLUTE,        /// Absolute anchor (top-left corner and size in absolute units).
+                                /// Matches our "Page" anchor -> ScAnchorType::SCA_PAGE
+        ANCHOR_ONECELL,         /// One-cell anchor (top-left corner at cell, size in absolute units).
+                                /// Matches our "Cell" anchor -> ScAnchorType::SCA_CELL
+        ANCHOR_TWOCELL,         /// Two-cell anchor (top-left and bottom-right corner at cell).
+                                /// Matches our "Cell (resize with cell)" anchor -> ScAnchorType::SCA_CELL_RESIZE
+        ANCHOR_VML
+    };
+    explicit            ShapeAnchor( const WorksheetHelper& rHelper );
+
+    /** Imports the shape anchor (one of the elements xdr:absoluteAnchor, xdr:oneCellAnchor, xdr:twoCellAnchor). */
+    void                importAnchor( sal_Int32 nElement, const AttributeList& rAttribs );
+    /** Imports the absolute anchor position from the xdr:pos element. */
+    void                importPos( const AttributeList& rAttribs );
+    /** Imports the absolute anchor size from the xdr:ext element. */
+    void                importExt( const AttributeList& rAttribs );
+    /** Imports the shape client data from the xdr:clientData element. */
+    void                importClientData( const AttributeList& rAttribs );
+    /** Sets an attribute of the cell-dependent anchor position from xdr:from and xdr:to elements. */
+    void                setCellPos( sal_Int32 nElement, sal_Int32 nParentContext, std::u16string_view rValue );
+    /** Imports the client anchor settings from a VML element. */
+    void                importVmlAnchor( std::u16string_view rAnchor );
+
+    /** Checks whether the shape is visible based on the anchor
+
+        If From and To anchor has the same attribute values, the shape
+        will not have width and height and thus we can assume that
+        such kind of shape will be not be visible
+    */
+    bool isAnchorValid() const;
+
+    /** Calculates the resulting shape anchor in EMUs. */
+    ::oox::drawingml::EmuRectangle calcAnchorRectEmu( const css::awt::Size& rPageSizeHmm ) const;
+    /** Calculates the resulting shape anchor in 1/100 mm. */
+    css::awt::Rectangle calcAnchorRectHmm( const css::awt::Size& rPageSizeHmm ) const;
+    AnchorType          getEditAs() const { return meEditAs; }
+private:
+    /** Converts the passed anchor to an absolute position in EMUs. */
+    ::oox::drawingml::EmuPoint calcCellAnchorEmu( const CellAnchorModel& rModel ) const;
+
+private:
+
+    /** Specifies how cell positions from CellAnchorModel have to be processed. */
+    enum class CellAnchorType
+    {
+        Emu,             /// Offsets are given in EMUs.
+        Pixel,           /// Offsets are given in screen pixels.
+    };
+
+    AnchorType          meAnchorType;       /// Type of this shape anchor.
+    CellAnchorType      meCellAnchorType;   /// Type of the cell anchor models.
+    AnchorPointModel    maPos;              /// Top-left position, if anchor is of type absolute.
+    AnchorSizeModel     maSize;             /// Anchor size, if anchor is not of type two-cell.
+    CellAnchorModel     maFrom;             /// Top-left position, if anchor is not of type absolute.
+    CellAnchorModel     maTo;               /// Bottom-right position, if anchor is of type two-cell.
+    AnchorClientDataModel maClientData;     /// Shape client data.
+    AnchorType          meEditAs;           /// Anchor mode as shown in the UI.
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/drawingfragment.hxx b/sc/source/filter/inc/drawingfragment.hxx
new file mode 100644
index 000000000..f93a13bc2
--- /dev/null
+++ b/sc/source/filter/inc/drawingfragment.hxx
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include "drawingbase.hxx"
+#include "excelhandlers.hxx"
+
+namespace oox::ole {
+    struct AxFontData;
+}
+
+namespace oox::xls {
+
+// DrawingML
+
+class ShapeMacroAttacher final : public ::oox::ole::VbaMacroAttacherBase
+{
+public:
+    explicit            ShapeMacroAttacher( const OUString& rMacroName,
+                            const css::uno::Reference< css::drawing::XShape >& rxShape );
+
+private:
+    virtual void        attachMacro( const OUString& rMacroUrl ) override;
+
+private:
+    css::uno::Reference< css::drawing::XShape > mxShape;
+};
+
+class Shape final : public ::oox::drawingml::Shape, public WorksheetHelper
+{
+public:
+    explicit            Shape(
+                            const WorksheetHelper& rHelper,
+                            const AttributeList& rAttribs,
+                            const char* pcServiceName );
+
+private:
+    virtual void        finalizeXShape(
+                            ::oox::core::XmlFilterBase& rFilter,
+                            const css::uno::Reference< css::drawing::XShapes >& rxShapes ) override;
+
+    OUString     maMacroName;
+};
+
+/** Context handler for creation of shapes embedded in group shapes. */
+class GroupShapeContext final : public ::oox::drawingml::ShapeGroupContext, public WorksheetHelper
+{
+public:
+    explicit            GroupShapeContext(
+                            const ::oox::core::FragmentHandler2& rParent,
+                            const WorksheetHelper& rHelper,
+                            const ::oox::drawingml::ShapePtr& rxParentShape,
+                            const ::oox::drawingml::ShapePtr& rxShape );
+
+    static ::oox::core::ContextHandlerRef
+                        createShapeContext(
+                            ::oox::core::FragmentHandler2& rParent,
+                            const WorksheetHelper& rHelper,
+                            sal_Int32 nElement,
+                            const AttributeList& rAttribs,
+                            const ::oox::drawingml::ShapePtr& rxParentShape,
+                            ::oox::drawingml::ShapePtr* pxShape = nullptr );
+
+private:
+    virtual ::oox::core::ContextHandlerRef
+                        onCreateContext(
+                            sal_Int32 nElement,
+                            const ::oox::AttributeList& rAttribs ) override;
+};
+
+/** Fragment handler for a complete sheet drawing. */
+class DrawingFragment final : public WorksheetFragmentBase
+{
+public:
+    explicit            DrawingFragment(
+                            const WorksheetHelper& rHelper,
+                            const OUString& rFragmentPath );
+
+private:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void        onCharacters( const OUString& rChars ) override;
+    virtual void        onEndElement() override;
+
+    typedef ::std::unique_ptr< ShapeAnchor > ShapeAnchorRef;
+
+    css::uno::Reference< css::drawing::XShapes >
+                        mxDrawPage;             /// Drawing page of this sheet.
+    ::oox::drawingml::ShapePtr mxShape;         /// Current top-level shape.
+    ShapeAnchorRef      mxAnchor;               /// Current anchor of top-level shape.
+};
+
+// VML
+
+class VmlControlMacroAttacher final : public ::oox::ole::VbaMacroAttacherBase
+{
+public:
+    explicit            VmlControlMacroAttacher( const OUString& rMacroName,
+                            const css::uno::Reference< css::container::XIndexContainer >& rxCtrlFormIC,
+                            sal_Int32 nCtrlIndex, sal_Int32 nCtrlType, sal_Int32 nDropStyle );
+
+private:
+    virtual void        attachMacro( const OUString& rMacroUrl ) override;
+
+private:
+    css::uno::Reference< css::container::XIndexContainer > mxCtrlFormIC;
+    sal_Int32           mnCtrlIndex;
+    sal_Int32           mnCtrlType;
+    sal_Int32           mnDropStyle;
+};
+
+class VmlDrawing final : public ::oox::vml::Drawing, public WorksheetHelper
+{
+public:
+    explicit            VmlDrawing( const WorksheetHelper& rHelper );
+
+    /** Returns the drawing shape for a cell note at the specified position. */
+    const ::oox::vml::ShapeBase* getNoteShape( const ScAddress& rPos ) const;
+
+    /** Filters cell note shapes. */
+    virtual bool        isShapeSupported( const ::oox::vml::ShapeBase& rShape ) const override;
+
+    /** Returns additional base names for automatic shape name creation. */
+    virtual OUString getShapeBaseName( const ::oox::vml::ShapeBase& rShape ) const override;
+
+    /** Calculates the shape rectangle from a cell anchor string. */
+    virtual bool        convertClientAnchor(
+                            css::awt::Rectangle& orShapeRect,
+                            const OUString& rShapeAnchor ) const override;
+
+    /** Creates a UNO control shape for legacy drawing controls. */
+    virtual css::uno::Reference< css::drawing::XShape >
+                        createAndInsertClientXShape(
+                            const ::oox::vml::ShapeBase& rShape,
+                            const css::uno::Reference< css::drawing::XShapes >& rxShapes,
+                            const css::awt::Rectangle& rShapeRect ) const override;
+
+    /** Updates the bounding box covering all shapes of this drawing. */
+    virtual void        notifyXShapeInserted(
+                            const css::uno::Reference< css::drawing::XShape >& rxShape,
+                            const css::awt::Rectangle& rShapeRect,
+                            const ::oox::vml::ShapeBase& rShape, bool bGroupChild ) override;
+
+private:
+    /** Converts the passed VML textbox text color to an OLE color. */
+    sal_uInt32          convertControlTextColor( const OUString& rTextColor ) const;
+    /** Converts the passed VML textbox font to an ActiveX form control font. */
+    void                convertControlFontData(
+                            ::oox::ole::AxFontData& rAxFontData, sal_uInt32& rnOleTextColor,
+                            const ::oox::vml::TextFontModel& rFontModel ) const;
+    /** Converts the caption, the font settings, and the horizontal alignment
+        from the passed VML textbox to ActiveX form control settings. */
+    void                convertControlText(
+                            ::oox::ole::AxFontData& rAxFontData, sal_uInt32& rnOleTextColor, OUString& rCaption,
+                            const ::oox::vml::TextBox* pTextBox, sal_Int32 nTextHAlign ) const;
+    /** Converts the passed VML shape background formatting to ActiveX control formatting. */
+    void                convertControlBackground(
+                            ::oox::ole::AxMorphDataModelBase& rAxModel,
+                            const ::oox::vml::ShapeBase& rShape ) const;
+
+private:
+    ::oox::ole::ControlConverter maControlConv;
+    ::oox::vml::TextFontModel maListBoxFont;
+};
+
+class VmlDrawingFragment final : public ::oox::vml::DrawingFragment, public WorksheetHelper
+{
+public:
+    explicit            VmlDrawingFragment(
+                            const WorksheetHelper& rHelper,
+                            const OUString& rFragmentPath );
+
+private:
+    virtual void        finalizeImport() override;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/eeimport.hxx b/sc/source/filter/inc/eeimport.hxx
new file mode 100644
index 000000000..d4ddc31f4
--- /dev/null
+++ b/sc/source/filter/inc/eeimport.hxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+
+class ScDocument;
+class ScEEParser;
+class ScTabEditEngine;
+class SvStream;
+
+struct ScEEParseEntry;
+
+typedef std::map RowHeightMap;
+
+class ScEEImport : public ScEEAbsImport
+{
+protected:
+    ScRange             maRange;
+    ScDocument*         mpDoc;
+    std::unique_ptr
+                        mpEngine;
+    std::unique_ptr        // needs mpEngine
+                        mpParser;      // must reset before mpEngine resets
+    RowHeightMap        maRowHeights;
+
+    bool                GraphicSize( SCCOL nCol, SCROW nRow,
+                                     ScEEParseEntry* );
+    void                InsertGraphic( SCCOL nCol, SCROW nRow, SCTAB nTab,
+                                       ScEEParseEntry* );
+public:
+    ScEEImport( ScDocument* pDoc, const ScRange& rRange );
+    virtual ~ScEEImport() override;
+
+    virtual ErrCode  Read( SvStream& rStream, const OUString& rBaseURL ) override;
+    virtual ScRange  GetRange() override { return maRange; }
+    virtual void     WriteToDocument( bool bSizeColsRows = false,
+                                      double nOutputFactor = 1.0,
+                                      SvNumberFormatter* pFormatter = nullptr,
+                                      bool bConvertDate = true ) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/eeparser.hxx b/sc/source/filter/inc/eeparser.hxx
new file mode 100644
index 000000000..190caa4ec
--- /dev/null
+++ b/sc/source/filter/inc/eeparser.hxx
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+const char nHorizontal = 1;
+const char nVertical = 2;
+
+struct ScHTMLImage
+{
+    OUString            aURL;
+    Size                aSize;
+    Point               aSpace;
+    OUString            aFilterName;
+    std::unique_ptr
+                        pGraphic;       // is taken over by WriteToDocument
+    char                nDir;           // 1==hori, 2==verti, 3==both
+
+    ScHTMLImage() :
+        aSize( 0, 0 ), aSpace( 0, 0 ), nDir( nHorizontal )
+        {}
+};
+
+struct ScEEParseEntry
+{
+    SfxItemSet          aItemSet;
+    ESelection          aSel;           // Selection in EditEngine
+    std::optional
+                        pValStr;        // HTML possibly SDVAL string
+    std::optional
+                        pNumStr;        // HTML possibly SDNUM string
+    std::optional
+                        pName;          // HTML possibly anchor/RangeName
+    OUString            aAltText;       // HTML IMG ALT Text
+    std::vector< std::unique_ptr > maImageList;       // graphics in this cell
+    SCCOL               nCol;           // relative to the beginning of the parse
+    SCROW               nRow;
+    sal_uInt16          nTab;           // HTML TableInTable
+    sal_uInt16          nTwips;         // RTF ColAdjust etc.
+    SCCOL               nColOverlap;    // merged cells if >1
+    SCROW               nRowOverlap;    // merged cells if >1
+    sal_uInt16          nOffset;        // HTML PixelOffset
+    sal_uInt16          nWidth;         // HTML PixelWidth
+    bool                bHasGraphic:1;  // HTML any image loaded
+    bool                bEntirePara:1;  // true = use entire paragraph, false = use selection
+
+    ScEEParseEntry( SfxItemPool* pPool ) :
+        aItemSet( *pPool ),
+        nCol(SCCOL_MAX), nRow(SCROW_MAX), nTab(0),
+        nTwips(0), nColOverlap(1), nRowOverlap(1),
+        nOffset(0), nWidth(0), bHasGraphic(false), bEntirePara(true)
+        {}
+
+    ScEEParseEntry( const SfxItemSet& rItemSet ) :
+        aItemSet( rItemSet ),
+        nCol(SCCOL_MAX), nRow(SCROW_MAX), nTab(0),
+        nTwips(0), nColOverlap(1), nRowOverlap(1),
+        nOffset(0), nWidth(0), bHasGraphic(false), bEntirePara(true)
+        {}
+
+    ~ScEEParseEntry()
+    {
+        maImageList.clear();
+    }
+};
+
+class EditEngine;
+
+typedef std::map ColWidthsMap;
+
+class ScEEParser
+{
+protected:
+    EditEngine*         pEdit;
+    rtl::Reference  pPool;
+    rtl::Reference  pDocPool;
+    std::vector> maList;
+    std::shared_ptr mxActEntry;
+    ColWidthsMap        maColWidths;
+    int                 nRtfLastToken;
+    SCCOL               nColCnt;
+    SCROW               nRowCnt;
+    SCCOL               nColMax;
+    SCROW               nRowMax;
+
+    void                NewActEntry( const ScEEParseEntry* );
+
+public:
+                        ScEEParser( EditEngine* );
+    virtual             ~ScEEParser();
+
+    virtual ErrCode         Read( SvStream&, const OUString& rBaseURL ) = 0;
+
+    const ColWidthsMap&     GetColWidths() const { return maColWidths; }
+    ColWidthsMap&           GetColWidths() { return maColWidths; }
+    void                    GetDimensions( SCCOL& nCols, SCROW& nRows ) const
+                                { nCols = nColMax; nRows = nRowMax; }
+
+    size_t                  ListSize() const{ return maList.size(); }
+    ScEEParseEntry*         ListEntry( size_t index ) { return maList[index].get(); }
+    const ScEEParseEntry*   ListEntry( size_t index ) const { return maList[index].get(); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excdefs.hxx b/sc/source/filter/inc/excdefs.hxx
new file mode 100644
index 000000000..149993e1e
--- /dev/null
+++ b/sc/source/filter/inc/excdefs.hxx
@@ -0,0 +1,93 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+
+// (0x009B, 0x009D, 0x009E) AUTOFILTER ========================================
+
+// flags
+const sal_uInt16 EXC_AFFLAG_AND             = 0x0000;
+const sal_uInt16 EXC_AFFLAG_OR              = 0x0001;
+const sal_uInt16 EXC_AFFLAG_ANDORMASK       = 0x0003;
+const sal_uInt16 EXC_AFFLAG_SIMPLE1         = 0x0004;
+const sal_uInt16 EXC_AFFLAG_SIMPLE2         = 0x0008;
+const sal_uInt16 EXC_AFFLAG_TOP10           = 0x0010;
+const sal_uInt16 EXC_AFFLAG_TOP10TOP        = 0x0020;
+const sal_uInt16 EXC_AFFLAG_TOP10PERC       = 0x0040;
+
+// data types
+const sal_uInt8 EXC_AFTYPE_NOTUSED          = 0x00;
+const sal_uInt8 EXC_AFTYPE_RK               = 0x02;
+const sal_uInt8 EXC_AFTYPE_DOUBLE           = 0x04;
+const sal_uInt8 EXC_AFTYPE_STRING           = 0x06;
+const sal_uInt8 EXC_AFTYPE_BOOLERR          = 0x08;
+const sal_uInt8 EXC_AFTYPE_INVALID          = 0x0A;
+const sal_uInt8 EXC_AFTYPE_EMPTY            = 0x0C;
+const sal_uInt8 EXC_AFTYPE_NOTEMPTY         = 0x0E;
+
+// comparison operands
+const sal_uInt8 EXC_AFOPER_NONE             = 0x00;
+const sal_uInt8 EXC_AFOPER_LESS             = 0x01;
+const sal_uInt8 EXC_AFOPER_EQUAL            = 0x02;
+const sal_uInt8 EXC_AFOPER_LESSEQUAL        = 0x03;
+const sal_uInt8 EXC_AFOPER_GREATER          = 0x04;
+const sal_uInt8 EXC_AFOPER_NOTEQUAL         = 0x05;
+const sal_uInt8 EXC_AFOPER_GREATEREQUAL     = 0x06;
+
+// (0x00AE, 0x00AF) SCENARIO, SCENMAN =========================================
+
+#define EXC_SCEN_MAXCELL            32
+
+// defines for change tracking ================================================
+
+inline constexpr OUStringLiteral EXC_STREAM_USERNAMES = u"User Names";
+inline constexpr OUStringLiteral EXC_STREAM_REVLOG = u"Revision Log";
+
+// opcodes
+#define EXC_CHTR_OP_COLFLAG         0x0001
+#define EXC_CHTR_OP_DELFLAG         0x0002
+#define EXC_CHTR_OP_INSROW          0x0000
+#define EXC_CHTR_OP_INSCOL          EXC_CHTR_OP_COLFLAG
+#define EXC_CHTR_OP_DELROW          EXC_CHTR_OP_DELFLAG
+#define EXC_CHTR_OP_DELCOL          (EXC_CHTR_OP_COLFLAG|EXC_CHTR_OP_DELFLAG)
+#define EXC_CHTR_OP_MOVE            0x0004
+#define EXC_CHTR_OP_INSTAB          0x0005
+#define EXC_CHTR_OP_CELL            0x0008
+#define EXC_CHTR_OP_FORMAT          0x000B
+#define EXC_CHTR_OP_UNKNOWN         0xFFFF
+
+// data types
+#define EXC_CHTR_TYPE_MASK          0x0007
+#define EXC_CHTR_TYPE_FORMATMASK    0xFF00
+#define EXC_CHTR_TYPE_EMPTY         0x0000
+#define EXC_CHTR_TYPE_RK            0x0001
+#define EXC_CHTR_TYPE_DOUBLE        0x0002
+#define EXC_CHTR_TYPE_STRING        0x0003
+#define EXC_CHTR_TYPE_BOOL          0x0004
+#define EXC_CHTR_TYPE_FORMULA       0x0005
+
+// accept flags
+#define EXC_CHTR_NOTHING            0x0000
+#define EXC_CHTR_ACCEPT             0x0001
+#define EXC_CHTR_REJECT             0x0003
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excdoc.hxx b/sc/source/filter/inc/excdoc.hxx
new file mode 100644
index 000000000..40748b38f
--- /dev/null
+++ b/sc/source/filter/inc/excdoc.hxx
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 "xeroot.hxx"
+#include "xerecord.hxx"
+#include "excrecds.hxx"
+#include 
+
+// Forwards -
+
+class SvStream;
+class XclExpNote;
+class XclExpStream;
+class XclExpXmlStream;
+class XclExpChangeTrack;
+
+
+class XclExpCellTable;
+
+class ExcTable final : public XclExpRecordBase, public XclExpRoot
+{
+private:
+    typedef XclExpRecordList< ExcBundlesheetBase >  ExcBoundsheetList;
+    typedef rtl::Reference< XclExpCellTable >       XclExpCellTableRef;
+    typedef XclExpRecordList< XclExpNote >          XclExpNoteList;
+    typedef rtl::Reference< XclExpNoteList >        XclExpNoteListRef;
+
+    XclExpRecordList<>          aRecList;
+    XclExpCellTableRef          mxCellTable;
+
+    SCTAB                       mnScTab;    // table number SC document
+    sal_uInt16                  nExcTab;    // table number Excel document
+
+    XclExpNoteListRef           mxNoteList;
+
+    // re-create and forget pRec; delete is done by ExcTable itself!
+    void                        Add( XclExpRecordBase* pRec );
+
+public:
+                                ExcTable( const XclExpRoot& rRoot );
+                                ExcTable( const XclExpRoot& rRoot, SCTAB nScTab );
+                                virtual ~ExcTable() override;
+
+    void FillAsHeaderBinary( ExcBoundsheetList& rBoundsheetList );
+    void FillAsHeaderXml( ExcBoundsheetList& rBoundsheetList );
+
+    void FillAsTableBinary( SCTAB nCodeNameIdx );
+    void FillAsTableXml();
+
+    void                        FillAsEmptyTable( SCTAB nCodeNameIdx );
+
+    void                        Write( XclExpStream& );
+    void                        WriteXml( XclExpXmlStream& );
+};
+
+class ExcDocument final : protected XclExpRoot
+{
+friend class ExcTable;
+
+private:
+    typedef XclExpRecordList< ExcTable >            ExcTableList;
+    typedef XclExpRecordList< ExcBundlesheetBase >  ExcBoundsheetList;
+    typedef ExcBoundsheetList::RecordRefType        ExcBoundsheetRef;
+
+    ExcTable            aHeader;
+
+    ExcTableList        maTableList;
+    ExcBoundsheetList   maBoundsheetList;
+
+    std::unique_ptr m_xExpChangeTrack;
+
+public:
+    explicit                    ExcDocument( const XclExpRoot& rRoot );
+    virtual                     ~ExcDocument() override;
+
+    void                ReadDoc();
+    void                Write( SvStream& rSvStrm );
+    void                WriteXml( XclExpXmlStream& );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excelchartconverter.hxx b/sc/source/filter/inc/excelchartconverter.hxx
new file mode 100644
index 000000000..363966a4c
--- /dev/null
+++ b/sc/source/filter/inc/excelchartconverter.hxx
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include "workbookhelper.hxx"
+
+namespace oox::xls {
+
+class ExcelChartConverter final : public ::oox::drawingml::chart::ChartConverter, public WorkbookHelper
+{
+public:
+    explicit            ExcelChartConverter( const WorkbookHelper& rHelper );
+    virtual             ~ExcelChartConverter() override;
+
+    /** Creates an external data provider that is able to use spreadsheet data. */
+    virtual void        createDataProvider(
+                            const css::uno::Reference< css::chart2::XChartDocument >& rxChartDoc ) override;
+
+    /** Creates a data sequence from the passed formula. */
+    virtual css::uno::Reference
+        createDataSequence(
+            const css::uno::Reference& rxDataProvider,
+            const oox::drawingml::chart::DataSequenceModel& rDataSeq, const OUString& rRole,
+            const OUString& aRoleQualifier ) override;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excelfilter.hxx b/sc/source/filter/inc/excelfilter.hxx
new file mode 100644
index 000000000..2aeb675cd
--- /dev/null
+++ b/sc/source/filter/inc/excelfilter.hxx
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+
+namespace oox::xls {
+
+class WorkbookGlobals;
+
+class ExcelFilter final : public ::oox::core::XmlFilterBase
+{
+public:
+    /// @throws css::uno::RuntimeException
+    explicit            ExcelFilter(
+                            const css::uno::Reference< css::uno::XComponentContext >& rxContext );
+    virtual             ~ExcelFilter() override;
+
+    void                registerWorkbookGlobals( WorkbookGlobals& rBookGlob );
+    WorkbookGlobals&    getWorkbookGlobals() const;
+    void                unregisterWorkbookGlobals();
+
+    virtual bool        importDocument() override;
+    virtual bool        exportDocument() noexcept override;
+
+    virtual const ::oox::drawingml::Theme* getCurrentTheme() const override;
+    virtual ::oox::vml::Drawing* getVmlDrawing() override;
+    virtual ::oox::drawingml::table::TableStyleListPtr getTableStyles() override;
+    virtual ::oox::drawingml::chart::ChartConverter* getChartConverter() override;
+    virtual void useInternalChartDataTable( bool bInternal ) override;
+
+    virtual sal_Bool SAL_CALL filter( const css::uno::Sequence< css::beans::PropertyValue >& rDescriptor ) override;
+
+private:
+    virtual GraphicHelper* implCreateGraphicHelper() const override;
+    virtual ::oox::ole::VbaProject* implCreateVbaProject() const override;
+    virtual OUString SAL_CALL getImplementationName() override;
+
+    WorkbookGlobals*    mpBookGlob;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excelhandlers.hxx b/sc/source/filter/inc/excelhandlers.hxx
new file mode 100644
index 000000000..fd029e995
--- /dev/null
+++ b/sc/source/filter/inc/excelhandlers.hxx
@@ -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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include "workbookhelper.hxx"
+#include "worksheethelper.hxx"
+
+namespace oox::xls {
+
+/** Context handler derived from the WorkbookHelper helper class.
+
+    Used to import contexts in global workbook fragments.
+ */
+class WorkbookContextBase : public ::oox::core::ContextHandler2, public WorkbookHelper
+{
+public:
+    template< typename ParentType >
+    explicit     WorkbookContextBase( ParentType& rParent ) :
+                            ::oox::core::ContextHandler2( rParent ), WorkbookHelper( rParent ) {}
+};
+
+/** Context handler derived from the WorksheetHelper helper class.
+
+    Used to import contexts in sheet fragments.
+ */
+class WorksheetContextBase : public ::oox::core::ContextHandler2, public WorksheetHelper
+{
+public:
+    template< typename ParentType >
+    explicit     WorksheetContextBase( ParentType& rParent ) :
+                            ::oox::core::ContextHandler2( rParent ), WorksheetHelper( rParent ) {}
+};
+
+/** Fragment handler derived from the WorkbookHelper helper class.
+
+    Used to import global workbook fragments.
+ */
+class WorkbookFragmentBase : public ::oox::core::FragmentHandler2, public WorkbookHelper
+{
+public:
+    explicit            WorkbookFragmentBase(
+                            const WorkbookHelper& rHelper,
+                            const OUString& rFragmentPath );
+};
+
+/** Fragment handler derived from the WorksheetHelper helper class.
+
+    Used to import sheet fragments.
+ */
+class WorksheetFragmentBase : public ::oox::core::FragmentHandler2, public WorksheetHelper
+{
+public:
+    explicit            WorksheetFragmentBase(
+                            const WorksheetHelper& rHelper,
+                            const OUString& rFragmentPath );
+};
+
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excelvbaproject.hxx b/sc/source/filter/inc/excelvbaproject.hxx
new file mode 100644
index 000000000..7c1047291
--- /dev/null
+++ b/sc/source/filter/inc/excelvbaproject.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+
+namespace com::sun::star {
+        namespace sheet { class XSpreadsheetDocument; }
+}
+
+namespace oox::xls {
+
+/** Special implementation of the VBA project for the Excel filters. */
+class ExcelVbaProject final : public ::oox::ole::VbaProject
+{
+public:
+    explicit            ExcelVbaProject(
+                            const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+                            const css::uno::Reference< css::sheet::XSpreadsheetDocument >& rxDocument );
+
+private:
+    /** Adds dummy modules for sheets without imported code name. */
+    virtual void        prepareImport() override;
+
+    css::uno::Reference< css::sheet::XSpreadsheetDocument >
+                        mxDocument;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excform.hxx b/sc/source/filter/inc/excform.hxx
new file mode 100644
index 000000000..4bc19b185
--- /dev/null
+++ b/sc/source/filter/inc/excform.hxx
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 "xlformula.hxx"
+#include "xiroot.hxx"
+#include "xltools.hxx"
+#include "formel.hxx"
+
+#include 
+
+class ScFormulaCell;
+class ScRangeList;
+
+class ExcelToSc : public ExcelConverterBase, protected XclImpRoot
+{
+protected:
+    enum ExtensionType { EXTENSION_ARRAY, EXTENSION_NLR, EXTENSION_MEMAREA };
+    typedef ::std::vector< ExtensionType >          ExtensionTypeVec;
+
+    static const sal_uInt16 nRowMask;
+
+    XclFunctionProvider maFuncProv;
+    const XclBiff       meBiff;
+
+    void                DoMulArgs( DefTokenId eId, sal_uInt8 nNumArgs );
+
+    void                ExcRelToScRel( sal_uInt16 nRow, sal_uInt8 nCol, ScSingleRefData&, const bool bName );
+
+public:
+    ExcelToSc( XclImpRoot& rRoot );
+    virtual             ~ExcelToSc() override;
+    virtual ConvErr     Convert( std::unique_ptr&, XclImpStream& rStrm, std::size_t nFormulaLen,
+                                 bool bAllowArrays, const FORMULA_TYPE eFT = FT_CellFormula ) override;
+
+    virtual ConvErr     Convert( ScRangeListTabs&, XclImpStream& rStrm, std::size_t nFormulaLen, SCTAB nTab, const FORMULA_TYPE eFT = FT_CellFormula ) override;
+
+    virtual void        ConvertExternName( std::unique_ptr& rpArray, XclImpStream& rStrm, std::size_t nFormulaLen,
+                                           const OUString& rUrl, const ::std::vector& rTabNames );
+
+    virtual void        GetAbsRefs( ScRangeList& rRangeList, XclImpStream& rStrm, std::size_t nLen );
+
+    std::unique_ptr GetDummy();
+    std::unique_ptr GetBoolErr( XclBoolError );
+
+    static bool ReadSharedFormulaPosition( XclImpStream& rStrm, SCCOL& rCol, SCROW& rRow );
+    const ScTokenArray* GetSharedFormula( const ScAddress& rRefPos ) const;
+
+    static void         SetError( ScFormulaCell& rCell, const ConvErr eErr );
+
+    static inline bool  IsComplColRange( const sal_uInt16 nCol1, const sal_uInt16 nCol2 );
+    static inline bool  IsComplRowRange( const sal_uInt16 nRow1, const sal_uInt16 nRow2 );
+
+    void                SetComplCol( ScComplexRefData& );
+    void                SetComplRow( ScComplexRefData& );
+
+    void                ReadExtensions( const ExtensionTypeVec& rExtensions,
+                                        XclImpStream& aIn );
+    void                ReadExtensionArray( unsigned int n,
+                                            XclImpStream& aIn );
+    static void         ReadExtensionNlr( XclImpStream& aIn );
+    void                ReadExtensionMemArea( XclImpStream& aIn );
+};
+
+inline bool ExcelToSc::IsComplColRange( const sal_uInt16 nCol1, const sal_uInt16 nCol2 )
+{
+    return ( nCol1 == 0x00 ) && ( nCol2 == 0xFF );
+}
+
+inline bool ExcelToSc::IsComplRowRange( const sal_uInt16 nRow1, const sal_uInt16 nRow2 )
+{
+    return ( ( nRow1 & 0x3FFF ) == 0x0000 ) && ( ( nRow2 & 0x3FFF ) == 0x3FFF );
+}
+
+class XclImpLinkManager;
+class XclImpExtName;
+
+class ExcelToSc8 : public ExcelToSc
+{
+public:
+
+    struct ExternalTabInfo
+    {
+        ScRange         maRange;
+        OUString maTabName;
+        sal_uInt16      mnFileId;
+        bool            mbExternal;
+
+        ExternalTabInfo();
+    };
+
+private:
+    const XclImpLinkManager&    rLinkMan;
+
+    void                ExcRelToScRel8( sal_uInt16 nRow, sal_uInt16 nCol, ScSingleRefData&,
+                            const bool bName );
+
+    bool                GetExternalFileIdFromXti( sal_uInt16 nIxti, sal_uInt16& rFileId ) const;
+
+    virtual bool        Read3DTabReference( sal_uInt16 nIxti, SCTAB& rFirstTab, SCTAB& rLastTab, ExternalTabInfo& rExtInfo );
+
+    bool                HandleOleLink(sal_uInt16 nXtiIndex, const XclImpExtName& rExtName, ExternalTabInfo& rExtInfo);
+public:
+    ExcelToSc8( XclImpRoot& rRoot );
+    virtual             ~ExcelToSc8() override;
+
+    virtual ConvErr     Convert( std::unique_ptr& rpTokArray, XclImpStream& rStrm, std::size_t nFormulaLen, bool bAllowArrays, const FORMULA_TYPE eFT = FT_CellFormula ) override;
+
+    virtual ConvErr     Convert( ScRangeListTabs&, XclImpStream& rStrm, std::size_t nFormulaLen, SCTAB nTab, const FORMULA_TYPE eFT = FT_CellFormula ) override;
+
+    virtual void        ConvertExternName( std::unique_ptr& rpArray, XclImpStream& rStrm, std::size_t nFormulaLen,
+                                           const OUString& rUrl, const ::std::vector& rTabNames ) override;
+
+    static inline bool  IsComplRowRange( const sal_uInt16 nRow1, const sal_uInt16 nRow2 );
+
+    virtual void        GetAbsRefs( ScRangeList& rRangeList, XclImpStream& rStrm, std::size_t nLen ) override;
+};
+
+inline bool ExcelToSc8::IsComplRowRange( const sal_uInt16 nRow1, const sal_uInt16 nRow2 )
+{
+    return ( nRow1 == 0x0000 ) && ( nRow2 == 0xFFFF );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excimp8.hxx b/sc/source/filter/inc/excimp8.hxx
new file mode 100644
index 000000000..4096ca068
--- /dev/null
+++ b/sc/source/filter/inc/excimp8.hxx
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 "imp_op.hxx"
+#include "root.hxx"
+#include "excscen.hxx"
+#include 
+
+class ScDBData;
+class XclImpStream;
+
+class ImportExcel8 : public ImportExcel
+{
+public:
+                            ImportExcel8( XclImpRootData& rImpData, SvStream& rStrm );
+    virtual                 ~ImportExcel8() override;
+
+    virtual ErrCode         Read() override;
+
+    void                    Calccount();              // 0x0C
+    void                    Precision();              // 0x0E
+    void                    Delta();                  // 0x10
+    void                    Iteration();              // 0x11
+    void                    Boundsheet();             // 0x85
+    void                    FilterMode();             // 0x9B
+    void                    AutoFilterInfo();         // 0x9D
+    void                    AutoFilter();             // 0x9E
+    void                    Scenman();                // 0xAE
+    void                    Scenario();               // 0xAF
+    void                    ReadBasic();              // 0xD3
+    void                    Labelsst();               // 0xFD
+
+    void                    FeatHdr();                // 0x0867
+    void                    Feat();                   // 0x0868
+
+    virtual void            EndSheet() override;
+    virtual void            PostDocLoad() override;
+
+private:
+    ExcScenarioList maScenList;
+};
+
+
+class XclImpAutoFilterData : private ExcRoot
+{
+private:
+    ScDBData*                   pCurrDBData;
+    ScQueryParam                aParam;
+    ScRange                     aCriteriaRange;
+    bool                        bActive:1;
+    bool                        bCriteria:1;
+    bool                        bAutoOrAdvanced:1;
+
+    void                        SetCellAttribs();
+    void                        InsertQueryParam();
+
+protected:
+public:
+                                XclImpAutoFilterData(
+                                    RootData* pRoot,
+                                    const ScRange& rRange);
+
+    bool                 IsActive() const    { return bActive; }
+    bool                 IsFiltered() const  { return bAutoOrAdvanced; }
+    SCTAB                Tab() const         { return aParam.nTab; }
+    SCCOL                StartCol() const    { return aParam.nCol1; }
+    SCROW                StartRow() const    { return aParam.nRow1; }
+    SCCOL                EndCol() const      { return aParam.nCol2; }
+    SCROW                EndRow() const      { return aParam.nRow2; }
+
+    void ReadAutoFilter( XclImpStream& rStrm, svl::SharedStringPool& rPool );
+
+    void                 Activate()          { bActive = true; }
+    void                        SetAdvancedRange( const ScRange* pRange );
+    void                        SetExtractPos( const ScAddress& rAddr );
+    void                 SetAutoOrAdvanced()  { bAutoOrAdvanced = true; }
+    void                        Apply();
+    void                        EnableRemoveFilter();
+};
+
+class XclImpAutoFilterBuffer
+{
+public:
+
+    void                        Insert( RootData* pRoot, const ScRange& rRange);
+    void                        AddAdvancedRange( const ScRange& rRange );
+    void                        AddExtractPos( const ScRange& rRange );
+    void                        Apply();
+
+    XclImpAutoFilterData*       GetByTab( SCTAB nTab );
+
+private:
+    typedef std::shared_ptr XclImpAutoFilterSharePtr;
+    std::vector maFilters;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excrecds.hxx b/sc/source/filter/inc/excrecds.hxx
new file mode 100644
index 000000000..2e4885a88
--- /dev/null
+++ b/sc/source/filter/inc/excrecds.hxx
@@ -0,0 +1,457 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+
+#include "xerecord.hxx"
+#include "xeroot.hxx"
+#include "excdefs.hxx"
+
+//------------------------------------------------------------------ Forwards -
+
+class ScDBData;
+struct ScQueryEntry;
+
+//----------------------------------------------------------- class ExcRecord -
+
+class ExcRecord : public XclExpRecord
+{
+public:
+    virtual void            Save( XclExpStream& rStrm ) override;
+    virtual void            SaveXml( XclExpXmlStream& rStrm ) override;
+
+    virtual sal_uInt16          GetNum() const = 0;
+    virtual std::size_t     GetLen() const = 0;
+
+protected:
+    virtual void            SaveCont( XclExpStream& rStrm );
+
+private:
+    /** Writes the body of the record. */
+    virtual void            WriteBody( XclExpStream& rStrm ) override;
+};
+
+//--------------------------------------------------------- class ExcEmptyRec -
+
+class ExcEmptyRec : public ExcRecord
+{
+private:
+protected:
+public:
+    virtual void            Save( XclExpStream& rStrm ) override;
+    virtual sal_uInt16          GetNum() const override;
+    virtual std::size_t     GetLen() const override;
+};
+
+//--------------------------------------------------------- class ExcDummyRec -
+
+class ExcDummyRec : public ExcRecord
+{
+protected:
+public:
+    virtual void            Save( XclExpStream& rStrm ) override;
+    virtual sal_uInt16          GetNum() const override;
+    virtual const sal_uInt8*        GetData() const = 0;    // byte data must contain header and body
+};
+
+//------------------------------------------------------- class ExcBoolRecord -
+// stores bool as 16bit val ( 0x0000 | 0x0001 )
+
+class ExcBoolRecord : public ExcRecord
+{
+private:
+    virtual void            SaveCont( XclExpStream& rStrm ) override;
+
+protected:
+    bool                    bVal;
+
+    ExcBoolRecord() : bVal( false ) {}
+
+public:
+    virtual std::size_t     GetLen() const override;
+};
+
+//--------------------------------------------------------- class ExcBof_Base -
+
+class ExcBof_Base : public ExcRecord
+{
+private:
+protected:
+    sal_uInt16                  nDocType;
+    sal_uInt16                  nVers;
+    sal_uInt16                  nRupBuild;
+    sal_uInt16                  nRupYear;
+public:
+                            ExcBof_Base();
+};
+
+//-------------------------------------------------------------- class ExcBof -
+// Header Record for WORKSHEETS
+
+class ExcBof : public ExcBof_Base
+{
+private:
+    virtual void            SaveCont( XclExpStream& rStrm ) override;
+public:
+                            ExcBof();
+
+    virtual sal_uInt16          GetNum() const override;
+    virtual std::size_t     GetLen() const override;
+};
+
+//------------------------------------------------------------- class ExcBofW -
+// Header Record for WORKBOOKS
+
+class ExcBofW : public ExcBof_Base
+{
+private:
+    virtual void            SaveCont( XclExpStream& rStrm ) override;
+public:
+                            ExcBofW();
+
+    virtual sal_uInt16          GetNum() const override;
+    virtual std::size_t     GetLen() const override;
+};
+
+//-------------------------------------------------------------- class ExcEof -
+
+class ExcEof : public ExcRecord
+{
+private:
+public:
+    virtual sal_uInt16          GetNum() const override;
+    virtual std::size_t     GetLen() const override;
+};
+
+//--------------------------------------------------------- class ExcDummy_00 -
+// INTERFACEHDR to FNGROUPCOUNT (see excrecds.cxx)
+
+class ExcDummy_00 : public ExcDummyRec
+{
+private:
+    static const sal_uInt8      pMyData[];
+    static const std::size_t   nMyLen;
+public:
+    virtual std::size_t        GetLen() const override;
+    virtual const sal_uInt8*        GetData() const override;
+};
+
+// EXC_ID_WINDOWPROTECTION
+class XclExpWindowProtection : public   XclExpBoolRecord
+{
+    public:
+        XclExpWindowProtection(bool bValue);
+
+    virtual void            SaveXml( XclExpXmlStream& rStrm ) override;
+};
+
+// EXC_ID_PROTECT  Document Protection
+class XclExpProtection : public XclExpBoolRecord
+{
+    public:
+        XclExpProtection(bool bValue);
+};
+
+class XclExpSheetProtection : public XclExpProtection
+{
+    SCTAB                   mnTab;
+    public:
+        XclExpSheetProtection(bool bValue, SCTAB nTab);
+    virtual void            SaveXml( XclExpXmlStream& rStrm ) override;
+};
+
+class XclExpPassHash : public XclExpRecord
+{
+public:
+    XclExpPassHash(const css::uno::Sequence& aHash);
+    virtual ~XclExpPassHash() override;
+
+private:
+    virtual void    WriteBody(XclExpStream& rStrm) override;
+
+private:
+    sal_uInt16  mnHash;
+};
+
+//-------------------------------------------------------- class ExcDummy_04x -
+// PASSWORD to BOOKBOOL (see excrecds.cxx), no 1904
+
+class ExcDummy_040 : public ExcDummyRec
+{
+private:
+    static const sal_uInt8      pMyData[];
+    static const std::size_t   nMyLen;
+public:
+    virtual std::size_t        GetLen() const override;
+    virtual const sal_uInt8*        GetData() const override;
+};
+
+class ExcDummy_041 : public ExcDummyRec
+{
+private:
+    static const sal_uInt8     pMyData[];
+    static const std::size_t   nMyLen;
+public:
+    virtual std::size_t        GetLen() const override;
+    virtual const sal_uInt8*   GetData() const override;
+};
+
+//------------------------------------------------------------- class Exc1904 -
+
+class Exc1904 : public ExcBoolRecord
+{
+public:
+                            Exc1904( const ScDocument& rDoc );
+    virtual sal_uInt16      GetNum() const override;
+
+    virtual void            SaveXml( XclExpXmlStream& rStrm ) override;
+private:
+    bool                    bDateCompatibility;
+};
+
+//------------------------------------------------------ class ExcBundlesheet -
+
+class ExcBundlesheetBase : public ExcRecord
+{
+protected:
+    sal_uInt64              m_nStrPos;
+    sal_uInt64              m_nOwnPos;    // Position after # and Len
+    sal_uInt16              nGrbit;
+    SCTAB                   nTab;
+
+                            ExcBundlesheetBase();
+
+public:
+                            ExcBundlesheetBase( const RootData& rRootData, SCTAB nTab );
+
+    void                    SetStreamPos(sal_uInt64 const nStrPos) { m_nStrPos = nStrPos; }
+    void                    UpdateStreamPos( XclExpStream& rStrm );
+
+    virtual sal_uInt16          GetNum() const override;
+};
+
+class ExcBundlesheet : public ExcBundlesheetBase
+{
+private:
+    OString            aName;
+
+    virtual void            SaveCont( XclExpStream& rStrm ) override;
+
+public:
+                            ExcBundlesheet( const RootData& rRootData, SCTAB nTab );
+    virtual std::size_t     GetLen() const override;
+};
+
+//--------------------------------------------------------- class ExcDummy_02 -
+// sheet dummies: CALCMODE to SETUP
+
+class ExcDummy_02a : public ExcDummyRec
+{
+private:
+    static const sal_uInt8      pMyData[];
+    static const std::size_t   nMyLen;
+public:
+    virtual std::size_t        GetLen() const override;
+    virtual const sal_uInt8*        GetData() const override;
+};
+
+/** This record contains the Windows country IDs for the UI and document language. */
+class XclExpCountry : public XclExpRecord
+{
+public:
+    explicit                    XclExpCountry( const XclExpRoot& rRoot );
+
+private:
+    sal_uInt16                  mnUICountry;        /// The UI country ID.
+    sal_uInt16                  mnDocCountry;       /// The document country ID.
+
+    /** Writes the body of the COUNTRY record. */
+    virtual void                WriteBody( XclExpStream& rStrm ) override;
+};
+
+// XclExpWsbool ===============================================================
+
+class XclExpWsbool : public XclExpUInt16Record
+{
+public:
+    explicit XclExpWsbool( bool bFitToPages );
+};
+
+/**
+ * Save sheetPr element and its children for xlsx export.
+ */
+class XclExpXmlSheetPr : public XclExpRecordBase
+{
+public:
+    explicit XclExpXmlSheetPr(
+        bool bFitToPages, SCTAB nScTab, const Color& rTabColor, XclExpFilterManager* pManager );
+
+    virtual void SaveXml( XclExpXmlStream& rStrm ) override;
+
+private:
+    SCTAB mnScTab;
+    XclExpFilterManager* mpManager;
+    bool mbFitToPage;
+    Color maTabColor;
+};
+
+class XclExpFiltermode : public XclExpEmptyRecord
+{
+public:
+    explicit            XclExpFiltermode();
+};
+
+class XclExpAutofilterinfo : public XclExpUInt16Record
+{
+public:
+    explicit            XclExpAutofilterinfo( const ScAddress& rStartPos, SCCOL nScCol );
+
+    const ScAddress& GetStartPos() const { return maStartPos; }
+    SCCOL        GetColCount() const { return static_cast< SCCOL >( GetValue() ); }
+
+private:
+    ScAddress           maStartPos;
+};
+
+class ExcFilterCondition
+{
+private:
+    sal_uInt8               nType;
+    sal_uInt8               nOper;
+    std::unique_ptr
+                            pText;
+
+protected:
+public:
+                            ExcFilterCondition();
+                            ~ExcFilterCondition();
+
+    bool             IsEmpty() const     { return (nType == EXC_AFTYPE_NOTUSED); }
+    std::size_t             GetTextBytes() const;
+
+    void                    SetCondition( sal_uInt8 nTp, sal_uInt8 nOp, const OUString* pT );
+
+    void                    Save( XclExpStream& rStrm );
+    void                    SaveXml( XclExpXmlStream& rStrm );
+    void                    SaveText( XclExpStream& rStrm );
+};
+
+class XclExpAutofilter : public XclExpRecord, protected XclExpRoot
+{
+private:
+    enum FilterType
+    {
+        FilterCondition,
+        MultiValue,
+        BlankValue,
+        ColorValue
+    };
+    FilterType              meType;
+    sal_uInt16              nCol;
+    sal_uInt16              nFlags;
+    bool                    bHasBlankValue;
+    ExcFilterCondition      aCond[ 2 ];
+    std::vector> maMultiValues; // first->values, second->bDateFormat
+    std::vector> maColorValues; // first->Color, second->bIsBackgroundColor (vs. TextColor)
+
+    bool                    AddCondition( ScQueryConnect eConn, sal_uInt8 nType,
+                                sal_uInt8 nOp, const OUString* pText, bool bSimple = false );
+
+    virtual void            WriteBody( XclExpStream& rStrm ) override;
+
+public:
+                            XclExpAutofilter( const XclExpRoot& rRoot, sal_uInt16 nC );
+
+    sal_uInt16       GetCol() const          { return nCol; }
+    bool             HasTop10() const        { return ::get_flag( nFlags, EXC_AFFLAG_TOP10 ); }
+
+    bool                    HasCondition() const;
+    bool                    AddEntry( const ScQueryEntry& rEntry );
+    void                    AddMultiValueEntry( const ScQueryEntry& rEntry );
+    void AddColorEntry( const ScQueryEntry& rEntry );
+
+    virtual void            SaveXml( XclExpXmlStream& rStrm ) override;
+};
+
+class ExcAutoFilterRecs : public XclExpRecordBase, protected XclExpRoot
+{
+public:
+    /** @param  pDefinedData
+                If nullptr, obtain anonymous ScDBData from sheet nTab.
+                Else, use defined database range; used with XclExpTables.
+     */
+    explicit            ExcAutoFilterRecs( const XclExpRoot& rRoot, SCTAB nTab, const ScDBData* pDefinedData );
+    virtual             ~ExcAutoFilterRecs() override;
+
+    void                AddObjRecs();
+
+    virtual void        Save( XclExpStream& rStrm ) override;
+    virtual void        SaveXml( XclExpXmlStream& rStrm ) override;
+
+    bool                HasFilterMode() const;
+
+private:
+    XclExpAutofilter*   GetByCol( SCCOL nCol ); // always 0-based
+    bool                IsFiltered( SCCOL nCol );
+
+private:
+    typedef XclExpRecordList< XclExpAutofilter >    XclExpAutofilterList;
+    typedef XclExpAutofilterList::RecordRefType     XclExpAutofilterRef;
+
+    XclExpAutofilterList maFilterList;
+    rtl::Reference m_pFilterMode;
+    rtl::Reference m_pFilterInfo;
+    ScRange                 maRef;
+    bool mbAutoFilter;
+
+    ScRange maSortRef; // sort area without headers
+    std::vector< std::tuple > maSortCustomList; // sorted column with list of sorted items
+};
+
+/** Sheet filter manager. Contains auto filters or advanced filters from all sheets. */
+class XclExpFilterManager : protected XclExpRoot
+{
+public:
+    explicit            XclExpFilterManager( const XclExpRoot& rRoot );
+
+    /** Creates the filter records for the specified sheet.
+        @descr  Creates and inserts related built-in NAME records. Therefore this
+            function is called from the name buffer itself. */
+    void                InitTabFilter( SCTAB nScTab );
+
+    /** Returns a record object containing all filter records for the specified sheet. */
+    XclExpRecordRef     CreateRecord( SCTAB nScTab );
+
+    /** Returns whether or not FilterMode is present */
+    bool                HasFilterMode( SCTAB nScTab );
+
+private:
+    using               XclExpRoot::CreateRecord;
+
+    typedef rtl::Reference< ExcAutoFilterRecs >  XclExpTabFilterRef;
+    typedef ::std::map< SCTAB, XclExpTabFilterRef > XclExpTabFilterMap;
+
+    XclExpTabFilterMap  maFilterMap;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/excscen.hxx b/sc/source/filter/inc/excscen.hxx
new file mode 100644
index 000000000..81e97e1b1
--- /dev/null
+++ b/sc/source/filter/inc/excscen.hxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+
+struct RootData;
+class XclImpRoot;
+class XclImpStream;
+
+class ExcScenarioCell
+{
+private:
+    OUString                        aValue;
+public:
+    const sal_uInt16                nCol;
+    const sal_uInt16                nRow;
+
+    ExcScenarioCell( const sal_uInt16 nC, const sal_uInt16 nR );
+
+    void SetValue( const OUString& rVal ) { aValue = rVal; }
+
+    const OUString& GetValue() const { return aValue; }
+};
+
+class ExcScenario final
+{
+public:
+    ExcScenario( XclImpStream& rIn, const RootData& rRoot );
+
+    void Apply( const XclImpRoot& rRoot, const bool bLast );
+
+private:
+    OUString         aName;
+    OUString         aComment;
+    sal_uInt8        nProtected;
+    const sal_uInt16 nTab;
+    std::vector aEntries;
+};
+
+struct ExcScenarioList
+{
+    ExcScenarioList () : nLastScenario(0) {}
+
+    void Apply( const XclImpRoot& rRoot );
+
+    sal_uInt16 nLastScenario;
+    std::vector< std::unique_ptr > aEntries;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/exp_op.hxx b/sc/source/filter/inc/exp_op.hxx
new file mode 100644
index 000000000..87ed95b2e
--- /dev/null
+++ b/sc/source/filter/inc/exp_op.hxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include "xeroot.hxx"
+
+class ExcDocument;
+
+class ExportTyp
+{
+protected:
+                        ~ExportTyp() {}
+
+    SvStream&           aOut;
+public:
+                        ExportTyp( SvStream& aStream ) : aOut( aStream ) {}
+
+    virtual ErrCode     Write() = 0;
+};
+
+class ExportBiff5 : public ExportTyp, protected XclExpRoot
+{
+private:
+    std::unique_ptr
+                        pExcDoc;
+
+protected:
+    RootData*           pExcRoot;
+
+public:
+                        ExportBiff5( XclExpRootData& rExpData, SvStream& rStrm );
+    virtual             ~ExportBiff5() override;
+    ErrCode             Write() override;
+};
+
+class ExportBiff8 : public ExportBiff5
+{
+public:
+                        ExportBiff8( XclExpRootData& rExpData, SvStream& rStrm );
+    virtual             ~ExportBiff8() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/expbase.hxx b/sc/source/filter/inc/expbase.hxx
new file mode 100644
index 000000000..84cc558cb
--- /dev/null
+++ b/sc/source/filter/inc/expbase.hxx
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+
+class SvNumberFormatter;
+class SvStream;
+class ScFieldEditEngine;
+
+class ScExportBase
+{
+protected:
+
+    SvStream&           rStrm;
+    ScRange             aRange;
+    ScDocument*         pDoc;
+    SvNumberFormatter*  pFormatter;
+    std::unique_ptr
+                        pEditEngine;
+
+public:
+
+                        ScExportBase( SvStream&, ScDocument*, const ScRange& );
+    virtual             ~ScExportBase();
+
+                        // Trim borders of hidden Cols/Rows,
+                        // return: sal_True if range exists
+                        // Start/End/Col/Row must have valid starting values
+    bool                TrimDataArea( SCTAB nTab, SCCOL& nStartCol,
+                            SCROW& nStartRow, SCCOL& nEndCol, SCROW& nEndRow ) const;
+
+                        // Get Data Area of a table,
+                        // adjust borders of hidden Cols/Rows,
+                        // return: sal_True if range exists
+    bool                GetDataArea( SCTAB nTab, SCCOL& nStartCol,
+                            SCROW& nStartRow, SCCOL& nEndCol, SCROW& nEndRow ) const;
+
+                        // table does not exist or is empty
+    bool                IsEmptyTable( SCTAB nTab ) const;
+
+    ScFieldEditEngine&  GetEditEngine() const;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/export/SparklineExt.hxx b/sc/source/filter/inc/export/SparklineExt.hxx
new file mode 100644
index 000000000..f2bff1c7d
--- /dev/null
+++ b/sc/source/filter/inc/export/SparklineExt.hxx
@@ -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/.
+ */
+
+#pragma once
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+
+namespace xcl::exp
+{
+/** Export for sparkline type of  element - top sparkline element. */
+class SparklineExt : public XclExpExt
+{
+public:
+    SparklineExt(const XclExpRoot& rRoot);
+
+    void SaveXml(XclExpXmlStream& rStream) override;
+    void addSparklineGroup(XclExpXmlStream& rStream, sc::SparklineGroup& rSparklineGroup,
+                           std::vector> const& rSparklines);
+
+    static void
+    addSparklineGroupAttributes(rtl::Reference& pAttrList,
+                                sc::SparklineAttributes& rSparklineAttributes);
+    static void addSparklineGroupColors(XclExpXmlStream& rStream,
+                                        sc::SparklineAttributes& rSparklineAttributes);
+
+    XclExpExtType GetType() override { return XclExpExtSparklineType; }
+};
+
+/** Determines if sparklines needs to be exported and initiates the export. */
+class SparklineBuffer : public XclExpRecordBase, protected XclExpRoot
+{
+public:
+    explicit SparklineBuffer(const XclExpRoot& rRoot, const XclExtLstRef& xExtLst);
+};
+
+} // end namespace xcl::exp
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/externallinkbuffer.hxx b/sc/source/filter/inc/externallinkbuffer.hxx
new file mode 100644
index 000000000..374a54b38
--- /dev/null
+++ b/sc/source/filter/inc/externallinkbuffer.hxx
@@ -0,0 +1,350 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+
+#include 
+#include 
+#include "defnamesbuffer.hxx"
+#include "formulabase.hxx"
+
+namespace com::sun::star {
+    namespace sheet { struct DDEItemInfo; }
+    namespace sheet { class XDDELink; }
+    namespace sheet { class XExternalDocLink; }
+    namespace sheet { class XExternalSheetCache; }
+}
+
+namespace oox::core {
+    class Relations;
+}
+
+namespace oox::xls {
+
+struct ExternalNameModel
+{
+    bool                mbNotify;           /// Notify application on data change.
+    bool                mbPreferPic;        /// Picture link.
+    bool                mbStdDocName;       /// Name is the StdDocumentName for DDE.
+    bool                mbOleObj;           /// Name is an OLE object.
+    bool                mbIconified;        /// Iconified object link.
+
+    explicit            ExternalNameModel();
+};
+
+class ExternalLink;
+
+class ExternalName : public DefinedNameBase
+{
+public:
+    explicit            ExternalName( const ExternalLink& rParentLink );
+
+    /** Appends the passed value to the result set. */
+    template< typename Type >
+    void         appendResultValue( const Type& rValue )
+                            { if( maCurrIt != maResults.end() ) (*maCurrIt++) <<= rValue; }
+
+    /** Imports the definedName element. */
+    void                importDefinedName( const AttributeList& rAttribs );
+    /** Imports the ddeItem element describing an item of a DDE link. */
+    void                importDdeItem( const AttributeList& rAttribs );
+    /** Imports the values element containing the size of the DDE result matrix. */
+    void                importValues( const AttributeList& rAttribs );
+    /** Imports the oleItem element describing an object of an OLE link. */
+    void                importOleItem( const AttributeList& rAttribs );
+
+    /** Imports the EXTERNALNAME record containing the name (only). */
+    void                importExternalName( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALNAMEFLAGS record containing the settings of an external name. */
+    void                importExternalNameFlags( SequenceInputStream& rStrm );
+    /** Imports the DDEITEMVALUES record containing the size of the DDE result matrix. */
+    void                importDdeItemValues( SequenceInputStream& rStrm );
+    /** Imports the DDEITEM_BOOL record containing a boolean value in a link result. */
+    void                importDdeItemBool( SequenceInputStream& rStrm );
+    /** Imports the DDEITEM_DOUBLE record containing a double value in a link result. */
+    void                importDdeItemDouble( SequenceInputStream& rStrm );
+    /** Imports the DDEITEM_ERROR record containing an error code in a link result. */
+    void                importDdeItemError( SequenceInputStream& rStrm );
+    /** Imports the DDEITEM_STRING record containing a string in a link result. */
+    void                importDdeItemString( SequenceInputStream& rStrm );
+
+    /** Returns the DDE item info needed by the XML formula parser. */
+    bool                getDdeItemInfo(
+                            css::sheet::DDEItemInfo& orItemInfo ) const;
+
+    /** Returns the complete DDE link data of this DDE item. */
+    bool                getDdeLinkData(
+                            OUString& orDdeServer,
+                            OUString& orDdeTopic,
+                            OUString& orDdeItem );
+
+private:
+    /** Sets the size of the result matrix. */
+    void                setResultSize( sal_Int32 nColumns, sal_Int32 nRows );
+
+private:
+    typedef Matrix< css::uno::Any > ResultMatrix;
+
+    const ExternalLink& mrParentLink;       /// External link this name belongs to.
+    ExternalNameModel   maExtNameModel;     /// Additional name data.
+    ResultMatrix        maResults;          /// DDE/OLE link results.
+    ResultMatrix::iterator maCurrIt;        /// Current position in result matrix.
+    css::uno::Reference< css::sheet::XDDELink >
+                        mxDdeLink;          /// Interface of a DDE link.
+    bool                mbDdeLinkCreated;   /// True = already tried to create the DDE link.
+};
+
+typedef std::shared_ptr< ExternalName > ExternalNameRef;
+
+/** Contains indexes for a range of sheets in the spreadsheet document. */
+class LinkSheetRange
+{
+public:
+    explicit     LinkSheetRange() { setDeleted(); }
+
+    /** Sets this struct to deleted state. */
+    void                setDeleted();
+    /** Sets this struct to "use current sheet" state. */
+    void                setSameSheet();
+    /** Sets the passed absolute sheet range to the members of this struct. */
+    void                setRange( sal_Int32 nFirst, sal_Int32 nLast );
+    /** Sets the passed external sheet cache range to the members of this struct. */
+    void                setExternalRange( sal_Int32 nDocLink, sal_Int32 nFirst, sal_Int32 nLast );
+
+    /** Returns true, if the sheet indexes are valid and different. */
+    bool         isDeleted() const { return mnFirst < 0; }
+    /** Returns true, if the sheet range points to an external document. */
+    bool         isExternal() const { return !isDeleted() && (meType == LINKSHEETRANGE_EXTERNAL); }
+    /** Returns true, if the sheet indexes are valid and different. */
+    bool         isSameSheet() const { return meType == LINKSHEETRANGE_SAMESHEET; }
+    /** Returns true, if the sheet indexes are valid and different. */
+    bool         is3dRange() const { return (0 <= mnFirst) && (mnFirst < mnLast); }
+
+    sal_Int32    getDocLinkIndex() const { return mnDocLink; }
+    sal_Int32    getFirstSheet() const { return mnFirst; }
+    sal_Int32    getLastSheet() const { return mnLast; }
+
+private:
+    enum LinkSheetRangeType
+    {
+        LINKSHEETRANGE_INTERNAL,    /// Sheet range in the own document.
+        LINKSHEETRANGE_EXTERNAL,    /// Sheet range in an external document.
+        LINKSHEETRANGE_SAMESHEET    /// Current sheet depending on context.
+    };
+
+    LinkSheetRangeType  meType;         /// Link sheet range type.
+    sal_Int32           mnDocLink;      /// Document link token index for external links.
+    sal_Int32           mnFirst;        /// Index of the first sheet or index of first external sheet cache.
+    sal_Int32           mnLast;         /// Index of the last sheet or index of last external sheet cache.
+};
+
+enum class ExternalLinkType
+{
+    Self,          /// Link refers to the current workbook.
+    Same,          /// Link refers to the current sheet.
+    External,      /// Link refers to an external spreadsheet document.
+    // let's ignore xlStartup and xlAlternateStartup for now
+    PathMissing,   /// Just for round-tripping (FIXME: Functionality not actually implemented after all.)
+    Library,       /// Link refers to an external add-in.
+    DDE,           /// DDE link.
+    OLE,           /// OLE link.
+    Unknown        /// Unknown or unsupported link type.
+};
+
+template< typename charT, typename traits >
+inline std::basic_ostream & operator <<(
+    std::basic_ostream & stream, const ExternalLinkType& type )
+{
+    switch (type)
+    {
+    case ExternalLinkType::Self: return stream << "self";
+    case ExternalLinkType::Same: return stream << "same";
+    case ExternalLinkType::External: return stream << "external";
+    case ExternalLinkType::PathMissing: return stream << "pathmissing";
+    case ExternalLinkType::Library: return stream << "library";
+    case ExternalLinkType::DDE: return stream << "dde";
+    case ExternalLinkType::OLE: return stream << "ole";
+    case ExternalLinkType::Unknown: return stream << "unknown";
+    default: return stream << static_cast(type);
+    }
+}
+
+class ExternalLink : public WorkbookHelper
+{
+public:
+    explicit            ExternalLink( const WorkbookHelper& rHelper );
+
+    /** Imports the externalReference element containing the relation identifier. */
+    void                importExternalReference( const AttributeList& rAttribs );
+    /** Imports the externalBook element describing an externally linked document. */
+    void                importExternalBook( const ::oox::core::Relations& rRelations, const AttributeList& rAttribs );
+    /** Imports the sheetName element containing the sheet name in an externally linked document. */
+    void                importSheetName( const AttributeList& rAttribs );
+    /** Imports the definedName element describing an external name. */
+    void                importDefinedName( const AttributeList& rAttribs );
+    /** Imports the ddeLink element describing a DDE link. */
+    void                importDdeLink( const AttributeList& rAttribs );
+    /** Imports the ddeItem element describing an item of a DDE link. */
+    ExternalNameRef     importDdeItem( const AttributeList& rAttribs );
+    /** Imports the oleLink element describing an OLE link. */
+    void                importOleLink( const ::oox::core::Relations& rRelations, const AttributeList& rAttribs );
+    /** Imports the oleItem element describing an object of an OLE link. */
+    ExternalNameRef     importOleItem( const AttributeList& rAttribs );
+
+    /** Imports the EXTERNALBOOK record describing an externally linked document, DDE link, or OLE link. */
+    void                importExternalBook( const ::oox::core::Relations& rRelations, SequenceInputStream& rStrm );
+    /** Imports the EXTSHEETNAMES record containing the sheet names in an externally linked document. */
+    void                importExtSheetNames( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALNAME record describing an external name. */
+    ExternalNameRef     importExternalName( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALREF record from the passed stream. */
+    void                importExternalRef( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALSELF record from the passed stream. */
+    void                importExternalSelf( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALSAME record from the passed stream. */
+    void                importExternalSame( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALADDIN record from the passed stream. */
+    void                importExternalAddin( SequenceInputStream& rStrm );
+
+    /** Sets the link type to 'self reference'. */
+    void         setSelfLinkType() { meLinkType = ExternalLinkType::Self; }
+
+    /** Returns the type of this external link. */
+    ExternalLinkType getLinkType() const { return meLinkType; }
+
+    /** Returns the relation identifier for the external link fragment. */
+    const OUString& getRelId() const { return maRelId; }
+    /** Returns the class name of this external link. */
+    const OUString& getClassName() const { return maClassName; }
+    /** Returns the target URL of this external link. */
+    const OUString& getTargetUrl() const { return maTargetUrl; }
+    /** Returns the link info needed by the XML formula parser. */
+    css::sheet::ExternalLinkInfo getLinkInfo() const;
+
+    /** Returns the type of the external library if this is a library link. */
+    FunctionLibraryType getFuncLibraryType() const;
+
+    /** Returns the token index of the external document. */
+    sal_Int32           getDocumentLinkIndex() const;
+    /** Returns the external sheet cache index or for the passed sheet. */
+    sal_Int32           getSheetCacheIndex( sal_Int32 nTabId ) const;
+    /** Returns the sheet cache of the external sheet with the passed index. */
+    css::uno::Reference< css::sheet::XExternalSheetCache >
+                        getSheetCache( sal_Int32 nTabId ) const;
+
+    /** Returns the internal sheet range or range of external sheet caches for the passed sheet range (BIFF only). */
+    void                getSheetRange( LinkSheetRange& orSheetRange, sal_Int32 nTabId1, sal_Int32 nTabId2 ) const;
+
+    /** Returns the external name with the passed zero-based index. */
+    ExternalNameRef     getNameByIndex( sal_Int32 nIndex ) const;
+
+private:
+    void                setExternalTargetUrl( const OUString& rTargetUrl, const OUString& rTargetType );
+    void                setDdeOleTargetUrl( const OUString& rClassName, const OUString& rTargetUrl, ExternalLinkType eLinkType );
+    void                parseExternalReference( const ::oox::core::Relations& rRelations, const OUString& rRelId );
+
+    /** Creates an external document link and the sheet cache for the passed sheet name. */
+    void                insertExternalSheet( const OUString& rSheetName );
+
+    ExternalNameRef     createExternalName();
+
+private:
+    ExternalLinkType    meLinkType;         /// Type of this link object.
+    FunctionLibraryType meFuncLibType;      /// Type of the function library, if link type is ExternalLinkType::Library.
+    OUString            maRelId;            /// Relation identifier for the external link fragment.
+    OUString            maClassName;        /// DDE service, OLE class name.
+    OUString            maTargetUrl;        /// Target link, DDE topic, OLE target.
+    css::uno::Reference< css::sheet::XExternalDocLink >
+                        mxDocLink;          /// Interface for an external document.
+    std::vector< sal_Int32 > maSheetCaches; /// External sheet cache indexes.
+    RefVector< ExternalName >  maExtNames;         /// Defined names in external document.
+};
+
+typedef std::shared_ptr< ExternalLink > ExternalLinkRef;
+
+/** Represents a REF entry in the BIFF12 EXTERNALSHEETS or in the BIFF8
+    EXTERNSHEET record.
+
+    This struct is used to map ref identifiers to external books (BIFF12:
+    EXTERNALREF records, BIFF8: EXTERNALBOOK records), and provides sheet
+    indexes into the sheet list of the external document.
+ */
+struct RefSheetsModel
+{
+    sal_Int32           mnExtRefId;         /// Zero-based index into list of external documents.
+    sal_Int32           mnTabId1;           /// Zero-based index to first sheet in external document.
+    sal_Int32           mnTabId2;           /// Zero-based index to last sheet in external document.
+
+    explicit            RefSheetsModel();
+
+    void                readBiff12Data( SequenceInputStream& rStrm );
+};
+
+class ExternalLinkBuffer : public WorkbookHelper
+{
+public:
+    explicit            ExternalLinkBuffer( const WorkbookHelper& rHelper );
+
+    /** Imports the externalReference element containing . */
+    ExternalLinkRef     importExternalReference( const AttributeList& rAttribs );
+
+    /** Imports the EXTERNALREF record from the passed stream. */
+    ExternalLinkRef     importExternalRef( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALSELF record from the passed stream. */
+    void                importExternalSelf( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALSAME record from the passed stream. */
+    void                importExternalSame( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALADDIN record from the passed stream. */
+    void                importExternalAddin( SequenceInputStream& rStrm );
+    /** Imports the EXTERNALSHEETS record from the passed stream. */
+    void                importExternalSheets( SequenceInputStream& rStrm );
+
+    /** Returns the sequence of link infos needed by the XML formula parser. */
+    css::uno::Sequence< css::sheet::ExternalLinkInfo >
+                        getLinkInfos() const;
+
+    /** Returns the external link for the passed reference identifier. */
+    ExternalLinkRef     getExternalLink( sal_Int32 nRefId, bool bUseRefSheets = true ) const;
+
+    /** Returns the sheet range for the specified reference (BIFF8 only). */
+    LinkSheetRange      getSheetRange( sal_Int32 nRefId ) const;
+
+private:
+    /** Creates a new external link and inserts it into the list of links. */
+    ExternalLinkRef     createExternalLink();
+
+    /** Returns the specified sheet indexes for a reference identifier. */
+    const RefSheetsModel* getRefSheets( sal_Int32 nRefId ) const;
+
+private:
+    typedef RefVector< ExternalLink >       ExternalLinkVec;
+    typedef ::std::vector< RefSheetsModel > RefSheetsModelVec;
+
+    ExternalLinkRef     mxSelfRef;          /// Implicit self reference at index 0.
+    ExternalLinkVec     maLinks;            /// List of link structures for all kinds of links.
+    ExternalLinkVec     maExtLinks;         /// Real external links needed for formula parser.
+    RefSheetsModelVec   maRefSheets;        /// Sheet indexes for reference ids.
+    bool                mbUseRefSheets;     /// True = use maRefSheets list (BIFF12 only).
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/externallinkfragment.hxx b/sc/source/filter/inc/externallinkfragment.hxx
new file mode 100644
index 000000000..f14ba0b0a
--- /dev/null
+++ b/sc/source/filter/inc/externallinkfragment.hxx
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 "excelhandlers.hxx"
+#include "externallinkbuffer.hxx"
+
+namespace oox::xls {
+
+/** This class implements importing the sheetData element in external sheets.
+
+    The sheetData element embedded in the externalBook element contains cached
+    cells from externally linked sheets.
+ */
+class ExternalSheetDataContext : public WorkbookContextBase
+{
+public:
+    explicit            ExternalSheetDataContext(
+                            WorkbookFragmentBase& rFragment,
+                            const css::uno::Reference< css::sheet::XExternalSheetCache >& rxSheetCache );
+
+protected:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void        onCharacters( const OUString& rChars ) override;
+
+    virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override;
+
+private:
+    /** Imports cell settings from a c element. */
+    void                importCell( const AttributeList& rAttribs );
+
+    /** Imports the EXTCELL_BLANK from the passed stream. */
+    void                importExtCellBlank( SequenceInputStream& rStrm );
+    /** Imports the EXTCELL_BOOL from the passed stream. */
+    void                importExtCellBool( SequenceInputStream& rStrm );
+    /** Imports the EXTCELL_DOUBLE from the passed stream. */
+    void                importExtCellDouble( SequenceInputStream& rStrm );
+    /** Imports the EXTCELL_ERROR from the passed stream. */
+    void                importExtCellError( SequenceInputStream& rStrm );
+    /** Imports the EXTCELL_STRING from the passed stream. */
+    void                importExtCellString( SequenceInputStream& rStrm );
+
+    /** Sets the passed cell value to the current position in the sheet cache. */
+    void                setCellValue( const css::uno::Any& rValue );
+
+private:
+    css::uno::Reference< css::sheet::XExternalSheetCache >
+                        mxSheetCache;               /// The sheet cache used to store external cell values.
+    ScAddress           maCurrPos;                  /// Position of current cell.
+    sal_Int32           mnCurrType;                 /// Data type of current cell.
+};
+
+class ExternalLinkFragment : public WorkbookFragmentBase
+{
+public:
+    explicit            ExternalLinkFragment(
+                            const WorkbookHelper& rHelper,
+                            const OUString& rFragmentPath,
+                            ExternalLink& rExtLink );
+
+protected:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void        onCharacters( const OUString& rChars ) override;
+    virtual void        onEndElement() override;
+
+    virtual ::oox::core::ContextHandlerRef onCreateRecordContext( sal_Int32 nRecId, SequenceInputStream& rStrm ) override;
+
+    virtual const ::oox::core::RecordInfo* getRecordInfos() const override;
+
+private:
+    ::oox::core::ContextHandlerRef createSheetDataContext( sal_Int32 nSheetId );
+
+private:
+    ExternalLink&       mrExtLink;
+    ExternalNameRef     mxExtName;
+    OUString     maResultValue;
+    sal_Int32           mnResultType;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/extlstcontext.hxx b/sc/source/filter/inc/extlstcontext.hxx
new file mode 100644
index 000000000..8635c6029
--- /dev/null
+++ b/sc/source/filter/inc/extlstcontext.hxx
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "excelhandlers.hxx"
+#include 
+#include "condformatbuffer.hxx"
+
+#include 
+#include 
+
+extern sal_Int32 rStyleIdx; // Holds index of the   style (Will be reset by finalize import)
+
+struct ScDataBarFormatData;
+namespace oox { class AttributeList; }
+namespace oox::xls { class WorkbookFragment; }
+namespace oox::xls { class WorksheetFragment; }
+
+namespace oox::xls {
+
+class ExtCfRuleContext : public WorksheetContextBase
+{
+public:
+    explicit ExtCfRuleContext( WorksheetContextBase& rFragment, ScDataBarFormatData* pDataBar );
+
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void        onStartElement( const AttributeList& rAttribs ) override;
+
+private:
+    ScDataBarFormatData* mpTarget;
+
+    bool mbFirstEntry;
+};
+
+struct ExtCondFormatRuleModel
+{
+    sal_Int32 nPriority;
+    ScConditionMode eOperator;
+    OUString aFormula;
+    OUString aStyle;
+};
+
+class ExtConditionalFormattingContext : public WorksheetContextBase
+{
+public:
+    explicit ExtConditionalFormattingContext(WorksheetContextBase& rFragment);
+
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void onStartElement( const AttributeList& rAttribs ) override;
+    virtual void onCharacters(const OUString& rCharacters) override;
+    virtual void onEndElement() override;
+
+private:
+    ExtCondFormatRuleModel maModel;
+    sal_Int32 nFormulaCount;
+    OUString aChars; // Characters of between xml elements.
+    sal_Int32 nPriority; // Priority of last cfRule element.
+    ScConditionMode eOperator; // Used only when cfRule type is "cellIs"
+    bool isPreviousElementF;   // Used to distinguish alone  from  and 
+    std::vector > maEntries;
+    std::unique_ptr mpCurrentRule;
+    std::vector maPriorities;
+    std::vector maModels;
+};
+
+/**
+ * Handle ExtLst entries in xlsx. These entries are a way to extend the standard
+ * without actually changing it
+ *
+ * Needed right now for data bars
+ *
+ * ExtLstLocalContext is for the entry in the datastructure that needs to be extended
+ */
+class ExtLstLocalContext : public WorksheetContextBase
+{
+public:
+    explicit ExtLstLocalContext( WorksheetContextBase& rFragment, ScDataBarFormatData* pTarget ); // until now a ExtLst always extends an existing entry
+
+protected:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void        onStartElement( const AttributeList& rAttribs ) override;
+    virtual void        onCharacters( const OUString& rChars ) override;
+
+private:
+    ScDataBarFormatData* mpTarget;
+};
+
+/**
+ * A single ext entry. Will be skipped until the actual entry with the correct uri is found
+ */
+class ExtGlobalContext : public WorksheetContextBase
+{
+public:
+    explicit ExtGlobalContext( WorksheetContextBase& rFragment );
+
+protected:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void        onStartElement( const AttributeList& rAttribs ) override;
+
+private:
+};
+
+/**
+ * Used for the actual ExtLst containing the new extended definition.
+ * Uses the saved data from the ExtLstLocalContext
+ */
+class ExtLstGlobalContext : public WorksheetContextBase
+{
+public:
+    explicit ExtLstGlobalContext( WorksheetFragment& rFragment );
+
+protected:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+};
+
+class ExtGlobalWorkbookContext : public WorkbookContextBase
+{
+public:
+    explicit ExtGlobalWorkbookContext( WorkbookContextBase& rFragment );
+
+protected:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+    virtual void        onStartElement( const AttributeList& rAttribs ) override;
+
+private:
+};
+
+class ExtLstGlobalWorkbookContext : public WorkbookContextBase
+{
+public:
+    explicit ExtLstGlobalWorkbookContext( WorkbookFragment& rFragment );
+
+protected:
+    virtual ::oox::core::ContextHandlerRef onCreateContext( sal_Int32 nElement, const AttributeList& rAttribs ) override;
+};
+
+} //namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/fapihelper.hxx b/sc/source/filter/inc/fapihelper.hxx
new file mode 100644
index 000000000..7449f1193
--- /dev/null
+++ b/sc/source/filter/inc/fapihelper.hxx
@@ -0,0 +1,294 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+#include 
+#include "ftools.hxx"
+
+namespace com::sun::star {
+    namespace lang { class XMultiServiceFactory; }
+}
+
+namespace com::sun::star::beans { struct NamedValue; }
+namespace com::sun::star::beans { class XPropertySet; }
+namespace com::sun::star::beans { class XMultiPropertySet; }
+
+namespace comphelper { class IDocPasswordVerifier; }
+
+// Static helper functions ====================================================
+
+class SfxMedium;
+class SfxObjectShell;
+
+/** Static API helper functions. */
+class ScfApiHelper
+{
+public:
+    /** Converts a non-empty vector into a UNO sequence containing elements of the same type. */
+    template< typename Type >
+    static css::uno::Sequence< Type >
+                            VectorToSequence( const ::std::vector< Type >& rVector );
+
+    /** Returns the service name provided via the XServiceName interface, or an empty string on error. */
+    static OUString GetServiceName( const css::uno::Reference< css::uno::XInterface >& xInt );
+
+    /** Returns the multi service factory from a document shell. */
+    static css::uno::Reference< css::lang::XMultiServiceFactory > GetServiceFactory( const SfxObjectShell* pShell );
+
+    /** Creates an instance from the passed service name, using the passed service factory. */
+    static css::uno::Reference< css::uno::XInterface > CreateInstance(
+                            const css::uno::Reference< css::lang::XMultiServiceFactory >& xFactory,
+                            const OUString& rServiceName );
+
+    /** Creates an instance from the passed service name, using the service factory of the passed object. */
+    static css::uno::Reference< css::uno::XInterface > CreateInstance(
+                            const SfxObjectShell* pShell,
+                            const OUString& rServiceName );
+
+    /** Creates an instance from the passed service name, using the process service factory. */
+    static css::uno::Reference< css::uno::XInterface > CreateInstance( const OUString& rServiceName );
+
+    /** Opens a password dialog and returns the encryption data.
+        @return  The encryption data or an empty sequence on 'Cancel' or any error. */
+    static css::uno::Sequence< css::beans::NamedValue > QueryEncryptionDataForMedium( SfxMedium& rMedium,
+                            ::comphelper::IDocPasswordVerifier& rVerifier,
+                            const ::std::vector< OUString >* pDefaultPasswords );
+};
+
+template< typename Type >
+css::uno::Sequence< Type > ScfApiHelper::VectorToSequence( const ::std::vector< Type >& rVector )
+{
+    OSL_ENSURE( !rVector.empty(), "ScfApiHelper::VectorToSequence - vector is empty" );
+    return css::uno::Sequence(rVector.data(), static_cast< sal_Int32 >(rVector.size()));
+}
+
+// Property sets ==============================================================
+
+/** A wrapper for a UNO property set.
+
+    This class provides functions to silently get and set properties (without
+    exceptions, without the need to check validity of the UNO property set).
+
+    An instance is constructed with the reference to a UNO property set or any
+    other interface (the constructor will query for the XPropertySet interface
+    then). The reference to the property set will be kept as long as the
+    instance of this class is alive.
+
+    The functions GetProperties() and SetProperties() try to handle all passed
+    values at once, using the XMultiPropertySet interface. If the
+    implementation does not support the XMultiPropertySet interface, all
+    properties are handled separately in a loop.
+ */
+class ScfPropertySet
+{
+public:
+    explicit     ScfPropertySet() {}
+    /** Constructs a property set wrapper with the passed UNO property set. */
+    explicit     ScfPropertySet( const css::uno::Reference< css::beans::XPropertySet > & xPropSet ) { Set( xPropSet ); }
+    /** Constructs a property set wrapper after querying the XPropertySet interface. */
+    template< typename InterfaceType >
+    explicit     ScfPropertySet( const css::uno::Reference< InterfaceType >& xInterface ) { Set( xInterface ); }
+
+                        ~ScfPropertySet();
+    //TODO:
+    ScfPropertySet(ScfPropertySet const &) = default;
+    ScfPropertySet(ScfPropertySet &&) = default;
+    ScfPropertySet & operator =(ScfPropertySet const &) = default;
+    ScfPropertySet & operator =(ScfPropertySet &&) = default;
+
+    /** Sets the passed UNO property set and releases the old UNO property set. */
+    void                Set( css::uno::Reference< css::beans::XPropertySet > const & xPropSet );
+    /** Queries the passed interface for an XPropertySet and releases the old UNO property set. */
+    template< typename InterfaceType >
+    void         Set( css::uno::Reference< InterfaceType > xInterface )
+                            { Set( css::uno::Reference< css::beans::XPropertySet >( xInterface, css::uno::UNO_QUERY ) ); }
+
+    /** Returns true, if the contained XPropertySet interface is valid. */
+    bool         Is() const { return mxPropSet.is(); }
+
+    /** Returns the contained XPropertySet interface. */
+    const css::uno::Reference< css::beans::XPropertySet >& GetApiPropertySet() const { return mxPropSet; }
+
+    /** Returns the service name provided via the XServiceName interface, or an empty string on error. */
+    OUString     GetServiceName() const;
+
+    // Get properties ---------------------------------------------------------
+
+    /** Returns true, if the property set contains the specified property. */
+    bool                HasProperty( const OUString& rPropName ) const;
+
+    /** Gets the specified property from the property set.
+        @return  true, if the Any could be filled with the property value. */
+    bool                GetAnyProperty( css::uno::Any& rValue, const OUString& rPropName ) const;
+
+    /** Gets the specified property from the property set.
+        @return  true, if the passed variable could be filled with the property value. */
+    template< typename Type >
+    bool         GetProperty( Type& rValue, const OUString& rPropName ) const
+                            { css::uno::Any aAny; return GetAnyProperty( aAny, rPropName ) && (aAny >>= rValue); }
+
+    /** Gets the specified Boolean property from the property set.
+        @return  true = property contains true; false = property contains false or error occurred. */
+    bool                GetBoolProperty( const OUString& rPropName ) const;
+
+    /** Gets the specified Boolean property from the property set. */
+    OUString       GetStringProperty( const OUString& rPropName ) const;
+
+    /** Gets the specified color property from the property set.
+        @return  true, if the passed color variable could be filled with the property value. */
+    bool                GetColorProperty( Color& rColor, const OUString& rPropName ) const;
+
+    /** Gets the specified properties from the property set. Tries to use the XMultiPropertySet interface.
+        @param rPropNames  The property names. MUST be ordered alphabetically.
+        @param rValues  The related property values. */
+    void                GetProperties( css::uno::Sequence< css::uno::Any >& rValues, const css::uno::Sequence< OUString >& rPropNames ) const;
+
+    // Set properties ---------------------------------------------------------
+
+    /** Puts the passed Any into the property set. */
+    void                SetAnyProperty( const OUString& rPropName, const css::uno::Any& rValue );
+
+    /** Puts the passed value into the property set. */
+    template< typename Type >
+    void         SetProperty( const OUString& rPropName, const Type& rValue )
+                            { SetAnyProperty( rPropName, css::uno::Any( rValue ) ); }
+
+    /** Puts the passed Boolean value into the property set. */
+    void         SetBoolProperty( const OUString& rPropName, bool bValue )
+                            { SetAnyProperty( rPropName, css::uno::Any( bValue ) ); }
+
+    /** Puts the passed string into the property set. */
+    void         SetStringProperty( const OUString& rPropName, const OUString& rValue )
+                            { SetProperty( rPropName, rValue ); }
+
+    /** Puts the passed color into the property set. */
+    void         SetColorProperty( const OUString& rPropName, const Color& rColor )
+                            { SetProperty( rPropName, sal_Int32( rColor ) ); }
+
+    /** Puts the passed properties into the property set. Tries to use the XMultiPropertySet interface.
+        @param rPropNames  The property names. MUST be ordered alphabetically.
+        @param rValues  The related property values. */
+    void                SetProperties( const css::uno::Sequence< OUString > & rPropNames, const css::uno::Sequence< css::uno::Any >& rValues );
+
+private:
+    css::uno::Reference< css::beans::XPropertySet >       mxPropSet;          /// The mandatory property set interface.
+    css::uno::Reference< css::beans::XMultiPropertySet >  mxMultiPropSet;     /// The optional multi property set interface.
+};
+
+/** Generic helper class for reading from and writing to property sets.
+
+    Usage:
+    1)  Call the constructor with a null-terminated array of ASCII strings.
+    2a) Read properties from a property set: Call the ReadFromPropertySet()
+        function, then get the properties with the ReadValue() functions or the
+        operator>> stream operator. The properties are returned in order of the
+        array of property names passed in the constructor.
+    2b) Write properties to a property set: Call InitializeWrite() to start a
+        new cycle. Set the values with the WriteValue() functions or the
+        operator<< stream operator. The order of the properties is equal to the
+        array of property names passed in the constructor. Finally, call the
+        WriteToPropertySet() function.
+ */
+class ScfPropSetHelper
+{
+public:
+    /** @param ppPropNames  A null-terminated array of ASCII property names. */
+    explicit            ScfPropSetHelper( const char* const* ppcPropNames );
+
+    // read properties --------------------------------------------------------
+
+    /** Reads all values from the passed property set. */
+    void                ReadFromPropertySet( const ScfPropertySet& rPropSet );
+
+    /** Reads the next value from the value sequence. */
+    template< typename Type >
+    void                ReadValue( Type& rValue );
+    /** Reads an Any from the value sequence. */
+    void                ReadValue( css::uno::Any& rAny );
+    /** Reads a color value from the value sequence. */
+    void                ReadValue( Color& rColor );
+    /** Reads a C++ boolean value from the value sequence. */
+    void                ReadValue( bool& rbValue );
+
+    // write properties -------------------------------------------------------
+
+    /** Must be called before reading or storing property values in the helper. */
+    void                InitializeWrite();
+
+    /** Writes the next value to the value sequence. */
+    template< typename Type >
+    void                WriteValue( const Type& rValue );
+    /** Writes an Any to the value sequence. */
+    void                WriteValue( const css::uno::Any& rAny );
+    /** Writes a color value to the value sequence. */
+    void         WriteValue( const Color& rColor )
+                            { WriteValue( sal_Int32( rColor ) ); }
+    /** Writes a C++ boolean value to the value sequence. */
+    void                WriteValue( bool rbValue );
+
+    /** Writes all values to the passed property set. */
+    void                WriteToPropertySet( ScfPropertySet& rPropSet ) const;
+
+private:
+    /** Returns a pointer to the next Any to be written to. */
+    css::uno::Any*             GetNextAny();
+
+private:
+    css::uno::Sequence< OUString >       maNameSeq;          /// Sequence of property names.
+    css::uno::Sequence< css::uno::Any >  maValueSeq;         /// Sequence of property values.
+    ScfInt32Vec         maNameOrder;        /// Maps initial order to alphabetical order.
+    size_t              mnNextIdx;          /// Counter for next Any to be processed.
+};
+
+template< typename Type >
+void ScfPropSetHelper::ReadValue( Type& rValue )
+{
+    css::uno::Any* pAny = GetNextAny();
+    if (pAny)
+        *pAny >>= rValue;
+}
+
+template< typename Type >
+void ScfPropSetHelper::WriteValue( const Type& rValue )
+{
+    css::uno::Any* pAny = GetNextAny();
+    if( pAny )
+        *pAny <<= rValue;
+}
+
+template< typename Type >
+ScfPropSetHelper& operator>>( ScfPropSetHelper& rPropSetHelper, Type& rValue )
+{
+    rPropSetHelper.ReadValue( rValue );
+    return rPropSetHelper;
+}
+
+template< typename Type >
+ScfPropSetHelper& operator<<( ScfPropSetHelper& rPropSetHelper, const Type& rValue )
+{
+    rPropSetHelper.WriteValue( rValue );
+    return rPropSetHelper;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/flttypes.hxx b/sc/source/filter/inc/flttypes.hxx
new file mode 100644
index 000000000..9863278c0
--- /dev/null
+++ b/sc/source/filter/inc/flttypes.hxx
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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
+
+enum BiffTyp
+{
+    BiffX = 0x0000,
+    Biff2 = 0x2000, Biff2M = 0x2002, Biff2C = 0x2004,
+    Biff3 = 0x3000, Biff3W = 0x3001, Biff3M = 0x3002, Biff3C = 0x3004,
+    Biff4 = 0x4000, Biff4W = 0x4001, Biff4M = 0x4002, Biff4C = 0x4004,
+    Biff5 = 0x5000, Biff5W = 0x5001, Biff5V = 0x5002, Biff5C = 0x5004, Biff5M4 = 0x5008,
+    Biff8 = 0x8000, Biff8W = 0x8001, Biff8V = 0x8002, Biff8C = 0x8004, Biff8M4 = 0x8008
+};
+
+enum class Lotus123Typ
+{
+    X,
+    WK3,
+    WK4,
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/formel.hxx b/sc/source/filter/inc/formel.hxx
new file mode 100644
index 000000000..2c01a560e
--- /dev/null
+++ b/sc/source/filter/inc/formel.hxx
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+
+#include "tokstack.hxx"
+#include "xiroot.hxx"
+
+#include 
+#include 
+#include 
+
+namespace svl {
+
+class SharedStringPool;
+
+}
+
+class XclImpStream;
+class ScTokenArray;
+
+enum class ConvErr
+{
+    OK = 0,
+    Ni,      // unimplemented/unknown opcode occurred
+    Count    // did not get all bytes of formula
+};
+
+enum FORMULA_TYPE
+{
+    FT_CellFormula,
+    FT_RangeName,
+    FT_SharedFormula,
+    FT_CondFormat
+};
+
+class ScRangeListTabs : protected XclImpRoot
+{
+    typedef ::std::vector RangeListType;
+    typedef ::std::map TabRangeType;
+    TabRangeType m_TabRanges;
+    RangeListType::const_iterator maItrCur;
+    RangeListType::const_iterator maItrCurEnd;
+
+public:
+    ScRangeListTabs( const XclImpRoot& rRoot );
+    ~ScRangeListTabs();
+
+    void Append( const ScAddress& aSRD, SCTAB nTab );
+    void Append( const ScRange& aCRD, SCTAB nTab );
+
+    const ScRange* First ( SCTAB nTab );
+    const ScRange* Next ();
+
+    bool HasRanges () const { return !m_TabRanges.empty(); }
+};
+
+class ConverterBase
+{
+protected:
+    TokenPool           aPool;          // user token + predefined token
+    TokenStack          aStack;
+    ScAddress           aEingPos;
+
+    ConverterBase( svl::SharedStringPool& rSPool );
+    virtual             ~ConverterBase();
+
+    void                Reset();
+};
+
+class ExcelConverterBase : public ConverterBase
+{
+protected:
+    ExcelConverterBase( svl::SharedStringPool& rSPool );
+    virtual             ~ExcelConverterBase() override;
+
+public:
+    void                Reset();
+    void                Reset( const ScAddress& rEingPos );
+
+    virtual ConvErr     Convert( std::unique_ptr& rpErg, XclImpStream& rStrm, std::size_t nFormulaLen,
+                                 bool bAllowArrays, const FORMULA_TYPE eFT = FT_CellFormula ) = 0;
+    virtual ConvErr     Convert( ScRangeListTabs&, XclImpStream& rStrm, std::size_t nFormulaLen, SCTAB nTab,
+                                    const FORMULA_TYPE eFT = FT_CellFormula ) = 0;
+};
+
+class LotusConverterBase : public ConverterBase
+{
+protected:
+    SvStream&           aIn;
+    sal_Int32           nBytesLeft;
+
+    inline void         Ignore( const tools::Long nSeekRel );
+    inline void         Read( sal_uInt8& nByte );
+    inline void         Read( sal_uInt16& nUINT16 );
+    inline void         Read( sal_Int16& nINT16 );
+    inline void         Read( double& fDouble );
+    inline void         Read( sal_uInt32& nUINT32 );
+
+    LotusConverterBase( SvStream& rStr, svl::SharedStringPool& rSPool );
+    virtual             ~LotusConverterBase() override;
+
+public:
+    void                Reset( const ScAddress& rEingPos );
+
+    virtual void        Convert( std::unique_ptr& rpErg, sal_Int32& nRest ) = 0;
+
+    bool good() const { return aIn.good(); }
+
+protected:
+    using               ConverterBase::Reset;
+};
+
+inline void LotusConverterBase::Ignore( const tools::Long nSeekRel )
+{
+    aIn.SeekRel( nSeekRel );
+    nBytesLeft -= nSeekRel;
+}
+
+inline void LotusConverterBase::Read( sal_uInt8& nByte )
+{
+    aIn.ReadUChar( nByte );
+    if (aIn.good())
+        nBytesLeft--;
+    else
+    {
+        // SvStream::ReadUChar() does not init a single char on failure. This
+        // behaviour is even tested in a unit test.
+        nByte = 0;
+        nBytesLeft = -1;    // bail out early
+    }
+}
+
+inline void LotusConverterBase::Read( sal_uInt16& nUINT16 )
+{
+    aIn.ReadUInt16( nUINT16 );
+    if (aIn.good())
+        nBytesLeft -= 2;
+    else
+        nBytesLeft = -1;    // bail out early
+}
+
+inline void LotusConverterBase::Read( sal_Int16& nINT16 )
+{
+    aIn.ReadInt16( nINT16 );
+    if (aIn.good())
+        nBytesLeft -= 2;
+    else
+        nBytesLeft = -1;    // bail out early
+}
+
+inline void LotusConverterBase::Read( double& fDouble )
+{
+    aIn.ReadDouble( fDouble );
+    if (aIn.good())
+        nBytesLeft -= 8;
+    else
+        nBytesLeft = -1;    // bail out early
+}
+
+inline void LotusConverterBase::Read( sal_uInt32& nUINT32 )
+{
+    aIn.ReadUInt32( nUINT32 );
+    if (aIn.good())
+        nBytesLeft -= 4;
+    else
+        nBytesLeft = -1;    // bail out early
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/formulabase.hxx b/sc/source/filter/inc/formulabase.hxx
new file mode 100644
index 000000000..aa0643f5a
--- /dev/null
+++ b/sc/source/filter/inc/formulabase.hxx
@@ -0,0 +1,780 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+#include 
+#include "workbookhelper.hxx"
+
+namespace com::sun::star {
+    namespace lang { class XMultiServiceFactory; }
+    namespace sheet { class XFormulaParser; }
+}
+
+namespace oox { template< typename Type > class Matrix; }
+namespace com::sun::star::sheet { struct FormulaOpCodeMapEntry; }
+namespace oox { class SequenceInputStream; }
+namespace oox::xls { struct BinAddress; }
+class ScRangeList;
+
+namespace oox::xls {
+
+// Constants ==================================================================
+
+const size_t BIFF_TOKARR_MAXLEN                 = 4096;     /// Maximum size of a token array.
+
+// token class flags ----------------------------------------------------------
+
+const sal_uInt8 BIFF_TOKCLASS_MASK              = 0x60;
+const sal_uInt8 BIFF_TOKCLASS_NONE              = 0x00;     /// 00-1F: Base tokens.
+const sal_uInt8 BIFF_TOKCLASS_REF               = 0x20;     /// 20-3F: Reference class tokens.
+const sal_uInt8 BIFF_TOKCLASS_VAL               = 0x40;     /// 40-5F: Value class tokens.
+const sal_uInt8 BIFF_TOKCLASS_ARR               = 0x60;     /// 60-7F: Array class tokens.
+
+const sal_uInt8 BIFF_TOKFLAG_INVALID            = 0x80;     /// This bit must be null for a valid token identifier.
+
+// base token identifiers -----------------------------------------------------
+
+const sal_uInt8 BIFF_TOKID_MASK                 = 0x1F;
+
+const sal_uInt8 BIFF_TOKID_NONE                 = 0x00;     /// Placeholder for invalid token id.
+const sal_uInt8 BIFF_TOKID_EXP                  = 0x01;     /// Array or shared formula reference.
+const sal_uInt8 BIFF_TOKID_TBL                  = 0x02;     /// Multiple operation reference.
+const sal_uInt8 BIFF_TOKID_ADD                  = 0x03;     /// Addition operator.
+const sal_uInt8 BIFF_TOKID_SUB                  = 0x04;     /// Subtraction operator.
+const sal_uInt8 BIFF_TOKID_MUL                  = 0x05;     /// Multiplication operator.
+const sal_uInt8 BIFF_TOKID_DIV                  = 0x06;     /// Division operator.
+const sal_uInt8 BIFF_TOKID_POWER                = 0x07;     /// Power operator.
+const sal_uInt8 BIFF_TOKID_CONCAT               = 0x08;     /// String concatenation operator.
+const sal_uInt8 BIFF_TOKID_LT                   = 0x09;     /// Less than operator.
+const sal_uInt8 BIFF_TOKID_LE                   = 0x0A;     /// Less than or equal operator.
+const sal_uInt8 BIFF_TOKID_EQ                   = 0x0B;     /// Equal operator.
+const sal_uInt8 BIFF_TOKID_GE                   = 0x0C;     /// Greater than or equal operator.
+const sal_uInt8 BIFF_TOKID_GT                   = 0x0D;     /// Greater than operator.
+const sal_uInt8 BIFF_TOKID_NE                   = 0x0E;     /// Not equal operator.
+const sal_uInt8 BIFF_TOKID_ISECT                = 0x0F;     /// Intersection operator.
+const sal_uInt8 BIFF_TOKID_LIST                 = 0x10;     /// List operator.
+const sal_uInt8 BIFF_TOKID_RANGE                = 0x11;     /// Range operator.
+const sal_uInt8 BIFF_TOKID_UPLUS                = 0x12;     /// Unary plus.
+const sal_uInt8 BIFF_TOKID_UMINUS               = 0x13;     /// Unary minus.
+const sal_uInt8 BIFF_TOKID_PERCENT              = 0x14;     /// Percent sign.
+const sal_uInt8 BIFF_TOKID_PAREN                = 0x15;     /// Parentheses.
+const sal_uInt8 BIFF_TOKID_MISSARG              = 0x16;     /// Missing argument.
+const sal_uInt8 BIFF_TOKID_STR                  = 0x17;     /// String constant.
+const sal_uInt8 BIFF_TOKID_NLR                  = 0x18;     /// Natural language reference (NLR).
+const sal_uInt8 BIFF_TOKID_ATTR                 = 0x19;     /// Special attribute.
+const sal_uInt8 BIFF_TOKID_SHEET                = 0x1A;     /// Start of a sheet reference (BIFF2-BIFF4).
+const sal_uInt8 BIFF_TOKID_ENDSHEET             = 0x1B;     /// End of a sheet reference (BIFF2-BIFF4).
+const sal_uInt8 BIFF_TOKID_ERR                  = 0x1C;     /// Error constant.
+const sal_uInt8 BIFF_TOKID_BOOL                 = 0x1D;     /// Boolean constant.
+const sal_uInt8 BIFF_TOKID_INT                  = 0x1E;     /// Integer constant.
+const sal_uInt8 BIFF_TOKID_NUM                  = 0x1F;     /// Floating-point constant.
+
+// base identifiers of classified tokens --------------------------------------
+
+const sal_uInt8 BIFF_TOKID_ARRAY                = 0x00;     /// Array constant.
+const sal_uInt8 BIFF_TOKID_FUNC                 = 0x01;     /// Function, fixed number of arguments.
+const sal_uInt8 BIFF_TOKID_FUNCVAR              = 0x02;     /// Function, variable number of arguments.
+const sal_uInt8 BIFF_TOKID_NAME                 = 0x03;     /// Defined name.
+const sal_uInt8 BIFF_TOKID_REF                  = 0x04;     /// 2D cell reference.
+const sal_uInt8 BIFF_TOKID_AREA                 = 0x05;     /// 2D area reference.
+const sal_uInt8 BIFF_TOKID_MEMAREA              = 0x06;     /// Constant reference subexpression.
+const sal_uInt8 BIFF_TOKID_MEMERR               = 0x07;     /// Deleted reference subexpression.
+const sal_uInt8 BIFF_TOKID_MEMNOMEM             = 0x08;     /// Constant reference subexpression without result.
+const sal_uInt8 BIFF_TOKID_MEMFUNC              = 0x09;     /// Variable reference subexpression.
+const sal_uInt8 BIFF_TOKID_REFERR               = 0x0A;     /// Deleted 2D cell reference.
+const sal_uInt8 BIFF_TOKID_AREAERR              = 0x0B;     /// Deleted 2D area reference.
+const sal_uInt8 BIFF_TOKID_REFN                 = 0x0C;     /// Relative 2D cell reference (in names).
+const sal_uInt8 BIFF_TOKID_AREAN                = 0x0D;     /// Relative 2D area reference (in names).
+const sal_uInt8 BIFF_TOKID_MEMAREAN             = 0x0E;     /// Reference subexpression (in names).
+const sal_uInt8 BIFF_TOKID_MEMNOMEMN            = 0x0F;     /// Reference subexpression (in names) without result.
+const sal_uInt8 BIFF_TOKID_FUNCCE               = 0x18;
+const sal_uInt8 BIFF_TOKID_NAMEX                = 0x19;     /// External reference.
+const sal_uInt8 BIFF_TOKID_REF3D                = 0x1A;     /// 3D cell reference.
+const sal_uInt8 BIFF_TOKID_AREA3D               = 0x1B;     /// 3D area reference.
+const sal_uInt8 BIFF_TOKID_REFERR3D             = 0x1C;     /// Deleted 3D cell reference.
+const sal_uInt8 BIFF_TOKID_AREAERR3D            = 0x1D;     /// Deleted 3D area reference
+
+// specific token constants ---------------------------------------------------
+
+const sal_uInt8 BIFF_TOK_ARRAY_DOUBLE           = 0;        /// Double value in an array.
+const sal_uInt8 BIFF_TOK_ARRAY_STRING           = 1;        /// String value in an array.
+const sal_uInt8 BIFF_TOK_ARRAY_BOOL             = 2;        /// Boolean value in an array.
+const sal_uInt8 BIFF_TOK_ARRAY_ERROR            = 4;        /// Error code in an array.
+
+const sal_uInt8 BIFF_TOK_BOOL_FALSE             = 0;        /// FALSE value of a tBool token.
+const sal_uInt8 BIFF_TOK_BOOL_TRUE              = 1;        /// TRUE value of a tBool token.
+
+const sal_uInt8 BIFF_TOK_ATTR_VOLATILE          = 0x01;     /// Volatile function.
+const sal_uInt8 BIFF_TOK_ATTR_IF                = 0x02;     /// Start of true condition in IF function.
+const sal_uInt8 BIFF_TOK_ATTR_CHOOSE            = 0x04;     /// Jump array of CHOOSE function.
+const sal_uInt8 BIFF_TOK_ATTR_SKIP              = 0x08;     /// Skip tokens.
+const sal_uInt8 BIFF_TOK_ATTR_SUM               = 0x10;     /// SUM function with one parameter.
+const sal_uInt8 BIFF_TOK_ATTR_ASSIGN            = 0x20;     /// BASIC style assignment.
+const sal_uInt8 BIFF_TOK_ATTR_SPACE             = 0x40;     /// Spaces in formula representation.
+const sal_uInt8 BIFF_TOK_ATTR_SPACE_VOLATILE    = 0x41;     /// Leading spaces and volatile formula.
+const sal_uInt8 BIFF_TOK_ATTR_IFERROR           = 0x80;     /// Start of condition in IFERROR function (BIFF12 only).
+
+const sal_uInt8 BIFF_TOK_ATTR_SPACE_SP          = 0x00;     /// Spaces before next token.
+const sal_uInt8 BIFF_TOK_ATTR_SPACE_BR          = 0x01;     /// Line breaks before next token.
+const sal_uInt8 BIFF_TOK_ATTR_SPACE_SP_OPEN     = 0x02;     /// Spaces before opening parenthesis.
+const sal_uInt8 BIFF_TOK_ATTR_SPACE_BR_OPEN     = 0x03;     /// Line breaks before opening parenthesis.
+const sal_uInt8 BIFF_TOK_ATTR_SPACE_SP_CLOSE    = 0x04;     /// Spaces before closing parenthesis.
+const sal_uInt8 BIFF_TOK_ATTR_SPACE_BR_CLOSE    = 0x05;     /// Line breaks before closing parenthesis.
+const sal_uInt8 BIFF_TOK_ATTR_SPACE_SP_PRE      = 0x06;     /// Spaces before formula (BIFF3).
+
+const sal_uInt16 BIFF_TOK_FUNCVAR_CMD           = 0x8000;   /// Macro command.
+const sal_uInt16 BIFF_TOK_FUNCVAR_FUNCIDMASK    = 0x7FFF;   /// Mask for function/command index.
+const sal_uInt8 BIFF_TOK_FUNCVAR_CMDPROMPT      = 0x80;     /// User prompt for macro commands.
+const sal_uInt8 BIFF_TOK_FUNCVAR_COUNTMASK      = 0x7F;     /// Mask for parameter count.
+
+const sal_uInt16 BIFF12_TOK_REF_COLMASK         = 0x3FFF;   /// Mask to extract column from reference (BIFF12).
+const sal_Int32 BIFF12_TOK_REF_ROWMASK          = 0xFFFFF;  /// Mask to extract row from reference (BIFF12).
+const sal_uInt16 BIFF12_TOK_REF_COLREL          = 0x4000;   /// True = column is relative (BIFF12).
+const sal_uInt16 BIFF12_TOK_REF_ROWREL          = 0x8000;   /// True = row is relative (BIFF12).
+
+const sal_uInt16 BIFF_TOK_REF_COLMASK           = 0x00FF;   /// Mask to extract BIFF8 column from reference.
+const sal_uInt16 BIFF_TOK_REF_ROWMASK           = 0x3FFF;   /// Mask to extract BIFF2-BIFF5 row from reference.
+const sal_uInt16 BIFF_TOK_REF_COLREL            = 0x4000;   /// True = column is relative.
+const sal_uInt16 BIFF_TOK_REF_ROWREL            = 0x8000;   /// True = row is relative.
+
+const sal_uInt16 BIFF12_TOK_TABLE_COLUMN         = 0x0001;   /// Table reference: Single column.
+const sal_uInt16 BIFF12_TOK_TABLE_COLRANGE       = 0x0002;   /// Table reference: Range of columns.
+const sal_uInt16 BIFF12_TOK_TABLE_ALL            = 0x0004;   /// Table reference: Special [#All] range.
+const sal_uInt16 BIFF12_TOK_TABLE_HEADERS        = 0x0008;   /// Table reference: Special [#Headers] range.
+const sal_uInt16 BIFF12_TOK_TABLE_DATA           = 0x0010;   /// Table reference: Special [#Data] range.
+const sal_uInt16 BIFF12_TOK_TABLE_TOTALS         = 0x0020;   /// Table reference: Special [#Totals] range.
+const sal_uInt16 BIFF12_TOK_TABLE_THISROW        = 0x0040;   /// Table reference: Special [#This Row] range.
+const sal_uInt16 BIFF12_TOK_TABLE_SP_BRACKETS    = 0x0080;   /// Table reference: Spaces in outer brackets.
+const sal_uInt16 BIFF12_TOK_TABLE_SP_SEP         = 0x0100;   /// Table reference: Spaces after separators.
+const sal_uInt16 BIFF12_TOK_TABLE_ROW            = 0x0200;   /// Table reference: Single row.
+const sal_uInt16 BIFF12_TOK_TABLE_CELL           = 0x0400;   /// Table reference: Single cell.
+
+const sal_uInt8 BIFF_TOK_NLR_ERR                = 0x01;     /// NLR: Invalid/deleted.
+const sal_uInt8 BIFF_TOK_NLR_ROWR               = 0x02;     /// NLR: Row index.
+const sal_uInt8 BIFF_TOK_NLR_COLR               = 0x03;     /// NLR: Column index.
+const sal_uInt8 BIFF_TOK_NLR_ROWV               = 0x06;     /// NLR: Value in row.
+const sal_uInt8 BIFF_TOK_NLR_COLV               = 0x07;     /// NLR: Value in column.
+const sal_uInt8 BIFF_TOK_NLR_RANGE              = 0x0A;     /// NLR: Range.
+const sal_uInt8 BIFF_TOK_NLR_SRANGE             = 0x0B;     /// Stacked NLR: Range.
+const sal_uInt8 BIFF_TOK_NLR_SROWR              = 0x0C;     /// Stacked NLR: Row index.
+const sal_uInt8 BIFF_TOK_NLR_SCOLR              = 0x0D;     /// Stacked NLR: Column index.
+const sal_uInt8 BIFF_TOK_NLR_SROWV              = 0x0E;     /// Stacked NLR: Value in row.
+const sal_uInt8 BIFF_TOK_NLR_SCOLV              = 0x0F;     /// Stacked NLR: Value in column.
+const sal_uInt8 BIFF_TOK_NLR_RANGEERR           = 0x10;     /// NLR: Invalid/deleted range.
+const sal_uInt8 BIFF_TOK_NLR_SXNAME             = 0x1D;     /// NLR: Pivot table name.
+const sal_uInt16 BIFF_TOK_NLR_REL               = 0x8000;   /// True = NLR is relative.
+const sal_uInt16 BIFF_TOK_NLR_MASK              = 0x3FFF;   /// Mask to extract BIFF8 column from NLR.
+
+const sal_uInt32 BIFF_TOK_NLR_ADDREL            = 0x80000000;   /// NLR relative (in appended data).
+const sal_uInt32 BIFF_TOK_NLR_ADDMASK           = 0x3FFFFFFF;   /// Mask for number of appended ranges.
+
+// function constants ---------------------------------------------------------
+
+const sal_uInt8 OOX_MAX_PARAMCOUNT              = 255;      /// Maximum parameter count for OOXML/BIFF12 files.
+const sal_uInt8 BIFF_MAX_PARAMCOUNT             = 30;       /// Maximum parameter count for BIFF2-BIFF8 files.
+
+const sal_uInt16 BIFF_FUNC_IF                   = 1;        /// Function identifier of the IF function.
+const sal_uInt16 BIFF_FUNC_SUM                  = 4;        /// Function identifier of the SUM function.
+const sal_uInt16 BIFF_FUNC_TRUE                 = 34;       /// Function identifier of the TRUE function.
+const sal_uInt16 BIFF_FUNC_FALSE                = 35;       /// Function identifier of the FALSE function.
+const sal_uInt16 BIFF_FUNC_ROWS                 = 76;       /// Function identifier of the ROWS function.
+const sal_uInt16 BIFF_FUNC_COLUMNS              = 77;       /// Function identifier of the COLUMNS function.
+const sal_uInt16 BIFF_FUNC_OFFSET               = 78;       /// Function identifier of the OFFSET function.
+const sal_uInt16 BIFF_FUNC_EXTERNCALL           = 255;      /// BIFF function id of the EXTERN.CALL function.
+const sal_uInt16 BIFF_FUNC_FLOOR                = 285;      /// Function identifier of the FLOOR function.
+const sal_uInt16 BIFF_FUNC_CEILING              = 288;      /// Function identifier of the CEILING function.
+const sal_uInt16 BIFF_FUNC_HYPERLINK            = 359;      /// Function identifier of the HYPERLINK function.
+const sal_uInt16 BIFF_FUNC_WEEKNUM              = 465;      /// Function identifier of the WEEKNUM function.
+
+// Formula type ===============================================================
+
+/** Enumerates all possible types of a formula. */
+enum class FormulaType
+{
+    Cell,           /// Simple cell formula, or reference to a shared formula name.
+    Array,          /// Array (matrix) formula.
+    SharedFormula,  /// Shared formula definition.
+    CondFormat,     /// Condition of a conditional format rule.
+    Validation      /// Condition of a data validation.
+};
+
+// Reference helpers ==========================================================
+
+/** A 2D formula cell reference struct with relative flags. */
+struct BinSingleRef2d
+{
+    sal_Int32           mnCol;              /// Column index.
+    sal_Int32           mnRow;              /// Row index.
+    bool                mbColRel;           /// True = relative column reference.
+    bool                mbRowRel;           /// True = relative row reference.
+
+    explicit            BinSingleRef2d();
+
+    void                setBiff12Data( sal_uInt16 nCol, sal_Int32 nRow, bool bRelativeAsOffset );
+
+    void                readBiff12Data( SequenceInputStream& rStrm, bool bRelativeAsOffset );
+};
+
+/** A 2D formula cell range reference struct with relative flags. */
+struct BinComplexRef2d
+{
+    BinSingleRef2d      maRef1;             /// Start (top-left) cell address.
+    BinSingleRef2d      maRef2;             /// End (bottom-right) cell address.
+
+    void                readBiff12Data( SequenceInputStream& rStrm, bool bRelativeAsOffset );
+};
+
+// Token vector, token sequence ===============================================
+
+typedef css::sheet::FormulaToken       ApiToken;
+typedef css::uno::Sequence< ApiToken > ApiTokenSequence;
+
+/** Contains the base address and type of a special token representing an array
+    formula or a shared formula (sal_False), or a table operation (sal_True). */
+typedef css::beans::Pair< css::table::CellAddress, sal_Bool > ApiSpecialTokenInfo;
+
+/** A vector of formula tokens with additional convenience functions. */
+class ApiTokenVector
+{
+public:
+    explicit            ApiTokenVector();
+
+    ApiToken& operator[]( size_t i ) { return mvTokens[i]; }
+
+    size_t size() const { return mvTokens.size(); }
+
+    ApiToken& back() { return mvTokens.back(); }
+    const ApiToken& back() const { return mvTokens.back(); }
+
+    void clear() { mvTokens.clear(); }
+
+    void pop_back() { mvTokens.pop_back(); }
+
+    void push_back( const ApiToken& rToken ) { mvTokens.push_back( rToken ); }
+
+    void reserve( size_t n ) { mvTokens.reserve( n ); }
+
+    void resize( size_t n ) { mvTokens.resize( n ); }
+
+    /** Appends a new token with the passed op-code, returns its data field. */
+    css::uno::Any&      append( sal_Int32 nOpCode );
+
+    /** Appends a new token with the passed op-code and data. */
+    template< typename Type >
+    void         append( sal_Int32 nOpCode, const Type& rData ) { append( nOpCode ) <<= rData; }
+
+    /** Converts to a sequence. */
+    ApiTokenSequence toSequence() const;
+
+private:
+    ::std::vector< ApiToken > mvTokens;
+};
+
+// Token sequence iterator ====================================================
+
+/** Token sequence iterator that is able to skip space tokens. */
+class ApiTokenIterator
+{
+public:
+    explicit            ApiTokenIterator( const ApiTokenSequence& rTokens, sal_Int32 nSpacesOpCode );
+    bool         is() const { return mpToken != mpTokenEnd; }
+    const ApiToken* operator->() const { return mpToken; }
+
+    ApiTokenIterator&   operator++();
+
+private:
+    void                skipSpaces();
+
+private:
+    const ApiToken*     mpToken;            /// Pointer to current token of the token sequence.
+    const ApiToken*     mpTokenEnd;         /// Pointer behind last token of the token sequence.
+    const sal_Int32     mnSpacesOpCode;     /// Op-code for whitespace tokens.
+};
+
+// List of API op-codes =======================================================
+
+/** Contains all API op-codes needed to build formulas with tokens. */
+struct ApiOpCodes
+{
+    // special
+    sal_Int32           OPCODE_UNKNOWN;         /// Internal: function name unknown to mapper.
+    sal_Int32           OPCODE_EXTERNAL;        /// External function call (e.g. add-ins).
+    // formula structure
+    sal_Int32           OPCODE_PUSH;            /// Op-code for common value operands.
+    sal_Int32           OPCODE_MISSING;         /// Placeholder for a missing function parameter.
+    sal_Int32           OPCODE_SPACES;          /// Spaces between other formula tokens.
+    sal_Int32           OPCODE_NAME;            /// Index of a defined name.
+    sal_Int32           OPCODE_DBAREA;          /// Index of a database area.
+    sal_Int32           OPCODE_NLR;             /// Natural language reference.
+    sal_Int32           OPCODE_DDE;             /// DDE link function.
+    sal_Int32           OPCODE_MACRO;           /// Macro function call.
+    sal_Int32           OPCODE_BAD;             /// Bad token (unknown name, formula error).
+    sal_Int32           OPCODE_NONAME;          /// Function style #NAME? error.
+    // separators
+    sal_Int32           OPCODE_OPEN;            /// Opening parenthesis.
+    sal_Int32           OPCODE_CLOSE;           /// Closing parenthesis.
+    sal_Int32           OPCODE_SEP;             /// Function parameter separator.
+    // array separators
+    sal_Int32           OPCODE_ARRAY_OPEN;      /// Opening brace for constant arrays.
+    sal_Int32           OPCODE_ARRAY_CLOSE;     /// Closing brace for constant arrays.
+    sal_Int32           OPCODE_ARRAY_ROWSEP;    /// Row separator in constant arrays.
+    sal_Int32           OPCODE_ARRAY_COLSEP;    /// Column separator in constant arrays.
+    // unary operators
+    sal_Int32           OPCODE_PLUS_SIGN;       /// Unary plus sign.
+    sal_Int32           OPCODE_MINUS_SIGN;      /// Unary minus sign.
+    sal_Int32           OPCODE_PERCENT;         /// Percent sign.
+    // binary operators
+    sal_Int32           OPCODE_ADD;             /// Addition operator.
+    sal_Int32           OPCODE_SUB;             /// Subtraction operator.
+    sal_Int32           OPCODE_MULT;            /// Multiplication operator.
+    sal_Int32           OPCODE_DIV;             /// Division operator.
+    sal_Int32           OPCODE_POWER;           /// Power operator.
+    sal_Int32           OPCODE_CONCAT;          /// String concatenation operator.
+    sal_Int32           OPCODE_EQUAL;           /// Compare equal operator.
+    sal_Int32           OPCODE_NOT_EQUAL;       /// Compare not equal operator.
+    sal_Int32           OPCODE_LESS;            /// Compare less operator.
+    sal_Int32           OPCODE_LESS_EQUAL;      /// Compare less or equal operator.
+    sal_Int32           OPCODE_GREATER;         /// Compare greater operator.
+    sal_Int32           OPCODE_GREATER_EQUAL;   /// Compare greater or equal operator.
+    sal_Int32           OPCODE_INTERSECT;       /// Range intersection operator.
+    sal_Int32           OPCODE_LIST;            /// Range list operator.
+    sal_Int32           OPCODE_RANGE;           /// Range operator.
+};
+
+// Function parameter info ====================================================
+
+/** Enumerates validity modes for a function parameter. */
+enum class FuncParamValidity
+{
+    Regular,         /// Parameter supported by Calc and Excel.
+    CalcOnly,        /// Parameter supported by Calc only.
+    ExcelOnly        /// Parameter supported by Excel only.
+};
+
+/** Structure that contains all needed information for a parameter in a
+    function.
+
+    The member meValid specifies which application supports the parameter. If
+    set to CALCONLY, import filters have to insert a default value for this
+    parameter, and export filters have to skip the parameter. If set to
+    EXCELONLY, import filters have to skip the parameter, and export filters
+    have to insert a default value for this parameter.
+
+    The member mbValType specifies whether the parameter requires tokens to be
+    of value type (VAL or ARR class).
+
+        If set to false, the parameter is called to be REFTYPE. Tokens with REF
+        default class can be inserted for the parameter (e.g. tAreaR tokens).
+
+        If set to true, the parameter is called to be VALTYPE. Tokens with REF
+        class need to be converted to VAL tokens first (e.g. tAreaR will be
+        converted to tAreaV), and further conversion is done according to this
+        new token class.
+
+    The member meConv specifies how to convert the current token class of the
+    token inserted for the parameter. If the token class is still REF this
+    means that the token has default REF class and the parameter is REFTYPE
+    (see member mbValType), the token will not be converted at all and remains
+    in REF class. Otherwise, token class conversion is depending on the actual
+    token class of the return value of the function containing this parameter.
+    The function may return REF class (tFuncR, tFuncVarR, tFuncCER), or it may
+    return VAL or ARR class (tFuncV, tFuncA, tFuncVarV, tFuncVarA, tFuncCEV,
+    tFuncCEA). Even if the function is able to return REF class, it may return
+    VAL or ARR class instead due to the VALTYPE data type of the parent
+    function parameter that calls the own function. Example: The INDIRECT
+    function returns REF class by default. But if called from a VALTYPE
+    function parameter, e.g. in the formula =ABS(INDIRECT("A1")), it returns
+    VAL or ARR class instead. Additionally, the repeating conversion types RPT
+    and RPX rely on the conversion executed for the function token class.
+
+        1) ORG:
+        Use the original class of the token (VAL or ARR), regardless of any
+        conversion done for the function return class.
+
+        2) VAL:
+        Convert ARR tokens to VAL class, regardless of any conversion done for
+        the function return class.
+
+        3) ARR:
+        Convert VAL tokens to ARR class, regardless of any conversion done for
+        the function return class.
+
+        4) RPT:
+        If the own function returns REF class (thus it is called from a REFTYPE
+        parameter, see above), and the parent conversion type (for the function
+        return class) was ORG, VAL, or ARR, ignore that conversion and always
+        use VAL conversion for the own token instead. If the parent conversion
+        type was RPT or RPX, repeat the conversion that would have been used if
+        the function would return value type.
+        If the own function returns value type (VAL or ARR class, see above),
+        and the parent conversion type (for the function return class) was ORG,
+        VAL, ARR, or RPT, repeat this conversion for the own token. If the
+        parent conversion type was RPX, always use ORG conversion type for the
+        own token instead.
+
+        5) RPX:
+        This type of conversion only occurs in functions returning VAL class by
+        default. If the own token is value type, and the VAL return class of
+        the own function has been changed to ARR class (due to direct ARR
+        conversion, or due to ARR conversion repeated by RPT or RPX), set the
+        own token to ARR type. Otherwise use the original token type (VAL
+        conversion from parent parameter will not be repeated at all). If
+        nested functions have RPT or value-type RPX parameters, they will not
+        repeat this conversion type, but will use ORG conversion instead (see
+        description of RPT above).
+
+        6) RPO:
+        This type of conversion is only used for the operands of all operators
+        (unary and binary arithmetic operators, comparison operators, and range
+        operators). It is not used for function parameters. On conversion, it
+        will be replaced by the last conversion type that was not the RPO
+        conversion. This leads to a slightly different behaviour than the RPT
+        conversion for operands in conjunction with a parent RPX conversion.
+ */
+struct FunctionParamInfo
+{
+    FuncParamValidity   meValid;        /// Parameter validity.
+};
+
+// Function data ==============================================================
+
+/** This enumeration contains constants for all known external libraries
+    containing supported sheet functions. */
+enum FunctionLibraryType
+{
+    FUNCLIB_UNKNOWN = 0,        /// Unknown library (must be zero).
+    FUNCLIB_EUROTOOL            /// EuroTool add-in with EUROCONVERT function.
+};
+
+/** Represents information for a spreadsheet function.
+
+    The member mpParamInfos points to a C-array of type information structures
+    for all parameters of the function. The last initialized structure
+    describing a regular parameter (member meValid == FuncParamValidity::Regular) in
+    this array is used repeatedly for all following parameters supported by a
+    function.
+ */
+struct FunctionInfo
+{
+    OUString     maOdfFuncName;      /// ODF function name.
+    OUString     maOoxFuncName;      /// OOXML function name.
+    OUString     maBiffMacroName;    /// Expected macro name in EXTERN.CALL function.
+    OUString     maExtProgName;      /// Programmatic function name for external functions.
+    FunctionLibraryType meFuncLibType;      /// The external library this function is part of.
+    sal_Int32           mnApiOpCode;        /// API function opcode.
+    sal_uInt16          mnBiff12FuncId;     /// BIFF12 function identifier.
+    sal_uInt16          mnBiffFuncId;       /// BIFF2-BIFF8 function identifier.
+    sal_uInt8           mnMinParamCount;    /// Minimum number of parameters.
+    sal_uInt8           mnMaxParamCount;    /// Maximum number of parameters.
+    sal_uInt8           mnRetClass;         /// BIFF token class of the return value.
+    const FunctionParamInfo* mpParamInfos;  /// Information about all parameters.
+    bool                mbParamPairs;       /// True = optional parameters are expected to appear in pairs.
+    bool                mbVolatile;         /// True = volatile function.
+    bool                mbExternal;         /// True = external function in Calc.
+    bool                mbInternal;         /// True = internal function in Calc. (Both can be true!)
+    bool                mbMacroFunc;        /// True = macro sheet function or command.
+    bool                mbVarParam;         /// True = use a tFuncVar token, also if min/max are equal.
+};
+
+typedef RefVector< FunctionInfo > FunctionInfoVector;
+
+// Function info parameter class iterator =====================================
+
+/** Iterator working on the mpParamInfos member of the FunctionInfo struct.
+
+    This iterator can be used to iterate through the array containing the
+    token class conversion information of function parameters. This iterator
+    repeats the last valid structure in the array - it stops automatically
+    before the first empty array entry or before the end of the array, even for
+    repeated calls to the increment operator.
+ */
+class FunctionParamInfoIterator
+{
+public:
+    explicit            FunctionParamInfoIterator( const FunctionInfo& rFuncInfo );
+
+    bool                isCalcOnlyParam() const;
+    bool                isExcelOnlyParam() const;
+    FunctionParamInfoIterator& operator++();
+
+private:
+    const FunctionParamInfo* mpParamInfo;
+    const FunctionParamInfo* mpParamInfoEnd;
+    bool                mbParamPairs;
+};
+
+// Base function provider =====================================================
+
+struct FunctionProviderImpl;
+
+/** Provides access to function info structs for all available sheet functions.
+ */
+class FunctionProvider  // not derived from WorkbookHelper to make it usable in file dumpers
+{
+public:
+    explicit            FunctionProvider(bool bImportFilter);
+    virtual             ~FunctionProvider();
+
+    FunctionProvider(FunctionProvider const &) = default;
+    FunctionProvider(FunctionProvider &&) = default;
+    FunctionProvider & operator =(FunctionProvider const &) = delete;
+    FunctionProvider & operator =(FunctionProvider &&) = delete;
+
+    /** Returns the function info for an OOXML function name, or 0 on error. */
+    const FunctionInfo* getFuncInfoFromOoxFuncName( const OUString& rFuncName ) const;
+
+    /** Returns the function info for a BIFF12 function index, or 0 on error. */
+    const FunctionInfo* getFuncInfoFromBiff12FuncId( sal_uInt16 nFuncId ) const;
+
+    /** Returns the function info for a macro function referred by the
+        EXTERN.CALL function, or 0 on error. */
+    const FunctionInfo* getFuncInfoFromMacroName( const OUString& rFuncName ) const;
+
+    /** Returns the library type associated with the passed URL of a function
+        library (function add-in). */
+    static FunctionLibraryType getFuncLibTypeFromLibraryName( std::u16string_view rLibraryName );
+
+protected:
+    /** Returns the list of all function infos. */
+    const FunctionInfoVector& getFuncs() const;
+
+private:
+    typedef std::shared_ptr< FunctionProviderImpl > FunctionProviderImplRef;
+    FunctionProviderImplRef mxFuncImpl;     /// Shared implementation between all copies of the provider.
+};
+
+// Op-code and function provider ==============================================
+
+struct OpCodeProviderImpl;
+
+/** Provides access to API op-codes for all available formula tokens and to
+    function info structs for all available sheet functions.
+ */
+class OpCodeProvider : public FunctionProvider // not derived from WorkbookHelper to make it usable as UNO service
+{
+public:
+    explicit            OpCodeProvider(const css::uno::Reference& rxModelFactory,
+                                       bool bImportFilter);
+    virtual             ~OpCodeProvider() override;
+
+    OpCodeProvider(OpCodeProvider const &) = default;
+    OpCodeProvider(OpCodeProvider &&) = default;
+    OpCodeProvider & operator =(OpCodeProvider const &) = delete;
+    OpCodeProvider & operator =(OpCodeProvider &&) = delete;
+
+    /** Returns the structure containing all token op-codes for operators and
+        special tokens used by the Calc document and its formula parser. */
+    const ApiOpCodes&   getOpCodes() const;
+
+    /** Returns the function info for an API token, or 0 on error. */
+    const FunctionInfo* getFuncInfoFromApiToken( const ApiToken& rToken ) const;
+
+    /** Returns the op-code map that is used by the OOXML formula parser. */
+    css::uno::Sequence< css::sheet::FormulaOpCodeMapEntry >
+                        getOoxParserMap() const;
+
+private:
+    typedef std::shared_ptr< OpCodeProviderImpl > OpCodeProviderImplRef;
+    OpCodeProviderImplRef mxOpCodeImpl;     /// Shared implementation between all copies of the provider.
+};
+
+// API formula parser wrapper =================================================
+
+/** A wrapper around the FormulaParser service provided by the Calc document. */
+class ApiParserWrapper : public OpCodeProvider
+{
+public:
+    explicit            ApiParserWrapper(
+                            const css::uno::Reference< css::lang::XMultiServiceFactory >& rxModelFactory,
+                            const OpCodeProvider& rOpCodeProv );
+
+    /** Returns read/write access to the formula parser property set. */
+    PropertySet& getParserProperties() { return maParserProps; }
+
+    /** Calls the XFormulaParser::parseFormula() function of the API parser. */
+    ApiTokenSequence    parseFormula(
+                            const OUString& rFormula,
+                            const ScAddress& rRefPos );
+
+private:
+    css::uno::Reference< css::sheet::XFormulaParser >
+                        mxParser;
+    PropertySet         maParserProps;
+};
+
+// Formula parser/printer base class for filters ==============================
+
+/** Base class for import formula parsers and export formula compilers. */
+class FormulaProcessorBase : public OpCodeProvider, protected ApiOpCodes, public WorkbookHelper
+{
+public:
+    explicit            FormulaProcessorBase( const WorkbookHelper& rHelper );
+
+    /** Generates a cell address string in A1 notation from the passed cell
+        address.
+
+        @param rAddress  The cell address containing column and row index.
+        @param bAbsolute  True = adds dollar signs before column and row.
+     */
+    static OUString generateAddress2dString(
+                            const ScAddress& rAddress,
+                            bool bAbsolute );
+
+    /** Generates a cell address string in A1 notation from the passed binary
+        cell address.
+
+        @param rAddress  The cell address containing column and row index.
+        @param bAbsolute  True = adds dollar signs before column and row.
+     */
+    static OUString generateAddress2dString(
+                            const BinAddress& rAddress,
+                            bool bAbsolute );
+
+    /** Generates a string in Calc formula notation from the passed string.
+
+        @param rString  The string value.
+
+        @return  The string enclosed in double quotes, where all contained
+            quote characters are doubled.
+     */
+    static OUString generateApiString( const OUString& rString );
+
+    /** Generates an array string in Calc formula notation from the passed
+        matrix with Any's containing double values or strings.
+
+        @param rMatrix  The matrix containing double values or strings.
+     */
+    static OUString generateApiArray( const Matrix< css::uno::Any >& rMatrix );
+
+    /** Tries to extract a single cell reference from a formula token sequence.
+
+        @param rTokens  The token sequence to be parsed. Should contain exactly
+            one address token or cell range address token. The token sequence
+            may contain whitespace tokens.
+
+        @return  If the token sequence is valid, this function returns an Any
+            containing a com.sun.star.sheet.SingleReference object, or a
+            com.sun.star.sheet.ComplexReference object. If the token sequence
+            contains too many, or unexpected tokens, an empty Any is returned.
+     */
+    css::uno::Any
+                        extractReference( const ApiTokenSequence& rTokens ) const;
+
+    /** Tries to extract a cell range address from a formula token sequence.
+        Only real absolute references will be accepted.
+
+        @param orAddress  (output parameter) If the token sequence is valid,
+            this parameter will contain the extracted cell range address. If
+            the token sequence contains unexpected tokens, nothing meaningful
+            is inserted, and the function returns false.
+
+        @param rTokens  The token sequence to be parsed. Should contain exactly
+            one cell range address token. The token sequence may contain
+            whitespace tokens.
+
+        @return  True, if the token sequence contains a valid cell range
+            address which has been extracted to orRange, false otherwise.
+     */
+    bool                extractCellRange(
+                            ScRange& orRange,
+                            const ApiTokenSequence& rTokens ) const;
+
+    /** Tries to extract a cell range list from a formula token sequence.
+        Only real absolute references will be accepted.
+
+        @param orRanges  (output parameter) If the token sequence is valid,
+            this parameter will contain the extracted cell range list. Deleted
+            cells or cell ranges (shown as #REF! error in a formula) will be
+            skipped. If the token sequence contains unexpected tokens, an empty
+            list is returned here.
+
+        @param rTokens  The token sequence to be parsed. Should contain cell
+            address tokens or cell range address tokens, separated by the
+            standard function parameter separator token. The token sequence may
+            contain parentheses and whitespace tokens.
+
+        @param nFilterBySheet  If non-negative, this function returns only cell
+            ranges located in the specified sheet, otherwise returns all cell
+            ranges contained in the token sequence.
+     */
+    void                extractCellRangeList(
+                            ScRangeList& orRanges,
+                            const ApiTokenSequence& rTokens,
+                            sal_Int32 nFilterBySheet ) const;
+
+    /** Tries to extract a string from a formula token sequence.
+
+        @param orString  (output parameter) The extracted string.
+
+        @param rTokens  The token sequence to be parsed. Should contain exactly
+            one string token, may contain whitespace tokens.
+
+        @return  True = token sequence is valid, output parameter orString
+            contains the string extracted from the token sequence.
+     */
+    bool                extractString(
+                            OUString& orString,
+                            const ApiTokenSequence& rTokens ) const;
+
+    /** Tries to extract information about a special token used for array
+        formulas, shared formulas, or table operations.
+
+        @param orTokenInfo  (output parameter) The extracted information about
+            the token. Contains the base address and the token type (sal_False
+            for array or shared formulas, sal_True for table operations).
+
+        @param rTokens  The token sequence to be parsed. If it contains exactly
+            one OPCODE_BAD token with special token information, this
+            information will be extracted.
+
+        @return  True = token sequence is valid, output parameter orTokenInfo
+            contains the token information extracted from the token sequence.
+     */
+    bool                extractSpecialTokenInfo(
+                            ApiSpecialTokenInfo& orTokenInfo,
+                            const ApiTokenSequence& rTokens ) const;
+
+    /** Converts a single string with separators in the passed formula token
+        sequence to a list of string tokens.
+
+        @param orTokens  (input/output parameter) Expects a single string token
+            in this token sequence (whitespace tokens are allowed). The string
+            is split into substrings. A list of string tokens separated with
+            parameter separator tokens is returned in this parameter.
+
+        @param cStringSep  The separator character used to split the input
+            string.
+
+        @param bTrimLeadingSpaces  True = removes leading whitespace from all
+            substrings inserted into the formula token sequence.
+     */
+    void                convertStringToStringList(
+                            ApiTokenSequence& orTokens,
+                            sal_Unicode cStringSep,
+                            bool bTrimLeadingSpaces ) const;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/formulabuffer.hxx b/sc/source/filter/inc/formulabuffer.hxx
new file mode 100644
index 000000000..36e41c7ba
--- /dev/null
+++ b/sc/source/filter/inc/formulabuffer.hxx
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "workbookhelper.hxx"
+#include 
+#include 
+
+namespace oox::xls {
+
+class FormulaBuffer : public WorkbookHelper
+{
+public:
+    /**
+     * Represents a shared formula definition.
+     */
+    struct SharedFormulaEntry
+    {
+        ScAddress maAddress;
+        OUString maTokenStr;
+        sal_Int32 mnSharedId;
+
+        SharedFormulaEntry(
+            const ScAddress& rAddress,
+            const OUString& rTokenStr, sal_Int32 nSharedId );
+    };
+
+    /**
+     * Represents a formula cell that uses shared formula.
+     */
+    struct SharedFormulaDesc
+    {
+        ScAddress maAddress;
+        OUString maCellValue;
+        sal_Int32 mnSharedId;
+        sal_Int32 mnValueType;
+
+        SharedFormulaDesc(
+            const ScAddress& rAddr, sal_Int32 nSharedId,
+            const OUString& rCellValue, sal_Int32 nValueType );
+    };
+
+    struct TokenAddressItem
+    {
+        OUString maTokenStr;
+        ScAddress maAddress;
+        TokenAddressItem( const OUString& rTokenStr, const ScAddress& rAddress ) : maTokenStr( rTokenStr ), maAddress( rAddress ) {}
+    };
+
+    struct TokenRangeAddressItem
+    {
+        TokenAddressItem maTokenAndAddress;
+        ScRange maRange;
+        TokenRangeAddressItem( const TokenAddressItem& rTokenAndAddress, const ScRange& rRange ) : maTokenAndAddress( rTokenAndAddress ), maRange( rRange ) {}
+    };
+
+    struct FormulaValue
+    {
+        ScAddress maAddress;
+        OUString maValueStr;
+        sal_Int32 mnCellType;
+    };
+
+    struct SheetItem
+    {
+        std::vector* mpCellFormulas;
+        std::vector* mpArrayFormulas;
+        std::vector* mpCellFormulaValues;
+        std::vector* mpSharedFormulaEntries;
+        std::vector* mpSharedFormulaIDs;
+
+        SheetItem();
+    };
+
+private:
+
+    std::mutex maMtxData;
+    // Vectors indexed by SCTAB - cf. SetSheetCount
+    std::vector< std::vector >         maCellFormulas;
+    std::vector< std::vector >    maCellArrayFormulas;
+    std::vector< std::vector >  maSharedFormulas; // sheet -> stuff needed to create shared formulae
+    std::vector< std::vector >   maSharedFormulaIds; // sheet -> list of shared formula descriptions
+    std::vector< std::vector >        maCellFormulaValues; // sheet -> stuff needed to create shared formulae
+
+    SheetItem getSheetItem( SCTAB nTab );
+
+public:
+    explicit            FormulaBuffer( const WorkbookHelper& rHelper );
+    void                finalizeImport();
+    void                setCellFormula( const ScAddress& rAddress, const OUString&  );
+
+    void setCellFormula(
+        const ScAddress& rAddress, sal_Int32 nSharedId,
+        const OUString& rCellValue, sal_Int32 nValueType );
+
+    void setCellFormulaValue(
+        const ScAddress& rAddress, const OUString& rValueStr, sal_Int32 nCellType );
+
+    void                setCellArrayFormula( const ScRange& rRangeAddress,
+                                             const ScAddress& rTokenAddress,
+                                             const OUString& );
+
+    void                createSharedFormulaMapEntry( const ScAddress& rAddress,
+                                                     sal_Int32 nSharedId, const OUString& rTokens );
+
+    /// ensure sizes of vectors matches the number of sheets
+    void SetSheetCount( SCTAB nSheets );
+};
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/formulaparser.hxx b/sc/source/filter/inc/formulaparser.hxx
new file mode 100644
index 000000000..3a2030227
--- /dev/null
+++ b/sc/source/filter/inc/formulaparser.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include 
+#include "formulabase.hxx"
+
+namespace oox::xls {
+
+// formula finalizer ==========================================================
+
+/** A generic formula token array finalizer.
+
+    After building a formula token array from alien binary file formats, or
+    parsing an XML formula string using the com.sun.star.sheet.FormulaParser
+    service, the token array is still not ready to be put into the spreadsheet
+    document. There may be functions with a wrong number of parameters (missing
+    but required parameters, or unsupported parameters) or intermediate tokens
+    used to encode references to macro functions or add-in functions. This
+    helper processes a passed token array and builds a new compatible token
+    array.
+
+    Derived classes may add more functionality by overwriting the virtual
+    functions.
+ */
+class FormulaFinalizer : public OpCodeProvider, protected ApiOpCodes
+{
+public:
+    explicit            FormulaFinalizer( const OpCodeProvider& rOpCodeProv );
+
+    /** Finalizes and returns the passed token array. */
+    ApiTokenSequence    finalizeTokenArray( const ApiTokenSequence& rTokens );
+
+protected:
+    /** Derived classed may try to find a function info struct from the passed
+        string extracted from an OPCODE_BAD token.
+
+        @param rTokenData  The string that has been found in an OPCODE_BAD
+            token preceding the function parentheses.
+     */
+    virtual const FunctionInfo* resolveBadFuncName( const OUString& rTokenData ) const;
+
+    /** Derived classed may try to find the name of a defined name with the
+        passed index extracted from an OPCODE_NAME token.
+
+        @param nTokenIndex  The index of the defined name that has been found
+            in an OPCODE_NAME token preceding the function parentheses.
+     */
+    virtual OUString resolveDefinedName( sal_Int32 nTokenIndex ) const;
+
+private:
+    typedef ::std::vector< const ApiToken* > ParameterPosVector;
+
+    const FunctionInfo* getFunctionInfo( ApiToken& orFuncToken );
+    const FunctionInfo* getExternCallInfo( ApiToken& orFuncToken, const ApiToken& rECToken );
+
+    void                processTokens( const ApiToken* pToken, const ApiToken* pTokenEnd );
+    const ApiToken*     processParameters( const FunctionInfo& rFuncInfo, const ApiToken* pToken, const ApiToken* pTokenEnd );
+
+    bool                isEmptyParameter( const ApiToken* pToken, const ApiToken* pTokenEnd ) const;
+    const ApiToken*     getSingleToken( const ApiToken* pToken, const ApiToken* pTokenEnd ) const;
+    const ApiToken*     skipParentheses( const ApiToken* pToken, const ApiToken* pTokenEnd ) const;
+    const ApiToken*     findParameters( ParameterPosVector& rParams, const ApiToken* pToken, const ApiToken* pTokenEnd ) const;
+    void                appendEmptyParameter( const FunctionInfo& rFuncInfo, size_t nParam );
+    void                appendCalcOnlyParameter( const FunctionInfo& rFuncInfo, size_t nParam, size_t nParamCount );
+    void                appendRequiredParameters( const FunctionInfo& rFuncInfo, size_t nParamCount );
+
+    bool                appendFinalToken( const ApiToken& rToken );
+
+private:
+    ApiTokenVector      maTokens;
+};
+
+class FormulaParserImpl;
+
+/** Import formula parser for OOXML and BIFF filters.
+
+    This class implements formula import for the OOXML and BIFF filter. One
+    instance is contained in the global filter data to prevent construction and
+    destruction of internal buffers for every imported formula.
+ */
+class FormulaParser : public FormulaProcessorBase
+{
+public:
+    explicit            FormulaParser( const WorkbookHelper& rHelper );
+    virtual             ~FormulaParser() override;
+
+    /** Converts an OOXML formula string. */
+    ApiTokenSequence    importFormula(
+                            const ScAddress& rBaseAddr,
+                            const OUString& rFormulaString ) const;
+
+    /** Imports and converts a BIFF12 token array from the passed stream. */
+    ApiTokenSequence    importFormula(
+                            const ScAddress& rBaseAddr,
+                            FormulaType eType,
+                            SequenceInputStream& rStrm ) const;
+
+    /** Converts the passed XML formula to an OLE link target. */
+    OUString     importOleTargetLink( const OUString& rFormulaString );
+
+    /** Imports and converts an OLE link target from the passed stream. */
+    OUString     importOleTargetLink( SequenceInputStream& rStrm );
+
+    /** Converts the passed formula to a macro name for a drawing shape. */
+    OUString     importMacroName( const OUString& rFormulaString );
+
+private:
+    ::std::unique_ptr< FormulaParserImpl > mxImpl;
+};
+
+} // namespace oox::xls
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/fprogressbar.hxx b/sc/source/filter/inc/fprogressbar.hxx
new file mode 100644
index 000000000..71baa3766
--- /dev/null
+++ b/sc/source/filter/inc/fprogressbar.hxx
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+#include 
+
+class SfxObjectShell;
+class SvStream;
+
+const sal_Int32 SCF_INV_SEGMENT = -1;
+
+/** Progress bar for complex progress representation.
+
+    The progress bar contains one or more segments, each with customizable
+    size. Each segment is represented by a unique identifier. While showing the
+    progress bar, several segments can be started simultaneously. The progress
+    bar displays the sum of all started segments on screen.
+
+    It is possible to create a full featured ScfProgressBar object from
+    any segment. This sub progress bar works only on that parent segment, with
+    the effect, that if the sub progress bar reaches 100%, the parent segment is
+    filled completely.
+
+    After adding segments, the progress bar has to be activated. In this step the
+    total size of all segments is calculated. Therefore it is not possible to add
+    more segments from here.
+
+    If a sub progress bar is created from a segment, and the main progress bar
+    has been started (but not the sub progress bar), it is still possible to add
+    segments to the sub progress bar. It is not allowed to get the sub progress bar
+    of a started segment. And it is not allowed to modify the segment containing
+    a sub progress bar directly.
+
+    Following a few code examples, how to use the progress bar.
+
+    Example 1: Simple progress bar (see also ScfSimpleProgressBar below).
+
+        ScfProgressBar aProgress( ... );
+        sal_Int32 nSeg = aProgress.AddSegment( 50 );        // segment with 50 steps (1 step = 2%)
+
+        aProgress.ActivateSegment( nSeg );                  // start segment nSeg
+        aProgress.Progress();                               // 0->1; display: 2%
+        aProgress.ProgressAbs( 9 );                         // 1->9; display: 18%
+
+    Example 2: Progress bar with 2 segments.
+
+        ScfProgressBar aProgress( ... );
+        sal_Int32 nSeg1 = aProgress.AddSegment( 70 );       // segment with 70 steps
+        sal_Int32 nSeg2 = aProgress.AddSegment( 30 );       // segment with 30 steps
+                                                            // both segments: 100 steps (1 step = 1%)
+
+        aProgress.ActivateSegment( nSeg1 );                 // start first segment
+        aProgress.Progress();                               // 0->1, display: 1%
+        aProgress.Progress( 2 );                            // 1->3, display: 3%
+        aProgress.ActivateSegment( nSeg2 );                 // start second segment
+        aProgress.Progress( 5 );                            // 0->5, display: 8% (5+3 steps)
+        aProgress.ActivateSegment( nSeg1 );                 // continue with first segment
+        aProgress.Progress();                               // 3->4, display: 9% (5+4 steps)
+
+    Example 3: Progress bar with 2 segments, one contains a sub progress bar.
+
+        ScfProgressBar aProgress( ... );
+        sal_Int32 nSeg1 = aProgress.AddSegment( 75 );       // segment with 75 steps
+        sal_Int32 nSeg2 = aProgress.AddSegment( 25 );       // segment with 25 steps
+                                                            // both segments: 100 steps (1 step = 1%)
+
+        aProgress.ActivateSegment( nSeg1 );                 // start first segment
+        aProgress.Progress();                               // 0->1, display: 1%
+
+        ScfProgressBar& rSubProgress = aProgress.GetSegmentProgressBar( nSeg2 );
+                                                            // sub progress bar from second segment
+        sal_Int32 nSubSeg = rSubProgress.AddSegment( 5 );   // 5 steps, mapped to second segment
+                                                            // => 1 step = 5 steps in parent = 5%
+
+        rSubProgress.ActivateSegment( nSubSeg );            // start the segment (auto activate parent segment)
+        rSubProgress.Progress();                            // 0->1 (0->5 in parent); display: 6% (1+5)
+
+        // not allowed (second segment active):   aProgress.Progress();
+        // not allowed (first segment not empty): aProgress.GetSegmentProgressBar( nSeg1 );
+ */
+class ScfProgressBar final
+{
+public:
+    ScfProgressBar(const ScfProgressBar&) = delete;
+    const ScfProgressBar operator=(const ScfProgressBar&) = delete;
+
+    explicit            ScfProgressBar(SfxObjectShell* pDocShell, const OUString& rText);
+    explicit            ScfProgressBar(SfxObjectShell* pDocShell, TranslateId pResId);
+                        ~ScfProgressBar();
+
+    /** Adds a new segment to the progress bar.
+        @return  the identifier of the segment. */
+    sal_Int32           AddSegment( std::size_t nSize );
+    /** Returns a complete progress bar for the specified segment.
+        @descr  The progress bar can be used to create sub segments inside of the
+        segment. Do not delete it (done by root progress bar)!
+        @return  A reference to an ScfProgressBar connected to the segment. */
+    ScfProgressBar&     GetSegmentProgressBar( sal_Int32 nSegment );
+
+    /** Returns true, if the current progress segment is already full. */
+    bool                IsFull() const;
+
+    /** Starts the progress bar or activates another segment. */
+    void                ActivateSegment( sal_Int32 nSegment );
+    /** Starts the progress bar (with first segment). */
+    void         Activate() { ActivateSegment( 0 ); }
+    /** Set current segment to the specified absolute position. */
+    void                ProgressAbs( std::size_t nPos );
+    /** Increase current segment by the passed value. */
+    void                Progress( std::size_t nDelta = 1 );
+
+private:
+    struct ScfProgressSegment;
+
+    /** Used to create sub progress bars. */
+    explicit            ScfProgressBar(
+                            ScfProgressBar& rParProgress,
+                            ScfProgressSegment* pParSegment );
+
+    /** Initializes all members on construction. */
+    void                Init( SfxObjectShell* pDocShell );
+
+    /** Returns the segment specified by list index. */
+    ScfProgressSegment* GetSegment( sal_Int32 nSegment );
+    /** Activates progress bar and sets current segment. */
+    void                SetCurrSegment( ScfProgressSegment* pSegment );
+    /** Increases mnTotalPos and calls the system progress bar. */
+    void                IncreaseProgressBar( std::size_t nDelta );
+
+private:
+    /** Contains all data of a segment of the progress bar. */
+    struct ScfProgressSegment
+    {
+        typedef ::std::unique_ptr< ScfProgressBar > ScfProgressBarPtr;
+
+        ScfProgressBarPtr   mxProgress;     /// Pointer to sub progress bar for this segment.
+        std::size_t         mnSize;         /// Size of this segment.
+        std::size_t         mnPos;          /// Current position of this segment.
+
+        explicit            ScfProgressSegment( std::size_t nSize );
+                            ~ScfProgressSegment();
+    };
+
+    typedef ::std::unique_ptr< ScProgress >         ScProgressPtr;
+    typedef std::vector< std::unique_ptr > ScfSegmentList;
+
+    ScfSegmentList      maSegments;         /// List of progress segments.
+    OUString            maText;             /// UI string for system progress.
+
+    ScProgressPtr       mxSysProgress;      /// System progress bar.
+    SfxObjectShell*     mpDocShell;         /// The document shell for the progress bar.
+    ScfProgressBar*     mpParentProgress;   /// Parent progress bar, if this is a segment progress bar.
+    ScfProgressSegment* mpParentSegment;    /// Parent segment, if this is a segment progress bar.
+    ScfProgressSegment* mpCurrSegment;      /// Current segment for progress.
+
+    std::size_t         mnTotalSize;        /// Total size of all segments.
+    std::size_t         mnTotalPos;         /// Sum of positions of all segments.
+    std::size_t         mnUnitSize;         /// Size between two calls of system progress.
+    std::size_t         mnNextUnitPos;      /// Limit for next system progress call.
+    std::size_t         mnSysProgressScale; /// Additionally scaling factor for system progress.
+    bool                mbInProgress;       /// true = progress bar started.
+};
+
+/** A simplified progress bar with only one segment. */
+class ScfSimpleProgressBar
+{
+public:
+    explicit            ScfSimpleProgressBar(std::size_t nSize, SfxObjectShell* pDocShell, const OUString& rText);
+    explicit            ScfSimpleProgressBar(std::size_t nSize, SfxObjectShell* pDocShell, TranslateId pResId);
+
+    /** Set progress bar to the specified position. */
+    void         ProgressAbs( std::size_t nPos ) { maProgress.ProgressAbs( nPos ); }
+
+private:
+    /** Initializes and starts the progress bar. */
+    void                Init( std::size_t nSize );
+
+private:
+    ScfProgressBar      maProgress;     /// The used progress bar.
+};
+
+/** A simplified progress bar based on the stream position of an existing stream. */
+class ScfStreamProgressBar
+{
+public:
+    explicit            ScfStreamProgressBar( SvStream& rStrm, SfxObjectShell* pDocShell );
+
+    /** Sets the progress bar to the current stream position. */
+    void                Progress();
+
+private:
+    /** Initializes and starts the progress bar. */
+    void                Init( SfxObjectShell* pDocShell, const OUString& rText );
+
+private:
+    typedef ::std::unique_ptr< ScfSimpleProgressBar > ScfSimpleProgressBarPtr;
+
+    ScfSimpleProgressBarPtr mxProgress; /// The used progress bar.
+    SvStream&           mrStrm;         /// The used stream.
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/ftools.hxx b/sc/source/filter/inc/ftools.hxx
new file mode 100644
index 000000000..1366a5197
--- /dev/null
+++ b/sc/source/filter/inc/ftools.hxx
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+// Common macros ==============================================================
+
+// items and item sets --------------------------------------------------------
+
+/** Expands to the item (with type 'itemtype') with Which-ID 'which'. */
+#define GETITEM( itemset, itemtype, which ) \
+    static_cast< const itemtype & >( (itemset).Get( which ) )
+
+/** Expands to the value of the SfxBoolItem with Which-ID 'which'. */
+#define GETITEMBOOL( itemset, which ) \
+    (static_cast( (itemset).Get( which )).GetValue() )
+
+// Global static helpers ======================================================
+
+// Value range limit helpers --------------------------------------------------
+
+/** Returns the value, if it is not less than nMin, otherwise nMin. */
+template< typename ReturnType, typename Type >
+inline ReturnType llimit_cast( Type nValue, ReturnType nMin )
+{ return static_cast< ReturnType >( ::std::max< Type >( nValue, nMin ) ); }
+
+/** Returns the value, if it is not greater than nMax, otherwise nMax. */
+template< typename ReturnType, typename Type >
+inline ReturnType ulimit_cast( Type nValue, ReturnType nMax )
+{ return static_cast< ReturnType >( ::std::min< Type >( nValue, nMax ) ); }
+
+/** Returns the value, if it fits into ReturnType, otherwise the maximum value of ReturnType. */
+template< typename ReturnType, typename Type >
+inline ReturnType ulimit_cast( Type nValue )
+{ return ulimit_cast( nValue, ::std::numeric_limits< ReturnType >::max() ); }
+
+/** Returns the value, if it is not less than nMin and not greater than nMax, otherwise one of the limits. */
+template< typename ReturnType, typename Type >
+inline ReturnType limit_cast( Type nValue, ReturnType nMin, ReturnType nMax )
+{ return static_cast< ReturnType >( ::std::clamp< Type >( nValue, nMin, nMax ) ); }
+
+/** Returns the value, if it fits into ReturnType, otherwise one of the limits of ReturnType. */
+template< typename ReturnType, typename Type >
+inline ReturnType limit_cast( Type nValue )
+{ return limit_cast( nValue, ::std::numeric_limits< ReturnType >::min(), ::std::numeric_limits< ReturnType >::max() ); }
+
+// Read from bitfields --------------------------------------------------------
+
+/** Returns true, if at least one of the bits set in nMask is set in nBitField. */
+template< typename Type >
+inline bool get_flag( Type nBitField, Type nMask )
+{ return (nBitField & nMask) != 0; }
+
+/** Returns nSet, if at least one bit of nMask is set in nBitField, otherwise nUnset. */
+template< typename ReturnType, typename Type >
+inline ReturnType get_flagvalue( Type nBitField, Type nMask, ReturnType nSet, ReturnType nUnset )
+{ return ::get_flag( nBitField, nMask ) ? nSet : nUnset; }
+
+/** Extracts a value from a bit field.
+    @descr  Returns in rnRet the data fragment from nBitField, that starts at bit nStartBit
+    (0-based, bit 0 is rightmost) with the width of nBitCount. rnRet will be right-aligned (normalized).
+    For instance: extract_value( n, 0x4321, 8, 4 ) stores 3 in n (value in bits 8-11). */
+template< typename ReturnType, typename Type >
+inline ReturnType extract_value( Type nBitField, sal_uInt8 nStartBit, sal_uInt8 nBitCount )
+{ return static_cast< ReturnType >( ((1UL << nBitCount) - 1) & (nBitField >> nStartBit) ); }
+
+// Write to bitfields ---------------------------------------------------------
+
+/** Sets or clears (according to bSet) all set bits of nMask in rnBitField. */
+template< typename Type >
+inline void set_flag( Type& rnBitField, Type nMask, bool bSet = true )
+{ if( bSet ) rnBitField |= nMask; else rnBitField &= ~nMask; }
+
+/** Inserts a value into a bitfield.
+    @descr  Inserts the lower nBitCount bits of nValue into rnBitField, starting
+    there at bit nStartBit. Other contents of rnBitField keep unchanged. */
+template< typename Type, typename InsertType >
+void insert_value( Type& rnBitField, InsertType nValue, sal_uInt8 nStartBit, sal_uInt8 nBitCount )
+{
+    unsigned int nMask = (1U << nBitCount) - 1;
+    Type nNewValue = static_cast< Type >( nValue & nMask );
+    rnBitField = (rnBitField & ~(nMask << nStartBit)) | (nNewValue << nStartBit);
+}
+
+class Color;
+class SfxPoolItem;
+class SfxItemSet;
+class ScStyleSheet;
+class ScStyleSheetPool;
+class SvStream;
+class SotStorage;
+class SotStorageStream;
+
+/** Contains static methods used anywhere in the filters. */
+class ScfTools
+{
+public:
+    /** We don't want anybody to instantiate this class, since it is just a
+        collection of static items. */
+    ScfTools() = delete;
+    ScfTools(const ScfTools&) = delete;
+    const ScfTools& operator=(const ScfTools&) = delete;
+
+// *** common methods *** -----------------------------------------------------
+
+    /** Reads a 10-byte-long-double and converts it to double. */
+    static void ReadLongDouble(SvStream& rStrm, double& fResult);
+    /** Returns system text encoding for byte string conversion. */
+    static rtl_TextEncoding GetSystemTextEncoding();
+    /** Returns a string representing the hexadecimal value of nValue. */
+    static OUString       GetHexStr( sal_uInt16 nValue );
+
+    /** Mixes RGB components with given transparence.
+        @param nTrans  Foreground transparence (0x00 == full nFore ... 0x80 = full nBack). */
+    static sal_uInt8    GetMixedColorComp( sal_uInt8 nFore, sal_uInt8 nBack, sal_uInt8 nTrans );
+    /** Mixes colors with given transparence.
+        @param nTrans  Foreground transparence (0x00 == full rFore ... 0x80 = full rBack). */
+    static Color        GetMixedColor( const Color& rFore, const Color& rBack, sal_uInt8 nTrans );
+
+// *** conversion of names *** ------------------------------------------------
+
+    /** Converts a string to a valid Calc defined name or database range name.
+        @descr  Defined names in Calc may contain letters, digits (*), underscores, periods (*),
+        colons (*), question marks, and dollar signs.
+        (*) = not allowed at first position. */
+    static OUString  ConvertToScDefinedName( const OUString& rName );
+
+// *** streams and storages *** -----------------------------------------------
+
+    /** Tries to open an existing storage with the specified name in the passed storage (read-only). */
+    static tools::SvRef OpenStorageRead( tools::SvRef const & xStrg, const OUString& rStrgName );
+    /** Creates and opens a storage with the specified name in the passed storage (read/write). */
+    static tools::SvRef OpenStorageWrite( tools::SvRef const & xStrg, const OUString& rStrgName );
+
+    /** Tries to open an existing stream with the specified name in the passed storage (read-only). */
+    static tools::SvRef OpenStorageStreamRead( tools::SvRef const & xStrg, const OUString& rStrmName );
+    /** Creates and opens a stream with the specified name in the passed storage (read/write). */
+    static tools::SvRef OpenStorageStreamWrite( tools::SvRef const & xStrg, const OUString& rStrmName );
+
+// *** item handling *** ------------------------------------------------------
+
+    /** Returns true, if the passed item set contains the item.
+        @param bDeep  true = Searches in parent item sets too. */
+    static bool         CheckItem( const SfxItemSet& rItemSet, sal_uInt16 nWhichId, bool bDeep );
+    /** Returns true, if the passed item set contains at least one of the items.
+        @param pnWhichIds  Zero-terminated array of Which-IDs.
+        @param bDeep  true = Searches in parent item sets too. */
+    static bool         CheckItems( const SfxItemSet& rItemSet, const sal_uInt16* pnWhichIds, bool bDeep );
+
+    /** Puts the item into the passed item set.
+        @descr  The item will be put into the item set, if bSkipPoolDef is false,
+        or if the item differs from the default pool item.
+        @param rItemSet  The destination item set.
+        @param rItem  The item to put into the item set.
+        @param nWhichId  The Which-ID to set with the item.
+        @param bSkipPoolDef  true = Do not put item if it is equal to pool default; false = Always put the item. */
+    static void         PutItem(
+                            SfxItemSet& rItemSet, const SfxPoolItem& rItem,
+                            sal_uInt16 nWhichId, bool bSkipPoolDef );
+
+    /** Puts the item into the passed item set.
+        @descr  The item will be put into the item set, if bSkipPoolDef is false,
+        or if the item differs from the default pool item.
+        @param rItemSet  The destination item set.
+        @param rItem  The item to put into the item set.
+        @param bSkipPoolDef  true = Do not put item if it is equal to pool default; false = Always put the item. */
+    static void         PutItem( SfxItemSet& rItemSet, const SfxPoolItem& rItem, bool bSkipPoolDef );
+
+// *** style sheet handling *** -----------------------------------------------
+
+    /** Creates and returns a cell style sheet and inserts it into the pool.
+        @descr  If the style sheet is already in the pool, another unused style name is used.
+        @param bForceName  Controls behaviour, if the style already exists:
+        true = Old existing style will be renamed; false = New style gets another name. */
+    static ScStyleSheet& MakeCellStyleSheet(
+                            ScStyleSheetPool& rPool,
+                            const OUString& rStyleName, bool bForceName );
+    /** Creates and returns a page style sheet and inserts it into the pool.
+        @descr  If the style sheet is already in the pool, another unused style name is used.
+        @param bForceName  Controls behaviour, if the style already exists:
+        true = Old existing style will be renamed; false = New style gets another name. */
+    static ScStyleSheet& MakePageStyleSheet(
+                            ScStyleSheetPool& rPool,
+                            const OUString& rStyleName, bool bForceName );
+
+// *** byte string import operations *** --------------------------------------
+
+    /** Reads and returns a zero terminated byte string and decreases a stream counter. */
+    static OString read_zeroTerminated_uInt8s_ToOString(SvStream& rStrm, sal_Int32& rnBytesLeft);
+    /** Reads and returns a zero terminated byte string and decreases a stream counter. */
+    static OUString read_zeroTerminated_uInt8s_ToOUString(SvStream& rStrm, sal_Int32& rnBytesLeft, rtl_TextEncoding eTextEnc)
+    {
+        return OStringToOUString(read_zeroTerminated_uInt8s_ToOString(rStrm, rnBytesLeft), eTextEnc);
+    }
+
+    /** Appends a zero terminated byte string. */
+    static void         AppendCString( SvStream& rStrm, OUString& rString, rtl_TextEncoding eTextEnc );
+
+// *** HTML table names <-> named range names *** -----------------------------
+
+    /** Returns the built-in range name for an HTML document. */
+    static const OUString& GetHTMLDocName();
+    /** Returns the built-in range name for all HTML tables. */
+    static const OUString& GetHTMLTablesName();
+    /** Returns the built-in range name for an HTML table, specified by table index. */
+    static OUString       GetNameFromHTMLIndex( sal_uInt32 nIndex );
+    /** Returns the built-in range name for an HTML table, specified by table name. */
+    static OUString       GetNameFromHTMLName( std::u16string_view rTabName );
+
+    /** Returns true, if rSource is the built-in range name for an HTML document. */
+    static bool         IsHTMLDocName( std::u16string_view rSource );
+    /** Returns true, if rSource is the built-in range name for all HTML tables. */
+    static bool         IsHTMLTablesName( std::u16string_view rSource );
+    /** Converts a built-in range name to an HTML table name.
+        @param rSource  The string to be determined.
+        @param rName  The HTML table name.
+        @return  true, if conversion was successful. */
+    static bool         GetHTMLNameFromName( const OUString& rSource, OUString& rName );
+
+private:
+    /** Returns the prefix for table index names. */
+    static const OUString& GetHTMLIndexPrefix();
+    /** Returns the prefix for table names. */
+    static const OUString& GetHTMLNamePrefix();
+};
+
+// Containers =================================================================
+
+typedef ::std::vector< sal_uInt8 >                  ScfUInt8Vec;
+typedef ::std::vector< sal_Int16 >                  ScfInt16Vec;
+typedef ::std::vector< sal_uInt16 >                 ScfUInt16Vec;
+typedef ::std::vector< sal_Int32 >                  ScfInt32Vec;
+typedef ::std::vector< sal_uInt32 >                 ScfUInt32Vec;
+typedef ::std::vector< OUString >            ScfStringVec;
+
+class ScFormatFilterPluginImpl : public ScFormatFilterPlugin
+{
+public:
+    ScFormatFilterPluginImpl();
+    virtual ~ScFormatFilterPluginImpl();
+    // various import filters
+    virtual ErrCode ScImportLotus123( SfxMedium&, ScDocument&, rtl_TextEncoding eSrc ) override;
+    virtual ErrCode ScImportQuattroPro(SvStream* pStream, ScDocument& rDoc) override;
+    virtual ErrCode ScImportExcel( SfxMedium&, ScDocument*, const EXCIMPFORMAT ) override;
+        // eFormat == EIF_AUTO  -> matching filter is used automatically
+        // eFormat == EIF_BIFF5 -> only Biff5 stream leads to success (even in an Excel97 doc)
+        // eFormat == EIF_BIFF8 -> only Biff8 stream leads to success (only in Excel97 docs)
+        // eFormat == EIF_BIFF_LE4 -> only non-storage files _could_ lead to success
+    virtual ErrCode ScImportDif( SvStream&, ScDocument*, const ScAddress& rInsPos,
+                 const rtl_TextEncoding eSrc ) override;
+    virtual ErrCode ScImportRTF( SvStream&, const OUString& rBaseURL, ScDocument*, ScRange& rRange ) override;
+    virtual ErrCode ScImportHTML( SvStream&, const OUString& rBaseURL, ScDocument*, ScRange& rRange,
+                                   double nOutputFactor, bool bCalcWidthHeight,
+                                   SvNumberFormatter* pFormatter, bool bConvertDate ) override;
+
+    virtual std::unique_ptr CreateRTFImport( ScDocument* pDoc, const ScRange& rRange ) override;
+    virtual std::unique_ptr CreateHTMLImport( ScDocument* pDocP, const OUString& rBaseURL, const ScRange& rRange ) override;
+    virtual OUString       GetHTMLRangeNameList( ScDocument& rDoc, const OUString& rOrigName ) override;
+
+    // various export filters
+    virtual ErrCode ScExportExcel5( SfxMedium&, ScDocument*, ExportFormatExcel eFormat, rtl_TextEncoding eDest ) override;
+    virtual void ScExportDif( SvStream&, ScDocument*, const ScAddress& rOutPos, const rtl_TextEncoding eDest ) override;
+    virtual void ScExportDif( SvStream&, ScDocument*, const ScRange& rRange, const rtl_TextEncoding eDest ) override;
+    virtual void ScExportHTML( SvStream&, const OUString& rBaseURL, ScDocument*, const ScRange& rRange, const rtl_TextEncoding eDest, bool bAll,
+                  const OUString& rStreamPath, OUString& rNonConvertibleChars, const OUString& rFilterOptions ) override;
+    virtual void ScExportRTF( SvStream&, ScDocument*, const ScRange& rRange, const rtl_TextEncoding eDest ) override;
+
+    virtual ScOrcusFilters* GetOrcusFilters() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/htmlexp.hxx b/sc/source/filter/inc/htmlexp.hxx
new file mode 100644
index 000000000..29d5b3b1b
--- /dev/null
+++ b/sc/source/filter/inc/htmlexp.hxx
@@ -0,0 +1,185 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include "expbase.hxx"
+
+class ScDocument;
+class SfxItemSet;
+class SdrPage;
+class Graphic;
+class SdrObject;
+class OutputDevice;
+class ScDrawLayer;
+class EditTextObject;
+enum class SvtScriptType : sal_uInt8;
+namespace editeng { class SvxBorderLine; }
+
+namespace sc {
+struct ColumnBlockPosition;
+}
+
+struct ScHTMLStyle
+{   // Defaults from stylesheet
+    Color               aBackgroundColor;
+    OUString            aFontFamilyName;
+    sal_uInt32          nFontHeight;        // Item-Value
+    sal_uInt16          nFontSizeNumber;    // HTML value 1-7
+    SvtScriptType       nDefaultScriptType; // Font values are valid for the default script type
+    bool                bInitialized;
+
+    ScHTMLStyle() :
+        nFontHeight(0),
+        nFontSizeNumber(2),
+        nDefaultScriptType(),
+        bInitialized(false)
+    {}
+
+    const ScHTMLStyle& operator=( const ScHTMLStyle& rScHTMLStyle )
+    {
+        aBackgroundColor   = rScHTMLStyle.aBackgroundColor;
+        aFontFamilyName    = rScHTMLStyle.aFontFamilyName;
+        nFontHeight        = rScHTMLStyle.nFontHeight;
+        nFontSizeNumber    = rScHTMLStyle.nFontSizeNumber;
+        nDefaultScriptType = rScHTMLStyle.nDefaultScriptType;
+        bInitialized       = rScHTMLStyle.bInitialized;
+        return *this;
+    }
+};
+
+struct ScHTMLGraphEntry
+{
+    ScRange             aRange;         // mapped range
+    Size                aSize;          // size in pixels
+    Size                aSpace;         // spacing in pixels
+    SdrObject*          pObject;
+    bool                bInCell;        // if output is in cell
+    bool                bWritten;
+
+    ScHTMLGraphEntry( SdrObject* pObj, const ScRange& rRange,
+                      const Size& rSize,  bool bIn, const Size& rSpace ) :
+        aRange( rRange ),
+        aSize( rSize ),
+        aSpace( rSpace ),
+        pObject( pObj ),
+        bInCell( bIn ),
+        bWritten( false )
+    {}
+};
+
+#define SC_HTML_FONTSIZES 7
+const short nIndentMax = 23;
+
+class ScHTMLExport : public ScExportBase
+{
+    // default HtmlFontSz[1-7]
+    static const sal_uInt16 nDefaultFontSize[SC_HTML_FONTSIZES];
+    // HtmlFontSz[1-7] in s*3.ini [user]
+    static sal_uInt16       nFontSize[SC_HTML_FONTSIZES];
+    static const char*  pFontSizeCss[SC_HTML_FONTSIZES];
+    static const sal_uInt16 nCellSpacing;
+    static const char sIndentSource[];
+
+    typedef std::unique_ptr > FileNameMapPtr;
+    typedef std::vector GraphEntryList;
+
+    GraphEntryList   aGraphList;
+    ScHTMLStyle      aHTMLStyle;
+    OUString         aBaseURL;
+    OUString         aStreamPath;
+    VclPtr pAppWin;        // for Pixel-work
+    FileNameMapPtr   pFileNameMap;        // for CopyLocalFileToINet
+    OUString         aNonConvertibleChars;   // collect nonconvertible characters
+    SCTAB            nUsedTables;
+    short            nIndent;
+    char             sIndent[nIndentMax+1];
+    bool             bAll;           // whole document
+    bool             bTabHasGraphics;
+    bool             bTabAlignedLeft;
+    bool             bCalcAsShown;
+    bool             bCopyLocalFileToINet;
+    bool             bTableDataHeight;
+    bool             mbSkipImages;
+    /// If HTML header and footer should be written as well, or just the content itself.
+    bool             mbSkipHeaderFooter;
+
+    const SfxItemSet& PageDefaults( SCTAB nTab );
+
+    void WriteBody();
+    void WriteHeader();
+    void WriteOverview();
+    void WriteTables();
+    void WriteCell( sc::ColumnBlockPosition& rBlockPos, SCCOL nCol, SCROW nRow, SCTAB nTab );
+    void WriteGraphEntry( ScHTMLGraphEntry* );
+    void WriteImage( OUString& rLinkName,
+                     const Graphic&, std::string_view rImgOptions,
+                     XOutFlags nXOutFlags = XOutFlags::NONE );
+            // nXOutFlags for XOutBitmap::WriteGraphic
+
+    // write to stream if and only if URL fields in edit cell
+    bool WriteFieldText( const EditTextObject* pData );
+
+    // copy a local file to internet if needed
+    void CopyLocalFileToINet( OUString& rFileNm, std::u16string_view rTargetNm );
+
+    void PrepareGraphics( ScDrawLayer*, SCTAB nTab,
+                          SCCOL nStartCol, SCROW nStartRow,
+                          SCCOL nEndCol, SCROW nEndRow );
+
+    void FillGraphList( const SdrPage*, SCTAB nTab,
+                        SCCOL nStartCol, SCROW nStartRow,
+                        SCCOL nEndCol, SCROW nEndRow );
+
+    static OString BorderToStyle(const char* pBorderName,
+                          const editeng::SvxBorderLine* pLine,
+                          bool& bInsertSemicolon);
+
+    static sal_uInt16  GetFontSizeNumber( sal_uInt16 nHeight );
+    static const char* GetFontSizeCss( sal_uInt16 nHeight );
+    sal_uInt16  ToPixel( sal_uInt16 nTwips );
+    Size        MMToPixel( const Size& r100thMMSize );
+    void        IncIndent( short nVal );
+
+    const char* GetIndentStr() const
+    {
+        return sIndent;
+    }
+
+public:
+                        ScHTMLExport( SvStream&, const OUString&, ScDocument*, const ScRange&,
+                                      bool bAll, const OUString& aStreamPath, std::u16string_view rFilterOptions );
+    virtual             ~ScHTMLExport() override;
+    void                Write();
+    const OUString&     GetNonConvertibleChars() const
+    {
+        return aNonConvertibleChars;
+    }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/htmlimp.hxx b/sc/source/filter/inc/htmlimp.hxx
new file mode 100644
index 000000000..76acc4471
--- /dev/null
+++ b/sc/source/filter/inc/htmlimp.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/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements. See the NOTICE file distributed
+ *   with this work for additional information regarding copyright
+ *   ownership. The ASF licenses this file to you under the Apache
+ *   License, Version 2.0 (the "License"); you may not use this 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 "eeimport.hxx"
+
+class ScHTMLImport : public ScEEImport
+{
+private:
+    static void         InsertRangeName( ScDocument& rDoc, const OUString& rName, const ScRange& rRange );
+
+public:
+    ScHTMLImport( ScDocument* pDoc, const OUString& rBaseURL, const ScRange& rRange, bool bCalcWidthHeight );
+
+    virtual void        WriteToDocument( bool bSizeColsRows = false, double nOutputFactor = 1.0,
+                                         SvNumberFormatter* pFormatter = nullptr, bool bConvertDate = true ) override;
+
+    static OUString     GetHTMLRangeNameList( const ScDocument& rDoc, std::u16string_view rOrigName );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/source/filter/inc/htmlpars.hxx b/sc/source/filter/inc/htmlpars.hxx
new file mode 100644
index 000000000..47ecc57b4
--- /dev/null
+++ b/sc/source/filter/inc/htmlpars.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include "eeparser.hxx"
+
+const sal_uInt32 SC_HTML_FONTSIZES = 7;        // like export, HTML options
+
+// Pixel tolerance for SeekOffset and related.
+const sal_uInt16 SC_HTML_OFFSET_TOLERANCE_SMALL = 1;    // single table
+const sal_uInt16 SC_HTML_OFFSET_TOLERANCE_LARGE = 10;   // nested
+
+// BASE class for HTML parser classes
+
+class ScHTMLTable;
+
+/**
+ * Collection of HTML style data parsed from the content of 
tag + ScHTMLSize aSpanSize( 1, 1 ); + std::optional pValStr, pNumStr; + const HTMLOptions& rOptions = static_cast(rInfo.pParser)->GetOptions(); + sal_uInt32 nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND; + for (const auto& rOption : rOptions) + { + switch (rOption.GetToken()) + { + case HtmlOptionId::COLSPAN: + aSpanSize.mnCols = static_cast( getLimitedValue( rOption.GetString().toInt32(), 1, 256 ) ); + break; + case HtmlOptionId::ROWSPAN: + aSpanSize.mnRows = static_cast( getLimitedValue( rOption.GetString().toInt32(), 1, 256 ) ); + break; + case HtmlOptionId::SDVAL: + pValStr = rOption.GetString(); + break; + case HtmlOptionId::SDNUM: + pNumStr = rOption.GetString(); + break; + case HtmlOptionId::CLASS: + { + // Pick up the number format associated with this class (if + // any). + OUString aClass = rOption.GetString(); + const ScHTMLStyles& rStyles = mpParser->GetStyles(); + const OUString& rVal = rStyles.getPropertyValue("td", aClass, "mso-number-format"); + if (!rVal.isEmpty()) + { + OUString aNumFmt = decodeNumberFormat(rVal); + + nNumberFormat = GetFormatTable()->GetEntryKey(aNumFmt); + if (nNumberFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nErrPos = 0; + SvNumFormatType nDummy; + bool bValidFmt = GetFormatTable()->PutEntry(aNumFmt, nErrPos, nDummy, nNumberFormat); + if (!bValidFmt) + nNumberFormat = NUMBERFORMAT_ENTRY_NOT_FOUND; + } + } + } + break; + default: break; + } + } + + ImplDataOn( aSpanSize ); + + if (nNumberFormat != NUMBERFORMAT_ENTRY_NOT_FOUND) + moDataItemSet->Put( SfxUInt32Item(ATTR_VALUE_FORMAT, nNumberFormat) ); + + ProcessFormatOptions( *moDataItemSet, rInfo ); + CreateNewEntry( rInfo ); + mxCurrEntry->pValStr = std::move(pValStr); + mxCurrEntry->pNumStr = std::move(pNumStr); + } + else + CreateNewEntry( rInfo ); +} + +void ScHTMLTable::DataOff( const HtmlImportInfo& rInfo ) +{ + PushEntry( rInfo, true ); + if( mpParentTable && !mbPreFormText ) // no cells allowed in global and preformatted tables + ImplDataOff(); + CreateNewEntry( rInfo ); +} + +void ScHTMLTable::BodyOn( const HtmlImportInfo& rInfo ) +{ + bool bPushed = PushEntry( rInfo ); + if( !mpParentTable ) + { + // do not start new row, if nothing (no title) precedes the body. + if( bPushed || !mbRowOn ) + ImplRowOn(); + if( bPushed || !mbDataOn ) + ImplDataOn( ScHTMLSize( 1, 1 ) ); + ProcessFormatOptions( *moDataItemSet, rInfo ); + } + CreateNewEntry( rInfo ); +} + +void ScHTMLTable::BodyOff( const HtmlImportInfo& rInfo ) +{ + PushEntry( rInfo ); + if( !mpParentTable ) + { + ImplDataOff(); + ImplRowOff(); + } + CreateNewEntry( rInfo ); +} + +ScHTMLTable* ScHTMLTable::CloseTable( const HtmlImportInfo& rInfo ) +{ + if( mpParentTable ) // not allowed to close global table + { + PushEntry( rInfo, mbDataOn ); + ImplDataOff(); + ImplRowOff(); + mpParentTable->PushTableEntry( GetTableId() ); + mpParentTable->CreateNewEntry( rInfo ); + if( mbPreFormText ) // enclose preformatted table with empty lines in parent table + mpParentTable->InsertLeadingEmptyLine(); + return mpParentTable; + } + return this; +} + +SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos ) const +{ + const ScSizeVec& rSizes = maCumSizes[ eOrient ]; + size_t nIndex = static_cast< size_t >( nCellPos ); + if( nIndex >= rSizes.size() ) return 0; + return (nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ]); +} + +SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellBegin, SCCOLROW nCellEnd ) const +{ + const ScSizeVec& rSizes = maCumSizes[ eOrient ]; + size_t nBeginIdx = static_cast< size_t >( std::max< SCCOLROW >( nCellBegin, 0 ) ); + size_t nEndIdx = static_cast< size_t >( std::min< SCCOLROW >( nCellEnd, static_cast< SCCOLROW >( rSizes.size() ) ) ); + if (nBeginIdx >= nEndIdx ) return 0; + return rSizes[ nEndIdx - 1 ] - ((nBeginIdx == 0) ? 0 : rSizes[ nBeginIdx - 1 ]); +} + +SCCOLROW ScHTMLTable::GetDocSize( ScHTMLOrient eOrient ) const +{ + const ScSizeVec& rSizes = maCumSizes[ eOrient ]; + return rSizes.empty() ? 0 : rSizes.back(); +} + +ScHTMLSize ScHTMLTable::GetDocSize( const ScHTMLPos& rCellPos ) const +{ + ScHTMLSize aCellSpan = GetSpan( rCellPos ); + return ScHTMLSize( + static_cast< SCCOL >( GetDocSize( tdCol, rCellPos.mnCol, rCellPos.mnCol + aCellSpan.mnCols ) ), + static_cast< SCROW >( GetDocSize( tdRow, rCellPos.mnRow, rCellPos.mnRow + aCellSpan.mnRows ) ) ); +} + +SCCOLROW ScHTMLTable::GetDocPos( ScHTMLOrient eOrient, SCCOLROW nCellPos ) const +{ + return maDocBasePos.Get( eOrient ) + GetDocSize( eOrient, 0, nCellPos ); +} + +ScHTMLPos ScHTMLTable::GetDocPos( const ScHTMLPos& rCellPos ) const +{ + return ScHTMLPos( + static_cast< SCCOL >( GetDocPos( tdCol, rCellPos.mnCol ) ), + static_cast< SCROW >( GetDocPos( tdRow, rCellPos.mnRow ) ) ); +} + +void ScHTMLTable::GetDocRange( ScRange& rRange ) const +{ + rRange.aStart = rRange.aEnd = maDocBasePos.MakeAddr(); + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + if (!rRange.aEnd.Move( static_cast< SCCOL >( GetDocSize( tdCol ) ) - 1, + static_cast< SCROW >( GetDocSize( tdRow ) ) - 1, 0, aErrorPos, mrDoc )) + { + assert(!"can't move"); + } +} + +void ScHTMLTable::ApplyCellBorders( ScDocument* pDoc, const ScAddress& rFirstPos ) const +{ + OSL_ENSURE( pDoc, "ScHTMLTable::ApplyCellBorders - no document" ); + if( pDoc && mbBorderOn ) + { + const SCCOL nLastCol = maSize.mnCols - 1; + const SCROW nLastRow = maSize.mnRows - 1; + const tools::Long nOuterLine = SvxBorderLineWidth::Medium; + const tools::Long nInnerLine = SvxBorderLineWidth::Hairline; + SvxBorderLine aOuterLine(nullptr, nOuterLine, SvxBorderLineStyle::SOLID); + SvxBorderLine aInnerLine(nullptr, nInnerLine, SvxBorderLineStyle::SOLID); + SvxBoxItem aBorderItem( ATTR_BORDER ); + + for( SCCOL nCol = 0; nCol <= nLastCol; ++nCol ) + { + SvxBorderLine* pLeftLine = (nCol == 0) ? &aOuterLine : &aInnerLine; + SvxBorderLine* pRightLine = (nCol == nLastCol) ? &aOuterLine : &aInnerLine; + SCCOL nCellCol1 = static_cast< SCCOL >( GetDocPos( tdCol, nCol ) ) + rFirstPos.Col(); + SCCOL nCellCol2 = nCellCol1 + static_cast< SCCOL >( GetDocSize( tdCol, nCol ) ) - 1; + for( SCROW nRow = 0; nRow <= nLastRow; ++nRow ) + { + SvxBorderLine* pTopLine = (nRow == 0) ? &aOuterLine : &aInnerLine; + SvxBorderLine* pBottomLine = (nRow == nLastRow) ? &aOuterLine : &aInnerLine; + SCROW nCellRow1 = GetDocPos( tdRow, nRow ) + rFirstPos.Row(); + SCROW nCellRow2 = nCellRow1 + GetDocSize( tdRow, nRow ) - 1; + for( SCCOL nCellCol = nCellCol1; nCellCol <= nCellCol2; ++nCellCol ) + { + aBorderItem.SetLine( (nCellCol == nCellCol1) ? pLeftLine : nullptr, SvxBoxItemLine::LEFT ); + aBorderItem.SetLine( (nCellCol == nCellCol2) ? pRightLine : nullptr, SvxBoxItemLine::RIGHT ); + for( SCROW nCellRow = nCellRow1; nCellRow <= nCellRow2; ++nCellRow ) + { + aBorderItem.SetLine( (nCellRow == nCellRow1) ? pTopLine : nullptr, SvxBoxItemLine::TOP ); + aBorderItem.SetLine( (nCellRow == nCellRow2) ? pBottomLine : nullptr, SvxBoxItemLine::BOTTOM ); + pDoc->ApplyAttr( nCellCol, nCellRow, rFirstPos.Tab(), aBorderItem ); + } + } + } + } + } + + for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter ) + aIter->ApplyCellBorders( pDoc, rFirstPos ); +} + +SvNumberFormatter* ScHTMLTable::GetFormatTable() +{ + return mpParser->GetDoc().GetFormatTable(); +} + +bool ScHTMLTable::IsEmptyCell() const +{ + return mpCurrEntryVector && mpCurrEntryVector->empty(); +} + +bool ScHTMLTable::IsSpaceCharInfo( const HtmlImportInfo& rInfo ) +{ + return (rInfo.nToken == HtmlTokenId::TEXTTOKEN) && (rInfo.aText.getLength() == 1) && (rInfo.aText[ 0 ] == ' '); +} + +ScHTMLTable::ScHTMLEntryPtr ScHTMLTable::CreateEntry() const +{ + return std::make_unique( GetCurrItemSet() ); +} + +void ScHTMLTable::CreateNewEntry( const HtmlImportInfo& rInfo ) +{ + OSL_ENSURE( !mxCurrEntry, "ScHTMLTable::CreateNewEntry - old entry still present" ); + mxCurrEntry = CreateEntry(); + mxCurrEntry->aSel = rInfo.aSelection; +} + +void ScHTMLTable::ImplPushEntryToVector( ScHTMLEntryVector& rEntryVector, ScHTMLEntryPtr& rxEntry ) +{ + // HTML entry list does not own the entries + rEntryVector.push_back( rxEntry.get() ); + // mrEEParseList (reference to member of ScEEParser) owns the entries + mrEEParseList.push_back(std::shared_ptr(rxEntry.release())); +} + +bool ScHTMLTable::PushEntry( ScHTMLEntryPtr& rxEntry ) +{ + bool bPushed = false; + if( rxEntry && rxEntry->HasContents() ) + { + if( mpCurrEntryVector ) + { + if( mbPushEmptyLine ) + { + ScHTMLEntryPtr xEmptyEntry = CreateEntry(); + ImplPushEntryToVector( *mpCurrEntryVector, xEmptyEntry ); + mbPushEmptyLine = false; + } + ImplPushEntryToVector( *mpCurrEntryVector, rxEntry ); + bPushed = true; + } + else if( mpParentTable ) + { + bPushed = mpParentTable->PushEntry( rxEntry ); + } + else + { + OSL_FAIL( "ScHTMLTable::PushEntry - cannot push entry, no parent found" ); + } + } + return bPushed; +} + +bool ScHTMLTable::PushEntry( const HtmlImportInfo& rInfo, bool bLastInCell ) +{ + OSL_ENSURE( mxCurrEntry, "ScHTMLTable::PushEntry - no current entry" ); + bool bPushed = false; + if( mxCurrEntry ) + { + mxCurrEntry->AdjustEnd( rInfo ); + mxCurrEntry->Strip( mrEditEngine ); + + // import entry always, if it is the last in cell, and cell is still empty + if( bLastInCell && IsEmptyCell() ) + { + mxCurrEntry->SetImportAlways(); + // don't insert empty lines before single empty entries + if( mxCurrEntry->IsEmpty() ) + mbPushEmptyLine = false; + } + + bPushed = PushEntry( mxCurrEntry ); + mxCurrEntry.reset(); + } + return bPushed; +} + +void ScHTMLTable::PushTableEntry( ScHTMLTableId nTableId ) +{ + OSL_ENSURE( nTableId != SC_HTML_GLOBAL_TABLE, "ScHTMLTable::PushTableEntry - cannot push global table" ); + if( nTableId != SC_HTML_GLOBAL_TABLE ) + { + ScHTMLEntryPtr xEntry( new ScHTMLEntry( maTableItemSet, nTableId ) ); + PushEntry( xEntry ); + } +} + +ScHTMLTable* ScHTMLTable::GetExistingTable( ScHTMLTableId nTableId ) const +{ + ScHTMLTable* pTable = ((nTableId != SC_HTML_GLOBAL_TABLE) && mxNestedTables) ? + mxNestedTables->FindTable( nTableId, false ) : nullptr; + OSL_ENSURE( pTable || (nTableId == SC_HTML_GLOBAL_TABLE), "ScHTMLTable::GetExistingTable - table not found" ); + return pTable; +} + +ScHTMLTable* ScHTMLTable::InsertNestedTable( const HtmlImportInfo& rInfo, bool bPreFormText ) +{ + if( !mxNestedTables ) + mxNestedTables.reset( new ScHTMLTableMap( *this ) ); + if( bPreFormText ) // enclose new preformatted table with empty lines + InsertLeadingEmptyLine(); + return mxNestedTables->CreateTable( rInfo, bPreFormText, mrDoc ); +} + +void ScHTMLTable::InsertNewCell( const ScHTMLSize& rSpanSize ) +{ + ScRange* pRange; + + /* Find an unused cell by skipping all merged ranges that cover the + current cell position stored in maCurrCell. */ + for (;;) + { + pRange = maVMergedCells.Find( maCurrCell.MakeAddr() ); + if (!pRange) + pRange = maHMergedCells.Find( maCurrCell.MakeAddr() ); + if (!pRange) + break; + maCurrCell.mnCol = pRange->aEnd.Col() + 1; + } + mpCurrEntryVector = &maEntryMap[ maCurrCell ]; + + /* If the new cell is merged horizontally, try to find collisions with + other vertically merged ranges. In this case, shrink existing + vertically merged ranges (do not shrink the new cell). */ + SCCOL nColEnd = maCurrCell.mnCol + rSpanSize.mnCols; + for( ScAddress aAddr( maCurrCell.MakeAddr() ); aAddr.Col() < nColEnd; aAddr.IncCol() ) + if( (pRange = maVMergedCells.Find( aAddr )) != nullptr ) + pRange->aEnd.SetRow( maCurrCell.mnRow - 1 ); + + // insert the new range into the cell lists + ScRange aNewRange( maCurrCell.MakeAddr() ); + ScAddress aErrorPos( ScAddress::UNINITIALIZED ); + if (!aNewRange.aEnd.Move( rSpanSize.mnCols - 1, rSpanSize.mnRows - 1, 0, aErrorPos, mrDoc )) + { + assert(!"can't move"); + } + if( rSpanSize.mnRows > 1 ) + { + maVMergedCells.push_back( aNewRange ); + /* Do not insert vertically merged ranges into maUsedCells yet, + because they may be shrunken (see above). The final vertically + merged ranges are inserted in FillEmptyCells(). */ + } + else + { + if( rSpanSize.mnCols > 1 ) + maHMergedCells.push_back( aNewRange ); + /* Insert horizontally merged ranges and single cells into + maUsedCells, they will not be changed anymore. */ + maUsedCells.Join( aNewRange ); + } + + // adjust table size + maSize.mnCols = std::max< SCCOL >( maSize.mnCols, aNewRange.aEnd.Col() + 1 ); + maSize.mnRows = std::max< SCROW >( maSize.mnRows, aNewRange.aEnd.Row() + 1 ); +} + +void ScHTMLTable::ImplRowOn() +{ + if( mbRowOn ) + ImplRowOff(); + moRowItemSet.emplace( maTableItemSet ); + maCurrCell.mnCol = 0; + mbRowOn = true; + mbDataOn = false; +} + +void ScHTMLTable::ImplRowOff() +{ + if( mbDataOn ) + ImplDataOff(); + if( mbRowOn ) + { + moRowItemSet.reset(); + ++maCurrCell.mnRow; + mbRowOn = mbDataOn = false; + } +} + +void ScHTMLTable::ImplDataOn( const ScHTMLSize& rSpanSize ) +{ + if( mbDataOn ) + ImplDataOff(); + if( !mbRowOn ) + ImplRowOn(); + moDataItemSet.emplace( *moRowItemSet ); + InsertNewCell( rSpanSize ); + mbDataOn = true; + mbPushEmptyLine = false; +} + +void ScHTMLTable::ImplDataOff() +{ + if( mbDataOn ) + { + moDataItemSet.reset(); + ++maCurrCell.mnCol; + mpCurrEntryVector = nullptr; + mbDataOn = false; + } +} + +void ScHTMLTable::ProcessFormatOptions( SfxItemSet& rItemSet, const HtmlImportInfo& rInfo ) +{ + // special handling for table header cells + if( rInfo.nToken == HtmlTokenId::TABLEHEADER_ON ) + { + rItemSet.Put( SvxWeightItem( WEIGHT_BOLD, ATTR_FONT_WEIGHT ) ); + rItemSet.Put( SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY ) ); + } + + const HTMLOptions& rOptions = static_cast(rInfo.pParser)->GetOptions(); + for (const auto& rOption : rOptions) + { + switch( rOption.GetToken() ) + { + case HtmlOptionId::ALIGN: + { + SvxCellHorJustify eVal = SvxCellHorJustify::Standard; + const OUString& rOptVal = rOption.GetString(); + if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_right ) ) + eVal = SvxCellHorJustify::Right; + else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_center ) ) + eVal = SvxCellHorJustify::Center; + else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_AL_left ) ) + eVal = SvxCellHorJustify::Left; + if( eVal != SvxCellHorJustify::Standard ) + rItemSet.Put( SvxHorJustifyItem( eVal, ATTR_HOR_JUSTIFY ) ); + } + break; + + case HtmlOptionId::VALIGN: + { + SvxCellVerJustify eVal = SvxCellVerJustify::Standard; + const OUString& rOptVal = rOption.GetString(); + if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_top ) ) + eVal = SvxCellVerJustify::Top; + else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_middle ) ) + eVal = SvxCellVerJustify::Center; + else if( rOptVal.equalsIgnoreAsciiCase( OOO_STRING_SVTOOLS_HTML_VA_bottom ) ) + eVal = SvxCellVerJustify::Bottom; + if( eVal != SvxCellVerJustify::Standard ) + rItemSet.Put( SvxVerJustifyItem( eVal, ATTR_VER_JUSTIFY ) ); + } + break; + + case HtmlOptionId::BGCOLOR: + { + Color aColor; + rOption.GetColor( aColor ); + rItemSet.Put( SvxBrushItem( aColor, ATTR_BACKGROUND ) ); + } + break; + default: break; + } + } +} + +void ScHTMLTable::SetDocSize( ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nSize ) +{ + OSL_ENSURE( nCellPos >= 0, "ScHTMLTable::SetDocSize - unexpected negative position" ); + ScSizeVec& rSizes = maCumSizes[ eOrient ]; + size_t nIndex = static_cast< size_t >( nCellPos ); + // expand with height/width == 1 + while( nIndex >= rSizes.size() ) + rSizes.push_back( rSizes.empty() ? 1 : (rSizes.back() + 1) ); + // update size of passed position and all following + // #i109987# only grow, don't shrink - use the largest needed size + SCCOLROW nDiff = nSize - ((nIndex == 0) ? rSizes.front() : (rSizes[ nIndex ] - rSizes[ nIndex - 1 ])); + if( nDiff > 0 ) + std::for_each(rSizes.begin() + nIndex, rSizes.end(), [&nDiff](SCCOLROW& rSize) { rSize += nDiff; }); +} + +void ScHTMLTable::CalcNeededDocSize( + ScHTMLOrient eOrient, SCCOLROW nCellPos, SCCOLROW nCellSpan, SCCOLROW nRealDocSize ) +{ + SCCOLROW nDiffSize = 0; + // in merged columns/rows: reduce needed size by size of leading columns + while( nCellSpan > 1 ) + { + nDiffSize += GetDocSize( eOrient, nCellPos ); + --nCellSpan; + ++nCellPos; + } + // set remaining needed size to last column/row + nRealDocSize -= std::min< SCCOLROW >( nRealDocSize - 1, nDiffSize ); + SetDocSize( eOrient, nCellPos, nRealDocSize ); +} + +void ScHTMLTable::FillEmptyCells() +{ + for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter ) + aIter->FillEmptyCells(); + + // insert the final vertically merged ranges into maUsedCells + for ( size_t i = 0, nRanges = maVMergedCells.size(); i < nRanges; ++i ) + { + ScRange & rRange = maVMergedCells[ i ]; + maUsedCells.Join( rRange ); + } + + for( ScAddress aAddr; aAddr.Row() < maSize.mnRows; aAddr.IncRow() ) + { + for( aAddr.SetCol( 0 ); aAddr.Col() < maSize.mnCols; aAddr.IncCol() ) + { + if( !maUsedCells.Find( aAddr ) ) + { + // create a range for the lock list (used to calc. cell span) + ScRange aRange( aAddr ); + do + { + aRange.aEnd.IncCol(); + } + while( (aRange.aEnd.Col() < maSize.mnCols) && !maUsedCells.Find( aRange.aEnd ) ); + aRange.aEnd.IncCol( -1 ); + maUsedCells.Join( aRange ); + + // insert a dummy entry + ScHTMLEntryPtr xEntry = CreateEntry(); + ImplPushEntryToVector( maEntryMap[ ScHTMLPos( aAddr ) ], xEntry ); + } + } + } +} + +void ScHTMLTable::RecalcDocSize() +{ + // recalc table sizes recursively from inner to outer + for( ScHTMLTableIterator aIter( mxNestedTables.get() ); aIter.is(); ++aIter ) + aIter->RecalcDocSize(); + + /* Two passes: first calculates the sizes of single columns/rows, then + the sizes of spanned columns/rows. This allows to fill nested tables + into merged cells optimally. */ + static const sal_uInt16 PASS_SINGLE = 0; + static const sal_uInt16 PASS_SPANNED = 1; + for( sal_uInt16 nPass = PASS_SINGLE; nPass <= PASS_SPANNED; ++nPass ) + { + // iterate through every table cell + for( const auto& [rCellPos, rEntryVector] : maEntryMap ) + { + ScHTMLSize aCellSpan = GetSpan( rCellPos ); + + // process the dimension of the current cell in this pass? + // (pass is single and span is 1) or (pass is not single and span is not 1) + bool bProcessColWidth = ((nPass == PASS_SINGLE) == (aCellSpan.mnCols == 1)); + bool bProcessRowHeight = ((nPass == PASS_SINGLE) == (aCellSpan.mnRows == 1)); + if( bProcessColWidth || bProcessRowHeight ) + { + ScHTMLSize aDocSize( 1, 0 ); // resulting size of the cell in document + + // expand the cell size for each cell parse entry + for( const auto& rpEntry : rEntryVector ) + { + ScHTMLTable* pTable = GetExistingTable( rpEntry->GetTableId() ); + // find entry with maximum width + if( bProcessColWidth && pTable ) + aDocSize.mnCols = std::max( aDocSize.mnCols, static_cast< SCCOL >( pTable->GetDocSize( tdCol ) ) ); + // add up height of each entry + if( bProcessRowHeight ) + aDocSize.mnRows += pTable ? pTable->GetDocSize( tdRow ) : 1; + } + if( !aDocSize.mnRows ) + aDocSize.mnRows = 1; + + if( bProcessColWidth ) + CalcNeededDocSize( tdCol, rCellPos.mnCol, aCellSpan.mnCols, aDocSize.mnCols ); + if( bProcessRowHeight ) + CalcNeededDocSize( tdRow, rCellPos.mnRow, aCellSpan.mnRows, aDocSize.mnRows ); + } + } + } +} + +void ScHTMLTable::RecalcDocPos( const ScHTMLPos& rBasePos ) +{ + maDocBasePos = rBasePos; + // after the previous assignment it is allowed to call GetDocPos() methods + + // iterate through every table cell + for( auto& [rCellPos, rEntryVector] : maEntryMap ) + { + // fixed doc position of the entire cell (first entry) + const ScHTMLPos aCellDocPos( GetDocPos( rCellPos ) ); + // fixed doc size of the entire cell + const ScHTMLSize aCellDocSize( GetDocSize( rCellPos ) ); + + // running doc position for single entries + ScHTMLPos aEntryDocPos( aCellDocPos ); + + ScHTMLEntry* pEntry = nullptr; + for( const auto& rpEntry : rEntryVector ) + { + pEntry = rpEntry; + if( ScHTMLTable* pTable = GetExistingTable( pEntry->GetTableId() ) ) + { + pTable->RecalcDocPos( aEntryDocPos ); // recalc nested table + pEntry->nCol = SCCOL_MAX; + pEntry->nRow = SCROW_MAX; + SCROW nTableRows = static_cast< SCROW >( pTable->GetDocSize( tdRow ) ); + + // use this entry to pad empty space right of table + if( mpParentTable ) // ... but not in global table + { + SCCOL nStartCol = aEntryDocPos.mnCol + static_cast< SCCOL >( pTable->GetDocSize( tdCol ) ); + SCCOL nNextCol = aEntryDocPos.mnCol + aCellDocSize.mnCols; + if( nStartCol < nNextCol ) + { + pEntry->nCol = nStartCol; + pEntry->nRow = aEntryDocPos.mnRow; + pEntry->nColOverlap = nNextCol - nStartCol; + pEntry->nRowOverlap = nTableRows; + } + } + aEntryDocPos.mnRow += nTableRows; + } + else + { + pEntry->nCol = aEntryDocPos.mnCol; + pEntry->nRow = aEntryDocPos.mnRow; + if( mpParentTable ) // do not merge in global table + pEntry->nColOverlap = aCellDocSize.mnCols; + ++aEntryDocPos.mnRow; + } + } + + // pEntry points now to last entry. + if( pEntry ) + { + if( (pEntry == rEntryVector.front()) && (pEntry->GetTableId() == SC_HTML_NO_TABLE) ) + { + // pEntry is the only entry in this cell - merge rows of cell with single non-table entry. + pEntry->nRowOverlap = aCellDocSize.mnRows; + } + else + { + // fill up incomplete entry lists + SCROW nFirstUnusedRow = aCellDocPos.mnRow + aCellDocSize.mnRows; + while( aEntryDocPos.mnRow < nFirstUnusedRow ) + { + ScHTMLEntryPtr xDummyEntry( new ScHTMLEntry( pEntry->GetItemSet() ) ); + xDummyEntry->nCol = aEntryDocPos.mnCol; + xDummyEntry->nRow = aEntryDocPos.mnRow; + xDummyEntry->nColOverlap = aCellDocSize.mnCols; + ImplPushEntryToVector( rEntryVector, xDummyEntry ); + ++aEntryDocPos.mnRow; + } + } + } + } +} + +ScHTMLGlobalTable::ScHTMLGlobalTable( + SfxItemPool& rPool, + EditEngine& rEditEngine, + std::vector>& rEEParseVector, + ScHTMLTableId& rnUnusedId, + ScHTMLParser* pParser, + const ScDocument& rDoc +) : + ScHTMLTable( rPool, rEditEngine, rEEParseVector, rnUnusedId, pParser, rDoc ) +{ +} + +ScHTMLGlobalTable::~ScHTMLGlobalTable() +{ +} + +void ScHTMLGlobalTable::Recalc() +{ + // Fills up empty cells with a dummy entry. */ + FillEmptyCells(); + // recalc table sizes of all nested tables and this table + RecalcDocSize(); + // recalc document positions of all entries in this table and in nested tables + RecalcDocPos( GetDocPos() ); +} + +ScHTMLQueryParser::ScHTMLQueryParser( EditEngine* pEditEngine, ScDocument* pDoc ) : + ScHTMLParser( pEditEngine, pDoc ), + mnUnusedId( SC_HTML_GLOBAL_TABLE ), + mbTitleOn( false ) +{ + mxGlobTable.reset( + new ScHTMLGlobalTable(*pPool, *pEdit, maList, mnUnusedId, this, *pDoc)); + mpCurrTable = mxGlobTable.get(); +} + +ScHTMLQueryParser::~ScHTMLQueryParser() +{ +} + +ErrCode ScHTMLQueryParser::Read( SvStream& rStrm, const OUString& rBaseURL ) +{ + SvKeyValueIteratorRef xValues; + SvKeyValueIterator* pAttributes = nullptr; + + SfxObjectShell* pObjSh = mpDoc->GetDocumentShell(); + if( pObjSh && pObjSh->IsLoading() ) + { + pAttributes = pObjSh->GetHeaderAttributes(); + } + else + { + /* When not loading, set up fake HTTP headers to force the SfxHTMLParser + to use UTF8 (used when pasting from clipboard) */ + const char* pCharSet = rtl_getBestMimeCharsetFromTextEncoding( RTL_TEXTENCODING_UTF8 ); + if( pCharSet ) + { + OUString aContentType = "text/html; charset=" + + OUString::createFromAscii( pCharSet ); + + xValues = new SvKeyValueIterator; + xValues->Append( SvKeyValue( OOO_STRING_SVTOOLS_HTML_META_content_type, aContentType ) ); + pAttributes = xValues.get(); + } + } + + Link aOldLink = pEdit->GetHtmlImportHdl(); + pEdit->SetHtmlImportHdl( LINK( this, ScHTMLQueryParser, HTMLImportHdl ) ); + ErrCode nErr = pEdit->Read( rStrm, rBaseURL, EETextFormat::Html, pAttributes ); + pEdit->SetHtmlImportHdl( aOldLink ); + + mxGlobTable->Recalc(); + nColMax = static_cast< SCCOL >( mxGlobTable->GetDocSize( tdCol ) - 1 ); + nRowMax = static_cast< SCROW >( mxGlobTable->GetDocSize( tdRow ) - 1 ); + + return nErr; +} + +const ScHTMLTable* ScHTMLQueryParser::GetGlobalTable() const +{ + return mxGlobTable.get(); +} + +void ScHTMLQueryParser::ProcessToken( const HtmlImportInfo& rInfo ) +{ + switch( rInfo.nToken ) + { +// --- meta data --- + case HtmlTokenId::META: MetaOn( rInfo ); break; // + +// --- title handling --- + case HtmlTokenId::TITLE_ON: TitleOn(); break; // + case HtmlTokenId::TITLE_OFF: TitleOff( rInfo ); break; // + + case HtmlTokenId::STYLE_ON: break; + case HtmlTokenId::STYLE_OFF: ParseStyle(rInfo.aText); break; + +// --- body handling --- + case HtmlTokenId::BODY_ON: mpCurrTable->BodyOn( rInfo ); break; // + case HtmlTokenId::BODY_OFF: mpCurrTable->BodyOff( rInfo ); break; // + +// --- insert text --- + case HtmlTokenId::TEXTTOKEN: InsertText( rInfo ); break; // any text + case HtmlTokenId::LINEBREAK: mpCurrTable->BreakOn(); break; //
+ case HtmlTokenId::HEAD1_ON: //

+ case HtmlTokenId::HEAD2_ON: //

+ case HtmlTokenId::HEAD3_ON: //

+ case HtmlTokenId::HEAD4_ON: //

+ case HtmlTokenId::HEAD5_ON: //

+ case HtmlTokenId::HEAD6_ON: //
+ case HtmlTokenId::PARABREAK_ON: mpCurrTable->HeadingOn(); break; //

+ +// --- misc. contents --- + case HtmlTokenId::ANCHOR_ON: mpCurrTable->AnchorOn(); break; // + +// --- table handling --- + case HtmlTokenId::TABLE_ON: TableOn( rInfo ); break; // + case HtmlTokenId::TABLE_OFF: TableOff( rInfo ); break; //
+ case HtmlTokenId::CAPTION_ON: mpCurrTable->CaptionOn(); break; //

+ case HtmlTokenId::CAPTION_OFF: mpCurrTable->CaptionOff(); break; //
+ case HtmlTokenId::TABLEDATA_ON: mpCurrTable->DataOn( rInfo ); break; // + case HtmlTokenId::TABLEHEADER_OFF: // + case HtmlTokenId::TABLEDATA_OFF: mpCurrTable->DataOff( rInfo ); break; //