Calculation.php 228 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Calculation;
  3. use PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack;
  4. use PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger;
  5. use PhpOffice\PhpSpreadsheet\Calculation\Token\Stack;
  6. use PhpOffice\PhpSpreadsheet\Cell\Cell;
  7. use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
  8. use PhpOffice\PhpSpreadsheet\DefinedName;
  9. use PhpOffice\PhpSpreadsheet\ReferenceHelper;
  10. use PhpOffice\PhpSpreadsheet\Shared;
  11. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  12. use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  13. use ReflectionMethod;
  14. class Calculation
  15. {
  16. /** Constants */
  17. /** Regular Expressions */
  18. // Numeric operand
  19. const CALCULATION_REGEXP_NUMBER = '[-+]?\d*\.?\d+(e[-+]?\d+)?';
  20. // String operand
  21. const CALCULATION_REGEXP_STRING = '"(?:[^"]|"")*"';
  22. // Opening bracket
  23. const CALCULATION_REGEXP_OPENBRACE = '\(';
  24. // Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
  25. const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\(';
  26. // Cell reference (cell or range of cells, with or without a sheet reference)
  27. const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])';
  28. // Cell reference (with or without a sheet reference) ensuring absolute/relative
  29. const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.])';
  30. const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?[a-z]{1,3})):(?![.*])';
  31. const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*])';
  32. // Cell reference (with or without a sheet reference) ensuring absolute/relative
  33. // Cell ranges ensuring absolute/relative
  34. const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3})';
  35. const CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7})';
  36. // Defined Names: Named Range of cells, or Named Formulae
  37. const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)';
  38. // Error
  39. const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?';
  40. /** constants */
  41. const RETURN_ARRAY_AS_ERROR = 'error';
  42. const RETURN_ARRAY_AS_VALUE = 'value';
  43. const RETURN_ARRAY_AS_ARRAY = 'array';
  44. const FORMULA_OPEN_FUNCTION_BRACE = '{';
  45. const FORMULA_CLOSE_FUNCTION_BRACE = '}';
  46. const FORMULA_STRING_QUOTE = '"';
  47. private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
  48. /**
  49. * Instance of this class.
  50. *
  51. * @var Calculation
  52. */
  53. private static $instance;
  54. /**
  55. * Instance of the spreadsheet this Calculation Engine is using.
  56. *
  57. * @var Spreadsheet
  58. */
  59. private $spreadsheet;
  60. /**
  61. * Calculation cache.
  62. *
  63. * @var array
  64. */
  65. private $calculationCache = [];
  66. /**
  67. * Calculation cache enabled.
  68. *
  69. * @var bool
  70. */
  71. private $calculationCacheEnabled = true;
  72. /**
  73. * Used to generate unique store keys.
  74. *
  75. * @var int
  76. */
  77. private $branchStoreKeyCounter = 0;
  78. private $branchPruningEnabled = true;
  79. /**
  80. * List of operators that can be used within formulae
  81. * The true/false value indicates whether it is a binary operator or a unary operator.
  82. *
  83. * @var array
  84. */
  85. private static $operators = [
  86. '+' => true, '-' => true, '*' => true, '/' => true,
  87. '^' => true, '&' => true, '%' => false, '~' => false,
  88. '>' => true, '<' => true, '=' => true, '>=' => true,
  89. '<=' => true, '<>' => true, '|' => true, ':' => true,
  90. ];
  91. /**
  92. * List of binary operators (those that expect two operands).
  93. *
  94. * @var array
  95. */
  96. private static $binaryOperators = [
  97. '+' => true, '-' => true, '*' => true, '/' => true,
  98. '^' => true, '&' => true, '>' => true, '<' => true,
  99. '=' => true, '>=' => true, '<=' => true, '<>' => true,
  100. '|' => true, ':' => true,
  101. ];
  102. /**
  103. * The debug log generated by the calculation engine.
  104. *
  105. * @var Logger
  106. */
  107. private $debugLog;
  108. /**
  109. * Flag to determine how formula errors should be handled
  110. * If true, then a user error will be triggered
  111. * If false, then an exception will be thrown.
  112. *
  113. * @var bool
  114. */
  115. public $suppressFormulaErrors = false;
  116. /**
  117. * Error message for any error that was raised/thrown by the calculation engine.
  118. *
  119. * @var null|string
  120. */
  121. public $formulaError;
  122. /**
  123. * Reference Helper.
  124. *
  125. * @var ReferenceHelper
  126. */
  127. private static $referenceHelper;
  128. /**
  129. * An array of the nested cell references accessed by the calculation engine, used for the debug log.
  130. *
  131. * @var CyclicReferenceStack
  132. */
  133. private $cyclicReferenceStack;
  134. private $cellStack = [];
  135. /**
  136. * Current iteration counter for cyclic formulae
  137. * If the value is 0 (or less) then cyclic formulae will throw an exception,
  138. * otherwise they will iterate to the limit defined here before returning a result.
  139. *
  140. * @var int
  141. */
  142. private $cyclicFormulaCounter = 1;
  143. private $cyclicFormulaCell = '';
  144. /**
  145. * Number of iterations for cyclic formulae.
  146. *
  147. * @var int
  148. */
  149. public $cyclicFormulaCount = 1;
  150. /**
  151. * Epsilon Precision used for comparisons in calculations.
  152. *
  153. * @var float
  154. */
  155. private $delta = 0.1e-12;
  156. /**
  157. * The current locale setting.
  158. *
  159. * @var string
  160. */
  161. private static $localeLanguage = 'en_us'; // US English (default locale)
  162. /**
  163. * List of available locale settings
  164. * Note that this is read for the locale subdirectory only when requested.
  165. *
  166. * @var string[]
  167. */
  168. private static $validLocaleLanguages = [
  169. 'en', // English (default language)
  170. ];
  171. /**
  172. * Locale-specific argument separator for function arguments.
  173. *
  174. * @var string
  175. */
  176. private static $localeArgumentSeparator = ',';
  177. private static $localeFunctions = [];
  178. /**
  179. * Locale-specific translations for Excel constants (True, False and Null).
  180. *
  181. * @var array<string, string>
  182. */
  183. public static $localeBoolean = [
  184. 'TRUE' => 'TRUE',
  185. 'FALSE' => 'FALSE',
  186. 'NULL' => 'NULL',
  187. ];
  188. /**
  189. * Excel constant string translations to their PHP equivalents
  190. * Constant conversion from text name/value to actual (datatyped) value.
  191. *
  192. * @var array<string, mixed>
  193. */
  194. private static $excelConstants = [
  195. 'TRUE' => true,
  196. 'FALSE' => false,
  197. 'NULL' => null,
  198. ];
  199. // PhpSpreadsheet functions
  200. private static $phpSpreadsheetFunctions = [
  201. 'ABS' => [
  202. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  203. 'functionCall' => [MathTrig\Absolute::class, 'evaluate'],
  204. 'argumentCount' => '1',
  205. ],
  206. 'ACCRINT' => [
  207. 'category' => Category::CATEGORY_FINANCIAL,
  208. 'functionCall' => [Financial\Securities\AccruedInterest::class, 'periodic'],
  209. 'argumentCount' => '4-8',
  210. ],
  211. 'ACCRINTM' => [
  212. 'category' => Category::CATEGORY_FINANCIAL,
  213. 'functionCall' => [Financial\Securities\AccruedInterest::class, 'atMaturity'],
  214. 'argumentCount' => '3-5',
  215. ],
  216. 'ACOS' => [
  217. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  218. 'functionCall' => [MathTrig\Trig\Cosine::class, 'acos'],
  219. 'argumentCount' => '1',
  220. ],
  221. 'ACOSH' => [
  222. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  223. 'functionCall' => [MathTrig\Trig\Cosine::class, 'acosh'],
  224. 'argumentCount' => '1',
  225. ],
  226. 'ACOT' => [
  227. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  228. 'functionCall' => [MathTrig\Trig\Cotangent::class, 'acot'],
  229. 'argumentCount' => '1',
  230. ],
  231. 'ACOTH' => [
  232. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  233. 'functionCall' => [MathTrig\Trig\Cotangent::class, 'acoth'],
  234. 'argumentCount' => '1',
  235. ],
  236. 'ADDRESS' => [
  237. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  238. 'functionCall' => [LookupRef\Address::class, 'cell'],
  239. 'argumentCount' => '2-5',
  240. ],
  241. 'AGGREGATE' => [
  242. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  243. 'functionCall' => [Functions::class, 'DUMMY'],
  244. 'argumentCount' => '3+',
  245. ],
  246. 'AMORDEGRC' => [
  247. 'category' => Category::CATEGORY_FINANCIAL,
  248. 'functionCall' => [Financial\Amortization::class, 'AMORDEGRC'],
  249. 'argumentCount' => '6,7',
  250. ],
  251. 'AMORLINC' => [
  252. 'category' => Category::CATEGORY_FINANCIAL,
  253. 'functionCall' => [Financial\Amortization::class, 'AMORLINC'],
  254. 'argumentCount' => '6,7',
  255. ],
  256. 'AND' => [
  257. 'category' => Category::CATEGORY_LOGICAL,
  258. 'functionCall' => [Logical\Operations::class, 'logicalAnd'],
  259. 'argumentCount' => '1+',
  260. ],
  261. 'ARABIC' => [
  262. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  263. 'functionCall' => [MathTrig\Arabic::class, 'evaluate'],
  264. 'argumentCount' => '1',
  265. ],
  266. 'AREAS' => [
  267. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  268. 'functionCall' => [Functions::class, 'DUMMY'],
  269. 'argumentCount' => '1',
  270. ],
  271. 'ARRAYTOTEXT' => [
  272. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  273. 'functionCall' => [Functions::class, 'DUMMY'],
  274. 'argumentCount' => '?',
  275. ],
  276. 'ASC' => [
  277. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  278. 'functionCall' => [Functions::class, 'DUMMY'],
  279. 'argumentCount' => '1',
  280. ],
  281. 'ASIN' => [
  282. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  283. 'functionCall' => [MathTrig\Trig\Sine::class, 'asin'],
  284. 'argumentCount' => '1',
  285. ],
  286. 'ASINH' => [
  287. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  288. 'functionCall' => [MathTrig\Trig\Sine::class, 'asinh'],
  289. 'argumentCount' => '1',
  290. ],
  291. 'ATAN' => [
  292. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  293. 'functionCall' => [MathTrig\Trig\Tangent::class, 'atan'],
  294. 'argumentCount' => '1',
  295. ],
  296. 'ATAN2' => [
  297. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  298. 'functionCall' => [MathTrig\Trig\Tangent::class, 'atan2'],
  299. 'argumentCount' => '2',
  300. ],
  301. 'ATANH' => [
  302. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  303. 'functionCall' => [MathTrig\Trig\Tangent::class, 'atanh'],
  304. 'argumentCount' => '1',
  305. ],
  306. 'AVEDEV' => [
  307. 'category' => Category::CATEGORY_STATISTICAL,
  308. 'functionCall' => [Statistical\Averages::class, 'averageDeviations'],
  309. 'argumentCount' => '1+',
  310. ],
  311. 'AVERAGE' => [
  312. 'category' => Category::CATEGORY_STATISTICAL,
  313. 'functionCall' => [Statistical\Averages::class, 'average'],
  314. 'argumentCount' => '1+',
  315. ],
  316. 'AVERAGEA' => [
  317. 'category' => Category::CATEGORY_STATISTICAL,
  318. 'functionCall' => [Statistical\Averages::class, 'averageA'],
  319. 'argumentCount' => '1+',
  320. ],
  321. 'AVERAGEIF' => [
  322. 'category' => Category::CATEGORY_STATISTICAL,
  323. 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIF'],
  324. 'argumentCount' => '2,3',
  325. ],
  326. 'AVERAGEIFS' => [
  327. 'category' => Category::CATEGORY_STATISTICAL,
  328. 'functionCall' => [Statistical\Conditional::class, 'AVERAGEIFS'],
  329. 'argumentCount' => '3+',
  330. ],
  331. 'BAHTTEXT' => [
  332. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  333. 'functionCall' => [Functions::class, 'DUMMY'],
  334. 'argumentCount' => '1',
  335. ],
  336. 'BASE' => [
  337. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  338. 'functionCall' => [MathTrig\Base::class, 'evaluate'],
  339. 'argumentCount' => '2,3',
  340. ],
  341. 'BESSELI' => [
  342. 'category' => Category::CATEGORY_ENGINEERING,
  343. 'functionCall' => [Engineering\BesselI::class, 'BESSELI'],
  344. 'argumentCount' => '2',
  345. ],
  346. 'BESSELJ' => [
  347. 'category' => Category::CATEGORY_ENGINEERING,
  348. 'functionCall' => [Engineering\BesselJ::class, 'BESSELJ'],
  349. 'argumentCount' => '2',
  350. ],
  351. 'BESSELK' => [
  352. 'category' => Category::CATEGORY_ENGINEERING,
  353. 'functionCall' => [Engineering\BesselK::class, 'BESSELK'],
  354. 'argumentCount' => '2',
  355. ],
  356. 'BESSELY' => [
  357. 'category' => Category::CATEGORY_ENGINEERING,
  358. 'functionCall' => [Engineering\BesselY::class, 'BESSELY'],
  359. 'argumentCount' => '2',
  360. ],
  361. 'BETADIST' => [
  362. 'category' => Category::CATEGORY_STATISTICAL,
  363. 'functionCall' => [Statistical\Distributions\Beta::class, 'distribution'],
  364. 'argumentCount' => '3-5',
  365. ],
  366. 'BETA.DIST' => [
  367. 'category' => Category::CATEGORY_STATISTICAL,
  368. 'functionCall' => [Functions::class, 'DUMMY'],
  369. 'argumentCount' => '4-6',
  370. ],
  371. 'BETAINV' => [
  372. 'category' => Category::CATEGORY_STATISTICAL,
  373. 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'],
  374. 'argumentCount' => '3-5',
  375. ],
  376. 'BETA.INV' => [
  377. 'category' => Category::CATEGORY_STATISTICAL,
  378. 'functionCall' => [Statistical\Distributions\Beta::class, 'inverse'],
  379. 'argumentCount' => '3-5',
  380. ],
  381. 'BIN2DEC' => [
  382. 'category' => Category::CATEGORY_ENGINEERING,
  383. 'functionCall' => [Engineering\ConvertBinary::class, 'toDecimal'],
  384. 'argumentCount' => '1',
  385. ],
  386. 'BIN2HEX' => [
  387. 'category' => Category::CATEGORY_ENGINEERING,
  388. 'functionCall' => [Engineering\ConvertBinary::class, 'toHex'],
  389. 'argumentCount' => '1,2',
  390. ],
  391. 'BIN2OCT' => [
  392. 'category' => Category::CATEGORY_ENGINEERING,
  393. 'functionCall' => [Engineering\ConvertBinary::class, 'toOctal'],
  394. 'argumentCount' => '1,2',
  395. ],
  396. 'BINOMDIST' => [
  397. 'category' => Category::CATEGORY_STATISTICAL,
  398. 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'],
  399. 'argumentCount' => '4',
  400. ],
  401. 'BINOM.DIST' => [
  402. 'category' => Category::CATEGORY_STATISTICAL,
  403. 'functionCall' => [Statistical\Distributions\Binomial::class, 'distribution'],
  404. 'argumentCount' => '4',
  405. ],
  406. 'BINOM.DIST.RANGE' => [
  407. 'category' => Category::CATEGORY_STATISTICAL,
  408. 'functionCall' => [Statistical\Distributions\Binomial::class, 'range'],
  409. 'argumentCount' => '3,4',
  410. ],
  411. 'BINOM.INV' => [
  412. 'category' => Category::CATEGORY_STATISTICAL,
  413. 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'],
  414. 'argumentCount' => '3',
  415. ],
  416. 'BITAND' => [
  417. 'category' => Category::CATEGORY_ENGINEERING,
  418. 'functionCall' => [Engineering\BitWise::class, 'BITAND'],
  419. 'argumentCount' => '2',
  420. ],
  421. 'BITOR' => [
  422. 'category' => Category::CATEGORY_ENGINEERING,
  423. 'functionCall' => [Engineering\BitWise::class, 'BITOR'],
  424. 'argumentCount' => '2',
  425. ],
  426. 'BITXOR' => [
  427. 'category' => Category::CATEGORY_ENGINEERING,
  428. 'functionCall' => [Engineering\BitWise::class, 'BITXOR'],
  429. 'argumentCount' => '2',
  430. ],
  431. 'BITLSHIFT' => [
  432. 'category' => Category::CATEGORY_ENGINEERING,
  433. 'functionCall' => [Engineering\BitWise::class, 'BITLSHIFT'],
  434. 'argumentCount' => '2',
  435. ],
  436. 'BITRSHIFT' => [
  437. 'category' => Category::CATEGORY_ENGINEERING,
  438. 'functionCall' => [Engineering\BitWise::class, 'BITRSHIFT'],
  439. 'argumentCount' => '2',
  440. ],
  441. 'CEILING' => [
  442. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  443. 'functionCall' => [MathTrig\Ceiling::class, 'ceiling'],
  444. 'argumentCount' => '1-2', // 2 for Excel, 1-2 for Ods/Gnumeric
  445. ],
  446. 'CEILING.MATH' => [
  447. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  448. 'functionCall' => [MathTrig\Ceiling::class, 'math'],
  449. 'argumentCount' => '1-3',
  450. ],
  451. 'CEILING.PRECISE' => [
  452. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  453. 'functionCall' => [MathTrig\Ceiling::class, 'precise'],
  454. 'argumentCount' => '1,2',
  455. ],
  456. 'CELL' => [
  457. 'category' => Category::CATEGORY_INFORMATION,
  458. 'functionCall' => [Functions::class, 'DUMMY'],
  459. 'argumentCount' => '1,2',
  460. ],
  461. 'CHAR' => [
  462. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  463. 'functionCall' => [TextData\CharacterConvert::class, 'character'],
  464. 'argumentCount' => '1',
  465. ],
  466. 'CHIDIST' => [
  467. 'category' => Category::CATEGORY_STATISTICAL,
  468. 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'],
  469. 'argumentCount' => '2',
  470. ],
  471. 'CHISQ.DIST' => [
  472. 'category' => Category::CATEGORY_STATISTICAL,
  473. 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionLeftTail'],
  474. 'argumentCount' => '3',
  475. ],
  476. 'CHISQ.DIST.RT' => [
  477. 'category' => Category::CATEGORY_STATISTICAL,
  478. 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'distributionRightTail'],
  479. 'argumentCount' => '2',
  480. ],
  481. 'CHIINV' => [
  482. 'category' => Category::CATEGORY_STATISTICAL,
  483. 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'],
  484. 'argumentCount' => '2',
  485. ],
  486. 'CHISQ.INV' => [
  487. 'category' => Category::CATEGORY_STATISTICAL,
  488. 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseLeftTail'],
  489. 'argumentCount' => '2',
  490. ],
  491. 'CHISQ.INV.RT' => [
  492. 'category' => Category::CATEGORY_STATISTICAL,
  493. 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'inverseRightTail'],
  494. 'argumentCount' => '2',
  495. ],
  496. 'CHITEST' => [
  497. 'category' => Category::CATEGORY_STATISTICAL,
  498. 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'],
  499. 'argumentCount' => '2',
  500. ],
  501. 'CHISQ.TEST' => [
  502. 'category' => Category::CATEGORY_STATISTICAL,
  503. 'functionCall' => [Statistical\Distributions\ChiSquared::class, 'test'],
  504. 'argumentCount' => '2',
  505. ],
  506. 'CHOOSE' => [
  507. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  508. 'functionCall' => [LookupRef\Selection::class, 'CHOOSE'],
  509. 'argumentCount' => '2+',
  510. ],
  511. 'CLEAN' => [
  512. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  513. 'functionCall' => [TextData\Trim::class, 'nonPrintable'],
  514. 'argumentCount' => '1',
  515. ],
  516. 'CODE' => [
  517. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  518. 'functionCall' => [TextData\CharacterConvert::class, 'code'],
  519. 'argumentCount' => '1',
  520. ],
  521. 'COLUMN' => [
  522. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  523. 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMN'],
  524. 'argumentCount' => '-1',
  525. 'passCellReference' => true,
  526. 'passByReference' => [true],
  527. ],
  528. 'COLUMNS' => [
  529. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  530. 'functionCall' => [LookupRef\RowColumnInformation::class, 'COLUMNS'],
  531. 'argumentCount' => '1',
  532. ],
  533. 'COMBIN' => [
  534. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  535. 'functionCall' => [MathTrig\Combinations::class, 'withoutRepetition'],
  536. 'argumentCount' => '2',
  537. ],
  538. 'COMBINA' => [
  539. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  540. 'functionCall' => [MathTrig\Combinations::class, 'withRepetition'],
  541. 'argumentCount' => '2',
  542. ],
  543. 'COMPLEX' => [
  544. 'category' => Category::CATEGORY_ENGINEERING,
  545. 'functionCall' => [Engineering\Complex::class, 'COMPLEX'],
  546. 'argumentCount' => '2,3',
  547. ],
  548. 'CONCAT' => [
  549. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  550. 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'],
  551. 'argumentCount' => '1+',
  552. ],
  553. 'CONCATENATE' => [
  554. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  555. 'functionCall' => [TextData\Concatenate::class, 'CONCATENATE'],
  556. 'argumentCount' => '1+',
  557. ],
  558. 'CONFIDENCE' => [
  559. 'category' => Category::CATEGORY_STATISTICAL,
  560. 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'],
  561. 'argumentCount' => '3',
  562. ],
  563. 'CONFIDENCE.NORM' => [
  564. 'category' => Category::CATEGORY_STATISTICAL,
  565. 'functionCall' => [Statistical\Confidence::class, 'CONFIDENCE'],
  566. 'argumentCount' => '3',
  567. ],
  568. 'CONFIDENCE.T' => [
  569. 'category' => Category::CATEGORY_STATISTICAL,
  570. 'functionCall' => [Functions::class, 'DUMMY'],
  571. 'argumentCount' => '3',
  572. ],
  573. 'CONVERT' => [
  574. 'category' => Category::CATEGORY_ENGINEERING,
  575. 'functionCall' => [Engineering\ConvertUOM::class, 'CONVERT'],
  576. 'argumentCount' => '3',
  577. ],
  578. 'CORREL' => [
  579. 'category' => Category::CATEGORY_STATISTICAL,
  580. 'functionCall' => [Statistical\Trends::class, 'CORREL'],
  581. 'argumentCount' => '2',
  582. ],
  583. 'COS' => [
  584. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  585. 'functionCall' => [MathTrig\Trig\Cosine::class, 'cos'],
  586. 'argumentCount' => '1',
  587. ],
  588. 'COSH' => [
  589. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  590. 'functionCall' => [MathTrig\Trig\Cosine::class, 'cosh'],
  591. 'argumentCount' => '1',
  592. ],
  593. 'COT' => [
  594. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  595. 'functionCall' => [MathTrig\Trig\Cotangent::class, 'cot'],
  596. 'argumentCount' => '1',
  597. ],
  598. 'COTH' => [
  599. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  600. 'functionCall' => [MathTrig\Trig\Cotangent::class, 'coth'],
  601. 'argumentCount' => '1',
  602. ],
  603. 'COUNT' => [
  604. 'category' => Category::CATEGORY_STATISTICAL,
  605. 'functionCall' => [Statistical\Counts::class, 'COUNT'],
  606. 'argumentCount' => '1+',
  607. ],
  608. 'COUNTA' => [
  609. 'category' => Category::CATEGORY_STATISTICAL,
  610. 'functionCall' => [Statistical\Counts::class, 'COUNTA'],
  611. 'argumentCount' => '1+',
  612. ],
  613. 'COUNTBLANK' => [
  614. 'category' => Category::CATEGORY_STATISTICAL,
  615. 'functionCall' => [Statistical\Counts::class, 'COUNTBLANK'],
  616. 'argumentCount' => '1',
  617. ],
  618. 'COUNTIF' => [
  619. 'category' => Category::CATEGORY_STATISTICAL,
  620. 'functionCall' => [Statistical\Conditional::class, 'COUNTIF'],
  621. 'argumentCount' => '2',
  622. ],
  623. 'COUNTIFS' => [
  624. 'category' => Category::CATEGORY_STATISTICAL,
  625. 'functionCall' => [Statistical\Conditional::class, 'COUNTIFS'],
  626. 'argumentCount' => '2+',
  627. ],
  628. 'COUPDAYBS' => [
  629. 'category' => Category::CATEGORY_FINANCIAL,
  630. 'functionCall' => [Financial\Coupons::class, 'COUPDAYBS'],
  631. 'argumentCount' => '3,4',
  632. ],
  633. 'COUPDAYS' => [
  634. 'category' => Category::CATEGORY_FINANCIAL,
  635. 'functionCall' => [Financial\Coupons::class, 'COUPDAYS'],
  636. 'argumentCount' => '3,4',
  637. ],
  638. 'COUPDAYSNC' => [
  639. 'category' => Category::CATEGORY_FINANCIAL,
  640. 'functionCall' => [Financial\Coupons::class, 'COUPDAYSNC'],
  641. 'argumentCount' => '3,4',
  642. ],
  643. 'COUPNCD' => [
  644. 'category' => Category::CATEGORY_FINANCIAL,
  645. 'functionCall' => [Financial\Coupons::class, 'COUPNCD'],
  646. 'argumentCount' => '3,4',
  647. ],
  648. 'COUPNUM' => [
  649. 'category' => Category::CATEGORY_FINANCIAL,
  650. 'functionCall' => [Financial\Coupons::class, 'COUPNUM'],
  651. 'argumentCount' => '3,4',
  652. ],
  653. 'COUPPCD' => [
  654. 'category' => Category::CATEGORY_FINANCIAL,
  655. 'functionCall' => [Financial\Coupons::class, 'COUPPCD'],
  656. 'argumentCount' => '3,4',
  657. ],
  658. 'COVAR' => [
  659. 'category' => Category::CATEGORY_STATISTICAL,
  660. 'functionCall' => [Statistical\Trends::class, 'COVAR'],
  661. 'argumentCount' => '2',
  662. ],
  663. 'COVARIANCE.P' => [
  664. 'category' => Category::CATEGORY_STATISTICAL,
  665. 'functionCall' => [Statistical\Trends::class, 'COVAR'],
  666. 'argumentCount' => '2',
  667. ],
  668. 'COVARIANCE.S' => [
  669. 'category' => Category::CATEGORY_STATISTICAL,
  670. 'functionCall' => [Functions::class, 'DUMMY'],
  671. 'argumentCount' => '2',
  672. ],
  673. 'CRITBINOM' => [
  674. 'category' => Category::CATEGORY_STATISTICAL,
  675. 'functionCall' => [Statistical\Distributions\Binomial::class, 'inverse'],
  676. 'argumentCount' => '3',
  677. ],
  678. 'CSC' => [
  679. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  680. 'functionCall' => [MathTrig\Trig\Cosecant::class, 'csc'],
  681. 'argumentCount' => '1',
  682. ],
  683. 'CSCH' => [
  684. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  685. 'functionCall' => [MathTrig\Trig\Cosecant::class, 'csch'],
  686. 'argumentCount' => '1',
  687. ],
  688. 'CUBEKPIMEMBER' => [
  689. 'category' => Category::CATEGORY_CUBE,
  690. 'functionCall' => [Functions::class, 'DUMMY'],
  691. 'argumentCount' => '?',
  692. ],
  693. 'CUBEMEMBER' => [
  694. 'category' => Category::CATEGORY_CUBE,
  695. 'functionCall' => [Functions::class, 'DUMMY'],
  696. 'argumentCount' => '?',
  697. ],
  698. 'CUBEMEMBERPROPERTY' => [
  699. 'category' => Category::CATEGORY_CUBE,
  700. 'functionCall' => [Functions::class, 'DUMMY'],
  701. 'argumentCount' => '?',
  702. ],
  703. 'CUBERANKEDMEMBER' => [
  704. 'category' => Category::CATEGORY_CUBE,
  705. 'functionCall' => [Functions::class, 'DUMMY'],
  706. 'argumentCount' => '?',
  707. ],
  708. 'CUBESET' => [
  709. 'category' => Category::CATEGORY_CUBE,
  710. 'functionCall' => [Functions::class, 'DUMMY'],
  711. 'argumentCount' => '?',
  712. ],
  713. 'CUBESETCOUNT' => [
  714. 'category' => Category::CATEGORY_CUBE,
  715. 'functionCall' => [Functions::class, 'DUMMY'],
  716. 'argumentCount' => '?',
  717. ],
  718. 'CUBEVALUE' => [
  719. 'category' => Category::CATEGORY_CUBE,
  720. 'functionCall' => [Functions::class, 'DUMMY'],
  721. 'argumentCount' => '?',
  722. ],
  723. 'CUMIPMT' => [
  724. 'category' => Category::CATEGORY_FINANCIAL,
  725. 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'interest'],
  726. 'argumentCount' => '6',
  727. ],
  728. 'CUMPRINC' => [
  729. 'category' => Category::CATEGORY_FINANCIAL,
  730. 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class, 'principal'],
  731. 'argumentCount' => '6',
  732. ],
  733. 'DATE' => [
  734. 'category' => Category::CATEGORY_DATE_AND_TIME,
  735. 'functionCall' => [DateTimeExcel\Date::class, 'fromYMD'],
  736. 'argumentCount' => '3',
  737. ],
  738. 'DATEDIF' => [
  739. 'category' => Category::CATEGORY_DATE_AND_TIME,
  740. 'functionCall' => [DateTimeExcel\Difference::class, 'interval'],
  741. 'argumentCount' => '2,3',
  742. ],
  743. 'DATESTRING' => [
  744. 'category' => Category::CATEGORY_DATE_AND_TIME,
  745. 'functionCall' => [Functions::class, 'DUMMY'],
  746. 'argumentCount' => '?',
  747. ],
  748. 'DATEVALUE' => [
  749. 'category' => Category::CATEGORY_DATE_AND_TIME,
  750. 'functionCall' => [DateTimeExcel\DateValue::class, 'fromString'],
  751. 'argumentCount' => '1',
  752. ],
  753. 'DAVERAGE' => [
  754. 'category' => Category::CATEGORY_DATABASE,
  755. 'functionCall' => [Database\DAverage::class, 'evaluate'],
  756. 'argumentCount' => '3',
  757. ],
  758. 'DAY' => [
  759. 'category' => Category::CATEGORY_DATE_AND_TIME,
  760. 'functionCall' => [DateTimeExcel\DateParts::class, 'day'],
  761. 'argumentCount' => '1',
  762. ],
  763. 'DAYS' => [
  764. 'category' => Category::CATEGORY_DATE_AND_TIME,
  765. 'functionCall' => [DateTimeExcel\Days::class, 'between'],
  766. 'argumentCount' => '2',
  767. ],
  768. 'DAYS360' => [
  769. 'category' => Category::CATEGORY_DATE_AND_TIME,
  770. 'functionCall' => [DateTimeExcel\Days360::class, 'between'],
  771. 'argumentCount' => '2,3',
  772. ],
  773. 'DB' => [
  774. 'category' => Category::CATEGORY_FINANCIAL,
  775. 'functionCall' => [Financial\Depreciation::class, 'DB'],
  776. 'argumentCount' => '4,5',
  777. ],
  778. 'DBCS' => [
  779. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  780. 'functionCall' => [Functions::class, 'DUMMY'],
  781. 'argumentCount' => '1',
  782. ],
  783. 'DCOUNT' => [
  784. 'category' => Category::CATEGORY_DATABASE,
  785. 'functionCall' => [Database\DCount::class, 'evaluate'],
  786. 'argumentCount' => '3',
  787. ],
  788. 'DCOUNTA' => [
  789. 'category' => Category::CATEGORY_DATABASE,
  790. 'functionCall' => [Database\DCountA::class, 'evaluate'],
  791. 'argumentCount' => '3',
  792. ],
  793. 'DDB' => [
  794. 'category' => Category::CATEGORY_FINANCIAL,
  795. 'functionCall' => [Financial\Depreciation::class, 'DDB'],
  796. 'argumentCount' => '4,5',
  797. ],
  798. 'DEC2BIN' => [
  799. 'category' => Category::CATEGORY_ENGINEERING,
  800. 'functionCall' => [Engineering\ConvertDecimal::class, 'toBinary'],
  801. 'argumentCount' => '1,2',
  802. ],
  803. 'DEC2HEX' => [
  804. 'category' => Category::CATEGORY_ENGINEERING,
  805. 'functionCall' => [Engineering\ConvertDecimal::class, 'toHex'],
  806. 'argumentCount' => '1,2',
  807. ],
  808. 'DEC2OCT' => [
  809. 'category' => Category::CATEGORY_ENGINEERING,
  810. 'functionCall' => [Engineering\ConvertDecimal::class, 'toOctal'],
  811. 'argumentCount' => '1,2',
  812. ],
  813. 'DECIMAL' => [
  814. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  815. 'functionCall' => [Functions::class, 'DUMMY'],
  816. 'argumentCount' => '2',
  817. ],
  818. 'DEGREES' => [
  819. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  820. 'functionCall' => [MathTrig\Angle::class, 'toDegrees'],
  821. 'argumentCount' => '1',
  822. ],
  823. 'DELTA' => [
  824. 'category' => Category::CATEGORY_ENGINEERING,
  825. 'functionCall' => [Engineering\Compare::class, 'DELTA'],
  826. 'argumentCount' => '1,2',
  827. ],
  828. 'DEVSQ' => [
  829. 'category' => Category::CATEGORY_STATISTICAL,
  830. 'functionCall' => [Statistical\Deviations::class, 'sumSquares'],
  831. 'argumentCount' => '1+',
  832. ],
  833. 'DGET' => [
  834. 'category' => Category::CATEGORY_DATABASE,
  835. 'functionCall' => [Database\DGet::class, 'evaluate'],
  836. 'argumentCount' => '3',
  837. ],
  838. 'DISC' => [
  839. 'category' => Category::CATEGORY_FINANCIAL,
  840. 'functionCall' => [Financial\Securities\Rates::class, 'discount'],
  841. 'argumentCount' => '4,5',
  842. ],
  843. 'DMAX' => [
  844. 'category' => Category::CATEGORY_DATABASE,
  845. 'functionCall' => [Database\DMax::class, 'evaluate'],
  846. 'argumentCount' => '3',
  847. ],
  848. 'DMIN' => [
  849. 'category' => Category::CATEGORY_DATABASE,
  850. 'functionCall' => [Database\DMin::class, 'evaluate'],
  851. 'argumentCount' => '3',
  852. ],
  853. 'DOLLAR' => [
  854. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  855. 'functionCall' => [TextData\Format::class, 'DOLLAR'],
  856. 'argumentCount' => '1,2',
  857. ],
  858. 'DOLLARDE' => [
  859. 'category' => Category::CATEGORY_FINANCIAL,
  860. 'functionCall' => [Financial\Dollar::class, 'decimal'],
  861. 'argumentCount' => '2',
  862. ],
  863. 'DOLLARFR' => [
  864. 'category' => Category::CATEGORY_FINANCIAL,
  865. 'functionCall' => [Financial\Dollar::class, 'fractional'],
  866. 'argumentCount' => '2',
  867. ],
  868. 'DPRODUCT' => [
  869. 'category' => Category::CATEGORY_DATABASE,
  870. 'functionCall' => [Database\DProduct::class, 'evaluate'],
  871. 'argumentCount' => '3',
  872. ],
  873. 'DSTDEV' => [
  874. 'category' => Category::CATEGORY_DATABASE,
  875. 'functionCall' => [Database\DStDev::class, 'evaluate'],
  876. 'argumentCount' => '3',
  877. ],
  878. 'DSTDEVP' => [
  879. 'category' => Category::CATEGORY_DATABASE,
  880. 'functionCall' => [Database\DStDevP::class, 'evaluate'],
  881. 'argumentCount' => '3',
  882. ],
  883. 'DSUM' => [
  884. 'category' => Category::CATEGORY_DATABASE,
  885. 'functionCall' => [Database\DSum::class, 'evaluate'],
  886. 'argumentCount' => '3',
  887. ],
  888. 'DURATION' => [
  889. 'category' => Category::CATEGORY_FINANCIAL,
  890. 'functionCall' => [Functions::class, 'DUMMY'],
  891. 'argumentCount' => '5,6',
  892. ],
  893. 'DVAR' => [
  894. 'category' => Category::CATEGORY_DATABASE,
  895. 'functionCall' => [Database\DVar::class, 'evaluate'],
  896. 'argumentCount' => '3',
  897. ],
  898. 'DVARP' => [
  899. 'category' => Category::CATEGORY_DATABASE,
  900. 'functionCall' => [Database\DVarP::class, 'evaluate'],
  901. 'argumentCount' => '3',
  902. ],
  903. 'ECMA.CEILING' => [
  904. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  905. 'functionCall' => [Functions::class, 'DUMMY'],
  906. 'argumentCount' => '1,2',
  907. ],
  908. 'EDATE' => [
  909. 'category' => Category::CATEGORY_DATE_AND_TIME,
  910. 'functionCall' => [DateTimeExcel\Month::class, 'adjust'],
  911. 'argumentCount' => '2',
  912. ],
  913. 'EFFECT' => [
  914. 'category' => Category::CATEGORY_FINANCIAL,
  915. 'functionCall' => [Financial\InterestRate::class, 'effective'],
  916. 'argumentCount' => '2',
  917. ],
  918. 'ENCODEURL' => [
  919. 'category' => Category::CATEGORY_WEB,
  920. 'functionCall' => [Web\Service::class, 'urlEncode'],
  921. 'argumentCount' => '1',
  922. ],
  923. 'EOMONTH' => [
  924. 'category' => Category::CATEGORY_DATE_AND_TIME,
  925. 'functionCall' => [DateTimeExcel\Month::class, 'lastDay'],
  926. 'argumentCount' => '2',
  927. ],
  928. 'ERF' => [
  929. 'category' => Category::CATEGORY_ENGINEERING,
  930. 'functionCall' => [Engineering\Erf::class, 'ERF'],
  931. 'argumentCount' => '1,2',
  932. ],
  933. 'ERF.PRECISE' => [
  934. 'category' => Category::CATEGORY_ENGINEERING,
  935. 'functionCall' => [Engineering\Erf::class, 'ERFPRECISE'],
  936. 'argumentCount' => '1',
  937. ],
  938. 'ERFC' => [
  939. 'category' => Category::CATEGORY_ENGINEERING,
  940. 'functionCall' => [Engineering\ErfC::class, 'ERFC'],
  941. 'argumentCount' => '1',
  942. ],
  943. 'ERFC.PRECISE' => [
  944. 'category' => Category::CATEGORY_ENGINEERING,
  945. 'functionCall' => [Engineering\ErfC::class, 'ERFC'],
  946. 'argumentCount' => '1',
  947. ],
  948. 'ERROR.TYPE' => [
  949. 'category' => Category::CATEGORY_INFORMATION,
  950. 'functionCall' => [Functions::class, 'errorType'],
  951. 'argumentCount' => '1',
  952. ],
  953. 'EVEN' => [
  954. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  955. 'functionCall' => [MathTrig\Round::class, 'even'],
  956. 'argumentCount' => '1',
  957. ],
  958. 'EXACT' => [
  959. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  960. 'functionCall' => [TextData\Text::class, 'exact'],
  961. 'argumentCount' => '2',
  962. ],
  963. 'EXP' => [
  964. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  965. 'functionCall' => [MathTrig\Exp::class, 'evaluate'],
  966. 'argumentCount' => '1',
  967. ],
  968. 'EXPONDIST' => [
  969. 'category' => Category::CATEGORY_STATISTICAL,
  970. 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'],
  971. 'argumentCount' => '3',
  972. ],
  973. 'EXPON.DIST' => [
  974. 'category' => Category::CATEGORY_STATISTICAL,
  975. 'functionCall' => [Statistical\Distributions\Exponential::class, 'distribution'],
  976. 'argumentCount' => '3',
  977. ],
  978. 'FACT' => [
  979. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  980. 'functionCall' => [MathTrig\Factorial::class, 'fact'],
  981. 'argumentCount' => '1',
  982. ],
  983. 'FACTDOUBLE' => [
  984. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  985. 'functionCall' => [MathTrig\Factorial::class, 'factDouble'],
  986. 'argumentCount' => '1',
  987. ],
  988. 'FALSE' => [
  989. 'category' => Category::CATEGORY_LOGICAL,
  990. 'functionCall' => [Logical\Boolean::class, 'FALSE'],
  991. 'argumentCount' => '0',
  992. ],
  993. 'FDIST' => [
  994. 'category' => Category::CATEGORY_STATISTICAL,
  995. 'functionCall' => [Functions::class, 'DUMMY'],
  996. 'argumentCount' => '3',
  997. ],
  998. 'F.DIST' => [
  999. 'category' => Category::CATEGORY_STATISTICAL,
  1000. 'functionCall' => [Statistical\Distributions\F::class, 'distribution'],
  1001. 'argumentCount' => '4',
  1002. ],
  1003. 'F.DIST.RT' => [
  1004. 'category' => Category::CATEGORY_STATISTICAL,
  1005. 'functionCall' => [Functions::class, 'DUMMY'],
  1006. 'argumentCount' => '3',
  1007. ],
  1008. 'FILTER' => [
  1009. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1010. 'functionCall' => [Functions::class, 'DUMMY'],
  1011. 'argumentCount' => '3+',
  1012. ],
  1013. 'FILTERXML' => [
  1014. 'category' => Category::CATEGORY_WEB,
  1015. 'functionCall' => [Functions::class, 'DUMMY'],
  1016. 'argumentCount' => '2',
  1017. ],
  1018. 'FIND' => [
  1019. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1020. 'functionCall' => [TextData\Search::class, 'sensitive'],
  1021. 'argumentCount' => '2,3',
  1022. ],
  1023. 'FINDB' => [
  1024. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1025. 'functionCall' => [TextData\Search::class, 'sensitive'],
  1026. 'argumentCount' => '2,3',
  1027. ],
  1028. 'FINV' => [
  1029. 'category' => Category::CATEGORY_STATISTICAL,
  1030. 'functionCall' => [Functions::class, 'DUMMY'],
  1031. 'argumentCount' => '3',
  1032. ],
  1033. 'F.INV' => [
  1034. 'category' => Category::CATEGORY_STATISTICAL,
  1035. 'functionCall' => [Functions::class, 'DUMMY'],
  1036. 'argumentCount' => '3',
  1037. ],
  1038. 'F.INV.RT' => [
  1039. 'category' => Category::CATEGORY_STATISTICAL,
  1040. 'functionCall' => [Functions::class, 'DUMMY'],
  1041. 'argumentCount' => '3',
  1042. ],
  1043. 'FISHER' => [
  1044. 'category' => Category::CATEGORY_STATISTICAL,
  1045. 'functionCall' => [Statistical\Distributions\Fisher::class, 'distribution'],
  1046. 'argumentCount' => '1',
  1047. ],
  1048. 'FISHERINV' => [
  1049. 'category' => Category::CATEGORY_STATISTICAL,
  1050. 'functionCall' => [Statistical\Distributions\Fisher::class, 'inverse'],
  1051. 'argumentCount' => '1',
  1052. ],
  1053. 'FIXED' => [
  1054. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1055. 'functionCall' => [TextData\Format::class, 'FIXEDFORMAT'],
  1056. 'argumentCount' => '1-3',
  1057. ],
  1058. 'FLOOR' => [
  1059. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1060. 'functionCall' => [MathTrig\Floor::class, 'floor'],
  1061. 'argumentCount' => '1-2', // Excel requries 2, Ods/Gnumeric 1-2
  1062. ],
  1063. 'FLOOR.MATH' => [
  1064. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1065. 'functionCall' => [MathTrig\Floor::class, 'math'],
  1066. 'argumentCount' => '1-3',
  1067. ],
  1068. 'FLOOR.PRECISE' => [
  1069. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1070. 'functionCall' => [MathTrig\Floor::class, 'precise'],
  1071. 'argumentCount' => '1-2',
  1072. ],
  1073. 'FORECAST' => [
  1074. 'category' => Category::CATEGORY_STATISTICAL,
  1075. 'functionCall' => [Statistical\Trends::class, 'FORECAST'],
  1076. 'argumentCount' => '3',
  1077. ],
  1078. 'FORECAST.ETS' => [
  1079. 'category' => Category::CATEGORY_STATISTICAL,
  1080. 'functionCall' => [Functions::class, 'DUMMY'],
  1081. 'argumentCount' => '3-6',
  1082. ],
  1083. 'FORECAST.ETS.CONFINT' => [
  1084. 'category' => Category::CATEGORY_STATISTICAL,
  1085. 'functionCall' => [Functions::class, 'DUMMY'],
  1086. 'argumentCount' => '3-6',
  1087. ],
  1088. 'FORECAST.ETS.SEASONALITY' => [
  1089. 'category' => Category::CATEGORY_STATISTICAL,
  1090. 'functionCall' => [Functions::class, 'DUMMY'],
  1091. 'argumentCount' => '2-4',
  1092. ],
  1093. 'FORECAST.ETS.STAT' => [
  1094. 'category' => Category::CATEGORY_STATISTICAL,
  1095. 'functionCall' => [Functions::class, 'DUMMY'],
  1096. 'argumentCount' => '3-6',
  1097. ],
  1098. 'FORECAST.LINEAR' => [
  1099. 'category' => Category::CATEGORY_STATISTICAL,
  1100. 'functionCall' => [Statistical\Trends::class, 'FORECAST'],
  1101. 'argumentCount' => '3',
  1102. ],
  1103. 'FORMULATEXT' => [
  1104. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1105. 'functionCall' => [LookupRef\Formula::class, 'text'],
  1106. 'argumentCount' => '1',
  1107. 'passCellReference' => true,
  1108. 'passByReference' => [true],
  1109. ],
  1110. 'FREQUENCY' => [
  1111. 'category' => Category::CATEGORY_STATISTICAL,
  1112. 'functionCall' => [Functions::class, 'DUMMY'],
  1113. 'argumentCount' => '2',
  1114. ],
  1115. 'FTEST' => [
  1116. 'category' => Category::CATEGORY_STATISTICAL,
  1117. 'functionCall' => [Functions::class, 'DUMMY'],
  1118. 'argumentCount' => '2',
  1119. ],
  1120. 'F.TEST' => [
  1121. 'category' => Category::CATEGORY_STATISTICAL,
  1122. 'functionCall' => [Functions::class, 'DUMMY'],
  1123. 'argumentCount' => '2',
  1124. ],
  1125. 'FV' => [
  1126. 'category' => Category::CATEGORY_FINANCIAL,
  1127. 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'futureValue'],
  1128. 'argumentCount' => '3-5',
  1129. ],
  1130. 'FVSCHEDULE' => [
  1131. 'category' => Category::CATEGORY_FINANCIAL,
  1132. 'functionCall' => [Financial\CashFlow\Single::class, 'futureValue'],
  1133. 'argumentCount' => '2',
  1134. ],
  1135. 'GAMMA' => [
  1136. 'category' => Category::CATEGORY_STATISTICAL,
  1137. 'functionCall' => [Statistical\Distributions\Gamma::class, 'gamma'],
  1138. 'argumentCount' => '1',
  1139. ],
  1140. 'GAMMADIST' => [
  1141. 'category' => Category::CATEGORY_STATISTICAL,
  1142. 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'],
  1143. 'argumentCount' => '4',
  1144. ],
  1145. 'GAMMA.DIST' => [
  1146. 'category' => Category::CATEGORY_STATISTICAL,
  1147. 'functionCall' => [Statistical\Distributions\Gamma::class, 'distribution'],
  1148. 'argumentCount' => '4',
  1149. ],
  1150. 'GAMMAINV' => [
  1151. 'category' => Category::CATEGORY_STATISTICAL,
  1152. 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'],
  1153. 'argumentCount' => '3',
  1154. ],
  1155. 'GAMMA.INV' => [
  1156. 'category' => Category::CATEGORY_STATISTICAL,
  1157. 'functionCall' => [Statistical\Distributions\Gamma::class, 'inverse'],
  1158. 'argumentCount' => '3',
  1159. ],
  1160. 'GAMMALN' => [
  1161. 'category' => Category::CATEGORY_STATISTICAL,
  1162. 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'],
  1163. 'argumentCount' => '1',
  1164. ],
  1165. 'GAMMALN.PRECISE' => [
  1166. 'category' => Category::CATEGORY_STATISTICAL,
  1167. 'functionCall' => [Statistical\Distributions\Gamma::class, 'ln'],
  1168. 'argumentCount' => '1',
  1169. ],
  1170. 'GAUSS' => [
  1171. 'category' => Category::CATEGORY_STATISTICAL,
  1172. 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'gauss'],
  1173. 'argumentCount' => '1',
  1174. ],
  1175. 'GCD' => [
  1176. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1177. 'functionCall' => [MathTrig\Gcd::class, 'evaluate'],
  1178. 'argumentCount' => '1+',
  1179. ],
  1180. 'GEOMEAN' => [
  1181. 'category' => Category::CATEGORY_STATISTICAL,
  1182. 'functionCall' => [Statistical\Averages\Mean::class, 'geometric'],
  1183. 'argumentCount' => '1+',
  1184. ],
  1185. 'GESTEP' => [
  1186. 'category' => Category::CATEGORY_ENGINEERING,
  1187. 'functionCall' => [Engineering\Compare::class, 'GESTEP'],
  1188. 'argumentCount' => '1,2',
  1189. ],
  1190. 'GETPIVOTDATA' => [
  1191. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1192. 'functionCall' => [Functions::class, 'DUMMY'],
  1193. 'argumentCount' => '2+',
  1194. ],
  1195. 'GROWTH' => [
  1196. 'category' => Category::CATEGORY_STATISTICAL,
  1197. 'functionCall' => [Statistical\Trends::class, 'GROWTH'],
  1198. 'argumentCount' => '1-4',
  1199. ],
  1200. 'HARMEAN' => [
  1201. 'category' => Category::CATEGORY_STATISTICAL,
  1202. 'functionCall' => [Statistical\Averages\Mean::class, 'harmonic'],
  1203. 'argumentCount' => '1+',
  1204. ],
  1205. 'HEX2BIN' => [
  1206. 'category' => Category::CATEGORY_ENGINEERING,
  1207. 'functionCall' => [Engineering\ConvertHex::class, 'toBinary'],
  1208. 'argumentCount' => '1,2',
  1209. ],
  1210. 'HEX2DEC' => [
  1211. 'category' => Category::CATEGORY_ENGINEERING,
  1212. 'functionCall' => [Engineering\ConvertHex::class, 'toDecimal'],
  1213. 'argumentCount' => '1',
  1214. ],
  1215. 'HEX2OCT' => [
  1216. 'category' => Category::CATEGORY_ENGINEERING,
  1217. 'functionCall' => [Engineering\ConvertHex::class, 'toOctal'],
  1218. 'argumentCount' => '1,2',
  1219. ],
  1220. 'HLOOKUP' => [
  1221. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1222. 'functionCall' => [LookupRef\HLookup::class, 'lookup'],
  1223. 'argumentCount' => '3,4',
  1224. ],
  1225. 'HOUR' => [
  1226. 'category' => Category::CATEGORY_DATE_AND_TIME,
  1227. 'functionCall' => [DateTimeExcel\TimeParts::class, 'hour'],
  1228. 'argumentCount' => '1',
  1229. ],
  1230. 'HYPERLINK' => [
  1231. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1232. 'functionCall' => [LookupRef\Hyperlink::class, 'set'],
  1233. 'argumentCount' => '1,2',
  1234. 'passCellReference' => true,
  1235. ],
  1236. 'HYPGEOMDIST' => [
  1237. 'category' => Category::CATEGORY_STATISTICAL,
  1238. 'functionCall' => [Statistical\Distributions\HyperGeometric::class, 'distribution'],
  1239. 'argumentCount' => '4',
  1240. ],
  1241. 'HYPGEOM.DIST' => [
  1242. 'category' => Category::CATEGORY_STATISTICAL,
  1243. 'functionCall' => [Functions::class, 'DUMMY'],
  1244. 'argumentCount' => '5',
  1245. ],
  1246. 'IF' => [
  1247. 'category' => Category::CATEGORY_LOGICAL,
  1248. 'functionCall' => [Logical\Conditional::class, 'statementIf'],
  1249. 'argumentCount' => '1-3',
  1250. ],
  1251. 'IFERROR' => [
  1252. 'category' => Category::CATEGORY_LOGICAL,
  1253. 'functionCall' => [Logical\Conditional::class, 'IFERROR'],
  1254. 'argumentCount' => '2',
  1255. ],
  1256. 'IFNA' => [
  1257. 'category' => Category::CATEGORY_LOGICAL,
  1258. 'functionCall' => [Logical\Conditional::class, 'IFNA'],
  1259. 'argumentCount' => '2',
  1260. ],
  1261. 'IFS' => [
  1262. 'category' => Category::CATEGORY_LOGICAL,
  1263. 'functionCall' => [Logical\Conditional::class, 'IFS'],
  1264. 'argumentCount' => '2+',
  1265. ],
  1266. 'IMABS' => [
  1267. 'category' => Category::CATEGORY_ENGINEERING,
  1268. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMABS'],
  1269. 'argumentCount' => '1',
  1270. ],
  1271. 'IMAGINARY' => [
  1272. 'category' => Category::CATEGORY_ENGINEERING,
  1273. 'functionCall' => [Engineering\Complex::class, 'IMAGINARY'],
  1274. 'argumentCount' => '1',
  1275. ],
  1276. 'IMARGUMENT' => [
  1277. 'category' => Category::CATEGORY_ENGINEERING,
  1278. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMARGUMENT'],
  1279. 'argumentCount' => '1',
  1280. ],
  1281. 'IMCONJUGATE' => [
  1282. 'category' => Category::CATEGORY_ENGINEERING,
  1283. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCONJUGATE'],
  1284. 'argumentCount' => '1',
  1285. ],
  1286. 'IMCOS' => [
  1287. 'category' => Category::CATEGORY_ENGINEERING,
  1288. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOS'],
  1289. 'argumentCount' => '1',
  1290. ],
  1291. 'IMCOSH' => [
  1292. 'category' => Category::CATEGORY_ENGINEERING,
  1293. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOSH'],
  1294. 'argumentCount' => '1',
  1295. ],
  1296. 'IMCOT' => [
  1297. 'category' => Category::CATEGORY_ENGINEERING,
  1298. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCOT'],
  1299. 'argumentCount' => '1',
  1300. ],
  1301. 'IMCSC' => [
  1302. 'category' => Category::CATEGORY_ENGINEERING,
  1303. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSC'],
  1304. 'argumentCount' => '1',
  1305. ],
  1306. 'IMCSCH' => [
  1307. 'category' => Category::CATEGORY_ENGINEERING,
  1308. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMCSCH'],
  1309. 'argumentCount' => '1',
  1310. ],
  1311. 'IMDIV' => [
  1312. 'category' => Category::CATEGORY_ENGINEERING,
  1313. 'functionCall' => [Engineering\ComplexOperations::class, 'IMDIV'],
  1314. 'argumentCount' => '2',
  1315. ],
  1316. 'IMEXP' => [
  1317. 'category' => Category::CATEGORY_ENGINEERING,
  1318. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMEXP'],
  1319. 'argumentCount' => '1',
  1320. ],
  1321. 'IMLN' => [
  1322. 'category' => Category::CATEGORY_ENGINEERING,
  1323. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLN'],
  1324. 'argumentCount' => '1',
  1325. ],
  1326. 'IMLOG10' => [
  1327. 'category' => Category::CATEGORY_ENGINEERING,
  1328. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG10'],
  1329. 'argumentCount' => '1',
  1330. ],
  1331. 'IMLOG2' => [
  1332. 'category' => Category::CATEGORY_ENGINEERING,
  1333. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMLOG2'],
  1334. 'argumentCount' => '1',
  1335. ],
  1336. 'IMPOWER' => [
  1337. 'category' => Category::CATEGORY_ENGINEERING,
  1338. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMPOWER'],
  1339. 'argumentCount' => '2',
  1340. ],
  1341. 'IMPRODUCT' => [
  1342. 'category' => Category::CATEGORY_ENGINEERING,
  1343. 'functionCall' => [Engineering\ComplexOperations::class, 'IMPRODUCT'],
  1344. 'argumentCount' => '1+',
  1345. ],
  1346. 'IMREAL' => [
  1347. 'category' => Category::CATEGORY_ENGINEERING,
  1348. 'functionCall' => [Engineering\Complex::class, 'IMREAL'],
  1349. 'argumentCount' => '1',
  1350. ],
  1351. 'IMSEC' => [
  1352. 'category' => Category::CATEGORY_ENGINEERING,
  1353. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSEC'],
  1354. 'argumentCount' => '1',
  1355. ],
  1356. 'IMSECH' => [
  1357. 'category' => Category::CATEGORY_ENGINEERING,
  1358. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSECH'],
  1359. 'argumentCount' => '1',
  1360. ],
  1361. 'IMSIN' => [
  1362. 'category' => Category::CATEGORY_ENGINEERING,
  1363. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSIN'],
  1364. 'argumentCount' => '1',
  1365. ],
  1366. 'IMSINH' => [
  1367. 'category' => Category::CATEGORY_ENGINEERING,
  1368. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSINH'],
  1369. 'argumentCount' => '1',
  1370. ],
  1371. 'IMSQRT' => [
  1372. 'category' => Category::CATEGORY_ENGINEERING,
  1373. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMSQRT'],
  1374. 'argumentCount' => '1',
  1375. ],
  1376. 'IMSUB' => [
  1377. 'category' => Category::CATEGORY_ENGINEERING,
  1378. 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUB'],
  1379. 'argumentCount' => '2',
  1380. ],
  1381. 'IMSUM' => [
  1382. 'category' => Category::CATEGORY_ENGINEERING,
  1383. 'functionCall' => [Engineering\ComplexOperations::class, 'IMSUM'],
  1384. 'argumentCount' => '1+',
  1385. ],
  1386. 'IMTAN' => [
  1387. 'category' => Category::CATEGORY_ENGINEERING,
  1388. 'functionCall' => [Engineering\ComplexFunctions::class, 'IMTAN'],
  1389. 'argumentCount' => '1',
  1390. ],
  1391. 'INDEX' => [
  1392. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1393. 'functionCall' => [LookupRef\Matrix::class, 'index'],
  1394. 'argumentCount' => '1-4',
  1395. ],
  1396. 'INDIRECT' => [
  1397. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1398. 'functionCall' => [LookupRef\Indirect::class, 'INDIRECT'],
  1399. 'argumentCount' => '1,2',
  1400. 'passCellReference' => true,
  1401. ],
  1402. 'INFO' => [
  1403. 'category' => Category::CATEGORY_INFORMATION,
  1404. 'functionCall' => [Functions::class, 'DUMMY'],
  1405. 'argumentCount' => '1',
  1406. ],
  1407. 'INT' => [
  1408. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1409. 'functionCall' => [MathTrig\IntClass::class, 'evaluate'],
  1410. 'argumentCount' => '1',
  1411. ],
  1412. 'INTERCEPT' => [
  1413. 'category' => Category::CATEGORY_STATISTICAL,
  1414. 'functionCall' => [Statistical\Trends::class, 'INTERCEPT'],
  1415. 'argumentCount' => '2',
  1416. ],
  1417. 'INTRATE' => [
  1418. 'category' => Category::CATEGORY_FINANCIAL,
  1419. 'functionCall' => [Financial\Securities\Rates::class, 'interest'],
  1420. 'argumentCount' => '4,5',
  1421. ],
  1422. 'IPMT' => [
  1423. 'category' => Category::CATEGORY_FINANCIAL,
  1424. 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'payment'],
  1425. 'argumentCount' => '4-6',
  1426. ],
  1427. 'IRR' => [
  1428. 'category' => Category::CATEGORY_FINANCIAL,
  1429. 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'rate'],
  1430. 'argumentCount' => '1,2',
  1431. ],
  1432. 'ISBLANK' => [
  1433. 'category' => Category::CATEGORY_INFORMATION,
  1434. 'functionCall' => [Functions::class, 'isBlank'],
  1435. 'argumentCount' => '1',
  1436. ],
  1437. 'ISERR' => [
  1438. 'category' => Category::CATEGORY_INFORMATION,
  1439. 'functionCall' => [Functions::class, 'isErr'],
  1440. 'argumentCount' => '1',
  1441. ],
  1442. 'ISERROR' => [
  1443. 'category' => Category::CATEGORY_INFORMATION,
  1444. 'functionCall' => [Functions::class, 'isError'],
  1445. 'argumentCount' => '1',
  1446. ],
  1447. 'ISEVEN' => [
  1448. 'category' => Category::CATEGORY_INFORMATION,
  1449. 'functionCall' => [Functions::class, 'isEven'],
  1450. 'argumentCount' => '1',
  1451. ],
  1452. 'ISFORMULA' => [
  1453. 'category' => Category::CATEGORY_INFORMATION,
  1454. 'functionCall' => [Functions::class, 'isFormula'],
  1455. 'argumentCount' => '1',
  1456. 'passCellReference' => true,
  1457. 'passByReference' => [true],
  1458. ],
  1459. 'ISLOGICAL' => [
  1460. 'category' => Category::CATEGORY_INFORMATION,
  1461. 'functionCall' => [Functions::class, 'isLogical'],
  1462. 'argumentCount' => '1',
  1463. ],
  1464. 'ISNA' => [
  1465. 'category' => Category::CATEGORY_INFORMATION,
  1466. 'functionCall' => [Functions::class, 'isNa'],
  1467. 'argumentCount' => '1',
  1468. ],
  1469. 'ISNONTEXT' => [
  1470. 'category' => Category::CATEGORY_INFORMATION,
  1471. 'functionCall' => [Functions::class, 'isNonText'],
  1472. 'argumentCount' => '1',
  1473. ],
  1474. 'ISNUMBER' => [
  1475. 'category' => Category::CATEGORY_INFORMATION,
  1476. 'functionCall' => [Functions::class, 'isNumber'],
  1477. 'argumentCount' => '1',
  1478. ],
  1479. 'ISO.CEILING' => [
  1480. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1481. 'functionCall' => [Functions::class, 'DUMMY'],
  1482. 'argumentCount' => '1,2',
  1483. ],
  1484. 'ISODD' => [
  1485. 'category' => Category::CATEGORY_INFORMATION,
  1486. 'functionCall' => [Functions::class, 'isOdd'],
  1487. 'argumentCount' => '1',
  1488. ],
  1489. 'ISOWEEKNUM' => [
  1490. 'category' => Category::CATEGORY_DATE_AND_TIME,
  1491. 'functionCall' => [DateTimeExcel\Week::class, 'isoWeekNumber'],
  1492. 'argumentCount' => '1',
  1493. ],
  1494. 'ISPMT' => [
  1495. 'category' => Category::CATEGORY_FINANCIAL,
  1496. 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'schedulePayment'],
  1497. 'argumentCount' => '4',
  1498. ],
  1499. 'ISREF' => [
  1500. 'category' => Category::CATEGORY_INFORMATION,
  1501. 'functionCall' => [Functions::class, 'DUMMY'],
  1502. 'argumentCount' => '1',
  1503. ],
  1504. 'ISTEXT' => [
  1505. 'category' => Category::CATEGORY_INFORMATION,
  1506. 'functionCall' => [Functions::class, 'isText'],
  1507. 'argumentCount' => '1',
  1508. ],
  1509. 'ISTHAIDIGIT' => [
  1510. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1511. 'functionCall' => [Functions::class, 'DUMMY'],
  1512. 'argumentCount' => '?',
  1513. ],
  1514. 'JIS' => [
  1515. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1516. 'functionCall' => [Functions::class, 'DUMMY'],
  1517. 'argumentCount' => '1',
  1518. ],
  1519. 'KURT' => [
  1520. 'category' => Category::CATEGORY_STATISTICAL,
  1521. 'functionCall' => [Statistical\Deviations::class, 'kurtosis'],
  1522. 'argumentCount' => '1+',
  1523. ],
  1524. 'LARGE' => [
  1525. 'category' => Category::CATEGORY_STATISTICAL,
  1526. 'functionCall' => [Statistical\Size::class, 'large'],
  1527. 'argumentCount' => '2',
  1528. ],
  1529. 'LCM' => [
  1530. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1531. 'functionCall' => [MathTrig\Lcm::class, 'evaluate'],
  1532. 'argumentCount' => '1+',
  1533. ],
  1534. 'LEFT' => [
  1535. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1536. 'functionCall' => [TextData\Extract::class, 'left'],
  1537. 'argumentCount' => '1,2',
  1538. ],
  1539. 'LEFTB' => [
  1540. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1541. 'functionCall' => [TextData\Extract::class, 'left'],
  1542. 'argumentCount' => '1,2',
  1543. ],
  1544. 'LEN' => [
  1545. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1546. 'functionCall' => [TextData\Text::class, 'length'],
  1547. 'argumentCount' => '1',
  1548. ],
  1549. 'LENB' => [
  1550. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1551. 'functionCall' => [TextData\Text::class, 'length'],
  1552. 'argumentCount' => '1',
  1553. ],
  1554. 'LINEST' => [
  1555. 'category' => Category::CATEGORY_STATISTICAL,
  1556. 'functionCall' => [Statistical\Trends::class, 'LINEST'],
  1557. 'argumentCount' => '1-4',
  1558. ],
  1559. 'LN' => [
  1560. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1561. 'functionCall' => [MathTrig\Logarithms::class, 'natural'],
  1562. 'argumentCount' => '1',
  1563. ],
  1564. 'LOG' => [
  1565. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1566. 'functionCall' => [MathTrig\Logarithms::class, 'withBase'],
  1567. 'argumentCount' => '1,2',
  1568. ],
  1569. 'LOG10' => [
  1570. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1571. 'functionCall' => [MathTrig\Logarithms::class, 'base10'],
  1572. 'argumentCount' => '1',
  1573. ],
  1574. 'LOGEST' => [
  1575. 'category' => Category::CATEGORY_STATISTICAL,
  1576. 'functionCall' => [Statistical\Trends::class, 'LOGEST'],
  1577. 'argumentCount' => '1-4',
  1578. ],
  1579. 'LOGINV' => [
  1580. 'category' => Category::CATEGORY_STATISTICAL,
  1581. 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'],
  1582. 'argumentCount' => '3',
  1583. ],
  1584. 'LOGNORMDIST' => [
  1585. 'category' => Category::CATEGORY_STATISTICAL,
  1586. 'functionCall' => [Statistical\Distributions\LogNormal::class, 'cumulative'],
  1587. 'argumentCount' => '3',
  1588. ],
  1589. 'LOGNORM.DIST' => [
  1590. 'category' => Category::CATEGORY_STATISTICAL,
  1591. 'functionCall' => [Statistical\Distributions\LogNormal::class, 'distribution'],
  1592. 'argumentCount' => '4',
  1593. ],
  1594. 'LOGNORM.INV' => [
  1595. 'category' => Category::CATEGORY_STATISTICAL,
  1596. 'functionCall' => [Statistical\Distributions\LogNormal::class, 'inverse'],
  1597. 'argumentCount' => '3',
  1598. ],
  1599. 'LOOKUP' => [
  1600. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1601. 'functionCall' => [LookupRef\Lookup::class, 'lookup'],
  1602. 'argumentCount' => '2,3',
  1603. ],
  1604. 'LOWER' => [
  1605. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1606. 'functionCall' => [TextData\CaseConvert::class, 'lower'],
  1607. 'argumentCount' => '1',
  1608. ],
  1609. 'MATCH' => [
  1610. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1611. 'functionCall' => [LookupRef\ExcelMatch::class, 'MATCH'],
  1612. 'argumentCount' => '2,3',
  1613. ],
  1614. 'MAX' => [
  1615. 'category' => Category::CATEGORY_STATISTICAL,
  1616. 'functionCall' => [Statistical\Maximum::class, 'max'],
  1617. 'argumentCount' => '1+',
  1618. ],
  1619. 'MAXA' => [
  1620. 'category' => Category::CATEGORY_STATISTICAL,
  1621. 'functionCall' => [Statistical\Maximum::class, 'maxA'],
  1622. 'argumentCount' => '1+',
  1623. ],
  1624. 'MAXIFS' => [
  1625. 'category' => Category::CATEGORY_STATISTICAL,
  1626. 'functionCall' => [Statistical\Conditional::class, 'MAXIFS'],
  1627. 'argumentCount' => '3+',
  1628. ],
  1629. 'MDETERM' => [
  1630. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1631. 'functionCall' => [MathTrig\MatrixFunctions::class, 'determinant'],
  1632. 'argumentCount' => '1',
  1633. ],
  1634. 'MDURATION' => [
  1635. 'category' => Category::CATEGORY_FINANCIAL,
  1636. 'functionCall' => [Functions::class, 'DUMMY'],
  1637. 'argumentCount' => '5,6',
  1638. ],
  1639. 'MEDIAN' => [
  1640. 'category' => Category::CATEGORY_STATISTICAL,
  1641. 'functionCall' => [Statistical\Averages::class, 'median'],
  1642. 'argumentCount' => '1+',
  1643. ],
  1644. 'MEDIANIF' => [
  1645. 'category' => Category::CATEGORY_STATISTICAL,
  1646. 'functionCall' => [Functions::class, 'DUMMY'],
  1647. 'argumentCount' => '2+',
  1648. ],
  1649. 'MID' => [
  1650. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1651. 'functionCall' => [TextData\Extract::class, 'mid'],
  1652. 'argumentCount' => '3',
  1653. ],
  1654. 'MIDB' => [
  1655. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1656. 'functionCall' => [TextData\Extract::class, 'mid'],
  1657. 'argumentCount' => '3',
  1658. ],
  1659. 'MIN' => [
  1660. 'category' => Category::CATEGORY_STATISTICAL,
  1661. 'functionCall' => [Statistical\Minimum::class, 'min'],
  1662. 'argumentCount' => '1+',
  1663. ],
  1664. 'MINA' => [
  1665. 'category' => Category::CATEGORY_STATISTICAL,
  1666. 'functionCall' => [Statistical\Minimum::class, 'minA'],
  1667. 'argumentCount' => '1+',
  1668. ],
  1669. 'MINIFS' => [
  1670. 'category' => Category::CATEGORY_STATISTICAL,
  1671. 'functionCall' => [Statistical\Conditional::class, 'MINIFS'],
  1672. 'argumentCount' => '3+',
  1673. ],
  1674. 'MINUTE' => [
  1675. 'category' => Category::CATEGORY_DATE_AND_TIME,
  1676. 'functionCall' => [DateTimeExcel\TimeParts::class, 'minute'],
  1677. 'argumentCount' => '1',
  1678. ],
  1679. 'MINVERSE' => [
  1680. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1681. 'functionCall' => [MathTrig\MatrixFunctions::class, 'inverse'],
  1682. 'argumentCount' => '1',
  1683. ],
  1684. 'MIRR' => [
  1685. 'category' => Category::CATEGORY_FINANCIAL,
  1686. 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'modifiedRate'],
  1687. 'argumentCount' => '3',
  1688. ],
  1689. 'MMULT' => [
  1690. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1691. 'functionCall' => [MathTrig\MatrixFunctions::class, 'multiply'],
  1692. 'argumentCount' => '2',
  1693. ],
  1694. 'MOD' => [
  1695. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1696. 'functionCall' => [MathTrig\Operations::class, 'mod'],
  1697. 'argumentCount' => '2',
  1698. ],
  1699. 'MODE' => [
  1700. 'category' => Category::CATEGORY_STATISTICAL,
  1701. 'functionCall' => [Statistical\Averages::class, 'mode'],
  1702. 'argumentCount' => '1+',
  1703. ],
  1704. 'MODE.MULT' => [
  1705. 'category' => Category::CATEGORY_STATISTICAL,
  1706. 'functionCall' => [Functions::class, 'DUMMY'],
  1707. 'argumentCount' => '1+',
  1708. ],
  1709. 'MODE.SNGL' => [
  1710. 'category' => Category::CATEGORY_STATISTICAL,
  1711. 'functionCall' => [Statistical\Averages::class, 'mode'],
  1712. 'argumentCount' => '1+',
  1713. ],
  1714. 'MONTH' => [
  1715. 'category' => Category::CATEGORY_DATE_AND_TIME,
  1716. 'functionCall' => [DateTimeExcel\DateParts::class, 'month'],
  1717. 'argumentCount' => '1',
  1718. ],
  1719. 'MROUND' => [
  1720. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1721. 'functionCall' => [MathTrig\Round::class, 'multiple'],
  1722. 'argumentCount' => '2',
  1723. ],
  1724. 'MULTINOMIAL' => [
  1725. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1726. 'functionCall' => [MathTrig\Factorial::class, 'multinomial'],
  1727. 'argumentCount' => '1+',
  1728. ],
  1729. 'MUNIT' => [
  1730. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1731. 'functionCall' => [MathTrig\MatrixFunctions::class, 'identity'],
  1732. 'argumentCount' => '1',
  1733. ],
  1734. 'N' => [
  1735. 'category' => Category::CATEGORY_INFORMATION,
  1736. 'functionCall' => [Functions::class, 'n'],
  1737. 'argumentCount' => '1',
  1738. ],
  1739. 'NA' => [
  1740. 'category' => Category::CATEGORY_INFORMATION,
  1741. 'functionCall' => [Functions::class, 'NA'],
  1742. 'argumentCount' => '0',
  1743. ],
  1744. 'NEGBINOMDIST' => [
  1745. 'category' => Category::CATEGORY_STATISTICAL,
  1746. 'functionCall' => [Statistical\Distributions\Binomial::class, 'negative'],
  1747. 'argumentCount' => '3',
  1748. ],
  1749. 'NEGBINOM.DIST' => [
  1750. 'category' => Category::CATEGORY_STATISTICAL,
  1751. 'functionCall' => [Functions::class, 'DUMMY'],
  1752. 'argumentCount' => '4',
  1753. ],
  1754. 'NETWORKDAYS' => [
  1755. 'category' => Category::CATEGORY_DATE_AND_TIME,
  1756. 'functionCall' => [DateTimeExcel\NetworkDays::class, 'count'],
  1757. 'argumentCount' => '2-3',
  1758. ],
  1759. 'NETWORKDAYS.INTL' => [
  1760. 'category' => Category::CATEGORY_DATE_AND_TIME,
  1761. 'functionCall' => [Functions::class, 'DUMMY'],
  1762. 'argumentCount' => '2-4',
  1763. ],
  1764. 'NOMINAL' => [
  1765. 'category' => Category::CATEGORY_FINANCIAL,
  1766. 'functionCall' => [Financial\InterestRate::class, 'nominal'],
  1767. 'argumentCount' => '2',
  1768. ],
  1769. 'NORMDIST' => [
  1770. 'category' => Category::CATEGORY_STATISTICAL,
  1771. 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'],
  1772. 'argumentCount' => '4',
  1773. ],
  1774. 'NORM.DIST' => [
  1775. 'category' => Category::CATEGORY_STATISTICAL,
  1776. 'functionCall' => [Statistical\Distributions\Normal::class, 'distribution'],
  1777. 'argumentCount' => '4',
  1778. ],
  1779. 'NORMINV' => [
  1780. 'category' => Category::CATEGORY_STATISTICAL,
  1781. 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'],
  1782. 'argumentCount' => '3',
  1783. ],
  1784. 'NORM.INV' => [
  1785. 'category' => Category::CATEGORY_STATISTICAL,
  1786. 'functionCall' => [Statistical\Distributions\Normal::class, 'inverse'],
  1787. 'argumentCount' => '3',
  1788. ],
  1789. 'NORMSDIST' => [
  1790. 'category' => Category::CATEGORY_STATISTICAL,
  1791. 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'cumulative'],
  1792. 'argumentCount' => '1',
  1793. ],
  1794. 'NORM.S.DIST' => [
  1795. 'category' => Category::CATEGORY_STATISTICAL,
  1796. 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'distribution'],
  1797. 'argumentCount' => '1,2',
  1798. ],
  1799. 'NORMSINV' => [
  1800. 'category' => Category::CATEGORY_STATISTICAL,
  1801. 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'],
  1802. 'argumentCount' => '1',
  1803. ],
  1804. 'NORM.S.INV' => [
  1805. 'category' => Category::CATEGORY_STATISTICAL,
  1806. 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'inverse'],
  1807. 'argumentCount' => '1',
  1808. ],
  1809. 'NOT' => [
  1810. 'category' => Category::CATEGORY_LOGICAL,
  1811. 'functionCall' => [Logical\Operations::class, 'NOT'],
  1812. 'argumentCount' => '1',
  1813. ],
  1814. 'NOW' => [
  1815. 'category' => Category::CATEGORY_DATE_AND_TIME,
  1816. 'functionCall' => [DateTimeExcel\Current::class, 'now'],
  1817. 'argumentCount' => '0',
  1818. ],
  1819. 'NPER' => [
  1820. 'category' => Category::CATEGORY_FINANCIAL,
  1821. 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'periods'],
  1822. 'argumentCount' => '3-5',
  1823. ],
  1824. 'NPV' => [
  1825. 'category' => Category::CATEGORY_FINANCIAL,
  1826. 'functionCall' => [Financial\CashFlow\Variable\Periodic::class, 'presentValue'],
  1827. 'argumentCount' => '2+',
  1828. ],
  1829. 'NUMBERSTRING' => [
  1830. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1831. 'functionCall' => [Functions::class, 'DUMMY'],
  1832. 'argumentCount' => '?',
  1833. ],
  1834. 'NUMBERVALUE' => [
  1835. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1836. 'functionCall' => [TextData\Format::class, 'NUMBERVALUE'],
  1837. 'argumentCount' => '1+',
  1838. ],
  1839. 'OCT2BIN' => [
  1840. 'category' => Category::CATEGORY_ENGINEERING,
  1841. 'functionCall' => [Engineering\ConvertOctal::class, 'toBinary'],
  1842. 'argumentCount' => '1,2',
  1843. ],
  1844. 'OCT2DEC' => [
  1845. 'category' => Category::CATEGORY_ENGINEERING,
  1846. 'functionCall' => [Engineering\ConvertOctal::class, 'toDecimal'],
  1847. 'argumentCount' => '1',
  1848. ],
  1849. 'OCT2HEX' => [
  1850. 'category' => Category::CATEGORY_ENGINEERING,
  1851. 'functionCall' => [Engineering\ConvertOctal::class, 'toHex'],
  1852. 'argumentCount' => '1,2',
  1853. ],
  1854. 'ODD' => [
  1855. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1856. 'functionCall' => [MathTrig\Round::class, 'odd'],
  1857. 'argumentCount' => '1',
  1858. ],
  1859. 'ODDFPRICE' => [
  1860. 'category' => Category::CATEGORY_FINANCIAL,
  1861. 'functionCall' => [Functions::class, 'DUMMY'],
  1862. 'argumentCount' => '8,9',
  1863. ],
  1864. 'ODDFYIELD' => [
  1865. 'category' => Category::CATEGORY_FINANCIAL,
  1866. 'functionCall' => [Functions::class, 'DUMMY'],
  1867. 'argumentCount' => '8,9',
  1868. ],
  1869. 'ODDLPRICE' => [
  1870. 'category' => Category::CATEGORY_FINANCIAL,
  1871. 'functionCall' => [Functions::class, 'DUMMY'],
  1872. 'argumentCount' => '7,8',
  1873. ],
  1874. 'ODDLYIELD' => [
  1875. 'category' => Category::CATEGORY_FINANCIAL,
  1876. 'functionCall' => [Functions::class, 'DUMMY'],
  1877. 'argumentCount' => '7,8',
  1878. ],
  1879. 'OFFSET' => [
  1880. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  1881. 'functionCall' => [LookupRef\Offset::class, 'OFFSET'],
  1882. 'argumentCount' => '3-5',
  1883. 'passCellReference' => true,
  1884. 'passByReference' => [true],
  1885. ],
  1886. 'OR' => [
  1887. 'category' => Category::CATEGORY_LOGICAL,
  1888. 'functionCall' => [Logical\Operations::class, 'logicalOr'],
  1889. 'argumentCount' => '1+',
  1890. ],
  1891. 'PDURATION' => [
  1892. 'category' => Category::CATEGORY_FINANCIAL,
  1893. 'functionCall' => [Financial\CashFlow\Single::class, 'periods'],
  1894. 'argumentCount' => '3',
  1895. ],
  1896. 'PEARSON' => [
  1897. 'category' => Category::CATEGORY_STATISTICAL,
  1898. 'functionCall' => [Statistical\Trends::class, 'CORREL'],
  1899. 'argumentCount' => '2',
  1900. ],
  1901. 'PERCENTILE' => [
  1902. 'category' => Category::CATEGORY_STATISTICAL,
  1903. 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'],
  1904. 'argumentCount' => '2',
  1905. ],
  1906. 'PERCENTILE.EXC' => [
  1907. 'category' => Category::CATEGORY_STATISTICAL,
  1908. 'functionCall' => [Functions::class, 'DUMMY'],
  1909. 'argumentCount' => '2',
  1910. ],
  1911. 'PERCENTILE.INC' => [
  1912. 'category' => Category::CATEGORY_STATISTICAL,
  1913. 'functionCall' => [Statistical\Percentiles::class, 'PERCENTILE'],
  1914. 'argumentCount' => '2',
  1915. ],
  1916. 'PERCENTRANK' => [
  1917. 'category' => Category::CATEGORY_STATISTICAL,
  1918. 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'],
  1919. 'argumentCount' => '2,3',
  1920. ],
  1921. 'PERCENTRANK.EXC' => [
  1922. 'category' => Category::CATEGORY_STATISTICAL,
  1923. 'functionCall' => [Functions::class, 'DUMMY'],
  1924. 'argumentCount' => '2,3',
  1925. ],
  1926. 'PERCENTRANK.INC' => [
  1927. 'category' => Category::CATEGORY_STATISTICAL,
  1928. 'functionCall' => [Statistical\Percentiles::class, 'PERCENTRANK'],
  1929. 'argumentCount' => '2,3',
  1930. ],
  1931. 'PERMUT' => [
  1932. 'category' => Category::CATEGORY_STATISTICAL,
  1933. 'functionCall' => [Statistical\Permutations::class, 'PERMUT'],
  1934. 'argumentCount' => '2',
  1935. ],
  1936. 'PERMUTATIONA' => [
  1937. 'category' => Category::CATEGORY_STATISTICAL,
  1938. 'functionCall' => [Statistical\Permutations::class, 'PERMUTATIONA'],
  1939. 'argumentCount' => '2',
  1940. ],
  1941. 'PHONETIC' => [
  1942. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  1943. 'functionCall' => [Functions::class, 'DUMMY'],
  1944. 'argumentCount' => '1',
  1945. ],
  1946. 'PHI' => [
  1947. 'category' => Category::CATEGORY_STATISTICAL,
  1948. 'functionCall' => [Functions::class, 'DUMMY'],
  1949. 'argumentCount' => '1',
  1950. ],
  1951. 'PI' => [
  1952. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1953. 'functionCall' => 'pi',
  1954. 'argumentCount' => '0',
  1955. ],
  1956. 'PMT' => [
  1957. 'category' => Category::CATEGORY_FINANCIAL,
  1958. 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'annuity'],
  1959. 'argumentCount' => '3-5',
  1960. ],
  1961. 'POISSON' => [
  1962. 'category' => Category::CATEGORY_STATISTICAL,
  1963. 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'],
  1964. 'argumentCount' => '3',
  1965. ],
  1966. 'POISSON.DIST' => [
  1967. 'category' => Category::CATEGORY_STATISTICAL,
  1968. 'functionCall' => [Statistical\Distributions\Poisson::class, 'distribution'],
  1969. 'argumentCount' => '3',
  1970. ],
  1971. 'POWER' => [
  1972. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  1973. 'functionCall' => [MathTrig\Operations::class, 'power'],
  1974. 'argumentCount' => '2',
  1975. ],
  1976. 'PPMT' => [
  1977. 'category' => Category::CATEGORY_FINANCIAL,
  1978. 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class, 'interestPayment'],
  1979. 'argumentCount' => '4-6',
  1980. ],
  1981. 'PRICE' => [
  1982. 'category' => Category::CATEGORY_FINANCIAL,
  1983. 'functionCall' => [Financial\Securities\Price::class, 'price'],
  1984. 'argumentCount' => '6,7',
  1985. ],
  1986. 'PRICEDISC' => [
  1987. 'category' => Category::CATEGORY_FINANCIAL,
  1988. 'functionCall' => [Financial\Securities\Price::class, 'priceDiscounted'],
  1989. 'argumentCount' => '4,5',
  1990. ],
  1991. 'PRICEMAT' => [
  1992. 'category' => Category::CATEGORY_FINANCIAL,
  1993. 'functionCall' => [Financial\Securities\Price::class, 'priceAtMaturity'],
  1994. 'argumentCount' => '5,6',
  1995. ],
  1996. 'PROB' => [
  1997. 'category' => Category::CATEGORY_STATISTICAL,
  1998. 'functionCall' => [Functions::class, 'DUMMY'],
  1999. 'argumentCount' => '3,4',
  2000. ],
  2001. 'PRODUCT' => [
  2002. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2003. 'functionCall' => [MathTrig\Operations::class, 'product'],
  2004. 'argumentCount' => '1+',
  2005. ],
  2006. 'PROPER' => [
  2007. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2008. 'functionCall' => [TextData\CaseConvert::class, 'proper'],
  2009. 'argumentCount' => '1',
  2010. ],
  2011. 'PV' => [
  2012. 'category' => Category::CATEGORY_FINANCIAL,
  2013. 'functionCall' => [Financial\CashFlow\Constant\Periodic::class, 'presentValue'],
  2014. 'argumentCount' => '3-5',
  2015. ],
  2016. 'QUARTILE' => [
  2017. 'category' => Category::CATEGORY_STATISTICAL,
  2018. 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'],
  2019. 'argumentCount' => '2',
  2020. ],
  2021. 'QUARTILE.EXC' => [
  2022. 'category' => Category::CATEGORY_STATISTICAL,
  2023. 'functionCall' => [Functions::class, 'DUMMY'],
  2024. 'argumentCount' => '2',
  2025. ],
  2026. 'QUARTILE.INC' => [
  2027. 'category' => Category::CATEGORY_STATISTICAL,
  2028. 'functionCall' => [Statistical\Percentiles::class, 'QUARTILE'],
  2029. 'argumentCount' => '2',
  2030. ],
  2031. 'QUOTIENT' => [
  2032. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2033. 'functionCall' => [MathTrig\Operations::class, 'quotient'],
  2034. 'argumentCount' => '2',
  2035. ],
  2036. 'RADIANS' => [
  2037. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2038. 'functionCall' => [MathTrig\Angle::class, 'toRadians'],
  2039. 'argumentCount' => '1',
  2040. ],
  2041. 'RAND' => [
  2042. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2043. 'functionCall' => [MathTrig\Random::class, 'rand'],
  2044. 'argumentCount' => '0',
  2045. ],
  2046. 'RANDARRAY' => [
  2047. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2048. 'functionCall' => [Functions::class, 'DUMMY'],
  2049. 'argumentCount' => '0-5',
  2050. ],
  2051. 'RANDBETWEEN' => [
  2052. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2053. 'functionCall' => [MathTrig\Random::class, 'randBetween'],
  2054. 'argumentCount' => '2',
  2055. ],
  2056. 'RANK' => [
  2057. 'category' => Category::CATEGORY_STATISTICAL,
  2058. 'functionCall' => [Statistical\Percentiles::class, 'RANK'],
  2059. 'argumentCount' => '2,3',
  2060. ],
  2061. 'RANK.AVG' => [
  2062. 'category' => Category::CATEGORY_STATISTICAL,
  2063. 'functionCall' => [Functions::class, 'DUMMY'],
  2064. 'argumentCount' => '2,3',
  2065. ],
  2066. 'RANK.EQ' => [
  2067. 'category' => Category::CATEGORY_STATISTICAL,
  2068. 'functionCall' => [Statistical\Percentiles::class, 'RANK'],
  2069. 'argumentCount' => '2,3',
  2070. ],
  2071. 'RATE' => [
  2072. 'category' => Category::CATEGORY_FINANCIAL,
  2073. 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class, 'rate'],
  2074. 'argumentCount' => '3-6',
  2075. ],
  2076. 'RECEIVED' => [
  2077. 'category' => Category::CATEGORY_FINANCIAL,
  2078. 'functionCall' => [Financial\Securities\Price::class, 'received'],
  2079. 'argumentCount' => '4-5',
  2080. ],
  2081. 'REPLACE' => [
  2082. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2083. 'functionCall' => [TextData\Replace::class, 'replace'],
  2084. 'argumentCount' => '4',
  2085. ],
  2086. 'REPLACEB' => [
  2087. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2088. 'functionCall' => [TextData\Replace::class, 'replace'],
  2089. 'argumentCount' => '4',
  2090. ],
  2091. 'REPT' => [
  2092. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2093. 'functionCall' => [TextData\Concatenate::class, 'builtinREPT'],
  2094. 'argumentCount' => '2',
  2095. ],
  2096. 'RIGHT' => [
  2097. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2098. 'functionCall' => [TextData\Extract::class, 'right'],
  2099. 'argumentCount' => '1,2',
  2100. ],
  2101. 'RIGHTB' => [
  2102. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2103. 'functionCall' => [TextData\Extract::class, 'right'],
  2104. 'argumentCount' => '1,2',
  2105. ],
  2106. 'ROMAN' => [
  2107. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2108. 'functionCall' => [MathTrig\Roman::class, 'evaluate'],
  2109. 'argumentCount' => '1,2',
  2110. ],
  2111. 'ROUND' => [
  2112. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2113. 'functionCall' => [MathTrig\Round::class, 'round'],
  2114. 'argumentCount' => '2',
  2115. ],
  2116. 'ROUNDBAHTDOWN' => [
  2117. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2118. 'functionCall' => [Functions::class, 'DUMMY'],
  2119. 'argumentCount' => '?',
  2120. ],
  2121. 'ROUNDBAHTUP' => [
  2122. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2123. 'functionCall' => [Functions::class, 'DUMMY'],
  2124. 'argumentCount' => '?',
  2125. ],
  2126. 'ROUNDDOWN' => [
  2127. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2128. 'functionCall' => [MathTrig\Round::class, 'down'],
  2129. 'argumentCount' => '2',
  2130. ],
  2131. 'ROUNDUP' => [
  2132. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2133. 'functionCall' => [MathTrig\Round::class, 'up'],
  2134. 'argumentCount' => '2',
  2135. ],
  2136. 'ROW' => [
  2137. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2138. 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROW'],
  2139. 'argumentCount' => '-1',
  2140. 'passCellReference' => true,
  2141. 'passByReference' => [true],
  2142. ],
  2143. 'ROWS' => [
  2144. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2145. 'functionCall' => [LookupRef\RowColumnInformation::class, 'ROWS'],
  2146. 'argumentCount' => '1',
  2147. ],
  2148. 'RRI' => [
  2149. 'category' => Category::CATEGORY_FINANCIAL,
  2150. 'functionCall' => [Financial\CashFlow\Single::class, 'interestRate'],
  2151. 'argumentCount' => '3',
  2152. ],
  2153. 'RSQ' => [
  2154. 'category' => Category::CATEGORY_STATISTICAL,
  2155. 'functionCall' => [Statistical\Trends::class, 'RSQ'],
  2156. 'argumentCount' => '2',
  2157. ],
  2158. 'RTD' => [
  2159. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2160. 'functionCall' => [Functions::class, 'DUMMY'],
  2161. 'argumentCount' => '1+',
  2162. ],
  2163. 'SEARCH' => [
  2164. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2165. 'functionCall' => [TextData\Search::class, 'insensitive'],
  2166. 'argumentCount' => '2,3',
  2167. ],
  2168. 'SEARCHB' => [
  2169. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2170. 'functionCall' => [TextData\Search::class, 'insensitive'],
  2171. 'argumentCount' => '2,3',
  2172. ],
  2173. 'SEC' => [
  2174. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2175. 'functionCall' => [MathTrig\Trig\Secant::class, 'sec'],
  2176. 'argumentCount' => '1',
  2177. ],
  2178. 'SECH' => [
  2179. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2180. 'functionCall' => [MathTrig\Trig\Secant::class, 'sech'],
  2181. 'argumentCount' => '1',
  2182. ],
  2183. 'SECOND' => [
  2184. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2185. 'functionCall' => [DateTimeExcel\TimeParts::class, 'second'],
  2186. 'argumentCount' => '1',
  2187. ],
  2188. 'SEQUENCE' => [
  2189. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2190. 'functionCall' => [Functions::class, 'DUMMY'],
  2191. 'argumentCount' => '2',
  2192. ],
  2193. 'SERIESSUM' => [
  2194. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2195. 'functionCall' => [MathTrig\SeriesSum::class, 'evaluate'],
  2196. 'argumentCount' => '4',
  2197. ],
  2198. 'SHEET' => [
  2199. 'category' => Category::CATEGORY_INFORMATION,
  2200. 'functionCall' => [Functions::class, 'DUMMY'],
  2201. 'argumentCount' => '0,1',
  2202. ],
  2203. 'SHEETS' => [
  2204. 'category' => Category::CATEGORY_INFORMATION,
  2205. 'functionCall' => [Functions::class, 'DUMMY'],
  2206. 'argumentCount' => '0,1',
  2207. ],
  2208. 'SIGN' => [
  2209. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2210. 'functionCall' => [MathTrig\Sign::class, 'evaluate'],
  2211. 'argumentCount' => '1',
  2212. ],
  2213. 'SIN' => [
  2214. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2215. 'functionCall' => [MathTrig\Trig\Sine::class, 'sin'],
  2216. 'argumentCount' => '1',
  2217. ],
  2218. 'SINH' => [
  2219. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2220. 'functionCall' => [MathTrig\Trig\Sine::class, 'sinh'],
  2221. 'argumentCount' => '1',
  2222. ],
  2223. 'SKEW' => [
  2224. 'category' => Category::CATEGORY_STATISTICAL,
  2225. 'functionCall' => [Statistical\Deviations::class, 'skew'],
  2226. 'argumentCount' => '1+',
  2227. ],
  2228. 'SKEW.P' => [
  2229. 'category' => Category::CATEGORY_STATISTICAL,
  2230. 'functionCall' => [Functions::class, 'DUMMY'],
  2231. 'argumentCount' => '1+',
  2232. ],
  2233. 'SLN' => [
  2234. 'category' => Category::CATEGORY_FINANCIAL,
  2235. 'functionCall' => [Financial\Depreciation::class, 'SLN'],
  2236. 'argumentCount' => '3',
  2237. ],
  2238. 'SLOPE' => [
  2239. 'category' => Category::CATEGORY_STATISTICAL,
  2240. 'functionCall' => [Statistical\Trends::class, 'SLOPE'],
  2241. 'argumentCount' => '2',
  2242. ],
  2243. 'SMALL' => [
  2244. 'category' => Category::CATEGORY_STATISTICAL,
  2245. 'functionCall' => [Statistical\Size::class, 'small'],
  2246. 'argumentCount' => '2',
  2247. ],
  2248. 'SORT' => [
  2249. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2250. 'functionCall' => [Functions::class, 'DUMMY'],
  2251. 'argumentCount' => '1+',
  2252. ],
  2253. 'SORTBY' => [
  2254. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2255. 'functionCall' => [Functions::class, 'DUMMY'],
  2256. 'argumentCount' => '2+',
  2257. ],
  2258. 'SQRT' => [
  2259. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2260. 'functionCall' => [MathTrig\Sqrt::class, 'sqrt'],
  2261. 'argumentCount' => '1',
  2262. ],
  2263. 'SQRTPI' => [
  2264. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2265. 'functionCall' => [MathTrig\Sqrt::class, 'pi'],
  2266. 'argumentCount' => '1',
  2267. ],
  2268. 'STANDARDIZE' => [
  2269. 'category' => Category::CATEGORY_STATISTICAL,
  2270. 'functionCall' => [Statistical\Standardize::class, 'execute'],
  2271. 'argumentCount' => '3',
  2272. ],
  2273. 'STDEV' => [
  2274. 'category' => Category::CATEGORY_STATISTICAL,
  2275. 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'],
  2276. 'argumentCount' => '1+',
  2277. ],
  2278. 'STDEV.S' => [
  2279. 'category' => Category::CATEGORY_STATISTICAL,
  2280. 'functionCall' => [Statistical\StandardDeviations::class, 'STDEV'],
  2281. 'argumentCount' => '1+',
  2282. ],
  2283. 'STDEV.P' => [
  2284. 'category' => Category::CATEGORY_STATISTICAL,
  2285. 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'],
  2286. 'argumentCount' => '1+',
  2287. ],
  2288. 'STDEVA' => [
  2289. 'category' => Category::CATEGORY_STATISTICAL,
  2290. 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVA'],
  2291. 'argumentCount' => '1+',
  2292. ],
  2293. 'STDEVP' => [
  2294. 'category' => Category::CATEGORY_STATISTICAL,
  2295. 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVP'],
  2296. 'argumentCount' => '1+',
  2297. ],
  2298. 'STDEVPA' => [
  2299. 'category' => Category::CATEGORY_STATISTICAL,
  2300. 'functionCall' => [Statistical\StandardDeviations::class, 'STDEVPA'],
  2301. 'argumentCount' => '1+',
  2302. ],
  2303. 'STEYX' => [
  2304. 'category' => Category::CATEGORY_STATISTICAL,
  2305. 'functionCall' => [Statistical\Trends::class, 'STEYX'],
  2306. 'argumentCount' => '2',
  2307. ],
  2308. 'SUBSTITUTE' => [
  2309. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2310. 'functionCall' => [TextData\Replace::class, 'substitute'],
  2311. 'argumentCount' => '3,4',
  2312. ],
  2313. 'SUBTOTAL' => [
  2314. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2315. 'functionCall' => [MathTrig\Subtotal::class, 'evaluate'],
  2316. 'argumentCount' => '2+',
  2317. 'passCellReference' => true,
  2318. ],
  2319. 'SUM' => [
  2320. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2321. 'functionCall' => [MathTrig\Sum::class, 'sumErroringStrings'],
  2322. 'argumentCount' => '1+',
  2323. ],
  2324. 'SUMIF' => [
  2325. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2326. 'functionCall' => [Statistical\Conditional::class, 'SUMIF'],
  2327. 'argumentCount' => '2,3',
  2328. ],
  2329. 'SUMIFS' => [
  2330. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2331. 'functionCall' => [Statistical\Conditional::class, 'SUMIFS'],
  2332. 'argumentCount' => '3+',
  2333. ],
  2334. 'SUMPRODUCT' => [
  2335. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2336. 'functionCall' => [MathTrig\Sum::class, 'product'],
  2337. 'argumentCount' => '1+',
  2338. ],
  2339. 'SUMSQ' => [
  2340. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2341. 'functionCall' => [MathTrig\SumSquares::class, 'sumSquare'],
  2342. 'argumentCount' => '1+',
  2343. ],
  2344. 'SUMX2MY2' => [
  2345. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2346. 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredMinusYSquared'],
  2347. 'argumentCount' => '2',
  2348. ],
  2349. 'SUMX2PY2' => [
  2350. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2351. 'functionCall' => [MathTrig\SumSquares::class, 'sumXSquaredPlusYSquared'],
  2352. 'argumentCount' => '2',
  2353. ],
  2354. 'SUMXMY2' => [
  2355. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2356. 'functionCall' => [MathTrig\SumSquares::class, 'sumXMinusYSquared'],
  2357. 'argumentCount' => '2',
  2358. ],
  2359. 'SWITCH' => [
  2360. 'category' => Category::CATEGORY_LOGICAL,
  2361. 'functionCall' => [Logical\Conditional::class, 'statementSwitch'],
  2362. 'argumentCount' => '3+',
  2363. ],
  2364. 'SYD' => [
  2365. 'category' => Category::CATEGORY_FINANCIAL,
  2366. 'functionCall' => [Financial\Depreciation::class, 'SYD'],
  2367. 'argumentCount' => '4',
  2368. ],
  2369. 'T' => [
  2370. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2371. 'functionCall' => [TextData\Text::class, 'test'],
  2372. 'argumentCount' => '1',
  2373. ],
  2374. 'TAN' => [
  2375. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2376. 'functionCall' => [MathTrig\Trig\Tangent::class, 'tan'],
  2377. 'argumentCount' => '1',
  2378. ],
  2379. 'TANH' => [
  2380. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2381. 'functionCall' => [MathTrig\Trig\Tangent::class, 'tanh'],
  2382. 'argumentCount' => '1',
  2383. ],
  2384. 'TBILLEQ' => [
  2385. 'category' => Category::CATEGORY_FINANCIAL,
  2386. 'functionCall' => [Financial\TreasuryBill::class, 'bondEquivalentYield'],
  2387. 'argumentCount' => '3',
  2388. ],
  2389. 'TBILLPRICE' => [
  2390. 'category' => Category::CATEGORY_FINANCIAL,
  2391. 'functionCall' => [Financial\TreasuryBill::class, 'price'],
  2392. 'argumentCount' => '3',
  2393. ],
  2394. 'TBILLYIELD' => [
  2395. 'category' => Category::CATEGORY_FINANCIAL,
  2396. 'functionCall' => [Financial\TreasuryBill::class, 'yield'],
  2397. 'argumentCount' => '3',
  2398. ],
  2399. 'TDIST' => [
  2400. 'category' => Category::CATEGORY_STATISTICAL,
  2401. 'functionCall' => [Statistical\Distributions\StudentT::class, 'distribution'],
  2402. 'argumentCount' => '3',
  2403. ],
  2404. 'T.DIST' => [
  2405. 'category' => Category::CATEGORY_STATISTICAL,
  2406. 'functionCall' => [Functions::class, 'DUMMY'],
  2407. 'argumentCount' => '3',
  2408. ],
  2409. 'T.DIST.2T' => [
  2410. 'category' => Category::CATEGORY_STATISTICAL,
  2411. 'functionCall' => [Functions::class, 'DUMMY'],
  2412. 'argumentCount' => '2',
  2413. ],
  2414. 'T.DIST.RT' => [
  2415. 'category' => Category::CATEGORY_STATISTICAL,
  2416. 'functionCall' => [Functions::class, 'DUMMY'],
  2417. 'argumentCount' => '2',
  2418. ],
  2419. 'TEXT' => [
  2420. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2421. 'functionCall' => [TextData\Format::class, 'TEXTFORMAT'],
  2422. 'argumentCount' => '2',
  2423. ],
  2424. 'TEXTJOIN' => [
  2425. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2426. 'functionCall' => [TextData\Concatenate::class, 'TEXTJOIN'],
  2427. 'argumentCount' => '3+',
  2428. ],
  2429. 'THAIDAYOFWEEK' => [
  2430. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2431. 'functionCall' => [Functions::class, 'DUMMY'],
  2432. 'argumentCount' => '?',
  2433. ],
  2434. 'THAIDIGIT' => [
  2435. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2436. 'functionCall' => [Functions::class, 'DUMMY'],
  2437. 'argumentCount' => '?',
  2438. ],
  2439. 'THAIMONTHOFYEAR' => [
  2440. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2441. 'functionCall' => [Functions::class, 'DUMMY'],
  2442. 'argumentCount' => '?',
  2443. ],
  2444. 'THAINUMSOUND' => [
  2445. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2446. 'functionCall' => [Functions::class, 'DUMMY'],
  2447. 'argumentCount' => '?',
  2448. ],
  2449. 'THAINUMSTRING' => [
  2450. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2451. 'functionCall' => [Functions::class, 'DUMMY'],
  2452. 'argumentCount' => '?',
  2453. ],
  2454. 'THAISTRINGLENGTH' => [
  2455. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2456. 'functionCall' => [Functions::class, 'DUMMY'],
  2457. 'argumentCount' => '?',
  2458. ],
  2459. 'THAIYEAR' => [
  2460. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2461. 'functionCall' => [Functions::class, 'DUMMY'],
  2462. 'argumentCount' => '?',
  2463. ],
  2464. 'TIME' => [
  2465. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2466. 'functionCall' => [DateTimeExcel\Time::class, 'fromHMS'],
  2467. 'argumentCount' => '3',
  2468. ],
  2469. 'TIMEVALUE' => [
  2470. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2471. 'functionCall' => [DateTimeExcel\TimeValue::class, 'fromString'],
  2472. 'argumentCount' => '1',
  2473. ],
  2474. 'TINV' => [
  2475. 'category' => Category::CATEGORY_STATISTICAL,
  2476. 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'],
  2477. 'argumentCount' => '2',
  2478. ],
  2479. 'T.INV' => [
  2480. 'category' => Category::CATEGORY_STATISTICAL,
  2481. 'functionCall' => [Statistical\Distributions\StudentT::class, 'inverse'],
  2482. 'argumentCount' => '2',
  2483. ],
  2484. 'T.INV.2T' => [
  2485. 'category' => Category::CATEGORY_STATISTICAL,
  2486. 'functionCall' => [Functions::class, 'DUMMY'],
  2487. 'argumentCount' => '2',
  2488. ],
  2489. 'TODAY' => [
  2490. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2491. 'functionCall' => [DateTimeExcel\Current::class, 'today'],
  2492. 'argumentCount' => '0',
  2493. ],
  2494. 'TRANSPOSE' => [
  2495. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2496. 'functionCall' => [LookupRef\Matrix::class, 'transpose'],
  2497. 'argumentCount' => '1',
  2498. ],
  2499. 'TREND' => [
  2500. 'category' => Category::CATEGORY_STATISTICAL,
  2501. 'functionCall' => [Statistical\Trends::class, 'TREND'],
  2502. 'argumentCount' => '1-4',
  2503. ],
  2504. 'TRIM' => [
  2505. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2506. 'functionCall' => [TextData\Trim::class, 'spaces'],
  2507. 'argumentCount' => '1',
  2508. ],
  2509. 'TRIMMEAN' => [
  2510. 'category' => Category::CATEGORY_STATISTICAL,
  2511. 'functionCall' => [Statistical\Averages\Mean::class, 'trim'],
  2512. 'argumentCount' => '2',
  2513. ],
  2514. 'TRUE' => [
  2515. 'category' => Category::CATEGORY_LOGICAL,
  2516. 'functionCall' => [Logical\Boolean::class, 'TRUE'],
  2517. 'argumentCount' => '0',
  2518. ],
  2519. 'TRUNC' => [
  2520. 'category' => Category::CATEGORY_MATH_AND_TRIG,
  2521. 'functionCall' => [MathTrig\Trunc::class, 'evaluate'],
  2522. 'argumentCount' => '1,2',
  2523. ],
  2524. 'TTEST' => [
  2525. 'category' => Category::CATEGORY_STATISTICAL,
  2526. 'functionCall' => [Functions::class, 'DUMMY'],
  2527. 'argumentCount' => '4',
  2528. ],
  2529. 'T.TEST' => [
  2530. 'category' => Category::CATEGORY_STATISTICAL,
  2531. 'functionCall' => [Functions::class, 'DUMMY'],
  2532. 'argumentCount' => '4',
  2533. ],
  2534. 'TYPE' => [
  2535. 'category' => Category::CATEGORY_INFORMATION,
  2536. 'functionCall' => [Functions::class, 'TYPE'],
  2537. 'argumentCount' => '1',
  2538. ],
  2539. 'UNICHAR' => [
  2540. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2541. 'functionCall' => [TextData\CharacterConvert::class, 'character'],
  2542. 'argumentCount' => '1',
  2543. ],
  2544. 'UNICODE' => [
  2545. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2546. 'functionCall' => [TextData\CharacterConvert::class, 'code'],
  2547. 'argumentCount' => '1',
  2548. ],
  2549. 'UNIQUE' => [
  2550. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2551. 'functionCall' => [Functions::class, 'DUMMY'],
  2552. 'argumentCount' => '1+',
  2553. ],
  2554. 'UPPER' => [
  2555. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2556. 'functionCall' => [TextData\CaseConvert::class, 'upper'],
  2557. 'argumentCount' => '1',
  2558. ],
  2559. 'USDOLLAR' => [
  2560. 'category' => Category::CATEGORY_FINANCIAL,
  2561. 'functionCall' => [Financial\Dollar::class, 'format'],
  2562. 'argumentCount' => '2',
  2563. ],
  2564. 'VALUE' => [
  2565. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2566. 'functionCall' => [TextData\Format::class, 'VALUE'],
  2567. 'argumentCount' => '1',
  2568. ],
  2569. 'VALUETOTEXT' => [
  2570. 'category' => Category::CATEGORY_TEXT_AND_DATA,
  2571. 'functionCall' => [Functions::class, 'DUMMY'],
  2572. 'argumentCount' => '?',
  2573. ],
  2574. 'VAR' => [
  2575. 'category' => Category::CATEGORY_STATISTICAL,
  2576. 'functionCall' => [Statistical\Variances::class, 'VAR'],
  2577. 'argumentCount' => '1+',
  2578. ],
  2579. 'VAR.P' => [
  2580. 'category' => Category::CATEGORY_STATISTICAL,
  2581. 'functionCall' => [Statistical\Variances::class, 'VARP'],
  2582. 'argumentCount' => '1+',
  2583. ],
  2584. 'VAR.S' => [
  2585. 'category' => Category::CATEGORY_STATISTICAL,
  2586. 'functionCall' => [Statistical\Variances::class, 'VAR'],
  2587. 'argumentCount' => '1+',
  2588. ],
  2589. 'VARA' => [
  2590. 'category' => Category::CATEGORY_STATISTICAL,
  2591. 'functionCall' => [Statistical\Variances::class, 'VARA'],
  2592. 'argumentCount' => '1+',
  2593. ],
  2594. 'VARP' => [
  2595. 'category' => Category::CATEGORY_STATISTICAL,
  2596. 'functionCall' => [Statistical\Variances::class, 'VARP'],
  2597. 'argumentCount' => '1+',
  2598. ],
  2599. 'VARPA' => [
  2600. 'category' => Category::CATEGORY_STATISTICAL,
  2601. 'functionCall' => [Statistical\Variances::class, 'VARPA'],
  2602. 'argumentCount' => '1+',
  2603. ],
  2604. 'VDB' => [
  2605. 'category' => Category::CATEGORY_FINANCIAL,
  2606. 'functionCall' => [Functions::class, 'DUMMY'],
  2607. 'argumentCount' => '5-7',
  2608. ],
  2609. 'VLOOKUP' => [
  2610. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2611. 'functionCall' => [LookupRef\VLookup::class, 'lookup'],
  2612. 'argumentCount' => '3,4',
  2613. ],
  2614. 'WEBSERVICE' => [
  2615. 'category' => Category::CATEGORY_WEB,
  2616. 'functionCall' => [Web\Service::class, 'webService'],
  2617. 'argumentCount' => '1',
  2618. ],
  2619. 'WEEKDAY' => [
  2620. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2621. 'functionCall' => [DateTimeExcel\Week::class, 'day'],
  2622. 'argumentCount' => '1,2',
  2623. ],
  2624. 'WEEKNUM' => [
  2625. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2626. 'functionCall' => [DateTimeExcel\Week::class, 'number'],
  2627. 'argumentCount' => '1,2',
  2628. ],
  2629. 'WEIBULL' => [
  2630. 'category' => Category::CATEGORY_STATISTICAL,
  2631. 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'],
  2632. 'argumentCount' => '4',
  2633. ],
  2634. 'WEIBULL.DIST' => [
  2635. 'category' => Category::CATEGORY_STATISTICAL,
  2636. 'functionCall' => [Statistical\Distributions\Weibull::class, 'distribution'],
  2637. 'argumentCount' => '4',
  2638. ],
  2639. 'WORKDAY' => [
  2640. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2641. 'functionCall' => [DateTimeExcel\WorkDay::class, 'date'],
  2642. 'argumentCount' => '2-3',
  2643. ],
  2644. 'WORKDAY.INTL' => [
  2645. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2646. 'functionCall' => [Functions::class, 'DUMMY'],
  2647. 'argumentCount' => '2-4',
  2648. ],
  2649. 'XIRR' => [
  2650. 'category' => Category::CATEGORY_FINANCIAL,
  2651. 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'rate'],
  2652. 'argumentCount' => '2,3',
  2653. ],
  2654. 'XLOOKUP' => [
  2655. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2656. 'functionCall' => [Functions::class, 'DUMMY'],
  2657. 'argumentCount' => '3-6',
  2658. ],
  2659. 'XNPV' => [
  2660. 'category' => Category::CATEGORY_FINANCIAL,
  2661. 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class, 'presentValue'],
  2662. 'argumentCount' => '3',
  2663. ],
  2664. 'XMATCH' => [
  2665. 'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
  2666. 'functionCall' => [Functions::class, 'DUMMY'],
  2667. 'argumentCount' => '2,3',
  2668. ],
  2669. 'XOR' => [
  2670. 'category' => Category::CATEGORY_LOGICAL,
  2671. 'functionCall' => [Logical\Operations::class, 'logicalXor'],
  2672. 'argumentCount' => '1+',
  2673. ],
  2674. 'YEAR' => [
  2675. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2676. 'functionCall' => [DateTimeExcel\DateParts::class, 'year'],
  2677. 'argumentCount' => '1',
  2678. ],
  2679. 'YEARFRAC' => [
  2680. 'category' => Category::CATEGORY_DATE_AND_TIME,
  2681. 'functionCall' => [DateTimeExcel\YearFrac::class, 'fraction'],
  2682. 'argumentCount' => '2,3',
  2683. ],
  2684. 'YIELD' => [
  2685. 'category' => Category::CATEGORY_FINANCIAL,
  2686. 'functionCall' => [Functions::class, 'DUMMY'],
  2687. 'argumentCount' => '6,7',
  2688. ],
  2689. 'YIELDDISC' => [
  2690. 'category' => Category::CATEGORY_FINANCIAL,
  2691. 'functionCall' => [Financial\Securities\Yields::class, 'yieldDiscounted'],
  2692. 'argumentCount' => '4,5',
  2693. ],
  2694. 'YIELDMAT' => [
  2695. 'category' => Category::CATEGORY_FINANCIAL,
  2696. 'functionCall' => [Financial\Securities\Yields::class, 'yieldAtMaturity'],
  2697. 'argumentCount' => '5,6',
  2698. ],
  2699. 'ZTEST' => [
  2700. 'category' => Category::CATEGORY_STATISTICAL,
  2701. 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'],
  2702. 'argumentCount' => '2-3',
  2703. ],
  2704. 'Z.TEST' => [
  2705. 'category' => Category::CATEGORY_STATISTICAL,
  2706. 'functionCall' => [Statistical\Distributions\StandardNormal::class, 'zTest'],
  2707. 'argumentCount' => '2-3',
  2708. ],
  2709. ];
  2710. // Internal functions used for special control purposes
  2711. private static $controlFunctions = [
  2712. 'MKMATRIX' => [
  2713. 'argumentCount' => '*',
  2714. 'functionCall' => [Internal\MakeMatrix::class, 'make'],
  2715. ],
  2716. 'NAME.ERROR' => [
  2717. 'argumentCount' => '*',
  2718. 'functionCall' => [Functions::class, 'NAME'],
  2719. ],
  2720. 'WILDCARDMATCH' => [
  2721. 'argumentCount' => '2',
  2722. 'functionCall' => [Internal\WildcardMatch::class, 'compare'],
  2723. ],
  2724. ];
  2725. public function __construct(?Spreadsheet $spreadsheet = null)
  2726. {
  2727. $this->delta = 1 * 10 ** (0 - ini_get('precision'));
  2728. $this->spreadsheet = $spreadsheet;
  2729. $this->cyclicReferenceStack = new CyclicReferenceStack();
  2730. $this->debugLog = new Logger($this->cyclicReferenceStack);
  2731. self::$referenceHelper = ReferenceHelper::getInstance();
  2732. }
  2733. private static function loadLocales(): void
  2734. {
  2735. $localeFileDirectory = __DIR__ . '/locale/';
  2736. foreach (glob($localeFileDirectory . '*', GLOB_ONLYDIR) as $filename) {
  2737. $filename = substr($filename, strlen($localeFileDirectory));
  2738. if ($filename != 'en') {
  2739. self::$validLocaleLanguages[] = $filename;
  2740. }
  2741. }
  2742. }
  2743. /**
  2744. * Get an instance of this class.
  2745. *
  2746. * @param ?Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object,
  2747. * or NULL to create a standalone calculation engine
  2748. */
  2749. public static function getInstance(?Spreadsheet $spreadsheet = null): self
  2750. {
  2751. if ($spreadsheet !== null) {
  2752. $instance = $spreadsheet->getCalculationEngine();
  2753. if (isset($instance)) {
  2754. return $instance;
  2755. }
  2756. }
  2757. if (!isset(self::$instance) || (self::$instance === null)) {
  2758. self::$instance = new self();
  2759. }
  2760. return self::$instance;
  2761. }
  2762. /**
  2763. * Flush the calculation cache for any existing instance of this class
  2764. * but only if a Calculation instance exists.
  2765. */
  2766. public function flushInstance(): void
  2767. {
  2768. $this->clearCalculationCache();
  2769. $this->clearBranchStore();
  2770. }
  2771. /**
  2772. * Get the Logger for this calculation engine instance.
  2773. *
  2774. * @return Logger
  2775. */
  2776. public function getDebugLog()
  2777. {
  2778. return $this->debugLog;
  2779. }
  2780. /**
  2781. * __clone implementation. Cloning should not be allowed in a Singleton!
  2782. */
  2783. final public function __clone()
  2784. {
  2785. throw new Exception('Cloning the calculation engine is not allowed!');
  2786. }
  2787. /**
  2788. * Return the locale-specific translation of TRUE.
  2789. *
  2790. * @return string locale-specific translation of TRUE
  2791. */
  2792. public static function getTRUE(): string
  2793. {
  2794. return self::$localeBoolean['TRUE'];
  2795. }
  2796. /**
  2797. * Return the locale-specific translation of FALSE.
  2798. *
  2799. * @return string locale-specific translation of FALSE
  2800. */
  2801. public static function getFALSE(): string
  2802. {
  2803. return self::$localeBoolean['FALSE'];
  2804. }
  2805. /**
  2806. * Set the Array Return Type (Array or Value of first element in the array).
  2807. *
  2808. * @param string $returnType Array return type
  2809. *
  2810. * @return bool Success or failure
  2811. */
  2812. public static function setArrayReturnType($returnType)
  2813. {
  2814. if (
  2815. ($returnType == self::RETURN_ARRAY_AS_VALUE) ||
  2816. ($returnType == self::RETURN_ARRAY_AS_ERROR) ||
  2817. ($returnType == self::RETURN_ARRAY_AS_ARRAY)
  2818. ) {
  2819. self::$returnArrayAsType = $returnType;
  2820. return true;
  2821. }
  2822. return false;
  2823. }
  2824. /**
  2825. * Return the Array Return Type (Array or Value of first element in the array).
  2826. *
  2827. * @return string $returnType Array return type
  2828. */
  2829. public static function getArrayReturnType()
  2830. {
  2831. return self::$returnArrayAsType;
  2832. }
  2833. /**
  2834. * Is calculation caching enabled?
  2835. *
  2836. * @return bool
  2837. */
  2838. public function getCalculationCacheEnabled()
  2839. {
  2840. return $this->calculationCacheEnabled;
  2841. }
  2842. /**
  2843. * Enable/disable calculation cache.
  2844. *
  2845. * @param bool $pValue
  2846. */
  2847. public function setCalculationCacheEnabled($pValue): void
  2848. {
  2849. $this->calculationCacheEnabled = $pValue;
  2850. $this->clearCalculationCache();
  2851. }
  2852. /**
  2853. * Enable calculation cache.
  2854. */
  2855. public function enableCalculationCache(): void
  2856. {
  2857. $this->setCalculationCacheEnabled(true);
  2858. }
  2859. /**
  2860. * Disable calculation cache.
  2861. */
  2862. public function disableCalculationCache(): void
  2863. {
  2864. $this->setCalculationCacheEnabled(false);
  2865. }
  2866. /**
  2867. * Clear calculation cache.
  2868. */
  2869. public function clearCalculationCache(): void
  2870. {
  2871. $this->calculationCache = [];
  2872. }
  2873. /**
  2874. * Clear calculation cache for a specified worksheet.
  2875. *
  2876. * @param string $worksheetName
  2877. */
  2878. public function clearCalculationCacheForWorksheet($worksheetName): void
  2879. {
  2880. if (isset($this->calculationCache[$worksheetName])) {
  2881. unset($this->calculationCache[$worksheetName]);
  2882. }
  2883. }
  2884. /**
  2885. * Rename calculation cache for a specified worksheet.
  2886. *
  2887. * @param string $fromWorksheetName
  2888. * @param string $toWorksheetName
  2889. */
  2890. public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName): void
  2891. {
  2892. if (isset($this->calculationCache[$fromWorksheetName])) {
  2893. $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName];
  2894. unset($this->calculationCache[$fromWorksheetName]);
  2895. }
  2896. }
  2897. /**
  2898. * Enable/disable calculation cache.
  2899. *
  2900. * @param mixed $enabled
  2901. */
  2902. public function setBranchPruningEnabled($enabled): void
  2903. {
  2904. $this->branchPruningEnabled = $enabled;
  2905. }
  2906. public function enableBranchPruning(): void
  2907. {
  2908. $this->setBranchPruningEnabled(true);
  2909. }
  2910. public function disableBranchPruning(): void
  2911. {
  2912. $this->setBranchPruningEnabled(false);
  2913. }
  2914. public function clearBranchStore(): void
  2915. {
  2916. $this->branchStoreKeyCounter = 0;
  2917. }
  2918. /**
  2919. * Get the currently defined locale code.
  2920. *
  2921. * @return string
  2922. */
  2923. public function getLocale()
  2924. {
  2925. return self::$localeLanguage;
  2926. }
  2927. private function getLocaleFile(string $localeDir, string $locale, string $language, string $file): string
  2928. {
  2929. $localeFileName = $localeDir . str_replace('_', DIRECTORY_SEPARATOR, $locale) .
  2930. DIRECTORY_SEPARATOR . $file;
  2931. if (!file_exists($localeFileName)) {
  2932. // If there isn't a locale specific file, look for a language specific file
  2933. $localeFileName = $localeDir . $language . DIRECTORY_SEPARATOR . $file;
  2934. if (!file_exists($localeFileName)) {
  2935. throw new Exception('Locale file not found');
  2936. }
  2937. }
  2938. return $localeFileName;
  2939. }
  2940. /**
  2941. * Set the locale code.
  2942. *
  2943. * @param string $locale The locale to use for formula translation, eg: 'en_us'
  2944. *
  2945. * @return bool
  2946. */
  2947. public function setLocale(string $locale)
  2948. {
  2949. // Identify our locale and language
  2950. $language = $locale = strtolower($locale);
  2951. if (strpos($locale, '_') !== false) {
  2952. [$language] = explode('_', $locale);
  2953. }
  2954. if (count(self::$validLocaleLanguages) == 1) {
  2955. self::loadLocales();
  2956. }
  2957. // Test whether we have any language data for this language (any locale)
  2958. if (in_array($language, self::$validLocaleLanguages)) {
  2959. // initialise language/locale settings
  2960. self::$localeFunctions = [];
  2961. self::$localeArgumentSeparator = ',';
  2962. self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL'];
  2963. // Default is US English, if user isn't requesting US english, then read the necessary data from the locale files
  2964. if ($locale !== 'en_us') {
  2965. $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]);
  2966. // Search for a file with a list of function names for locale
  2967. try {
  2968. $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions');
  2969. } catch (Exception $e) {
  2970. return false;
  2971. }
  2972. // Retrieve the list of locale or language specific function names
  2973. $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  2974. foreach ($localeFunctions as $localeFunction) {
  2975. [$localeFunction] = explode('##', $localeFunction); // Strip out comments
  2976. if (strpos($localeFunction, '=') !== false) {
  2977. [$fName, $lfName] = array_map('trim', explode('=', $localeFunction));
  2978. if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
  2979. self::$localeFunctions[$fName] = $lfName;
  2980. }
  2981. }
  2982. }
  2983. // Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
  2984. if (isset(self::$localeFunctions['TRUE'])) {
  2985. self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE'];
  2986. }
  2987. if (isset(self::$localeFunctions['FALSE'])) {
  2988. self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE'];
  2989. }
  2990. try {
  2991. $configFile = $this->getLocaleFile($localeDir, $locale, $language, 'config');
  2992. } catch (Exception $e) {
  2993. return false;
  2994. }
  2995. $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
  2996. foreach ($localeSettings as $localeSetting) {
  2997. [$localeSetting] = explode('##', $localeSetting); // Strip out comments
  2998. if (strpos($localeSetting, '=') !== false) {
  2999. [$settingName, $settingValue] = array_map('trim', explode('=', $localeSetting));
  3000. $settingName = strtoupper($settingName);
  3001. if ($settingValue !== '') {
  3002. switch ($settingName) {
  3003. case 'ARGUMENTSEPARATOR':
  3004. self::$localeArgumentSeparator = $settingValue;
  3005. break;
  3006. }
  3007. }
  3008. }
  3009. }
  3010. }
  3011. self::$functionReplaceFromExcel = self::$functionReplaceToExcel =
  3012. self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
  3013. self::$localeLanguage = $locale;
  3014. return true;
  3015. }
  3016. return false;
  3017. }
  3018. /**
  3019. * @param string $fromSeparator
  3020. * @param string $toSeparator
  3021. * @param string $formula
  3022. * @param bool $inBraces
  3023. *
  3024. * @return string
  3025. */
  3026. public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
  3027. {
  3028. $strlen = mb_strlen($formula);
  3029. for ($i = 0; $i < $strlen; ++$i) {
  3030. $chr = mb_substr($formula, $i, 1);
  3031. switch ($chr) {
  3032. case self::FORMULA_OPEN_FUNCTION_BRACE:
  3033. $inBraces = true;
  3034. break;
  3035. case self::FORMULA_CLOSE_FUNCTION_BRACE:
  3036. $inBraces = false;
  3037. break;
  3038. case $fromSeparator:
  3039. if (!$inBraces) {
  3040. $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
  3041. }
  3042. }
  3043. }
  3044. return $formula;
  3045. }
  3046. /**
  3047. * @param string[] $from
  3048. * @param string[] $to
  3049. * @param string $formula
  3050. * @param string $fromSeparator
  3051. * @param string $toSeparator
  3052. *
  3053. * @return string
  3054. */
  3055. private static function translateFormula(array $from, array $to, $formula, $fromSeparator, $toSeparator)
  3056. {
  3057. // Convert any Excel function names to the required language
  3058. if (self::$localeLanguage !== 'en_us') {
  3059. $inBraces = false;
  3060. // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
  3061. if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
  3062. // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
  3063. // the formula
  3064. $temp = explode(self::FORMULA_STRING_QUOTE, $formula);
  3065. $i = false;
  3066. foreach ($temp as &$value) {
  3067. // Only count/replace in alternating array entries
  3068. if ($i = !$i) {
  3069. $value = preg_replace($from, $to, $value);
  3070. $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
  3071. }
  3072. }
  3073. unset($value);
  3074. // Then rebuild the formula string
  3075. $formula = implode(self::FORMULA_STRING_QUOTE, $temp);
  3076. } else {
  3077. // If there's no quoted strings, then we do a simple count/replace
  3078. $formula = preg_replace($from, $to, $formula);
  3079. $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
  3080. }
  3081. }
  3082. return $formula;
  3083. }
  3084. private static $functionReplaceFromExcel = null;
  3085. private static $functionReplaceToLocale = null;
  3086. public function _translateFormulaToLocale($formula)
  3087. {
  3088. if (self::$functionReplaceFromExcel === null) {
  3089. self::$functionReplaceFromExcel = [];
  3090. foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
  3091. self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/Ui';
  3092. }
  3093. foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
  3094. self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui';
  3095. }
  3096. }
  3097. if (self::$functionReplaceToLocale === null) {
  3098. self::$functionReplaceToLocale = [];
  3099. foreach (self::$localeFunctions as $localeFunctionName) {
  3100. self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
  3101. }
  3102. foreach (self::$localeBoolean as $localeBoolean) {
  3103. self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
  3104. }
  3105. }
  3106. return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator);
  3107. }
  3108. private static $functionReplaceFromLocale = null;
  3109. private static $functionReplaceToExcel = null;
  3110. public function _translateFormulaToEnglish($formula)
  3111. {
  3112. if (self::$functionReplaceFromLocale === null) {
  3113. self::$functionReplaceFromLocale = [];
  3114. foreach (self::$localeFunctions as $localeFunctionName) {
  3115. self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/Ui';
  3116. }
  3117. foreach (self::$localeBoolean as $excelBoolean) {
  3118. self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui';
  3119. }
  3120. }
  3121. if (self::$functionReplaceToExcel === null) {
  3122. self::$functionReplaceToExcel = [];
  3123. foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
  3124. self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2';
  3125. }
  3126. foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
  3127. self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2';
  3128. }
  3129. }
  3130. return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ',');
  3131. }
  3132. public static function localeFunc($function)
  3133. {
  3134. if (self::$localeLanguage !== 'en_us') {
  3135. $functionName = trim($function, '(');
  3136. if (isset(self::$localeFunctions[$functionName])) {
  3137. $brace = ($functionName != $function);
  3138. $function = self::$localeFunctions[$functionName];
  3139. if ($brace) {
  3140. $function .= '(';
  3141. }
  3142. }
  3143. }
  3144. return $function;
  3145. }
  3146. /**
  3147. * Wrap string values in quotes.
  3148. *
  3149. * @param mixed $value
  3150. *
  3151. * @return mixed
  3152. */
  3153. public static function wrapResult($value)
  3154. {
  3155. if (is_string($value)) {
  3156. // Error values cannot be "wrapped"
  3157. if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) {
  3158. // Return Excel errors "as is"
  3159. return $value;
  3160. }
  3161. // Return strings wrapped in quotes
  3162. return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE;
  3163. } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
  3164. // Convert numeric errors to NaN error
  3165. return Functions::NAN();
  3166. }
  3167. return $value;
  3168. }
  3169. /**
  3170. * Remove quotes used as a wrapper to identify string values.
  3171. *
  3172. * @param mixed $value
  3173. *
  3174. * @return mixed
  3175. */
  3176. public static function unwrapResult($value)
  3177. {
  3178. if (is_string($value)) {
  3179. if ((isset($value[0])) && ($value[0] == self::FORMULA_STRING_QUOTE) && (substr($value, -1) == self::FORMULA_STRING_QUOTE)) {
  3180. return substr($value, 1, -1);
  3181. }
  3182. // Convert numeric errors to NAN error
  3183. } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
  3184. return Functions::NAN();
  3185. }
  3186. return $value;
  3187. }
  3188. /**
  3189. * Calculate cell value (using formula from a cell ID)
  3190. * Retained for backward compatibility.
  3191. *
  3192. * @param Cell $pCell Cell to calculate
  3193. *
  3194. * @return mixed
  3195. */
  3196. public function calculate(?Cell $pCell = null)
  3197. {
  3198. try {
  3199. return $this->calculateCellValue($pCell);
  3200. } catch (\Exception $e) {
  3201. throw new Exception($e->getMessage());
  3202. }
  3203. }
  3204. /**
  3205. * Calculate the value of a cell formula.
  3206. *
  3207. * @param Cell $pCell Cell to calculate
  3208. * @param bool $resetLog Flag indicating whether the debug log should be reset or not
  3209. *
  3210. * @return mixed
  3211. */
  3212. public function calculateCellValue(?Cell $pCell = null, $resetLog = true)
  3213. {
  3214. if ($pCell === null) {
  3215. return null;
  3216. }
  3217. $returnArrayAsType = self::$returnArrayAsType;
  3218. if ($resetLog) {
  3219. // Initialise the logging settings if requested
  3220. $this->formulaError = null;
  3221. $this->debugLog->clearLog();
  3222. $this->cyclicReferenceStack->clear();
  3223. $this->cyclicFormulaCounter = 1;
  3224. self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
  3225. }
  3226. // Execute the calculation for the cell formula
  3227. $this->cellStack[] = [
  3228. 'sheet' => $pCell->getWorksheet()->getTitle(),
  3229. 'cell' => $pCell->getCoordinate(),
  3230. ];
  3231. try {
  3232. $result = self::unwrapResult($this->_calculateFormulaValue($pCell->getValue(), $pCell->getCoordinate(), $pCell));
  3233. $cellAddress = array_pop($this->cellStack);
  3234. $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
  3235. } catch (\Exception $e) {
  3236. $cellAddress = array_pop($this->cellStack);
  3237. $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
  3238. throw new Exception($e->getMessage());
  3239. }
  3240. if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
  3241. self::$returnArrayAsType = $returnArrayAsType;
  3242. $testResult = Functions::flattenArray($result);
  3243. if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
  3244. return Functions::VALUE();
  3245. }
  3246. // If there's only a single cell in the array, then we allow it
  3247. if (count($testResult) != 1) {
  3248. // If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it
  3249. $r = array_keys($result);
  3250. $r = array_shift($r);
  3251. if (!is_numeric($r)) {
  3252. return Functions::VALUE();
  3253. }
  3254. if (is_array($result[$r])) {
  3255. $c = array_keys($result[$r]);
  3256. $c = array_shift($c);
  3257. if (!is_numeric($c)) {
  3258. return Functions::VALUE();
  3259. }
  3260. }
  3261. }
  3262. $result = array_shift($testResult);
  3263. }
  3264. self::$returnArrayAsType = $returnArrayAsType;
  3265. if ($result === null && $pCell->getWorksheet()->getSheetView()->getShowZeros()) {
  3266. return 0;
  3267. } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
  3268. return Functions::NAN();
  3269. }
  3270. return $result;
  3271. }
  3272. /**
  3273. * Validate and parse a formula string.
  3274. *
  3275. * @param string $formula Formula to parse
  3276. *
  3277. * @return array|bool
  3278. */
  3279. public function parseFormula($formula)
  3280. {
  3281. // Basic validation that this is indeed a formula
  3282. // We return an empty array if not
  3283. $formula = trim($formula);
  3284. if ((!isset($formula[0])) || ($formula[0] != '=')) {
  3285. return [];
  3286. }
  3287. $formula = ltrim(substr($formula, 1));
  3288. if (!isset($formula[0])) {
  3289. return [];
  3290. }
  3291. // Parse the formula and return the token stack
  3292. return $this->internalParseFormula($formula);
  3293. }
  3294. /**
  3295. * Calculate the value of a formula.
  3296. *
  3297. * @param string $formula Formula to parse
  3298. * @param string $cellID Address of the cell to calculate
  3299. * @param Cell $pCell Cell to calculate
  3300. *
  3301. * @return mixed
  3302. */
  3303. public function calculateFormula($formula, $cellID = null, ?Cell $pCell = null)
  3304. {
  3305. // Initialise the logging settings
  3306. $this->formulaError = null;
  3307. $this->debugLog->clearLog();
  3308. $this->cyclicReferenceStack->clear();
  3309. $resetCache = $this->getCalculationCacheEnabled();
  3310. if ($this->spreadsheet !== null && $cellID === null && $pCell === null) {
  3311. $cellID = 'A1';
  3312. $pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
  3313. } else {
  3314. // Disable calculation cacheing because it only applies to cell calculations, not straight formulae
  3315. // But don't actually flush any cache
  3316. $this->calculationCacheEnabled = false;
  3317. }
  3318. // Execute the calculation
  3319. try {
  3320. $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $pCell));
  3321. } catch (\Exception $e) {
  3322. throw new Exception($e->getMessage());
  3323. }
  3324. if ($this->spreadsheet === null) {
  3325. // Reset calculation cacheing to its previous state
  3326. $this->calculationCacheEnabled = $resetCache;
  3327. }
  3328. return $result;
  3329. }
  3330. /**
  3331. * @param mixed $cellValue
  3332. */
  3333. public function getValueFromCache(string $cellReference, &$cellValue): bool
  3334. {
  3335. $this->debugLog->writeDebugLog("Testing cache value for cell {$cellReference}");
  3336. // Is calculation cacheing enabled?
  3337. // If so, is the required value present in calculation cache?
  3338. if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
  3339. $this->debugLog->writeDebugLog("Retrieving value for cell {$cellReference} from cache");
  3340. // Return the cached result
  3341. $cellValue = $this->calculationCache[$cellReference];
  3342. return true;
  3343. }
  3344. return false;
  3345. }
  3346. /**
  3347. * @param string $cellReference
  3348. * @param mixed $cellValue
  3349. */
  3350. public function saveValueToCache($cellReference, $cellValue): void
  3351. {
  3352. if ($this->calculationCacheEnabled) {
  3353. $this->calculationCache[$cellReference] = $cellValue;
  3354. }
  3355. }
  3356. /**
  3357. * Parse a cell formula and calculate its value.
  3358. *
  3359. * @param string $formula The formula to parse and calculate
  3360. * @param string $cellID The ID (e.g. A3) of the cell that we are calculating
  3361. * @param Cell $pCell Cell to calculate
  3362. *
  3363. * @return mixed
  3364. */
  3365. public function _calculateFormulaValue($formula, $cellID = null, ?Cell $pCell = null)
  3366. {
  3367. $cellValue = null;
  3368. // Quote-Prefixed cell values cannot be formulae, but are treated as strings
  3369. if ($pCell !== null && $pCell->getStyle()->getQuotePrefix() === true) {
  3370. return self::wrapResult((string) $formula);
  3371. }
  3372. if (preg_match('/^=\s*cmd\s*\|/miu', $formula) !== 0) {
  3373. return self::wrapResult($formula);
  3374. }
  3375. // Basic validation that this is indeed a formula
  3376. // We simply return the cell value if not
  3377. $formula = trim($formula);
  3378. if ($formula[0] != '=') {
  3379. return self::wrapResult($formula);
  3380. }
  3381. $formula = ltrim(substr($formula, 1));
  3382. if (!isset($formula[0])) {
  3383. return self::wrapResult($formula);
  3384. }
  3385. $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
  3386. $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk";
  3387. $wsCellReference = $wsTitle . '!' . $cellID;
  3388. if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) {
  3389. return $cellValue;
  3390. }
  3391. $this->debugLog->writeDebugLog("Evaluating formula for cell {$wsCellReference}");
  3392. if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) {
  3393. if ($this->cyclicFormulaCount <= 0) {
  3394. $this->cyclicFormulaCell = '';
  3395. return $this->raiseFormulaError('Cyclic Reference in Formula');
  3396. } elseif ($this->cyclicFormulaCell === $wsCellReference) {
  3397. ++$this->cyclicFormulaCounter;
  3398. if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
  3399. $this->cyclicFormulaCell = '';
  3400. return $cellValue;
  3401. }
  3402. } elseif ($this->cyclicFormulaCell == '') {
  3403. if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
  3404. return $cellValue;
  3405. }
  3406. $this->cyclicFormulaCell = $wsCellReference;
  3407. }
  3408. }
  3409. $this->debugLog->writeDebugLog("Formula for cell {$wsCellReference} is {$formula}");
  3410. // Parse the formula onto the token stack and calculate the value
  3411. $this->cyclicReferenceStack->push($wsCellReference);
  3412. $cellValue = $this->processTokenStack($this->internalParseFormula($formula, $pCell), $cellID, $pCell);
  3413. $this->cyclicReferenceStack->pop();
  3414. // Save to calculation cache
  3415. if ($cellID !== null) {
  3416. $this->saveValueToCache($wsCellReference, $cellValue);
  3417. }
  3418. // Return the calculated value
  3419. return $cellValue;
  3420. }
  3421. /**
  3422. * Ensure that paired matrix operands are both matrices and of the same size.
  3423. *
  3424. * @param mixed $operand1 First matrix operand
  3425. * @param mixed $operand2 Second matrix operand
  3426. * @param int $resize Flag indicating whether the matrices should be resized to match
  3427. * and (if so), whether the smaller dimension should grow or the
  3428. * larger should shrink.
  3429. * 0 = no resize
  3430. * 1 = shrink to fit
  3431. * 2 = extend to fit
  3432. *
  3433. * @return array
  3434. */
  3435. private static function checkMatrixOperands(&$operand1, &$operand2, $resize = 1)
  3436. {
  3437. // Examine each of the two operands, and turn them into an array if they aren't one already
  3438. // Note that this function should only be called if one or both of the operand is already an array
  3439. if (!is_array($operand1)) {
  3440. [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand2);
  3441. $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
  3442. $resize = 0;
  3443. } elseif (!is_array($operand2)) {
  3444. [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand1);
  3445. $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
  3446. $resize = 0;
  3447. }
  3448. [$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1);
  3449. [$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2);
  3450. if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
  3451. $resize = 1;
  3452. }
  3453. if ($resize == 2) {
  3454. // Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger
  3455. self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
  3456. } elseif ($resize == 1) {
  3457. // Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller
  3458. self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
  3459. }
  3460. return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns];
  3461. }
  3462. /**
  3463. * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0.
  3464. *
  3465. * @param array $matrix matrix operand
  3466. *
  3467. * @return int[] An array comprising the number of rows, and number of columns
  3468. */
  3469. public static function getMatrixDimensions(array &$matrix)
  3470. {
  3471. $matrixRows = count($matrix);
  3472. $matrixColumns = 0;
  3473. foreach ($matrix as $rowKey => $rowValue) {
  3474. if (!is_array($rowValue)) {
  3475. $matrix[$rowKey] = [$rowValue];
  3476. $matrixColumns = max(1, $matrixColumns);
  3477. } else {
  3478. $matrix[$rowKey] = array_values($rowValue);
  3479. $matrixColumns = max(count($rowValue), $matrixColumns);
  3480. }
  3481. }
  3482. $matrix = array_values($matrix);
  3483. return [$matrixRows, $matrixColumns];
  3484. }
  3485. /**
  3486. * Ensure that paired matrix operands are both matrices of the same size.
  3487. *
  3488. * @param mixed $matrix1 First matrix operand
  3489. * @param mixed $matrix2 Second matrix operand
  3490. * @param int $matrix1Rows Row size of first matrix operand
  3491. * @param int $matrix1Columns Column size of first matrix operand
  3492. * @param int $matrix2Rows Row size of second matrix operand
  3493. * @param int $matrix2Columns Column size of second matrix operand
  3494. */
  3495. private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns): void
  3496. {
  3497. if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
  3498. if ($matrix2Rows < $matrix1Rows) {
  3499. for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
  3500. unset($matrix1[$i]);
  3501. }
  3502. }
  3503. if ($matrix2Columns < $matrix1Columns) {
  3504. for ($i = 0; $i < $matrix1Rows; ++$i) {
  3505. for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
  3506. unset($matrix1[$i][$j]);
  3507. }
  3508. }
  3509. }
  3510. }
  3511. if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
  3512. if ($matrix1Rows < $matrix2Rows) {
  3513. for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
  3514. unset($matrix2[$i]);
  3515. }
  3516. }
  3517. if ($matrix1Columns < $matrix2Columns) {
  3518. for ($i = 0; $i < $matrix2Rows; ++$i) {
  3519. for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
  3520. unset($matrix2[$i][$j]);
  3521. }
  3522. }
  3523. }
  3524. }
  3525. }
  3526. /**
  3527. * Ensure that paired matrix operands are both matrices of the same size.
  3528. *
  3529. * @param mixed $matrix1 First matrix operand
  3530. * @param mixed $matrix2 Second matrix operand
  3531. * @param int $matrix1Rows Row size of first matrix operand
  3532. * @param int $matrix1Columns Column size of first matrix operand
  3533. * @param int $matrix2Rows Row size of second matrix operand
  3534. * @param int $matrix2Columns Column size of second matrix operand
  3535. */
  3536. private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns): void
  3537. {
  3538. if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
  3539. if ($matrix2Columns < $matrix1Columns) {
  3540. for ($i = 0; $i < $matrix2Rows; ++$i) {
  3541. $x = $matrix2[$i][$matrix2Columns - 1];
  3542. for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
  3543. $matrix2[$i][$j] = $x;
  3544. }
  3545. }
  3546. }
  3547. if ($matrix2Rows < $matrix1Rows) {
  3548. $x = $matrix2[$matrix2Rows - 1];
  3549. for ($i = 0; $i < $matrix1Rows; ++$i) {
  3550. $matrix2[$i] = $x;
  3551. }
  3552. }
  3553. }
  3554. if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
  3555. if ($matrix1Columns < $matrix2Columns) {
  3556. for ($i = 0; $i < $matrix1Rows; ++$i) {
  3557. $x = $matrix1[$i][$matrix1Columns - 1];
  3558. for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
  3559. $matrix1[$i][$j] = $x;
  3560. }
  3561. }
  3562. }
  3563. if ($matrix1Rows < $matrix2Rows) {
  3564. $x = $matrix1[$matrix1Rows - 1];
  3565. for ($i = 0; $i < $matrix2Rows; ++$i) {
  3566. $matrix1[$i] = $x;
  3567. }
  3568. }
  3569. }
  3570. }
  3571. /**
  3572. * Format details of an operand for display in the log (based on operand type).
  3573. *
  3574. * @param mixed $value First matrix operand
  3575. *
  3576. * @return mixed
  3577. */
  3578. private function showValue($value)
  3579. {
  3580. if ($this->debugLog->getWriteDebugLog()) {
  3581. $testArray = Functions::flattenArray($value);
  3582. if (count($testArray) == 1) {
  3583. $value = array_pop($testArray);
  3584. }
  3585. if (is_array($value)) {
  3586. $returnMatrix = [];
  3587. $pad = $rpad = ', ';
  3588. foreach ($value as $row) {
  3589. if (is_array($row)) {
  3590. $returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row));
  3591. $rpad = '; ';
  3592. } else {
  3593. $returnMatrix[] = $this->showValue($row);
  3594. }
  3595. }
  3596. return '{ ' . implode($rpad, $returnMatrix) . ' }';
  3597. } elseif (is_string($value) && (trim($value, self::FORMULA_STRING_QUOTE) == $value)) {
  3598. return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE;
  3599. } elseif (is_bool($value)) {
  3600. return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
  3601. }
  3602. }
  3603. return Functions::flattenSingleValue($value);
  3604. }
  3605. /**
  3606. * Format type and details of an operand for display in the log (based on operand type).
  3607. *
  3608. * @param mixed $value First matrix operand
  3609. *
  3610. * @return null|string
  3611. */
  3612. private function showTypeDetails($value)
  3613. {
  3614. if ($this->debugLog->getWriteDebugLog()) {
  3615. $testArray = Functions::flattenArray($value);
  3616. if (count($testArray) == 1) {
  3617. $value = array_pop($testArray);
  3618. }
  3619. if ($value === null) {
  3620. return 'a NULL value';
  3621. } elseif (is_float($value)) {
  3622. $typeString = 'a floating point number';
  3623. } elseif (is_int($value)) {
  3624. $typeString = 'an integer number';
  3625. } elseif (is_bool($value)) {
  3626. $typeString = 'a boolean';
  3627. } elseif (is_array($value)) {
  3628. $typeString = 'a matrix';
  3629. } else {
  3630. if ($value == '') {
  3631. return 'an empty string';
  3632. } elseif ($value[0] == '#') {
  3633. return 'a ' . $value . ' error';
  3634. }
  3635. $typeString = 'a string';
  3636. }
  3637. return $typeString . ' with a value of ' . $this->showValue($value);
  3638. }
  3639. return null;
  3640. }
  3641. /**
  3642. * @param string $formula
  3643. *
  3644. * @return false|string False indicates an error
  3645. */
  3646. private function convertMatrixReferences($formula)
  3647. {
  3648. static $matrixReplaceFrom = [self::FORMULA_OPEN_FUNCTION_BRACE, ';', self::FORMULA_CLOSE_FUNCTION_BRACE];
  3649. static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
  3650. // Convert any Excel matrix references to the MKMATRIX() function
  3651. if (strpos($formula, self::FORMULA_OPEN_FUNCTION_BRACE) !== false) {
  3652. // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
  3653. if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
  3654. // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
  3655. // the formula
  3656. $temp = explode(self::FORMULA_STRING_QUOTE, $formula);
  3657. // Open and Closed counts used for trapping mismatched braces in the formula
  3658. $openCount = $closeCount = 0;
  3659. $i = false;
  3660. foreach ($temp as &$value) {
  3661. // Only count/replace in alternating array entries
  3662. if ($i = !$i) {
  3663. $openCount += substr_count($value, self::FORMULA_OPEN_FUNCTION_BRACE);
  3664. $closeCount += substr_count($value, self::FORMULA_CLOSE_FUNCTION_BRACE);
  3665. $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
  3666. }
  3667. }
  3668. unset($value);
  3669. // Then rebuild the formula string
  3670. $formula = implode(self::FORMULA_STRING_QUOTE, $temp);
  3671. } else {
  3672. // If there's no quoted strings, then we do a simple count/replace
  3673. $openCount = substr_count($formula, self::FORMULA_OPEN_FUNCTION_BRACE);
  3674. $closeCount = substr_count($formula, self::FORMULA_CLOSE_FUNCTION_BRACE);
  3675. $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
  3676. }
  3677. // Trap for mismatched braces and trigger an appropriate error
  3678. if ($openCount < $closeCount) {
  3679. if ($openCount > 0) {
  3680. return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'");
  3681. }
  3682. return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered");
  3683. } elseif ($openCount > $closeCount) {
  3684. if ($closeCount > 0) {
  3685. return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'");
  3686. }
  3687. return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered");
  3688. }
  3689. }
  3690. return $formula;
  3691. }
  3692. // Binary Operators
  3693. // These operators always work on two values
  3694. // Array key is the operator, the value indicates whether this is a left or right associative operator
  3695. private static $operatorAssociativity = [
  3696. '^' => 0, // Exponentiation
  3697. '*' => 0, '/' => 0, // Multiplication and Division
  3698. '+' => 0, '-' => 0, // Addition and Subtraction
  3699. '&' => 0, // Concatenation
  3700. '|' => 0, ':' => 0, // Intersect and Range
  3701. '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison
  3702. ];
  3703. // Comparison (Boolean) Operators
  3704. // These operators work on two values, but always return a boolean result
  3705. private static $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true];
  3706. // Operator Precedence
  3707. // This list includes all valid operators, whether binary (including boolean) or unary (such as %)
  3708. // Array key is the operator, the value is its precedence
  3709. private static $operatorPrecedence = [
  3710. ':' => 8, // Range
  3711. '|' => 7, // Intersect
  3712. '~' => 6, // Negation
  3713. '%' => 5, // Percentage
  3714. '^' => 4, // Exponentiation
  3715. '*' => 3, '/' => 3, // Multiplication and Division
  3716. '+' => 2, '-' => 2, // Addition and Subtraction
  3717. '&' => 1, // Concatenation
  3718. '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, // Comparison
  3719. ];
  3720. // Convert infix to postfix notation
  3721. /**
  3722. * @param string $formula
  3723. *
  3724. * @return array<int, mixed>|false
  3725. */
  3726. private function internalParseFormula($formula, ?Cell $pCell = null)
  3727. {
  3728. if (($formula = $this->convertMatrixReferences(trim($formula))) === false) {
  3729. return false;
  3730. }
  3731. // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
  3732. // so we store the parent worksheet so that we can re-attach it when necessary
  3733. $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
  3734. $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION .
  3735. '|' . self::CALCULATION_REGEXP_CELLREF .
  3736. '|' . self::CALCULATION_REGEXP_COLUMN_RANGE .
  3737. '|' . self::CALCULATION_REGEXP_ROW_RANGE .
  3738. '|' . self::CALCULATION_REGEXP_NUMBER .
  3739. '|' . self::CALCULATION_REGEXP_STRING .
  3740. '|' . self::CALCULATION_REGEXP_OPENBRACE .
  3741. '|' . self::CALCULATION_REGEXP_DEFINEDNAME .
  3742. '|' . self::CALCULATION_REGEXP_ERROR .
  3743. ')/sui';
  3744. // Start with initialisation
  3745. $index = 0;
  3746. $stack = new Stack();
  3747. $output = [];
  3748. $expectingOperator = false; // We use this test in syntax-checking the expression to determine when a
  3749. // - is a negation or + is a positive operator rather than an operation
  3750. $expectingOperand = false; // We use this test in syntax-checking the expression to determine whether an operand
  3751. // should be null in a function call
  3752. // IF branch pruning
  3753. // currently pending storeKey (last item of the storeKeysStack
  3754. $pendingStoreKey = null;
  3755. // stores a list of storeKeys (string[])
  3756. $pendingStoreKeysStack = [];
  3757. $expectingConditionMap = []; // ['storeKey' => true, ...]
  3758. $expectingThenMap = []; // ['storeKey' => true, ...]
  3759. $expectingElseMap = []; // ['storeKey' => true, ...]
  3760. $parenthesisDepthMap = []; // ['storeKey' => 4, ...]
  3761. // The guts of the lexical parser
  3762. // Loop through the formula extracting each operator and operand in turn
  3763. while (true) {
  3764. // Branch pruning: we adapt the output item to the context (it will
  3765. // be used to limit its computation)
  3766. $currentCondition = null;
  3767. $currentOnlyIf = null;
  3768. $currentOnlyIfNot = null;
  3769. $previousStoreKey = null;
  3770. $pendingStoreKey = end($pendingStoreKeysStack);
  3771. if ($this->branchPruningEnabled) {
  3772. // this is a condition ?
  3773. if (isset($expectingConditionMap[$pendingStoreKey]) && $expectingConditionMap[$pendingStoreKey]) {
  3774. $currentCondition = $pendingStoreKey;
  3775. $stackDepth = count($pendingStoreKeysStack);
  3776. if ($stackDepth > 1) { // nested if
  3777. $previousStoreKey = $pendingStoreKeysStack[$stackDepth - 2];
  3778. }
  3779. }
  3780. if (isset($expectingThenMap[$pendingStoreKey]) && $expectingThenMap[$pendingStoreKey]) {
  3781. $currentOnlyIf = $pendingStoreKey;
  3782. } elseif (isset($previousStoreKey)) {
  3783. if (isset($expectingThenMap[$previousStoreKey]) && $expectingThenMap[$previousStoreKey]) {
  3784. $currentOnlyIf = $previousStoreKey;
  3785. }
  3786. }
  3787. if (isset($expectingElseMap[$pendingStoreKey]) && $expectingElseMap[$pendingStoreKey]) {
  3788. $currentOnlyIfNot = $pendingStoreKey;
  3789. } elseif (isset($previousStoreKey)) {
  3790. if (isset($expectingElseMap[$previousStoreKey]) && $expectingElseMap[$previousStoreKey]) {
  3791. $currentOnlyIfNot = $previousStoreKey;
  3792. }
  3793. }
  3794. }
  3795. $opCharacter = $formula[$index]; // Get the first character of the value at the current index position
  3796. if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) {
  3797. $opCharacter .= $formula[++$index];
  3798. }
  3799. // Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand
  3800. $isOperandOrFunction = (bool) preg_match($regexpMatchString, substr($formula, $index), $match);
  3801. if ($opCharacter == '-' && !$expectingOperator) { // Is it a negation instead of a minus?
  3802. // Put a negation on the stack
  3803. $stack->push('Unary Operator', '~', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
  3804. ++$index; // and drop the negation symbol
  3805. } elseif ($opCharacter == '%' && $expectingOperator) {
  3806. // Put a percentage on the stack
  3807. $stack->push('Unary Operator', '%', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
  3808. ++$index;
  3809. } elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
  3810. ++$index; // Drop the redundant plus symbol
  3811. } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde or pipe, because they are legal
  3812. return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
  3813. } elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
  3814. while (
  3815. $stack->count() > 0 &&
  3816. ($o2 = $stack->last()) &&
  3817. isset(self::$operators[$o2['value']]) &&
  3818. @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
  3819. ) {
  3820. $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
  3821. }
  3822. // Finally put our current operator onto the stack
  3823. $stack->push('Binary Operator', $opCharacter, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
  3824. ++$index;
  3825. $expectingOperator = false;
  3826. } elseif ($opCharacter == ')' && $expectingOperator) { // Are we expecting to close a parenthesis?
  3827. $expectingOperand = false;
  3828. while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last (
  3829. if ($o2 === null) {
  3830. return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"');
  3831. }
  3832. $output[] = $o2;
  3833. }
  3834. $d = $stack->last(2);
  3835. // Branch pruning we decrease the depth whether is it a function
  3836. // call or a parenthesis
  3837. if (!empty($pendingStoreKey)) {
  3838. --$parenthesisDepthMap[$pendingStoreKey];
  3839. }
  3840. if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) { // Did this parenthesis just close a function?
  3841. if (!empty($pendingStoreKey) && $parenthesisDepthMap[$pendingStoreKey] == -1) {
  3842. // we are closing an IF(
  3843. if ($d['value'] != 'IF(') {
  3844. return $this->raiseFormulaError('Parser bug we should be in an "IF("');
  3845. }
  3846. if ($expectingConditionMap[$pendingStoreKey]) {
  3847. return $this->raiseFormulaError('We should not be expecting a condition');
  3848. }
  3849. $expectingThenMap[$pendingStoreKey] = false;
  3850. $expectingElseMap[$pendingStoreKey] = false;
  3851. --$parenthesisDepthMap[$pendingStoreKey];
  3852. array_pop($pendingStoreKeysStack);
  3853. unset($pendingStoreKey);
  3854. }
  3855. $functionName = $matches[1]; // Get the function name
  3856. $d = $stack->pop();
  3857. $argumentCount = $d['value']; // See how many arguments there were (argument count is the next value stored on the stack)
  3858. $output[] = $d; // Dump the argument count on the output
  3859. $output[] = $stack->pop(); // Pop the function and push onto the output
  3860. if (isset(self::$controlFunctions[$functionName])) {
  3861. $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
  3862. $functionCall = self::$controlFunctions[$functionName]['functionCall'];
  3863. } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
  3864. $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
  3865. $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
  3866. } else { // did we somehow push a non-function on the stack? this should never happen
  3867. return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
  3868. }
  3869. // Check the argument count
  3870. $argumentCountError = false;
  3871. $expectedArgumentCountString = null;
  3872. if (is_numeric($expectedArgumentCount)) {
  3873. if ($expectedArgumentCount < 0) {
  3874. if ($argumentCount > abs($expectedArgumentCount)) {
  3875. $argumentCountError = true;
  3876. $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount);
  3877. }
  3878. } else {
  3879. if ($argumentCount != $expectedArgumentCount) {
  3880. $argumentCountError = true;
  3881. $expectedArgumentCountString = $expectedArgumentCount;
  3882. }
  3883. }
  3884. } elseif ($expectedArgumentCount != '*') {
  3885. $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
  3886. switch ($argMatch[2]) {
  3887. case '+':
  3888. if ($argumentCount < $argMatch[1]) {
  3889. $argumentCountError = true;
  3890. $expectedArgumentCountString = $argMatch[1] . ' or more ';
  3891. }
  3892. break;
  3893. case '-':
  3894. if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
  3895. $argumentCountError = true;
  3896. $expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3];
  3897. }
  3898. break;
  3899. case ',':
  3900. if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
  3901. $argumentCountError = true;
  3902. $expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3];
  3903. }
  3904. break;
  3905. }
  3906. }
  3907. if ($argumentCountError) {
  3908. return $this->raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString . ' expected');
  3909. }
  3910. }
  3911. ++$index;
  3912. } elseif ($opCharacter == ',') { // Is this the separator for function arguments?
  3913. if (
  3914. !empty($pendingStoreKey) &&
  3915. $parenthesisDepthMap[$pendingStoreKey] == 0
  3916. ) {
  3917. // We must go to the IF next argument
  3918. if ($expectingConditionMap[$pendingStoreKey]) {
  3919. $expectingConditionMap[$pendingStoreKey] = false;
  3920. $expectingThenMap[$pendingStoreKey] = true;
  3921. } elseif ($expectingThenMap[$pendingStoreKey]) {
  3922. $expectingThenMap[$pendingStoreKey] = false;
  3923. $expectingElseMap[$pendingStoreKey] = true;
  3924. } elseif ($expectingElseMap[$pendingStoreKey]) {
  3925. return $this->raiseFormulaError('Reaching fourth argument of an IF');
  3926. }
  3927. }
  3928. while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last (
  3929. if ($o2 === null) {
  3930. return $this->raiseFormulaError('Formula Error: Unexpected ,');
  3931. }
  3932. $output[] = $o2; // pop the argument expression stuff and push onto the output
  3933. }
  3934. // If we've a comma when we're expecting an operand, then what we actually have is a null operand;
  3935. // so push a null onto the stack
  3936. if (($expectingOperand) || (!$expectingOperator)) {
  3937. $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
  3938. }
  3939. // make sure there was a function
  3940. $d = $stack->last(2);
  3941. if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) {
  3942. return $this->raiseFormulaError('Formula Error: Unexpected ,');
  3943. }
  3944. $d = $stack->pop();
  3945. $itemStoreKey = $d['storeKey'] ?? null;
  3946. $itemOnlyIf = $d['onlyIf'] ?? null;
  3947. $itemOnlyIfNot = $d['onlyIfNot'] ?? null;
  3948. $stack->push($d['type'], ++$d['value'], $d['reference'], $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // increment the argument count
  3949. $stack->push('Brace', '(', null, $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // put the ( back on, we'll need to pop back to it again
  3950. $expectingOperator = false;
  3951. $expectingOperand = true;
  3952. ++$index;
  3953. } elseif ($opCharacter == '(' && !$expectingOperator) {
  3954. if (!empty($pendingStoreKey)) { // Branch pruning: we go deeper
  3955. ++$parenthesisDepthMap[$pendingStoreKey];
  3956. }
  3957. $stack->push('Brace', '(', null, $currentCondition, $currentOnlyIf, $currentOnlyIf);
  3958. ++$index;
  3959. } elseif ($isOperandOrFunction && !$expectingOperator) { // do we now have a function/variable/number?
  3960. $expectingOperator = true;
  3961. $expectingOperand = false;
  3962. $val = $match[1];
  3963. $length = strlen($val);
  3964. if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) {
  3965. $val = preg_replace('/\s/u', '', $val);
  3966. if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function
  3967. $valToUpper = strtoupper($val);
  3968. } else {
  3969. $valToUpper = 'NAME.ERROR(';
  3970. }
  3971. // here $matches[1] will contain values like "IF"
  3972. // and $val "IF("
  3973. if ($this->branchPruningEnabled && ($valToUpper == 'IF(')) { // we handle a new if
  3974. $pendingStoreKey = $this->getUnusedBranchStoreKey();
  3975. $pendingStoreKeysStack[] = $pendingStoreKey;
  3976. $expectingConditionMap[$pendingStoreKey] = true;
  3977. $parenthesisDepthMap[$pendingStoreKey] = 0;
  3978. } else { // this is not an if but we go deeper
  3979. if (!empty($pendingStoreKey) && array_key_exists($pendingStoreKey, $parenthesisDepthMap)) {
  3980. ++$parenthesisDepthMap[$pendingStoreKey];
  3981. }
  3982. }
  3983. $stack->push('Function', $valToUpper, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
  3984. // tests if the function is closed right after opening
  3985. $ax = preg_match('/^\s*\)/u', substr($formula, $index + $length));
  3986. if ($ax) {
  3987. $stack->push('Operand Count for Function ' . $valToUpper . ')', 0, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
  3988. $expectingOperator = true;
  3989. } else {
  3990. $stack->push('Operand Count for Function ' . $valToUpper . ')', 1, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
  3991. $expectingOperator = false;
  3992. }
  3993. $stack->push('Brace', '(');
  3994. } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) {
  3995. // Watch for this case-change when modifying to allow cell references in different worksheets...
  3996. // Should only be applied to the actual cell column, not the worksheet name
  3997. // If the last entry on the stack was a : operator, then we have a cell range reference
  3998. $testPrevOp = $stack->last(1);
  3999. if ($testPrevOp !== null && $testPrevOp['value'] === ':') {
  4000. // If we have a worksheet reference, then we're playing with a 3D reference
  4001. if ($matches[2] == '') {
  4002. // Otherwise, we 'inherit' the worksheet reference from the start cell reference
  4003. // The start of the cell range reference should be the last entry in $output
  4004. $rangeStartCellRef = $output[count($output) - 1]['value'];
  4005. preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $rangeStartCellRef, $rangeStartMatches);
  4006. if ($rangeStartMatches[2] > '') {
  4007. $val = $rangeStartMatches[2] . '!' . $val;
  4008. }
  4009. } else {
  4010. $rangeStartCellRef = $output[count($output) - 1]['value'];
  4011. preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $rangeStartCellRef, $rangeStartMatches);
  4012. if ($rangeStartMatches[2] !== $matches[2]) {
  4013. return $this->raiseFormulaError('3D Range references are not yet supported');
  4014. }
  4015. }
  4016. } elseif (strpos($val, '!') === false && $pCellParent !== null) {
  4017. $worksheet = $pCellParent->getTitle();
  4018. $val = "'{$worksheet}'!{$val}";
  4019. }
  4020. $outputItem = $stack->getStackItem('Cell Reference', $val, $val, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
  4021. $output[] = $outputItem;
  4022. } else { // it's a variable, constant, string, number or boolean
  4023. $localeConstant = false;
  4024. $stackItemType = 'Value';
  4025. $stackItemReference = null;
  4026. // If the last entry on the stack was a : operator, then we may have a row or column range reference
  4027. $testPrevOp = $stack->last(1);
  4028. if ($testPrevOp !== null && $testPrevOp['value'] === ':') {
  4029. $stackItemType = 'Cell Reference';
  4030. $startRowColRef = $output[count($output) - 1]['value'];
  4031. [$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true);
  4032. $rangeSheetRef = $rangeWS1;
  4033. if ($rangeWS1 !== '') {
  4034. $rangeWS1 .= '!';
  4035. }
  4036. $rangeSheetRef = trim($rangeSheetRef, "'");
  4037. [$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true);
  4038. if ($rangeWS2 !== '') {
  4039. $rangeWS2 .= '!';
  4040. } else {
  4041. $rangeWS2 = $rangeWS1;
  4042. }
  4043. $refSheet = $pCellParent;
  4044. if ($pCellParent !== null && $rangeSheetRef !== '' && $rangeSheetRef !== $pCellParent->getTitle()) {
  4045. $refSheet = $pCellParent->getParent()->getSheetByName($rangeSheetRef);
  4046. }
  4047. if (ctype_digit($val) && $val <= 1048576) {
  4048. // Row range
  4049. $stackItemType = 'Row Reference';
  4050. $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataColumn($val) : 'XFD'; // Max 16,384 columns for Excel2007
  4051. $val = "{$rangeWS2}{$endRowColRef}{$val}";
  4052. } elseif (ctype_alpha($val) && strlen($val) <= 3) {
  4053. // Column range
  4054. $stackItemType = 'Column Reference';
  4055. $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataRow($val) : 1048576; // Max 1,048,576 rows for Excel2007
  4056. $val = "{$rangeWS2}{$val}{$endRowColRef}";
  4057. }
  4058. $stackItemReference = $val;
  4059. } elseif ($opCharacter == self::FORMULA_STRING_QUOTE) {
  4060. // UnEscape any quotes within the string
  4061. $val = self::wrapResult(str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($val)));
  4062. } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) {
  4063. $stackItemType = 'Constant';
  4064. $excelConstant = trim(strtoupper($val));
  4065. $val = self::$excelConstants[$excelConstant];
  4066. } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
  4067. $stackItemType = 'Constant';
  4068. $val = self::$excelConstants[$localeConstant];
  4069. } elseif (
  4070. preg_match('/^' . self::CALCULATION_REGEXP_ROW_RANGE . '/miu', substr($formula, $index), $rowRangeReference)
  4071. ) {
  4072. $val = $rowRangeReference[1];
  4073. $length = strlen($rowRangeReference[1]);
  4074. $stackItemType = 'Row Reference';
  4075. $column = 'A';
  4076. if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) {
  4077. $column = $pCellParent->getHighestDataColumn($val);
  4078. }
  4079. $val = "{$rowRangeReference[2]}{$column}{$rowRangeReference[7]}";
  4080. $stackItemReference = $val;
  4081. } elseif (
  4082. preg_match('/^' . self::CALCULATION_REGEXP_COLUMN_RANGE . '/miu', substr($formula, $index), $columnRangeReference)
  4083. ) {
  4084. $val = $columnRangeReference[1];
  4085. $length = strlen($val);
  4086. $stackItemType = 'Column Reference';
  4087. $row = '1';
  4088. if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) {
  4089. $row = $pCellParent->getHighestDataRow($val);
  4090. }
  4091. $val = "{$val}{$row}";
  4092. $stackItemReference = $val;
  4093. } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', $val, $match)) {
  4094. $stackItemType = 'Defined Name';
  4095. $stackItemReference = $val;
  4096. } elseif (is_numeric($val)) {
  4097. if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
  4098. $val = (float) $val;
  4099. } else {
  4100. $val = (int) $val;
  4101. }
  4102. }
  4103. $details = $stack->getStackItem($stackItemType, $val, $stackItemReference, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
  4104. if ($localeConstant) {
  4105. $details['localeValue'] = $localeConstant;
  4106. }
  4107. $output[] = $details;
  4108. }
  4109. $index += $length;
  4110. } elseif ($opCharacter == '$') { // absolute row or column range
  4111. ++$index;
  4112. } elseif ($opCharacter == ')') { // miscellaneous error checking
  4113. if ($expectingOperand) {
  4114. $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
  4115. $expectingOperand = false;
  4116. $expectingOperator = true;
  4117. } else {
  4118. return $this->raiseFormulaError("Formula Error: Unexpected ')'");
  4119. }
  4120. } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
  4121. return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
  4122. } else { // I don't even want to know what you did to get here
  4123. return $this->raiseFormulaError('Formula Error: An unexpected error occurred');
  4124. }
  4125. // Test for end of formula string
  4126. if ($index == strlen($formula)) {
  4127. // Did we end with an operator?.
  4128. // Only valid for the % unary operator
  4129. if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) {
  4130. return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
  4131. }
  4132. break;
  4133. }
  4134. // Ignore white space
  4135. while (($formula[$index] == "\n") || ($formula[$index] == "\r")) {
  4136. ++$index;
  4137. }
  4138. if ($formula[$index] == ' ') {
  4139. while ($formula[$index] == ' ') {
  4140. ++$index;
  4141. }
  4142. // If we're expecting an operator, but only have a space between the previous and next operands (and both are
  4143. // Cell References) then we have an INTERSECTION operator
  4144. if (
  4145. ($expectingOperator) &&
  4146. ((preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) &&
  4147. ($output[count($output) - 1]['type'] == 'Cell Reference') ||
  4148. (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', substr($formula, $index), $match)) &&
  4149. ($output[count($output) - 1]['type'] == 'Defined Name' || $output[count($output) - 1]['type'] == 'Value')
  4150. )
  4151. ) {
  4152. while (
  4153. $stack->count() > 0 &&
  4154. ($o2 = $stack->last()) &&
  4155. isset(self::$operators[$o2['value']]) &&
  4156. @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
  4157. ) {
  4158. $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
  4159. }
  4160. $stack->push('Binary Operator', '|'); // Put an Intersect Operator on the stack
  4161. $expectingOperator = false;
  4162. }
  4163. }
  4164. }
  4165. while (($op = $stack->pop()) !== null) { // pop everything off the stack and push onto output
  4166. if ((is_array($op) && $op['value'] == '(') || ($op === '(')) {
  4167. return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
  4168. }
  4169. $output[] = $op;
  4170. }
  4171. return $output;
  4172. }
  4173. private static function dataTestReference(&$operandData)
  4174. {
  4175. $operand = $operandData['value'];
  4176. if (($operandData['reference'] === null) && (is_array($operand))) {
  4177. $rKeys = array_keys($operand);
  4178. $rowKey = array_shift($rKeys);
  4179. $cKeys = array_keys(array_keys($operand[$rowKey]));
  4180. $colKey = array_shift($cKeys);
  4181. if (ctype_upper($colKey)) {
  4182. $operandData['reference'] = $colKey . $rowKey;
  4183. }
  4184. }
  4185. return $operand;
  4186. }
  4187. // evaluate postfix notation
  4188. /**
  4189. * @param mixed $tokens
  4190. * @param null|string $cellID
  4191. *
  4192. * @return array<int, mixed>|false
  4193. */
  4194. private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null)
  4195. {
  4196. if ($tokens == false) {
  4197. return false;
  4198. }
  4199. // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
  4200. // so we store the parent cell collection so that we can re-attach it when necessary
  4201. $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
  4202. $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
  4203. $stack = new Stack();
  4204. // Stores branches that have been pruned
  4205. $fakedForBranchPruning = [];
  4206. // help us to know when pruning ['branchTestId' => true/false]
  4207. $branchStore = [];
  4208. // Loop through each token in turn
  4209. foreach ($tokens as $tokenData) {
  4210. $token = $tokenData['value'];
  4211. // Branch pruning: skip useless resolutions
  4212. $storeKey = $tokenData['storeKey'] ?? null;
  4213. if ($this->branchPruningEnabled && isset($tokenData['onlyIf'])) {
  4214. $onlyIfStoreKey = $tokenData['onlyIf'];
  4215. $storeValue = $branchStore[$onlyIfStoreKey] ?? null;
  4216. $storeValueAsBool = ($storeValue === null) ?
  4217. true : (bool) Functions::flattenSingleValue($storeValue);
  4218. if (is_array($storeValue)) {
  4219. $wrappedItem = end($storeValue);
  4220. $storeValue = end($wrappedItem);
  4221. }
  4222. if (
  4223. isset($storeValue)
  4224. && (
  4225. !$storeValueAsBool
  4226. || Functions::isError($storeValue)
  4227. || ($storeValue === 'Pruned branch')
  4228. )
  4229. ) {
  4230. // If branching value is not true, we don't need to compute
  4231. if (!isset($fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey])) {
  4232. $stack->push('Value', 'Pruned branch (only if ' . $onlyIfStoreKey . ') ' . $token);
  4233. $fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey] = true;
  4234. }
  4235. if (isset($storeKey)) {
  4236. // We are processing an if condition
  4237. // We cascade the pruning to the depending branches
  4238. $branchStore[$storeKey] = 'Pruned branch';
  4239. $fakedForBranchPruning['onlyIfNot-' . $storeKey] = true;
  4240. $fakedForBranchPruning['onlyIf-' . $storeKey] = true;
  4241. }
  4242. continue;
  4243. }
  4244. }
  4245. if ($this->branchPruningEnabled && isset($tokenData['onlyIfNot'])) {
  4246. $onlyIfNotStoreKey = $tokenData['onlyIfNot'];
  4247. $storeValue = $branchStore[$onlyIfNotStoreKey] ?? null;
  4248. $storeValueAsBool = ($storeValue === null) ?
  4249. true : (bool) Functions::flattenSingleValue($storeValue);
  4250. if (is_array($storeValue)) {
  4251. $wrappedItem = end($storeValue);
  4252. $storeValue = end($wrappedItem);
  4253. }
  4254. if (
  4255. isset($storeValue)
  4256. && (
  4257. $storeValueAsBool
  4258. || Functions::isError($storeValue)
  4259. || ($storeValue === 'Pruned branch'))
  4260. ) {
  4261. // If branching value is true, we don't need to compute
  4262. if (!isset($fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey])) {
  4263. $stack->push('Value', 'Pruned branch (only if not ' . $onlyIfNotStoreKey . ') ' . $token);
  4264. $fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey] = true;
  4265. }
  4266. if (isset($storeKey)) {
  4267. // We are processing an if condition
  4268. // We cascade the pruning to the depending branches
  4269. $branchStore[$storeKey] = 'Pruned branch';
  4270. $fakedForBranchPruning['onlyIfNot-' . $storeKey] = true;
  4271. $fakedForBranchPruning['onlyIf-' . $storeKey] = true;
  4272. }
  4273. continue;
  4274. }
  4275. }
  4276. // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
  4277. if (isset(self::$binaryOperators[$token])) {
  4278. // We must have two operands, error if we don't
  4279. if (($operand2Data = $stack->pop()) === null) {
  4280. return $this->raiseFormulaError('Internal error - Operand value missing from stack');
  4281. }
  4282. if (($operand1Data = $stack->pop()) === null) {
  4283. return $this->raiseFormulaError('Internal error - Operand value missing from stack');
  4284. }
  4285. $operand1 = self::dataTestReference($operand1Data);
  4286. $operand2 = self::dataTestReference($operand2Data);
  4287. // Log what we're doing
  4288. if ($token == ':') {
  4289. $this->debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference']));
  4290. } else {
  4291. $this->debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2));
  4292. }
  4293. // Process the operation in the appropriate manner
  4294. switch ($token) {
  4295. // Comparison (Boolean) Operators
  4296. case '>': // Greater than
  4297. case '<': // Less than
  4298. case '>=': // Greater than or Equal to
  4299. case '<=': // Less than or Equal to
  4300. case '=': // Equality
  4301. case '<>': // Inequality
  4302. $result = $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
  4303. if (isset($storeKey)) {
  4304. $branchStore[$storeKey] = $result;
  4305. }
  4306. break;
  4307. // Binary Operators
  4308. case ':': // Range
  4309. if (strpos($operand1Data['reference'], '!') !== false) {
  4310. [$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true);
  4311. } else {
  4312. $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
  4313. }
  4314. [$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true);
  4315. if (empty($sheet2)) {
  4316. $sheet2 = $sheet1;
  4317. }
  4318. if ($sheet1 == $sheet2) {
  4319. if ($operand1Data['reference'] === null) {
  4320. if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
  4321. $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value'];
  4322. } elseif (trim($operand1Data['reference']) == '') {
  4323. $operand1Data['reference'] = $pCell->getCoordinate();
  4324. } else {
  4325. $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
  4326. }
  4327. }
  4328. if ($operand2Data['reference'] === null) {
  4329. if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
  4330. $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
  4331. } elseif (trim($operand2Data['reference']) == '') {
  4332. $operand2Data['reference'] = $pCell->getCoordinate();
  4333. } else {
  4334. $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
  4335. }
  4336. }
  4337. $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
  4338. $oCol = $oRow = [];
  4339. foreach ($oData as $oDatum) {
  4340. $oCR = Coordinate::coordinateFromString($oDatum);
  4341. $oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
  4342. $oRow[] = $oCR[1];
  4343. }
  4344. $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
  4345. if ($pCellParent !== null) {
  4346. $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
  4347. } else {
  4348. return $this->raiseFormulaError('Unable to access Cell Reference');
  4349. }
  4350. $stack->push('Cell Reference', $cellValue, $cellRef);
  4351. } else {
  4352. $stack->push('Error', Functions::REF(), null);
  4353. }
  4354. break;
  4355. case '+': // Addition
  4356. $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack);
  4357. if (isset($storeKey)) {
  4358. $branchStore[$storeKey] = $result;
  4359. }
  4360. break;
  4361. case '-': // Subtraction
  4362. $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack);
  4363. if (isset($storeKey)) {
  4364. $branchStore[$storeKey] = $result;
  4365. }
  4366. break;
  4367. case '*': // Multiplication
  4368. $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack);
  4369. if (isset($storeKey)) {
  4370. $branchStore[$storeKey] = $result;
  4371. }
  4372. break;
  4373. case '/': // Division
  4374. $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack);
  4375. if (isset($storeKey)) {
  4376. $branchStore[$storeKey] = $result;
  4377. }
  4378. break;
  4379. case '^': // Exponential
  4380. $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack);
  4381. if (isset($storeKey)) {
  4382. $branchStore[$storeKey] = $result;
  4383. }
  4384. break;
  4385. case '&': // Concatenation
  4386. // If either of the operands is a matrix, we need to treat them both as matrices
  4387. // (converting the other operand to a matrix if need be); then perform the required
  4388. // matrix operation
  4389. if (is_bool($operand1)) {
  4390. $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
  4391. }
  4392. if (is_bool($operand2)) {
  4393. $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
  4394. }
  4395. if ((is_array($operand1)) || (is_array($operand2))) {
  4396. // Ensure that both operands are arrays/matrices
  4397. self::checkMatrixOperands($operand1, $operand2, 2);
  4398. try {
  4399. // Convert operand 1 from a PHP array to a matrix
  4400. $matrix = new Shared\JAMA\Matrix($operand1);
  4401. // Perform the required operation against the operand 1 matrix, passing in operand 2
  4402. $matrixResult = $matrix->concat($operand2);
  4403. $result = $matrixResult->getArray();
  4404. } catch (\Exception $ex) {
  4405. $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
  4406. $result = '#VALUE!';
  4407. }
  4408. } else {
  4409. $result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE;
  4410. }
  4411. $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
  4412. $stack->push('Value', $result);
  4413. if (isset($storeKey)) {
  4414. $branchStore[$storeKey] = $result;
  4415. }
  4416. break;
  4417. case '|': // Intersect
  4418. $rowIntersect = array_intersect_key($operand1, $operand2);
  4419. $cellIntersect = $oCol = $oRow = [];
  4420. foreach (array_keys($rowIntersect) as $row) {
  4421. $oRow[] = $row;
  4422. foreach ($rowIntersect[$row] as $col => $data) {
  4423. $oCol[] = Coordinate::columnIndexFromString($col) - 1;
  4424. $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
  4425. }
  4426. }
  4427. if (count(Functions::flattenArray($cellIntersect)) === 0) {
  4428. $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
  4429. $stack->push('Error', Functions::null(), null);
  4430. } else {
  4431. $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' .
  4432. Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
  4433. $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
  4434. $stack->push('Value', $cellIntersect, $cellRef);
  4435. }
  4436. break;
  4437. }
  4438. // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
  4439. } elseif (($token === '~') || ($token === '%')) {
  4440. if (($arg = $stack->pop()) === null) {
  4441. return $this->raiseFormulaError('Internal error - Operand value missing from stack');
  4442. }
  4443. $arg = $arg['value'];
  4444. if ($token === '~') {
  4445. $this->debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg));
  4446. $multiplier = -1;
  4447. } else {
  4448. $this->debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg));
  4449. $multiplier = 0.01;
  4450. }
  4451. if (is_array($arg)) {
  4452. self::checkMatrixOperands($arg, $multiplier, 2);
  4453. try {
  4454. $matrix1 = new Shared\JAMA\Matrix($arg);
  4455. $matrixResult = $matrix1->arrayTimesEquals($multiplier);
  4456. $result = $matrixResult->getArray();
  4457. } catch (\Exception $ex) {
  4458. $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
  4459. $result = '#VALUE!';
  4460. }
  4461. $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
  4462. $stack->push('Value', $result);
  4463. if (isset($storeKey)) {
  4464. $branchStore[$storeKey] = $result;
  4465. }
  4466. } else {
  4467. $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack);
  4468. }
  4469. } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token ?? '', $matches)) {
  4470. $cellRef = null;
  4471. if (isset($matches[8])) {
  4472. if ($pCell === null) {
  4473. // We can't access the range, so return a REF error
  4474. $cellValue = Functions::REF();
  4475. } else {
  4476. $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
  4477. if ($matches[2] > '') {
  4478. $matches[2] = trim($matches[2], "\"'");
  4479. if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
  4480. // It's a Reference to an external spreadsheet (not currently supported)
  4481. return $this->raiseFormulaError('Unable to access External Workbook');
  4482. }
  4483. $matches[2] = trim($matches[2], "\"'");
  4484. $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]);
  4485. if ($pCellParent !== null) {
  4486. $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
  4487. } else {
  4488. return $this->raiseFormulaError('Unable to access Cell Reference');
  4489. }
  4490. $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
  4491. } else {
  4492. $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet');
  4493. if ($pCellParent !== null) {
  4494. $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
  4495. } else {
  4496. return $this->raiseFormulaError('Unable to access Cell Reference');
  4497. }
  4498. $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
  4499. }
  4500. }
  4501. } else {
  4502. if ($pCell === null) {
  4503. // We can't access the cell, so return a REF error
  4504. $cellValue = Functions::REF();
  4505. } else {
  4506. $cellRef = $matches[6] . $matches[7];
  4507. if ($matches[2] > '') {
  4508. $matches[2] = trim($matches[2], "\"'");
  4509. if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
  4510. // It's a Reference to an external spreadsheet (not currently supported)
  4511. return $this->raiseFormulaError('Unable to access External Workbook');
  4512. }
  4513. $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]);
  4514. if ($pCellParent !== null) {
  4515. $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
  4516. if ($cellSheet && $cellSheet->cellExists($cellRef)) {
  4517. $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
  4518. $pCell->attach($pCellParent);
  4519. } else {
  4520. $cellRef = ($cellSheet !== null) ? "{$matches[2]}!{$cellRef}" : $cellRef;
  4521. $cellValue = null;
  4522. }
  4523. } else {
  4524. return $this->raiseFormulaError('Unable to access Cell Reference');
  4525. }
  4526. $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
  4527. } else {
  4528. $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet');
  4529. if ($pCellParent->has($cellRef)) {
  4530. $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
  4531. $pCell->attach($pCellParent);
  4532. } else {
  4533. $cellValue = null;
  4534. }
  4535. $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
  4536. }
  4537. }
  4538. }
  4539. $stack->push('Cell Value', $cellValue, $cellRef);
  4540. if (isset($storeKey)) {
  4541. $branchStore[$storeKey] = $cellValue;
  4542. }
  4543. // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
  4544. } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token ?? '', $matches)) {
  4545. if ($pCellParent) {
  4546. $pCell->attach($pCellParent);
  4547. }
  4548. $functionName = $matches[1];
  4549. $argCount = $stack->pop();
  4550. $argCount = $argCount['value'];
  4551. if ($functionName != 'MKMATRIX') {
  4552. $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's'));
  4553. }
  4554. if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) { // function
  4555. $passByReference = false;
  4556. $passCellReference = false;
  4557. $functionCall = null;
  4558. if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
  4559. $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
  4560. $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
  4561. $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
  4562. } elseif (isset(self::$controlFunctions[$functionName])) {
  4563. $functionCall = self::$controlFunctions[$functionName]['functionCall'];
  4564. $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
  4565. $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
  4566. }
  4567. // get the arguments for this function
  4568. $args = $argArrayVals = [];
  4569. for ($i = 0; $i < $argCount; ++$i) {
  4570. $arg = $stack->pop();
  4571. $a = $argCount - $i - 1;
  4572. if (
  4573. ($passByReference) &&
  4574. (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) &&
  4575. (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])
  4576. ) {
  4577. if ($arg['reference'] === null) {
  4578. $args[] = $cellID;
  4579. if ($functionName != 'MKMATRIX') {
  4580. $argArrayVals[] = $this->showValue($cellID);
  4581. }
  4582. } else {
  4583. $args[] = $arg['reference'];
  4584. if ($functionName != 'MKMATRIX') {
  4585. $argArrayVals[] = $this->showValue($arg['reference']);
  4586. }
  4587. }
  4588. } else {
  4589. $args[] = self::unwrapResult($arg['value']);
  4590. if ($functionName != 'MKMATRIX') {
  4591. $argArrayVals[] = $this->showValue($arg['value']);
  4592. }
  4593. }
  4594. }
  4595. // Reverse the order of the arguments
  4596. krsort($args);
  4597. if (($passByReference) && ($argCount == 0)) {
  4598. $args[] = $cellID;
  4599. $argArrayVals[] = $this->showValue($cellID);
  4600. }
  4601. if ($functionName != 'MKMATRIX') {
  4602. if ($this->debugLog->getWriteDebugLog()) {
  4603. krsort($argArrayVals);
  4604. $this->debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)), ' )');
  4605. }
  4606. }
  4607. // Process the argument with the appropriate function call
  4608. $args = $this->addCellReference($args, $passCellReference, $functionCall, $pCell);
  4609. if (!is_array($functionCall)) {
  4610. foreach ($args as &$arg) {
  4611. $arg = Functions::flattenSingleValue($arg);
  4612. }
  4613. unset($arg);
  4614. }
  4615. $result = call_user_func_array($functionCall, $args);
  4616. if ($functionName != 'MKMATRIX') {
  4617. $this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result));
  4618. }
  4619. $stack->push('Value', self::wrapResult($result));
  4620. if (isset($storeKey)) {
  4621. $branchStore[$storeKey] = $result;
  4622. }
  4623. }
  4624. } else {
  4625. // if the token is a number, boolean, string or an Excel error, push it onto the stack
  4626. if (isset(self::$excelConstants[strtoupper($token ?? '')])) {
  4627. $excelConstant = strtoupper($token);
  4628. $stack->push('Constant Value', self::$excelConstants[$excelConstant]);
  4629. if (isset($storeKey)) {
  4630. $branchStore[$storeKey] = self::$excelConstants[$excelConstant];
  4631. }
  4632. $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant]));
  4633. } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == self::FORMULA_STRING_QUOTE) || ($token[0] == '#')) {
  4634. $stack->push('Value', $token);
  4635. if (isset($storeKey)) {
  4636. $branchStore[$storeKey] = $token;
  4637. }
  4638. // if the token is a named range or formula, evaluate it and push the result onto the stack
  4639. } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) {
  4640. $definedName = $matches[6];
  4641. if ($pCell === null || $pCellWorksheet === null) {
  4642. return $this->raiseFormulaError("undefined name '$token'");
  4643. }
  4644. $this->debugLog->writeDebugLog('Evaluating Defined Name ', $definedName);
  4645. $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet);
  4646. if ($namedRange === null) {
  4647. return $this->raiseFormulaError("undefined name '$definedName'");
  4648. }
  4649. $result = $this->evaluateDefinedName($pCell, $namedRange, $pCellWorksheet, $stack);
  4650. if (isset($storeKey)) {
  4651. $branchStore[$storeKey] = $result;
  4652. }
  4653. } else {
  4654. return $this->raiseFormulaError("undefined name '$token'");
  4655. }
  4656. }
  4657. }
  4658. // when we're out of tokens, the stack should have a single element, the final result
  4659. if ($stack->count() != 1) {
  4660. return $this->raiseFormulaError('internal error');
  4661. }
  4662. $output = $stack->pop();
  4663. $output = $output['value'];
  4664. return $output;
  4665. }
  4666. private function validateBinaryOperand(&$operand, &$stack)
  4667. {
  4668. if (is_array($operand)) {
  4669. if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) {
  4670. do {
  4671. $operand = array_pop($operand);
  4672. } while (is_array($operand));
  4673. }
  4674. }
  4675. // Numbers, matrices and booleans can pass straight through, as they're already valid
  4676. if (is_string($operand)) {
  4677. // We only need special validations for the operand if it is a string
  4678. // Start by stripping off the quotation marks we use to identify true excel string values internally
  4679. if ($operand > '' && $operand[0] == self::FORMULA_STRING_QUOTE) {
  4680. $operand = self::unwrapResult($operand);
  4681. }
  4682. // If the string is a numeric value, we treat it as a numeric, so no further testing
  4683. if (!is_numeric($operand)) {
  4684. // If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations
  4685. if ($operand > '' && $operand[0] == '#') {
  4686. $stack->push('Value', $operand);
  4687. $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($operand));
  4688. return false;
  4689. } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) {
  4690. // If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations
  4691. $stack->push('Error', '#VALUE!');
  4692. $this->debugLog->writeDebugLog('Evaluation Result is a ', $this->showTypeDetails('#VALUE!'));
  4693. return false;
  4694. }
  4695. }
  4696. }
  4697. // return a true if the value of the operand is one that we can use in normal binary operations
  4698. return true;
  4699. }
  4700. /**
  4701. * @param null|string $cellID
  4702. * @param mixed $operand1
  4703. * @param mixed $operand2
  4704. * @param string $operation
  4705. *
  4706. * @return array
  4707. */
  4708. private function executeArrayComparison($cellID, $operand1, $operand2, $operation, Stack &$stack, bool $recursingArrays)
  4709. {
  4710. $result = [];
  4711. if (!is_array($operand2)) {
  4712. // Operand 1 is an array, Operand 2 is a scalar
  4713. foreach ($operand1 as $x => $operandData) {
  4714. $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2));
  4715. $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack);
  4716. $r = $stack->pop();
  4717. $result[$x] = $r['value'];
  4718. }
  4719. } elseif (!is_array($operand1)) {
  4720. // Operand 1 is a scalar, Operand 2 is an array
  4721. foreach ($operand2 as $x => $operandData) {
  4722. $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData));
  4723. $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack);
  4724. $r = $stack->pop();
  4725. $result[$x] = $r['value'];
  4726. }
  4727. } else {
  4728. // Operand 1 and Operand 2 are both arrays
  4729. if (!$recursingArrays) {
  4730. self::checkMatrixOperands($operand1, $operand2, 2);
  4731. }
  4732. foreach ($operand1 as $x => $operandData) {
  4733. $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x]));
  4734. $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true);
  4735. $r = $stack->pop();
  4736. $result[$x] = $r['value'];
  4737. }
  4738. }
  4739. // Log the result details
  4740. $this->debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result));
  4741. // And push the result onto the stack
  4742. $stack->push('Array', $result);
  4743. return $result;
  4744. }
  4745. /**
  4746. * @param null|string $cellID
  4747. * @param mixed $operand1
  4748. * @param mixed $operand2
  4749. * @param string $operation
  4750. * @param bool $recursingArrays
  4751. *
  4752. * @return mixed
  4753. */
  4754. private function executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays = false)
  4755. {
  4756. // If we're dealing with matrix operations, we want a matrix result
  4757. if ((is_array($operand1)) || (is_array($operand2))) {
  4758. return $this->executeArrayComparison($cellID, $operand1, $operand2, $operation, $stack, $recursingArrays);
  4759. }
  4760. // Simple validate the two operands if they are string values
  4761. if (is_string($operand1) && $operand1 > '' && $operand1[0] == self::FORMULA_STRING_QUOTE) {
  4762. $operand1 = self::unwrapResult($operand1);
  4763. }
  4764. if (is_string($operand2) && $operand2 > '' && $operand2[0] == self::FORMULA_STRING_QUOTE) {
  4765. $operand2 = self::unwrapResult($operand2);
  4766. }
  4767. // Use case insensitive comparaison if not OpenOffice mode
  4768. if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
  4769. if (is_string($operand1)) {
  4770. $operand1 = Shared\StringHelper::strToUpper($operand1);
  4771. }
  4772. if (is_string($operand2)) {
  4773. $operand2 = Shared\StringHelper::strToUpper($operand2);
  4774. }
  4775. }
  4776. $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE;
  4777. // execute the necessary operation
  4778. switch ($operation) {
  4779. // Greater than
  4780. case '>':
  4781. if ($useLowercaseFirstComparison) {
  4782. $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0;
  4783. } else {
  4784. $result = ($operand1 > $operand2);
  4785. }
  4786. break;
  4787. // Less than
  4788. case '<':
  4789. if ($useLowercaseFirstComparison) {
  4790. $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0;
  4791. } else {
  4792. $result = ($operand1 < $operand2);
  4793. }
  4794. break;
  4795. // Equality
  4796. case '=':
  4797. if (is_numeric($operand1) && is_numeric($operand2)) {
  4798. $result = (abs($operand1 - $operand2) < $this->delta);
  4799. } else {
  4800. $result = $this->strcmpAllowNull($operand1, $operand2) == 0;
  4801. }
  4802. break;
  4803. // Greater than or equal
  4804. case '>=':
  4805. if (is_numeric($operand1) && is_numeric($operand2)) {
  4806. $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 > $operand2));
  4807. } elseif ($useLowercaseFirstComparison) {
  4808. $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0;
  4809. } else {
  4810. $result = $this->strcmpAllowNull($operand1, $operand2) >= 0;
  4811. }
  4812. break;
  4813. // Less than or equal
  4814. case '<=':
  4815. if (is_numeric($operand1) && is_numeric($operand2)) {
  4816. $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 < $operand2));
  4817. } elseif ($useLowercaseFirstComparison) {
  4818. $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0;
  4819. } else {
  4820. $result = $this->strcmpAllowNull($operand1, $operand2) <= 0;
  4821. }
  4822. break;
  4823. // Inequality
  4824. case '<>':
  4825. if (is_numeric($operand1) && is_numeric($operand2)) {
  4826. $result = (abs($operand1 - $operand2) > 1E-14);
  4827. } else {
  4828. $result = $this->strcmpAllowNull($operand1, $operand2) != 0;
  4829. }
  4830. break;
  4831. default:
  4832. throw new Exception('Unsupported binary comparison operation');
  4833. }
  4834. // Log the result details
  4835. $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
  4836. // And push the result onto the stack
  4837. $stack->push('Value', $result);
  4838. return $result;
  4839. }
  4840. /**
  4841. * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
  4842. *
  4843. * @param null|string $str1 First string value for the comparison
  4844. * @param null|string $str2 Second string value for the comparison
  4845. *
  4846. * @return int
  4847. */
  4848. private function strcmpLowercaseFirst($str1, $str2)
  4849. {
  4850. $inversedStr1 = Shared\StringHelper::strCaseReverse($str1);
  4851. $inversedStr2 = Shared\StringHelper::strCaseReverse($str2);
  4852. return strcmp($inversedStr1 ?? '', $inversedStr2 ?? '');
  4853. }
  4854. /**
  4855. * PHP8.1 deprecates passing null to strcmp.
  4856. *
  4857. * @param null|string $str1 First string value for the comparison
  4858. * @param null|string $str2 Second string value for the comparison
  4859. *
  4860. * @return int
  4861. */
  4862. private function strcmpAllowNull($str1, $str2)
  4863. {
  4864. return strcmp($str1 ?? '', $str2 ?? '');
  4865. }
  4866. /**
  4867. * @param mixed $operand1
  4868. * @param mixed $operand2
  4869. * @param mixed $operation
  4870. * @param string $matrixFunction
  4871. * @param mixed $stack
  4872. *
  4873. * @return bool|mixed
  4874. */
  4875. private function executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack)
  4876. {
  4877. // Validate the two operands
  4878. if (!$this->validateBinaryOperand($operand1, $stack)) {
  4879. return false;
  4880. }
  4881. if (!$this->validateBinaryOperand($operand2, $stack)) {
  4882. return false;
  4883. }
  4884. // If either of the operands is a matrix, we need to treat them both as matrices
  4885. // (converting the other operand to a matrix if need be); then perform the required
  4886. // matrix operation
  4887. if ((is_array($operand1)) || (is_array($operand2))) {
  4888. // Ensure that both operands are arrays/matrices of the same size
  4889. self::checkMatrixOperands($operand1, $operand2, 2);
  4890. try {
  4891. // Convert operand 1 from a PHP array to a matrix
  4892. $matrix = new Shared\JAMA\Matrix($operand1);
  4893. // Perform the required operation against the operand 1 matrix, passing in operand 2
  4894. $matrixResult = $matrix->$matrixFunction($operand2);
  4895. $result = $matrixResult->getArray();
  4896. } catch (\Exception $ex) {
  4897. $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
  4898. $result = '#VALUE!';
  4899. }
  4900. } else {
  4901. if (
  4902. (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) &&
  4903. ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
  4904. (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))
  4905. ) {
  4906. $result = Functions::VALUE();
  4907. } else {
  4908. // If we're dealing with non-matrix operations, execute the necessary operation
  4909. switch ($operation) {
  4910. // Addition
  4911. case '+':
  4912. $result = $operand1 + $operand2;
  4913. break;
  4914. // Subtraction
  4915. case '-':
  4916. $result = $operand1 - $operand2;
  4917. break;
  4918. // Multiplication
  4919. case '*':
  4920. $result = $operand1 * $operand2;
  4921. break;
  4922. // Division
  4923. case '/':
  4924. if ($operand2 == 0) {
  4925. // Trap for Divide by Zero error
  4926. $stack->push('Error', '#DIV/0!');
  4927. $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails('#DIV/0!'));
  4928. return false;
  4929. }
  4930. $result = $operand1 / $operand2;
  4931. break;
  4932. // Power
  4933. case '^':
  4934. $result = $operand1 ** $operand2;
  4935. break;
  4936. default:
  4937. throw new Exception('Unsupported numeric binary operation');
  4938. }
  4939. }
  4940. }
  4941. // Log the result details
  4942. $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
  4943. // And push the result onto the stack
  4944. $stack->push('Value', $result);
  4945. return $result;
  4946. }
  4947. // trigger an error, but nicely, if need be
  4948. protected function raiseFormulaError($errorMessage)
  4949. {
  4950. $this->formulaError = $errorMessage;
  4951. $this->cyclicReferenceStack->clear();
  4952. if (!$this->suppressFormulaErrors) {
  4953. throw new Exception($errorMessage);
  4954. }
  4955. trigger_error($errorMessage, E_USER_ERROR);
  4956. return false;
  4957. }
  4958. /**
  4959. * Extract range values.
  4960. *
  4961. * @param string $pRange String based range representation
  4962. * @param Worksheet $pSheet Worksheet
  4963. * @param bool $resetLog Flag indicating whether calculation log should be reset or not
  4964. *
  4965. * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.
  4966. */
  4967. public function extractCellRange(&$pRange = 'A1', ?Worksheet $pSheet = null, $resetLog = true)
  4968. {
  4969. // Return value
  4970. $returnValue = [];
  4971. if ($pSheet !== null) {
  4972. $pSheetName = $pSheet->getTitle();
  4973. if (strpos($pRange, '!') !== false) {
  4974. [$pSheetName, $pRange] = Worksheet::extractSheetTitle($pRange, true);
  4975. $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
  4976. }
  4977. // Extract range
  4978. $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
  4979. $pRange = $pSheetName . '!' . $pRange;
  4980. if (!isset($aReferences[1])) {
  4981. $currentCol = '';
  4982. $currentRow = 0;
  4983. // Single cell in range
  4984. sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
  4985. if ($pSheet->cellExists($aReferences[0])) {
  4986. $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
  4987. } else {
  4988. $returnValue[$currentRow][$currentCol] = null;
  4989. }
  4990. } else {
  4991. // Extract cell data for all cells in the range
  4992. foreach ($aReferences as $reference) {
  4993. $currentCol = '';
  4994. $currentRow = 0;
  4995. // Extract range
  4996. sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
  4997. if ($pSheet->cellExists($reference)) {
  4998. $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
  4999. } else {
  5000. $returnValue[$currentRow][$currentCol] = null;
  5001. }
  5002. }
  5003. }
  5004. }
  5005. return $returnValue;
  5006. }
  5007. /**
  5008. * Extract range values.
  5009. *
  5010. * @param string $pRange String based range representation
  5011. * @param Worksheet $pSheet Worksheet
  5012. * @param bool $resetLog Flag indicating whether calculation log should be reset or not
  5013. *
  5014. * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.
  5015. */
  5016. public function extractNamedRange(&$pRange = 'A1', ?Worksheet $pSheet = null, $resetLog = true)
  5017. {
  5018. // Return value
  5019. $returnValue = [];
  5020. if ($pSheet !== null) {
  5021. $pSheetName = $pSheet->getTitle();
  5022. if (strpos($pRange, '!') !== false) {
  5023. [$pSheetName, $pRange] = Worksheet::extractSheetTitle($pRange, true);
  5024. $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
  5025. }
  5026. // Named range?
  5027. $namedRange = DefinedName::resolveName($pRange, $pSheet);
  5028. if ($namedRange === null) {
  5029. return Functions::REF();
  5030. }
  5031. $pSheet = $namedRange->getWorksheet();
  5032. $pRange = $namedRange->getValue();
  5033. $splitRange = Coordinate::splitRange($pRange);
  5034. // Convert row and column references
  5035. if (ctype_alpha($splitRange[0][0])) {
  5036. $pRange = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
  5037. } elseif (ctype_digit($splitRange[0][0])) {
  5038. $pRange = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
  5039. }
  5040. // Extract range
  5041. $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
  5042. if (!isset($aReferences[1])) {
  5043. // Single cell (or single column or row) in range
  5044. [$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]);
  5045. if ($pSheet->cellExists($aReferences[0])) {
  5046. $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
  5047. } else {
  5048. $returnValue[$currentRow][$currentCol] = null;
  5049. }
  5050. } else {
  5051. // Extract cell data for all cells in the range
  5052. foreach ($aReferences as $reference) {
  5053. // Extract range
  5054. [$currentCol, $currentRow] = Coordinate::coordinateFromString($reference);
  5055. if ($pSheet->cellExists($reference)) {
  5056. $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
  5057. } else {
  5058. $returnValue[$currentRow][$currentCol] = null;
  5059. }
  5060. }
  5061. }
  5062. }
  5063. return $returnValue;
  5064. }
  5065. /**
  5066. * Is a specific function implemented?
  5067. *
  5068. * @param string $pFunction Function Name
  5069. *
  5070. * @return bool
  5071. */
  5072. public function isImplemented($pFunction)
  5073. {
  5074. $pFunction = strtoupper($pFunction);
  5075. $notImplemented = !isset(self::$phpSpreadsheetFunctions[$pFunction]) || (is_array(self::$phpSpreadsheetFunctions[$pFunction]['functionCall']) && self::$phpSpreadsheetFunctions[$pFunction]['functionCall'][1] === 'DUMMY');
  5076. return !$notImplemented;
  5077. }
  5078. /**
  5079. * Get a list of all implemented functions as an array of function objects.
  5080. */
  5081. public function getFunctions(): array
  5082. {
  5083. return self::$phpSpreadsheetFunctions;
  5084. }
  5085. /**
  5086. * Get a list of implemented Excel function names.
  5087. *
  5088. * @return array
  5089. */
  5090. public function getImplementedFunctionNames()
  5091. {
  5092. $returnValue = [];
  5093. foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
  5094. if ($this->isImplemented($functionName)) {
  5095. $returnValue[] = $functionName;
  5096. }
  5097. }
  5098. return $returnValue;
  5099. }
  5100. /**
  5101. * Add cell reference if needed while making sure that it is the last argument.
  5102. *
  5103. * @param bool $passCellReference
  5104. * @param array|string $functionCall
  5105. *
  5106. * @return array
  5107. */
  5108. private function addCellReference(array $args, $passCellReference, $functionCall, ?Cell $pCell = null)
  5109. {
  5110. if ($passCellReference) {
  5111. if (is_array($functionCall)) {
  5112. $className = $functionCall[0];
  5113. $methodName = $functionCall[1];
  5114. $reflectionMethod = new ReflectionMethod($className, $methodName);
  5115. $argumentCount = count($reflectionMethod->getParameters());
  5116. while (count($args) < $argumentCount - 1) {
  5117. $args[] = null;
  5118. }
  5119. }
  5120. $args[] = $pCell;
  5121. }
  5122. return $args;
  5123. }
  5124. private function getUnusedBranchStoreKey()
  5125. {
  5126. $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter;
  5127. ++$this->branchStoreKeyCounter;
  5128. return $storeKeyValue;
  5129. }
  5130. private function getTokensAsString($tokens)
  5131. {
  5132. $tokensStr = array_map(function ($token) {
  5133. $value = $token['value'] ?? 'no value';
  5134. while (is_array($value)) {
  5135. $value = array_pop($value);
  5136. }
  5137. return $value;
  5138. }, $tokens);
  5139. return '[ ' . implode(' | ', $tokensStr) . ' ]';
  5140. }
  5141. /**
  5142. * @return mixed|string
  5143. */
  5144. private function evaluateDefinedName(Cell $pCell, DefinedName $namedRange, Worksheet $pCellWorksheet, Stack $stack)
  5145. {
  5146. $definedNameScope = $namedRange->getScope();
  5147. if ($definedNameScope !== null && $definedNameScope !== $pCellWorksheet) {
  5148. // The defined name isn't in our current scope, so #REF
  5149. $result = Functions::REF();
  5150. $stack->push('Error', $result, $namedRange->getName());
  5151. return $result;
  5152. }
  5153. $definedNameValue = $namedRange->getValue();
  5154. $definedNameType = $namedRange->isFormula() ? 'Formula' : 'Range';
  5155. $definedNameWorksheet = $namedRange->getWorksheet();
  5156. if ($definedNameValue[0] !== '=') {
  5157. $definedNameValue = '=' . $definedNameValue;
  5158. }
  5159. $this->debugLog->writeDebugLog("Defined Name is a {$definedNameType} with a value of {$definedNameValue}");
  5160. $recursiveCalculationCell = ($definedNameWorksheet !== null && $definedNameWorksheet !== $pCellWorksheet)
  5161. ? $definedNameWorksheet->getCell('A1')
  5162. : $pCell;
  5163. $recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate();
  5164. // Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns
  5165. $definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet(
  5166. $definedNameValue,
  5167. Coordinate::columnIndexFromString($pCell->getColumn()) - 1,
  5168. $pCell->getRow() - 1
  5169. );
  5170. $this->debugLog->writeDebugLog("Value adjusted for relative references is {$definedNameValue}");
  5171. $recursiveCalculator = new self($this->spreadsheet);
  5172. $recursiveCalculator->getDebugLog()->setWriteDebugLog($this->getDebugLog()->getWriteDebugLog());
  5173. $recursiveCalculator->getDebugLog()->setEchoDebugLog($this->getDebugLog()->getEchoDebugLog());
  5174. $result = $recursiveCalculator->_calculateFormulaValue($definedNameValue, $recursiveCalculationCellAddress, $recursiveCalculationCell);
  5175. if ($this->getDebugLog()->getWriteDebugLog()) {
  5176. $this->debugLog->mergeDebugLog(array_slice($recursiveCalculator->getDebugLog()->getLog(), 3));
  5177. $this->debugLog->writeDebugLog("Evaluation Result for Named {$definedNameType} {$namedRange->getName()} is {$this->showTypeDetails($result)}");
  5178. }
  5179. $stack->push('Defined Name', $result, $namedRange->getName());
  5180. return $result;
  5181. }
  5182. }