99 '+' =>
true,
'-' =>
true,
'*' =>
true,
'/' =>
true,
100 '^' =>
true,
'&' =>
true,
'%' =>
false,
'~' =>
false,
101 '>' =>
true,
'<' =>
true,
'=' =>
true,
'>=' =>
true,
102 '<=' =>
true,
'<>' =>
true,
'|' =>
true,
':' =>
true,
111 '+' =>
true,
'-' =>
true,
'*' =>
true,
'/' =>
true,
112 '^' =>
true,
'&' =>
true,
'>' =>
true,
'<' =>
true,
113 '=' =>
true,
'>=' =>
true,
'<=' =>
true,
'<>' =>
true,
114 '|' =>
true,
':' =>
true,
234 'functionCall' => [MathTrig\Absolute::class,
'evaluate'],
235 'argumentCount' =>
'1',
239 'functionCall' => [Financial\Securities\AccruedInterest::class,
'periodic'],
240 'argumentCount' =>
'4-8',
244 'functionCall' => [Financial\Securities\AccruedInterest::class,
'atMaturity'],
245 'argumentCount' =>
'3-5',
249 'functionCall' => [MathTrig\Trig\Cosine::class,
'acos'],
250 'argumentCount' =>
'1',
254 'functionCall' => [MathTrig\Trig\Cosine::class,
'acosh'],
255 'argumentCount' =>
'1',
259 'functionCall' => [MathTrig\Trig\Cotangent::class,
'acot'],
260 'argumentCount' =>
'1',
264 'functionCall' => [MathTrig\Trig\Cotangent::class,
'acoth'],
265 'argumentCount' =>
'1',
269 'functionCall' => [LookupRef\Address::class,
'cell'],
270 'argumentCount' =>
'2-5',
274 'functionCall' => [Functions::class,
'DUMMY'],
275 'argumentCount' =>
'3+',
279 'functionCall' => [Financial\Amortization::class,
'AMORDEGRC'],
280 'argumentCount' =>
'6,7',
284 'functionCall' => [Financial\Amortization::class,
'AMORLINC'],
285 'argumentCount' =>
'6,7',
289 'functionCall' => [Logical\Operations::class,
'logicalAnd'],
290 'argumentCount' =>
'1+',
294 'functionCall' => [MathTrig\Arabic::class,
'evaluate'],
295 'argumentCount' =>
'1',
299 'functionCall' => [Functions::class,
'DUMMY'],
300 'argumentCount' =>
'1',
304 'functionCall' => [Functions::class,
'DUMMY'],
305 'argumentCount' =>
'?',
309 'functionCall' => [Functions::class,
'DUMMY'],
310 'argumentCount' =>
'1',
314 'functionCall' => [MathTrig\Trig\Sine::class,
'asin'],
315 'argumentCount' =>
'1',
319 'functionCall' => [MathTrig\Trig\Sine::class,
'asinh'],
320 'argumentCount' =>
'1',
324 'functionCall' => [MathTrig\Trig\Tangent::class,
'atan'],
325 'argumentCount' =>
'1',
329 'functionCall' => [MathTrig\Trig\Tangent::class,
'atan2'],
330 'argumentCount' =>
'2',
334 'functionCall' => [MathTrig\Trig\Tangent::class,
'atanh'],
335 'argumentCount' =>
'1',
339 'functionCall' => [Statistical\Averages::class,
'averageDeviations'],
340 'argumentCount' =>
'1+',
344 'functionCall' => [Statistical\Averages::class,
'average'],
345 'argumentCount' =>
'1+',
349 'functionCall' => [Statistical\Averages::class,
'averageA'],
350 'argumentCount' =>
'1+',
354 'functionCall' => [Statistical\Conditional::class,
'AVERAGEIF'],
355 'argumentCount' =>
'2,3',
359 'functionCall' => [Statistical\Conditional::class,
'AVERAGEIFS'],
360 'argumentCount' =>
'3+',
364 'functionCall' => [Functions::class,
'DUMMY'],
365 'argumentCount' =>
'1',
369 'functionCall' => [MathTrig\Base::class,
'evaluate'],
370 'argumentCount' =>
'2,3',
374 'functionCall' => [Engineering\BesselI::class,
'BESSELI'],
375 'argumentCount' =>
'2',
379 'functionCall' => [Engineering\BesselJ::class,
'BESSELJ'],
380 'argumentCount' =>
'2',
384 'functionCall' => [Engineering\BesselK::class,
'BESSELK'],
385 'argumentCount' =>
'2',
389 'functionCall' => [Engineering\BesselY::class,
'BESSELY'],
390 'argumentCount' =>
'2',
394 'functionCall' => [Statistical\Distributions\Beta::class,
'distribution'],
395 'argumentCount' =>
'3-5',
399 'functionCall' => [Functions::class,
'DUMMY'],
400 'argumentCount' =>
'4-6',
404 'functionCall' => [Statistical\Distributions\Beta::class,
'inverse'],
405 'argumentCount' =>
'3-5',
409 'functionCall' => [Statistical\Distributions\Beta::class,
'inverse'],
410 'argumentCount' =>
'3-5',
414 'functionCall' => [Engineering\ConvertBinary::class,
'toDecimal'],
415 'argumentCount' =>
'1',
419 'functionCall' => [Engineering\ConvertBinary::class,
'toHex'],
420 'argumentCount' =>
'1,2',
424 'functionCall' => [Engineering\ConvertBinary::class,
'toOctal'],
425 'argumentCount' =>
'1,2',
429 'functionCall' => [Statistical\Distributions\Binomial::class,
'distribution'],
430 'argumentCount' =>
'4',
434 'functionCall' => [Statistical\Distributions\Binomial::class,
'distribution'],
435 'argumentCount' =>
'4',
437 'BINOM.DIST.RANGE' => [
439 'functionCall' => [Statistical\Distributions\Binomial::class,
'range'],
440 'argumentCount' =>
'3,4',
444 'functionCall' => [Statistical\Distributions\Binomial::class,
'inverse'],
445 'argumentCount' =>
'3',
449 'functionCall' => [Engineering\BitWise::class,
'BITAND'],
450 'argumentCount' =>
'2',
454 'functionCall' => [Engineering\BitWise::class,
'BITOR'],
455 'argumentCount' =>
'2',
459 'functionCall' => [Engineering\BitWise::class,
'BITXOR'],
460 'argumentCount' =>
'2',
464 'functionCall' => [Engineering\BitWise::class,
'BITLSHIFT'],
465 'argumentCount' =>
'2',
469 'functionCall' => [Engineering\BitWise::class,
'BITRSHIFT'],
470 'argumentCount' =>
'2',
474 'functionCall' => [MathTrig\Ceiling::class,
'ceiling'],
475 'argumentCount' =>
'1-2',
479 'functionCall' => [MathTrig\Ceiling::class,
'math'],
480 'argumentCount' =>
'1-3',
482 'CEILING.PRECISE' => [
484 'functionCall' => [MathTrig\Ceiling::class,
'precise'],
485 'argumentCount' =>
'1,2',
489 'functionCall' => [Functions::class,
'DUMMY'],
490 'argumentCount' =>
'1,2',
494 'functionCall' => [TextData\CharacterConvert::class,
'character'],
495 'argumentCount' =>
'1',
499 'functionCall' => [Statistical\Distributions\ChiSquared::class,
'distributionRightTail'],
500 'argumentCount' =>
'2',
504 'functionCall' => [Statistical\Distributions\ChiSquared::class,
'distributionLeftTail'],
505 'argumentCount' =>
'3',
509 'functionCall' => [Statistical\Distributions\ChiSquared::class,
'distributionRightTail'],
510 'argumentCount' =>
'2',
514 'functionCall' => [Statistical\Distributions\ChiSquared::class,
'inverseRightTail'],
515 'argumentCount' =>
'2',
519 'functionCall' => [Statistical\Distributions\ChiSquared::class,
'inverseLeftTail'],
520 'argumentCount' =>
'2',
524 'functionCall' => [Statistical\Distributions\ChiSquared::class,
'inverseRightTail'],
525 'argumentCount' =>
'2',
529 'functionCall' => [Statistical\Distributions\ChiSquared::class,
'test'],
530 'argumentCount' =>
'2',
534 'functionCall' => [Statistical\Distributions\ChiSquared::class,
'test'],
535 'argumentCount' =>
'2',
539 'functionCall' => [LookupRef\Selection::class,
'CHOOSE'],
540 'argumentCount' =>
'2+',
544 'functionCall' => [TextData\Trim::class,
'nonPrintable'],
545 'argumentCount' =>
'1',
549 'functionCall' => [TextData\CharacterConvert::class,
'code'],
550 'argumentCount' =>
'1',
554 'functionCall' => [LookupRef\RowColumnInformation::class,
'COLUMN'],
555 'argumentCount' =>
'-1',
556 'passCellReference' =>
true,
557 'passByReference' => [
true],
561 'functionCall' => [LookupRef\RowColumnInformation::class,
'COLUMNS'],
562 'argumentCount' =>
'1',
566 'functionCall' => [MathTrig\Combinations::class,
'withoutRepetition'],
567 'argumentCount' =>
'2',
571 'functionCall' => [MathTrig\Combinations::class,
'withRepetition'],
572 'argumentCount' =>
'2',
576 'functionCall' => [Engineering\Complex::class,
'COMPLEX'],
577 'argumentCount' =>
'2,3',
581 'functionCall' => [TextData\Concatenate::class,
'CONCATENATE'],
582 'argumentCount' =>
'1+',
586 'functionCall' => [TextData\Concatenate::class,
'CONCATENATE'],
587 'argumentCount' =>
'1+',
591 'functionCall' => [Statistical\Confidence::class,
'CONFIDENCE'],
592 'argumentCount' =>
'3',
594 'CONFIDENCE.NORM' => [
596 'functionCall' => [Statistical\Confidence::class,
'CONFIDENCE'],
597 'argumentCount' =>
'3',
601 'functionCall' => [Functions::class,
'DUMMY'],
602 'argumentCount' =>
'3',
606 'functionCall' => [Engineering\ConvertUOM::class,
'CONVERT'],
607 'argumentCount' =>
'3',
611 'functionCall' => [Statistical\Trends::class,
'CORREL'],
612 'argumentCount' =>
'2',
616 'functionCall' => [MathTrig\Trig\Cosine::class,
'cos'],
617 'argumentCount' =>
'1',
621 'functionCall' => [MathTrig\Trig\Cosine::class,
'cosh'],
622 'argumentCount' =>
'1',
626 'functionCall' => [MathTrig\Trig\Cotangent::class,
'cot'],
627 'argumentCount' =>
'1',
631 'functionCall' => [MathTrig\Trig\Cotangent::class,
'coth'],
632 'argumentCount' =>
'1',
636 'functionCall' => [Statistical\Counts::class,
'COUNT'],
637 'argumentCount' =>
'1+',
641 'functionCall' => [Statistical\Counts::class,
'COUNTA'],
642 'argumentCount' =>
'1+',
646 'functionCall' => [Statistical\Counts::class,
'COUNTBLANK'],
647 'argumentCount' =>
'1',
651 'functionCall' => [Statistical\Conditional::class,
'COUNTIF'],
652 'argumentCount' =>
'2',
656 'functionCall' => [Statistical\Conditional::class,
'COUNTIFS'],
657 'argumentCount' =>
'2+',
661 'functionCall' => [Financial\Coupons::class,
'COUPDAYBS'],
662 'argumentCount' =>
'3,4',
666 'functionCall' => [Financial\Coupons::class,
'COUPDAYS'],
667 'argumentCount' =>
'3,4',
671 'functionCall' => [Financial\Coupons::class,
'COUPDAYSNC'],
672 'argumentCount' =>
'3,4',
676 'functionCall' => [Financial\Coupons::class,
'COUPNCD'],
677 'argumentCount' =>
'3,4',
681 'functionCall' => [Financial\Coupons::class,
'COUPNUM'],
682 'argumentCount' =>
'3,4',
686 'functionCall' => [Financial\Coupons::class,
'COUPPCD'],
687 'argumentCount' =>
'3,4',
691 'functionCall' => [Statistical\Trends::class,
'COVAR'],
692 'argumentCount' =>
'2',
696 'functionCall' => [Statistical\Trends::class,
'COVAR'],
697 'argumentCount' =>
'2',
701 'functionCall' => [Functions::class,
'DUMMY'],
702 'argumentCount' =>
'2',
706 'functionCall' => [Statistical\Distributions\Binomial::class,
'inverse'],
707 'argumentCount' =>
'3',
711 'functionCall' => [MathTrig\Trig\Cosecant::class,
'csc'],
712 'argumentCount' =>
'1',
716 'functionCall' => [MathTrig\Trig\Cosecant::class,
'csch'],
717 'argumentCount' =>
'1',
721 'functionCall' => [Functions::class,
'DUMMY'],
722 'argumentCount' =>
'?',
726 'functionCall' => [Functions::class,
'DUMMY'],
727 'argumentCount' =>
'?',
729 'CUBEMEMBERPROPERTY' => [
731 'functionCall' => [Functions::class,
'DUMMY'],
732 'argumentCount' =>
'?',
734 'CUBERANKEDMEMBER' => [
736 'functionCall' => [Functions::class,
'DUMMY'],
737 'argumentCount' =>
'?',
741 'functionCall' => [Functions::class,
'DUMMY'],
742 'argumentCount' =>
'?',
746 'functionCall' => [Functions::class,
'DUMMY'],
747 'argumentCount' =>
'?',
751 'functionCall' => [Functions::class,
'DUMMY'],
752 'argumentCount' =>
'?',
756 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class,
'interest'],
757 'argumentCount' =>
'6',
761 'functionCall' => [Financial\CashFlow\Constant\Periodic\Cumulative::class,
'principal'],
762 'argumentCount' =>
'6',
766 'functionCall' => [DateTimeExcel\Date::class,
'fromYMD'],
767 'argumentCount' =>
'3',
771 'functionCall' => [DateTimeExcel\Difference::class,
'interval'],
772 'argumentCount' =>
'2,3',
776 'functionCall' => [Functions::class,
'DUMMY'],
777 'argumentCount' =>
'?',
781 'functionCall' => [DateTimeExcel\DateValue::class,
'fromString'],
782 'argumentCount' =>
'1',
786 'functionCall' => [Database\DAverage::class,
'evaluate'],
787 'argumentCount' =>
'3',
791 'functionCall' => [DateTimeExcel\DateParts::class,
'day'],
792 'argumentCount' =>
'1',
796 'functionCall' => [DateTimeExcel\Days::class,
'between'],
797 'argumentCount' =>
'2',
801 'functionCall' => [DateTimeExcel\Days360::class,
'between'],
802 'argumentCount' =>
'2,3',
806 'functionCall' => [Financial\Depreciation::class,
'DB'],
807 'argumentCount' =>
'4,5',
811 'functionCall' => [Functions::class,
'DUMMY'],
812 'argumentCount' =>
'1',
816 'functionCall' => [Database\DCount::class,
'evaluate'],
817 'argumentCount' =>
'3',
821 'functionCall' => [Database\DCountA::class,
'evaluate'],
822 'argumentCount' =>
'3',
826 'functionCall' => [Financial\Depreciation::class,
'DDB'],
827 'argumentCount' =>
'4,5',
831 'functionCall' => [Engineering\ConvertDecimal::class,
'toBinary'],
832 'argumentCount' =>
'1,2',
836 'functionCall' => [Engineering\ConvertDecimal::class,
'toHex'],
837 'argumentCount' =>
'1,2',
841 'functionCall' => [Engineering\ConvertDecimal::class,
'toOctal'],
842 'argumentCount' =>
'1,2',
846 'functionCall' => [Functions::class,
'DUMMY'],
847 'argumentCount' =>
'2',
851 'functionCall' => [MathTrig\Angle::class,
'toDegrees'],
852 'argumentCount' =>
'1',
856 'functionCall' => [Engineering\Compare::class,
'DELTA'],
857 'argumentCount' =>
'1,2',
861 'functionCall' => [Statistical\Deviations::class,
'sumSquares'],
862 'argumentCount' =>
'1+',
866 'functionCall' => [Database\DGet::class,
'evaluate'],
867 'argumentCount' =>
'3',
871 'functionCall' => [Financial\Securities\Rates::class,
'discount'],
872 'argumentCount' =>
'4,5',
876 'functionCall' => [Database\DMax::class,
'evaluate'],
877 'argumentCount' =>
'3',
881 'functionCall' => [Database\DMin::class,
'evaluate'],
882 'argumentCount' =>
'3',
886 'functionCall' => [TextData\Format::class,
'DOLLAR'],
887 'argumentCount' =>
'1,2',
891 'functionCall' => [Financial\Dollar::class,
'decimal'],
892 'argumentCount' =>
'2',
896 'functionCall' => [Financial\Dollar::class,
'fractional'],
897 'argumentCount' =>
'2',
901 'functionCall' => [Database\DProduct::class,
'evaluate'],
902 'argumentCount' =>
'3',
906 'functionCall' => [Database\DStDev::class,
'evaluate'],
907 'argumentCount' =>
'3',
911 'functionCall' => [Database\DStDevP::class,
'evaluate'],
912 'argumentCount' =>
'3',
916 'functionCall' => [Database\DSum::class,
'evaluate'],
917 'argumentCount' =>
'3',
921 'functionCall' => [Functions::class,
'DUMMY'],
922 'argumentCount' =>
'5,6',
926 'functionCall' => [Database\DVar::class,
'evaluate'],
927 'argumentCount' =>
'3',
931 'functionCall' => [Database\DVarP::class,
'evaluate'],
932 'argumentCount' =>
'3',
936 'functionCall' => [Functions::class,
'DUMMY'],
937 'argumentCount' =>
'1,2',
941 'functionCall' => [DateTimeExcel\Month::class,
'adjust'],
942 'argumentCount' =>
'2',
946 'functionCall' => [Financial\InterestRate::class,
'effective'],
947 'argumentCount' =>
'2',
951 'functionCall' => [Web\Service::class,
'urlEncode'],
952 'argumentCount' =>
'1',
956 'functionCall' => [DateTimeExcel\Month::class,
'lastDay'],
957 'argumentCount' =>
'2',
961 'functionCall' => [Engineering\Erf::class,
'ERF'],
962 'argumentCount' =>
'1,2',
966 'functionCall' => [Engineering\Erf::class,
'ERFPRECISE'],
967 'argumentCount' =>
'1',
971 'functionCall' => [Engineering\ErfC::class,
'ERFC'],
972 'argumentCount' =>
'1',
976 'functionCall' => [Engineering\ErfC::class,
'ERFC'],
977 'argumentCount' =>
'1',
981 'functionCall' => [Functions::class,
'errorType'],
982 'argumentCount' =>
'1',
986 'functionCall' => [MathTrig\Round::class,
'even'],
987 'argumentCount' =>
'1',
991 'functionCall' => [TextData\Text::class,
'exact'],
992 'argumentCount' =>
'2',
996 'functionCall' => [MathTrig\Exp::class,
'evaluate'],
997 'argumentCount' =>
'1',
1001 'functionCall' => [Statistical\Distributions\Exponential::class,
'distribution'],
1002 'argumentCount' =>
'3',
1006 'functionCall' => [Statistical\Distributions\Exponential::class,
'distribution'],
1007 'argumentCount' =>
'3',
1011 'functionCall' => [MathTrig\Factorial::class,
'fact'],
1012 'argumentCount' =>
'1',
1016 'functionCall' => [MathTrig\Factorial::class,
'factDouble'],
1017 'argumentCount' =>
'1',
1021 'functionCall' => [Logical\Boolean::class,
'FALSE'],
1022 'argumentCount' =>
'0',
1026 'functionCall' => [Functions::class,
'DUMMY'],
1027 'argumentCount' =>
'3',
1031 'functionCall' => [Statistical\Distributions\F::class,
'distribution'],
1032 'argumentCount' =>
'4',
1036 'functionCall' => [Functions::class,
'DUMMY'],
1037 'argumentCount' =>
'3',
1041 'functionCall' => [Functions::class,
'DUMMY'],
1042 'argumentCount' =>
'3+',
1046 'functionCall' => [Functions::class,
'DUMMY'],
1047 'argumentCount' =>
'2',
1051 'functionCall' => [TextData\Search::class,
'sensitive'],
1052 'argumentCount' =>
'2,3',
1056 'functionCall' => [TextData\Search::class,
'sensitive'],
1057 'argumentCount' =>
'2,3',
1061 'functionCall' => [Functions::class,
'DUMMY'],
1062 'argumentCount' =>
'3',
1066 'functionCall' => [Functions::class,
'DUMMY'],
1067 'argumentCount' =>
'3',
1071 'functionCall' => [Functions::class,
'DUMMY'],
1072 'argumentCount' =>
'3',
1076 'functionCall' => [Statistical\Distributions\Fisher::class,
'distribution'],
1077 'argumentCount' =>
'1',
1081 'functionCall' => [Statistical\Distributions\Fisher::class,
'inverse'],
1082 'argumentCount' =>
'1',
1086 'functionCall' => [TextData\Format::class,
'FIXEDFORMAT'],
1087 'argumentCount' =>
'1-3',
1091 'functionCall' => [MathTrig\Floor::class,
'floor'],
1092 'argumentCount' =>
'1-2',
1096 'functionCall' => [MathTrig\Floor::class,
'math'],
1097 'argumentCount' =>
'1-3',
1099 'FLOOR.PRECISE' => [
1101 'functionCall' => [MathTrig\Floor::class,
'precise'],
1102 'argumentCount' =>
'1-2',
1106 'functionCall' => [Statistical\Trends::class,
'FORECAST'],
1107 'argumentCount' =>
'3',
1111 'functionCall' => [Functions::class,
'DUMMY'],
1112 'argumentCount' =>
'3-6',
1114 'FORECAST.ETS.CONFINT' => [
1116 'functionCall' => [Functions::class,
'DUMMY'],
1117 'argumentCount' =>
'3-6',
1119 'FORECAST.ETS.SEASONALITY' => [
1121 'functionCall' => [Functions::class,
'DUMMY'],
1122 'argumentCount' =>
'2-4',
1124 'FORECAST.ETS.STAT' => [
1126 'functionCall' => [Functions::class,
'DUMMY'],
1127 'argumentCount' =>
'3-6',
1129 'FORECAST.LINEAR' => [
1131 'functionCall' => [Statistical\Trends::class,
'FORECAST'],
1132 'argumentCount' =>
'3',
1136 'functionCall' => [LookupRef\Formula::class,
'text'],
1137 'argumentCount' =>
'1',
1138 'passCellReference' =>
true,
1139 'passByReference' => [
true],
1143 'functionCall' => [Functions::class,
'DUMMY'],
1144 'argumentCount' =>
'2',
1148 'functionCall' => [Functions::class,
'DUMMY'],
1149 'argumentCount' =>
'2',
1153 'functionCall' => [Functions::class,
'DUMMY'],
1154 'argumentCount' =>
'2',
1158 'functionCall' => [Financial\CashFlow\Constant\Periodic::class,
'futureValue'],
1159 'argumentCount' =>
'3-5',
1163 'functionCall' => [Financial\CashFlow\Single::class,
'futureValue'],
1164 'argumentCount' =>
'2',
1168 'functionCall' => [Statistical\Distributions\Gamma::class,
'gamma'],
1169 'argumentCount' =>
'1',
1173 'functionCall' => [Statistical\Distributions\Gamma::class,
'distribution'],
1174 'argumentCount' =>
'4',
1178 'functionCall' => [Statistical\Distributions\Gamma::class,
'distribution'],
1179 'argumentCount' =>
'4',
1183 'functionCall' => [Statistical\Distributions\Gamma::class,
'inverse'],
1184 'argumentCount' =>
'3',
1188 'functionCall' => [Statistical\Distributions\Gamma::class,
'inverse'],
1189 'argumentCount' =>
'3',
1193 'functionCall' => [Statistical\Distributions\Gamma::class,
'ln'],
1194 'argumentCount' =>
'1',
1196 'GAMMALN.PRECISE' => [
1198 'functionCall' => [Statistical\Distributions\Gamma::class,
'ln'],
1199 'argumentCount' =>
'1',
1203 'functionCall' => [Statistical\Distributions\StandardNormal::class,
'gauss'],
1204 'argumentCount' =>
'1',
1208 'functionCall' => [MathTrig\Gcd::class,
'evaluate'],
1209 'argumentCount' =>
'1+',
1213 'functionCall' => [Statistical\Averages\Mean::class,
'geometric'],
1214 'argumentCount' =>
'1+',
1218 'functionCall' => [Engineering\Compare::class,
'GESTEP'],
1219 'argumentCount' =>
'1,2',
1223 'functionCall' => [Functions::class,
'DUMMY'],
1224 'argumentCount' =>
'2+',
1228 'functionCall' => [Statistical\Trends::class,
'GROWTH'],
1229 'argumentCount' =>
'1-4',
1233 'functionCall' => [Statistical\Averages\Mean::class,
'harmonic'],
1234 'argumentCount' =>
'1+',
1238 'functionCall' => [Engineering\ConvertHex::class,
'toBinary'],
1239 'argumentCount' =>
'1,2',
1243 'functionCall' => [Engineering\ConvertHex::class,
'toDecimal'],
1244 'argumentCount' =>
'1',
1248 'functionCall' => [Engineering\ConvertHex::class,
'toOctal'],
1249 'argumentCount' =>
'1,2',
1253 'functionCall' => [LookupRef\HLookup::class,
'lookup'],
1254 'argumentCount' =>
'3,4',
1258 'functionCall' => [DateTimeExcel\TimeParts::class,
'hour'],
1259 'argumentCount' =>
'1',
1263 'functionCall' => [LookupRef\Hyperlink::class,
'set'],
1264 'argumentCount' =>
'1,2',
1265 'passCellReference' =>
true,
1269 'functionCall' => [Statistical\Distributions\HyperGeometric::class,
'distribution'],
1270 'argumentCount' =>
'4',
1274 'functionCall' => [Functions::class,
'DUMMY'],
1275 'argumentCount' =>
'5',
1279 'functionCall' => [Logical\Conditional::class,
'statementIf'],
1280 'argumentCount' =>
'1-3',
1284 'functionCall' => [Logical\Conditional::class,
'IFERROR'],
1285 'argumentCount' =>
'2',
1289 'functionCall' => [Logical\Conditional::class,
'IFNA'],
1290 'argumentCount' =>
'2',
1294 'functionCall' => [Logical\Conditional::class,
'IFS'],
1295 'argumentCount' =>
'2+',
1299 'functionCall' => [Engineering\ComplexFunctions::class,
'IMABS'],
1300 'argumentCount' =>
'1',
1304 'functionCall' => [Engineering\Complex::class,
'IMAGINARY'],
1305 'argumentCount' =>
'1',
1309 'functionCall' => [Engineering\ComplexFunctions::class,
'IMARGUMENT'],
1310 'argumentCount' =>
'1',
1314 'functionCall' => [Engineering\ComplexFunctions::class,
'IMCONJUGATE'],
1315 'argumentCount' =>
'1',
1319 'functionCall' => [Engineering\ComplexFunctions::class,
'IMCOS'],
1320 'argumentCount' =>
'1',
1324 'functionCall' => [Engineering\ComplexFunctions::class,
'IMCOSH'],
1325 'argumentCount' =>
'1',
1329 'functionCall' => [Engineering\ComplexFunctions::class,
'IMCOT'],
1330 'argumentCount' =>
'1',
1334 'functionCall' => [Engineering\ComplexFunctions::class,
'IMCSC'],
1335 'argumentCount' =>
'1',
1339 'functionCall' => [Engineering\ComplexFunctions::class,
'IMCSCH'],
1340 'argumentCount' =>
'1',
1344 'functionCall' => [Engineering\ComplexOperations::class,
'IMDIV'],
1345 'argumentCount' =>
'2',
1349 'functionCall' => [Engineering\ComplexFunctions::class,
'IMEXP'],
1350 'argumentCount' =>
'1',
1354 'functionCall' => [Engineering\ComplexFunctions::class,
'IMLN'],
1355 'argumentCount' =>
'1',
1359 'functionCall' => [Engineering\ComplexFunctions::class,
'IMLOG10'],
1360 'argumentCount' =>
'1',
1364 'functionCall' => [Engineering\ComplexFunctions::class,
'IMLOG2'],
1365 'argumentCount' =>
'1',
1369 'functionCall' => [Engineering\ComplexFunctions::class,
'IMPOWER'],
1370 'argumentCount' =>
'2',
1374 'functionCall' => [Engineering\ComplexOperations::class,
'IMPRODUCT'],
1375 'argumentCount' =>
'1+',
1379 'functionCall' => [Engineering\Complex::class,
'IMREAL'],
1380 'argumentCount' =>
'1',
1384 'functionCall' => [Engineering\ComplexFunctions::class,
'IMSEC'],
1385 'argumentCount' =>
'1',
1389 'functionCall' => [Engineering\ComplexFunctions::class,
'IMSECH'],
1390 'argumentCount' =>
'1',
1394 'functionCall' => [Engineering\ComplexFunctions::class,
'IMSIN'],
1395 'argumentCount' =>
'1',
1399 'functionCall' => [Engineering\ComplexFunctions::class,
'IMSINH'],
1400 'argumentCount' =>
'1',
1404 'functionCall' => [Engineering\ComplexFunctions::class,
'IMSQRT'],
1405 'argumentCount' =>
'1',
1409 'functionCall' => [Engineering\ComplexOperations::class,
'IMSUB'],
1410 'argumentCount' =>
'2',
1414 'functionCall' => [Engineering\ComplexOperations::class,
'IMSUM'],
1415 'argumentCount' =>
'1+',
1419 'functionCall' => [Engineering\ComplexFunctions::class,
'IMTAN'],
1420 'argumentCount' =>
'1',
1424 'functionCall' => [LookupRef\Matrix::class,
'index'],
1425 'argumentCount' =>
'1-4',
1429 'functionCall' => [LookupRef\Indirect::class,
'INDIRECT'],
1430 'argumentCount' =>
'1,2',
1431 'passCellReference' =>
true,
1435 'functionCall' => [Functions::class,
'DUMMY'],
1436 'argumentCount' =>
'1',
1440 'functionCall' => [MathTrig\IntClass::class,
'evaluate'],
1441 'argumentCount' =>
'1',
1445 'functionCall' => [Statistical\Trends::class,
'INTERCEPT'],
1446 'argumentCount' =>
'2',
1450 'functionCall' => [Financial\Securities\Rates::class,
'interest'],
1451 'argumentCount' =>
'4,5',
1455 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class,
'payment'],
1456 'argumentCount' =>
'4-6',
1460 'functionCall' => [Financial\CashFlow\Variable\Periodic::class,
'rate'],
1461 'argumentCount' =>
'1,2',
1465 'functionCall' => [Functions::class,
'isBlank'],
1466 'argumentCount' =>
'1',
1470 'functionCall' => [Functions::class,
'isErr'],
1471 'argumentCount' =>
'1',
1475 'functionCall' => [Functions::class,
'isError'],
1476 'argumentCount' =>
'1',
1480 'functionCall' => [Functions::class,
'isEven'],
1481 'argumentCount' =>
'1',
1485 'functionCall' => [Functions::class,
'isFormula'],
1486 'argumentCount' =>
'1',
1487 'passCellReference' =>
true,
1488 'passByReference' => [
true],
1492 'functionCall' => [Functions::class,
'isLogical'],
1493 'argumentCount' =>
'1',
1497 'functionCall' => [Functions::class,
'isNa'],
1498 'argumentCount' =>
'1',
1502 'functionCall' => [Functions::class,
'isNonText'],
1503 'argumentCount' =>
'1',
1507 'functionCall' => [Functions::class,
'isNumber'],
1508 'argumentCount' =>
'1',
1512 'functionCall' => [Functions::class,
'DUMMY'],
1513 'argumentCount' =>
'1,2',
1517 'functionCall' => [Functions::class,
'isOdd'],
1518 'argumentCount' =>
'1',
1522 'functionCall' => [DateTimeExcel\Week::class,
'isoWeekNumber'],
1523 'argumentCount' =>
'1',
1527 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class,
'schedulePayment'],
1528 'argumentCount' =>
'4',
1532 'functionCall' => [Functions::class,
'DUMMY'],
1533 'argumentCount' =>
'1',
1537 'functionCall' => [Functions::class,
'isText'],
1538 'argumentCount' =>
'1',
1542 'functionCall' => [Functions::class,
'DUMMY'],
1543 'argumentCount' =>
'?',
1547 'functionCall' => [Functions::class,
'DUMMY'],
1548 'argumentCount' =>
'1',
1552 'functionCall' => [Statistical\Deviations::class,
'kurtosis'],
1553 'argumentCount' =>
'1+',
1557 'functionCall' => [Statistical\Size::class,
'large'],
1558 'argumentCount' =>
'2',
1562 'functionCall' => [MathTrig\Lcm::class,
'evaluate'],
1563 'argumentCount' =>
'1+',
1567 'functionCall' => [TextData\Extract::class,
'left'],
1568 'argumentCount' =>
'1,2',
1572 'functionCall' => [TextData\Extract::class,
'left'],
1573 'argumentCount' =>
'1,2',
1577 'functionCall' => [TextData\Text::class,
'length'],
1578 'argumentCount' =>
'1',
1582 'functionCall' => [TextData\Text::class,
'length'],
1583 'argumentCount' =>
'1',
1587 'functionCall' => [Statistical\Trends::class,
'LINEST'],
1588 'argumentCount' =>
'1-4',
1592 'functionCall' => [MathTrig\Logarithms::class,
'natural'],
1593 'argumentCount' =>
'1',
1597 'functionCall' => [MathTrig\Logarithms::class,
'withBase'],
1598 'argumentCount' =>
'1,2',
1602 'functionCall' => [MathTrig\Logarithms::class,
'base10'],
1603 'argumentCount' =>
'1',
1607 'functionCall' => [Statistical\Trends::class,
'LOGEST'],
1608 'argumentCount' =>
'1-4',
1612 'functionCall' => [Statistical\Distributions\LogNormal::class,
'inverse'],
1613 'argumentCount' =>
'3',
1617 'functionCall' => [Statistical\Distributions\LogNormal::class,
'cumulative'],
1618 'argumentCount' =>
'3',
1622 'functionCall' => [Statistical\Distributions\LogNormal::class,
'distribution'],
1623 'argumentCount' =>
'4',
1627 'functionCall' => [Statistical\Distributions\LogNormal::class,
'inverse'],
1628 'argumentCount' =>
'3',
1632 'functionCall' => [LookupRef\Lookup::class,
'lookup'],
1633 'argumentCount' =>
'2,3',
1637 'functionCall' => [TextData\CaseConvert::class,
'lower'],
1638 'argumentCount' =>
'1',
1642 'functionCall' => [LookupRef\ExcelMatch::class,
'MATCH'],
1643 'argumentCount' =>
'2,3',
1647 'functionCall' => [Statistical\Maximum::class,
'max'],
1648 'argumentCount' =>
'1+',
1652 'functionCall' => [Statistical\Maximum::class,
'maxA'],
1653 'argumentCount' =>
'1+',
1657 'functionCall' => [Statistical\Conditional::class,
'MAXIFS'],
1658 'argumentCount' =>
'3+',
1662 'functionCall' => [MathTrig\MatrixFunctions::class,
'determinant'],
1663 'argumentCount' =>
'1',
1667 'functionCall' => [Functions::class,
'DUMMY'],
1668 'argumentCount' =>
'5,6',
1672 'functionCall' => [Statistical\Averages::class,
'median'],
1673 'argumentCount' =>
'1+',
1677 'functionCall' => [Functions::class,
'DUMMY'],
1678 'argumentCount' =>
'2+',
1682 'functionCall' => [TextData\Extract::class,
'mid'],
1683 'argumentCount' =>
'3',
1687 'functionCall' => [TextData\Extract::class,
'mid'],
1688 'argumentCount' =>
'3',
1692 'functionCall' => [Statistical\Minimum::class,
'min'],
1693 'argumentCount' =>
'1+',
1697 'functionCall' => [Statistical\Minimum::class,
'minA'],
1698 'argumentCount' =>
'1+',
1702 'functionCall' => [Statistical\Conditional::class,
'MINIFS'],
1703 'argumentCount' =>
'3+',
1707 'functionCall' => [DateTimeExcel\TimeParts::class,
'minute'],
1708 'argumentCount' =>
'1',
1712 'functionCall' => [MathTrig\MatrixFunctions::class,
'inverse'],
1713 'argumentCount' =>
'1',
1717 'functionCall' => [Financial\CashFlow\Variable\Periodic::class,
'modifiedRate'],
1718 'argumentCount' =>
'3',
1722 'functionCall' => [MathTrig\MatrixFunctions::class,
'multiply'],
1723 'argumentCount' =>
'2',
1727 'functionCall' => [MathTrig\Operations::class,
'mod'],
1728 'argumentCount' =>
'2',
1732 'functionCall' => [Statistical\Averages::class,
'mode'],
1733 'argumentCount' =>
'1+',
1737 'functionCall' => [Functions::class,
'DUMMY'],
1738 'argumentCount' =>
'1+',
1742 'functionCall' => [Statistical\Averages::class,
'mode'],
1743 'argumentCount' =>
'1+',
1747 'functionCall' => [DateTimeExcel\DateParts::class,
'month'],
1748 'argumentCount' =>
'1',
1752 'functionCall' => [MathTrig\Round::class,
'multiple'],
1753 'argumentCount' =>
'2',
1757 'functionCall' => [MathTrig\Factorial::class,
'multinomial'],
1758 'argumentCount' =>
'1+',
1762 'functionCall' => [MathTrig\MatrixFunctions::class,
'identity'],
1763 'argumentCount' =>
'1',
1767 'functionCall' => [Functions::class,
'n'],
1768 'argumentCount' =>
'1',
1772 'functionCall' => [Functions::class,
'NA'],
1773 'argumentCount' =>
'0',
1777 'functionCall' => [Statistical\Distributions\Binomial::class,
'negative'],
1778 'argumentCount' =>
'3',
1780 'NEGBINOM.DIST' => [
1782 'functionCall' => [Functions::class,
'DUMMY'],
1783 'argumentCount' =>
'4',
1787 'functionCall' => [DateTimeExcel\NetworkDays::class,
'count'],
1788 'argumentCount' =>
'2-3',
1790 'NETWORKDAYS.INTL' => [
1792 'functionCall' => [Functions::class,
'DUMMY'],
1793 'argumentCount' =>
'2-4',
1797 'functionCall' => [Financial\InterestRate::class,
'nominal'],
1798 'argumentCount' =>
'2',
1802 'functionCall' => [Statistical\Distributions\Normal::class,
'distribution'],
1803 'argumentCount' =>
'4',
1807 'functionCall' => [Statistical\Distributions\Normal::class,
'distribution'],
1808 'argumentCount' =>
'4',
1812 'functionCall' => [Statistical\Distributions\Normal::class,
'inverse'],
1813 'argumentCount' =>
'3',
1817 'functionCall' => [Statistical\Distributions\Normal::class,
'inverse'],
1818 'argumentCount' =>
'3',
1822 'functionCall' => [Statistical\Distributions\StandardNormal::class,
'cumulative'],
1823 'argumentCount' =>
'1',
1827 'functionCall' => [Statistical\Distributions\StandardNormal::class,
'distribution'],
1828 'argumentCount' =>
'1,2',
1832 'functionCall' => [Statistical\Distributions\StandardNormal::class,
'inverse'],
1833 'argumentCount' =>
'1',
1837 'functionCall' => [Statistical\Distributions\StandardNormal::class,
'inverse'],
1838 'argumentCount' =>
'1',
1842 'functionCall' => [Logical\Operations::class,
'NOT'],
1843 'argumentCount' =>
'1',
1847 'functionCall' => [DateTimeExcel\Current::class,
'now'],
1848 'argumentCount' =>
'0',
1852 'functionCall' => [Financial\CashFlow\Constant\Periodic::class,
'periods'],
1853 'argumentCount' =>
'3-5',
1857 'functionCall' => [Financial\CashFlow\Variable\Periodic::class,
'presentValue'],
1858 'argumentCount' =>
'2+',
1862 'functionCall' => [Functions::class,
'DUMMY'],
1863 'argumentCount' =>
'?',
1867 'functionCall' => [TextData\Format::class,
'NUMBERVALUE'],
1868 'argumentCount' =>
'1+',
1872 'functionCall' => [Engineering\ConvertOctal::class,
'toBinary'],
1873 'argumentCount' =>
'1,2',
1877 'functionCall' => [Engineering\ConvertOctal::class,
'toDecimal'],
1878 'argumentCount' =>
'1',
1882 'functionCall' => [Engineering\ConvertOctal::class,
'toHex'],
1883 'argumentCount' =>
'1,2',
1887 'functionCall' => [MathTrig\Round::class,
'odd'],
1888 'argumentCount' =>
'1',
1892 'functionCall' => [Functions::class,
'DUMMY'],
1893 'argumentCount' =>
'8,9',
1897 'functionCall' => [Functions::class,
'DUMMY'],
1898 'argumentCount' =>
'8,9',
1902 'functionCall' => [Functions::class,
'DUMMY'],
1903 'argumentCount' =>
'7,8',
1907 'functionCall' => [Functions::class,
'DUMMY'],
1908 'argumentCount' =>
'7,8',
1912 'functionCall' => [LookupRef\Offset::class,
'OFFSET'],
1913 'argumentCount' =>
'3-5',
1914 'passCellReference' =>
true,
1915 'passByReference' => [
true],
1919 'functionCall' => [Logical\Operations::class,
'logicalOr'],
1920 'argumentCount' =>
'1+',
1924 'functionCall' => [Financial\CashFlow\Single::class,
'periods'],
1925 'argumentCount' =>
'3',
1929 'functionCall' => [Statistical\Trends::class,
'CORREL'],
1930 'argumentCount' =>
'2',
1934 'functionCall' => [Statistical\Percentiles::class,
'PERCENTILE'],
1935 'argumentCount' =>
'2',
1937 'PERCENTILE.EXC' => [
1939 'functionCall' => [Functions::class,
'DUMMY'],
1940 'argumentCount' =>
'2',
1942 'PERCENTILE.INC' => [
1944 'functionCall' => [Statistical\Percentiles::class,
'PERCENTILE'],
1945 'argumentCount' =>
'2',
1949 'functionCall' => [Statistical\Percentiles::class,
'PERCENTRANK'],
1950 'argumentCount' =>
'2,3',
1952 'PERCENTRANK.EXC' => [
1954 'functionCall' => [Functions::class,
'DUMMY'],
1955 'argumentCount' =>
'2,3',
1957 'PERCENTRANK.INC' => [
1959 'functionCall' => [Statistical\Percentiles::class,
'PERCENTRANK'],
1960 'argumentCount' =>
'2,3',
1964 'functionCall' => [Statistical\Permutations::class,
'PERMUT'],
1965 'argumentCount' =>
'2',
1969 'functionCall' => [Statistical\Permutations::class,
'PERMUTATIONA'],
1970 'argumentCount' =>
'2',
1974 'functionCall' => [Functions::class,
'DUMMY'],
1975 'argumentCount' =>
'1',
1979 'functionCall' => [Functions::class,
'DUMMY'],
1980 'argumentCount' =>
'1',
1984 'functionCall' =>
'pi',
1985 'argumentCount' =>
'0',
1989 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class,
'annuity'],
1990 'argumentCount' =>
'3-5',
1994 'functionCall' => [Statistical\Distributions\Poisson::class,
'distribution'],
1995 'argumentCount' =>
'3',
1999 'functionCall' => [Statistical\Distributions\Poisson::class,
'distribution'],
2000 'argumentCount' =>
'3',
2004 'functionCall' => [MathTrig\Operations::class,
'power'],
2005 'argumentCount' =>
'2',
2009 'functionCall' => [Financial\CashFlow\Constant\Periodic\Payments::class,
'interestPayment'],
2010 'argumentCount' =>
'4-6',
2014 'functionCall' => [Financial\Securities\Price::class,
'price'],
2015 'argumentCount' =>
'6,7',
2019 'functionCall' => [Financial\Securities\Price::class,
'priceDiscounted'],
2020 'argumentCount' =>
'4,5',
2024 'functionCall' => [Financial\Securities\Price::class,
'priceAtMaturity'],
2025 'argumentCount' =>
'5,6',
2029 'functionCall' => [Functions::class,
'DUMMY'],
2030 'argumentCount' =>
'3,4',
2034 'functionCall' => [MathTrig\Operations::class,
'product'],
2035 'argumentCount' =>
'1+',
2039 'functionCall' => [TextData\CaseConvert::class,
'proper'],
2040 'argumentCount' =>
'1',
2044 'functionCall' => [Financial\CashFlow\Constant\Periodic::class,
'presentValue'],
2045 'argumentCount' =>
'3-5',
2049 'functionCall' => [Statistical\Percentiles::class,
'QUARTILE'],
2050 'argumentCount' =>
'2',
2054 'functionCall' => [Functions::class,
'DUMMY'],
2055 'argumentCount' =>
'2',
2059 'functionCall' => [Statistical\Percentiles::class,
'QUARTILE'],
2060 'argumentCount' =>
'2',
2064 'functionCall' => [MathTrig\Operations::class,
'quotient'],
2065 'argumentCount' =>
'2',
2069 'functionCall' => [MathTrig\Angle::class,
'toRadians'],
2070 'argumentCount' =>
'1',
2074 'functionCall' => [MathTrig\Random::class,
'rand'],
2075 'argumentCount' =>
'0',
2079 'functionCall' => [Functions::class,
'DUMMY'],
2080 'argumentCount' =>
'0-5',
2084 'functionCall' => [MathTrig\Random::class,
'randBetween'],
2085 'argumentCount' =>
'2',
2089 'functionCall' => [Statistical\Percentiles::class,
'RANK'],
2090 'argumentCount' =>
'2,3',
2094 'functionCall' => [Functions::class,
'DUMMY'],
2095 'argumentCount' =>
'2,3',
2099 'functionCall' => [Statistical\Percentiles::class,
'RANK'],
2100 'argumentCount' =>
'2,3',
2104 'functionCall' => [Financial\CashFlow\Constant\Periodic\Interest::class,
'rate'],
2105 'argumentCount' =>
'3-6',
2109 'functionCall' => [Financial\Securities\Price::class,
'received'],
2110 'argumentCount' =>
'4-5',
2114 'functionCall' => [TextData\Replace::class,
'replace'],
2115 'argumentCount' =>
'4',
2119 'functionCall' => [TextData\Replace::class,
'replace'],
2120 'argumentCount' =>
'4',
2124 'functionCall' => [TextData\Concatenate::class,
'builtinREPT'],
2125 'argumentCount' =>
'2',
2129 'functionCall' => [TextData\Extract::class,
'right'],
2130 'argumentCount' =>
'1,2',
2134 'functionCall' => [TextData\Extract::class,
'right'],
2135 'argumentCount' =>
'1,2',
2139 'functionCall' => [MathTrig\Roman::class,
'evaluate'],
2140 'argumentCount' =>
'1,2',
2144 'functionCall' => [MathTrig\Round::class,
'round'],
2145 'argumentCount' =>
'2',
2147 'ROUNDBAHTDOWN' => [
2149 'functionCall' => [Functions::class,
'DUMMY'],
2150 'argumentCount' =>
'?',
2154 'functionCall' => [Functions::class,
'DUMMY'],
2155 'argumentCount' =>
'?',
2159 'functionCall' => [MathTrig\Round::class,
'down'],
2160 'argumentCount' =>
'2',
2164 'functionCall' => [MathTrig\Round::class,
'up'],
2165 'argumentCount' =>
'2',
2169 'functionCall' => [LookupRef\RowColumnInformation::class,
'ROW'],
2170 'argumentCount' =>
'-1',
2171 'passCellReference' =>
true,
2172 'passByReference' => [
true],
2176 'functionCall' => [LookupRef\RowColumnInformation::class,
'ROWS'],
2177 'argumentCount' =>
'1',
2181 'functionCall' => [Financial\CashFlow\Single::class,
'interestRate'],
2182 'argumentCount' =>
'3',
2186 'functionCall' => [Statistical\Trends::class,
'RSQ'],
2187 'argumentCount' =>
'2',
2191 'functionCall' => [Functions::class,
'DUMMY'],
2192 'argumentCount' =>
'1+',
2196 'functionCall' => [TextData\Search::class,
'insensitive'],
2197 'argumentCount' =>
'2,3',
2201 'functionCall' => [TextData\Search::class,
'insensitive'],
2202 'argumentCount' =>
'2,3',
2206 'functionCall' => [MathTrig\Trig\Secant::class,
'sec'],
2207 'argumentCount' =>
'1',
2211 'functionCall' => [MathTrig\Trig\Secant::class,
'sech'],
2212 'argumentCount' =>
'1',
2216 'functionCall' => [DateTimeExcel\TimeParts::class,
'second'],
2217 'argumentCount' =>
'1',
2221 'functionCall' => [Functions::class,
'DUMMY'],
2222 'argumentCount' =>
'2',
2226 'functionCall' => [MathTrig\SeriesSum::class,
'evaluate'],
2227 'argumentCount' =>
'4',
2231 'functionCall' => [Functions::class,
'DUMMY'],
2232 'argumentCount' =>
'0,1',
2236 'functionCall' => [Functions::class,
'DUMMY'],
2237 'argumentCount' =>
'0,1',
2241 'functionCall' => [MathTrig\Sign::class,
'evaluate'],
2242 'argumentCount' =>
'1',
2246 'functionCall' => [MathTrig\Trig\Sine::class,
'sin'],
2247 'argumentCount' =>
'1',
2251 'functionCall' => [MathTrig\Trig\Sine::class,
'sinh'],
2252 'argumentCount' =>
'1',
2256 'functionCall' => [Statistical\Deviations::class,
'skew'],
2257 'argumentCount' =>
'1+',
2261 'functionCall' => [Functions::class,
'DUMMY'],
2262 'argumentCount' =>
'1+',
2266 'functionCall' => [Financial\Depreciation::class,
'SLN'],
2267 'argumentCount' =>
'3',
2271 'functionCall' => [Statistical\Trends::class,
'SLOPE'],
2272 'argumentCount' =>
'2',
2276 'functionCall' => [Statistical\Size::class,
'small'],
2277 'argumentCount' =>
'2',
2281 'functionCall' => [Functions::class,
'DUMMY'],
2282 'argumentCount' =>
'1+',
2286 'functionCall' => [Functions::class,
'DUMMY'],
2287 'argumentCount' =>
'2+',
2291 'functionCall' => [MathTrig\Sqrt::class,
'sqrt'],
2292 'argumentCount' =>
'1',
2296 'functionCall' => [MathTrig\Sqrt::class,
'pi'],
2297 'argumentCount' =>
'1',
2301 'functionCall' => [Statistical\Standardize::class,
'execute'],
2302 'argumentCount' =>
'3',
2306 'functionCall' => [Statistical\StandardDeviations::class,
'STDEV'],
2307 'argumentCount' =>
'1+',
2311 'functionCall' => [Statistical\StandardDeviations::class,
'STDEV'],
2312 'argumentCount' =>
'1+',
2316 'functionCall' => [Statistical\StandardDeviations::class,
'STDEVP'],
2317 'argumentCount' =>
'1+',
2321 'functionCall' => [Statistical\StandardDeviations::class,
'STDEVA'],
2322 'argumentCount' =>
'1+',
2326 'functionCall' => [Statistical\StandardDeviations::class,
'STDEVP'],
2327 'argumentCount' =>
'1+',
2331 'functionCall' => [Statistical\StandardDeviations::class,
'STDEVPA'],
2332 'argumentCount' =>
'1+',
2336 'functionCall' => [Statistical\Trends::class,
'STEYX'],
2337 'argumentCount' =>
'2',
2341 'functionCall' => [TextData\Replace::class,
'substitute'],
2342 'argumentCount' =>
'3,4',
2346 'functionCall' => [MathTrig\Subtotal::class,
'evaluate'],
2347 'argumentCount' =>
'2+',
2348 'passCellReference' =>
true,
2352 'functionCall' => [MathTrig\Sum::class,
'sumErroringStrings'],
2353 'argumentCount' =>
'1+',
2357 'functionCall' => [Statistical\Conditional::class,
'SUMIF'],
2358 'argumentCount' =>
'2,3',
2362 'functionCall' => [Statistical\Conditional::class,
'SUMIFS'],
2363 'argumentCount' =>
'3+',
2367 'functionCall' => [MathTrig\Sum::class,
'product'],
2368 'argumentCount' =>
'1+',
2372 'functionCall' => [MathTrig\SumSquares::class,
'sumSquare'],
2373 'argumentCount' =>
'1+',
2377 'functionCall' => [MathTrig\SumSquares::class,
'sumXSquaredMinusYSquared'],
2378 'argumentCount' =>
'2',
2382 'functionCall' => [MathTrig\SumSquares::class,
'sumXSquaredPlusYSquared'],
2383 'argumentCount' =>
'2',
2387 'functionCall' => [MathTrig\SumSquares::class,
'sumXMinusYSquared'],
2388 'argumentCount' =>
'2',
2392 'functionCall' => [Logical\Conditional::class,
'statementSwitch'],
2393 'argumentCount' =>
'3+',
2397 'functionCall' => [Financial\Depreciation::class,
'SYD'],
2398 'argumentCount' =>
'4',
2402 'functionCall' => [TextData\Text::class,
'test'],
2403 'argumentCount' =>
'1',
2407 'functionCall' => [MathTrig\Trig\Tangent::class,
'tan'],
2408 'argumentCount' =>
'1',
2412 'functionCall' => [MathTrig\Trig\Tangent::class,
'tanh'],
2413 'argumentCount' =>
'1',
2417 'functionCall' => [Financial\TreasuryBill::class,
'bondEquivalentYield'],
2418 'argumentCount' =>
'3',
2422 'functionCall' => [Financial\TreasuryBill::class,
'price'],
2423 'argumentCount' =>
'3',
2427 'functionCall' => [Financial\TreasuryBill::class,
'yield'],
2428 'argumentCount' =>
'3',
2432 'functionCall' => [Statistical\Distributions\StudentT::class,
'distribution'],
2433 'argumentCount' =>
'3',
2437 'functionCall' => [Functions::class,
'DUMMY'],
2438 'argumentCount' =>
'3',
2442 'functionCall' => [Functions::class,
'DUMMY'],
2443 'argumentCount' =>
'2',
2447 'functionCall' => [Functions::class,
'DUMMY'],
2448 'argumentCount' =>
'2',
2452 'functionCall' => [TextData\Format::class,
'TEXTFORMAT'],
2453 'argumentCount' =>
'2',
2457 'functionCall' => [TextData\Concatenate::class,
'TEXTJOIN'],
2458 'argumentCount' =>
'3+',
2460 'THAIDAYOFWEEK' => [
2462 'functionCall' => [Functions::class,
'DUMMY'],
2463 'argumentCount' =>
'?',
2467 'functionCall' => [Functions::class,
'DUMMY'],
2468 'argumentCount' =>
'?',
2470 'THAIMONTHOFYEAR' => [
2472 'functionCall' => [Functions::class,
'DUMMY'],
2473 'argumentCount' =>
'?',
2477 'functionCall' => [Functions::class,
'DUMMY'],
2478 'argumentCount' =>
'?',
2480 'THAINUMSTRING' => [
2482 'functionCall' => [Functions::class,
'DUMMY'],
2483 'argumentCount' =>
'?',
2485 'THAISTRINGLENGTH' => [
2487 'functionCall' => [Functions::class,
'DUMMY'],
2488 'argumentCount' =>
'?',
2492 'functionCall' => [Functions::class,
'DUMMY'],
2493 'argumentCount' =>
'?',
2497 'functionCall' => [DateTimeExcel\Time::class,
'fromHMS'],
2498 'argumentCount' =>
'3',
2502 'functionCall' => [DateTimeExcel\TimeValue::class,
'fromString'],
2503 'argumentCount' =>
'1',
2507 'functionCall' => [Statistical\Distributions\StudentT::class,
'inverse'],
2508 'argumentCount' =>
'2',
2512 'functionCall' => [Statistical\Distributions\StudentT::class,
'inverse'],
2513 'argumentCount' =>
'2',
2517 'functionCall' => [Functions::class,
'DUMMY'],
2518 'argumentCount' =>
'2',
2522 'functionCall' => [DateTimeExcel\Current::class,
'today'],
2523 'argumentCount' =>
'0',
2527 'functionCall' => [LookupRef\Matrix::class,
'transpose'],
2528 'argumentCount' =>
'1',
2532 'functionCall' => [Statistical\Trends::class,
'TREND'],
2533 'argumentCount' =>
'1-4',
2537 'functionCall' => [TextData\Trim::class,
'spaces'],
2538 'argumentCount' =>
'1',
2542 'functionCall' => [Statistical\Averages\Mean::class,
'trim'],
2543 'argumentCount' =>
'2',
2547 'functionCall' => [Logical\Boolean::class,
'TRUE'],
2548 'argumentCount' =>
'0',
2552 'functionCall' => [MathTrig\Trunc::class,
'evaluate'],
2553 'argumentCount' =>
'1,2',
2557 'functionCall' => [Functions::class,
'DUMMY'],
2558 'argumentCount' =>
'4',
2562 'functionCall' => [Functions::class,
'DUMMY'],
2563 'argumentCount' =>
'4',
2567 'functionCall' => [Functions::class,
'TYPE'],
2568 'argumentCount' =>
'1',
2572 'functionCall' => [TextData\CharacterConvert::class,
'character'],
2573 'argumentCount' =>
'1',
2577 'functionCall' => [TextData\CharacterConvert::class,
'code'],
2578 'argumentCount' =>
'1',
2582 'functionCall' => [Functions::class,
'DUMMY'],
2583 'argumentCount' =>
'1+',
2587 'functionCall' => [TextData\CaseConvert::class,
'upper'],
2588 'argumentCount' =>
'1',
2592 'functionCall' => [Financial\Dollar::class,
'format'],
2593 'argumentCount' =>
'2',
2597 'functionCall' => [TextData\Format::class,
'VALUE'],
2598 'argumentCount' =>
'1',
2602 'functionCall' => [Functions::class,
'DUMMY'],
2603 'argumentCount' =>
'?',
2607 'functionCall' => [Statistical\Variances::class,
'VAR'],
2608 'argumentCount' =>
'1+',
2612 'functionCall' => [Statistical\Variances::class,
'VARP'],
2613 'argumentCount' =>
'1+',
2617 'functionCall' => [Statistical\Variances::class,
'VAR'],
2618 'argumentCount' =>
'1+',
2622 'functionCall' => [Statistical\Variances::class,
'VARA'],
2623 'argumentCount' =>
'1+',
2627 'functionCall' => [Statistical\Variances::class,
'VARP'],
2628 'argumentCount' =>
'1+',
2632 'functionCall' => [Statistical\Variances::class,
'VARPA'],
2633 'argumentCount' =>
'1+',
2637 'functionCall' => [Functions::class,
'DUMMY'],
2638 'argumentCount' =>
'5-7',
2642 'functionCall' => [LookupRef\VLookup::class,
'lookup'],
2643 'argumentCount' =>
'3,4',
2647 'functionCall' => [Web\Service::class,
'webService'],
2648 'argumentCount' =>
'1',
2652 'functionCall' => [DateTimeExcel\Week::class,
'day'],
2653 'argumentCount' =>
'1,2',
2657 'functionCall' => [DateTimeExcel\Week::class,
'number'],
2658 'argumentCount' =>
'1,2',
2662 'functionCall' => [Statistical\Distributions\Weibull::class,
'distribution'],
2663 'argumentCount' =>
'4',
2667 'functionCall' => [Statistical\Distributions\Weibull::class,
'distribution'],
2668 'argumentCount' =>
'4',
2672 'functionCall' => [DateTimeExcel\WorkDay::class,
'date'],
2673 'argumentCount' =>
'2-3',
2677 'functionCall' => [Functions::class,
'DUMMY'],
2678 'argumentCount' =>
'2-4',
2682 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class,
'rate'],
2683 'argumentCount' =>
'2,3',
2687 'functionCall' => [Functions::class,
'DUMMY'],
2688 'argumentCount' =>
'3-6',
2692 'functionCall' => [Financial\CashFlow\Variable\NonPeriodic::class,
'presentValue'],
2693 'argumentCount' =>
'3',
2697 'functionCall' => [Functions::class,
'DUMMY'],
2698 'argumentCount' =>
'2,3',
2702 'functionCall' => [Logical\Operations::class,
'logicalXor'],
2703 'argumentCount' =>
'1+',
2707 'functionCall' => [DateTimeExcel\DateParts::class,
'year'],
2708 'argumentCount' =>
'1',
2712 'functionCall' => [DateTimeExcel\YearFrac::class,
'fraction'],
2713 'argumentCount' =>
'2,3',
2717 'functionCall' => [Functions::class,
'DUMMY'],
2718 'argumentCount' =>
'6,7',
2722 'functionCall' => [Financial\Securities\Yields::class,
'yieldDiscounted'],
2723 'argumentCount' =>
'4,5',
2727 'functionCall' => [Financial\Securities\Yields::class,
'yieldAtMaturity'],
2728 'argumentCount' =>
'5,6',
2732 'functionCall' => [Statistical\Distributions\StandardNormal::class,
'zTest'],
2733 'argumentCount' =>
'2-3',
2737 'functionCall' => [Statistical\Distributions\StandardNormal::class,
'zTest'],
2738 'argumentCount' =>
'2-3',
2745 'argumentCount' =>
'*',
2746 'functionCall' => [Internal\MakeMatrix::class,
'make'],
2749 'argumentCount' =>
'*',
2750 'functionCall' => [Functions::class,
'NAME'],
2752 'WILDCARDMATCH' => [
2753 'argumentCount' =>
'2',
2754 'functionCall' => [Internal\WildcardMatch::class,
'compare'],
2760 $this->delta = 1 * 10 ** (0 - ini_get(
'precision'));
2764 $this->debugLog =
new Logger($this->cyclicReferenceStack);
2770 $localeFileDirectory = __DIR__ .
'/locale/';
2771 foreach (glob($localeFileDirectory .
'*', GLOB_ONLYDIR) as
$filename) {
2772 $filename = substr($filename, strlen($localeFileDirectory));
2773 if ($filename !=
'en') {
2774 self::$validLocaleLanguages[] =
$filename;
2794 if (!isset(self::$instance) || (self::$instance === null)) {
2795 self::$instance =
new self();
2798 return self::$instance;
2826 throw new Exception(
'Cloning the calculation engine is not allowed!');
2836 return self::$localeBoolean[
'TRUE'];
2846 return self::$localeBoolean[
'FALSE'];
2859 ($returnType == self::RETURN_ARRAY_AS_VALUE) ||
2860 ($returnType == self::RETURN_ARRAY_AS_ERROR) ||
2861 ($returnType == self::RETURN_ARRAY_AS_ARRAY)
2863 self::$returnArrayAsType = $returnType;
2878 return self::$returnArrayAsType;
2898 $this->calculationCacheEnabled = $pValue;
2923 $this->calculationCache = [];
2933 if (isset($this->calculationCache[$worksheetName])) {
2934 unset($this->calculationCache[$worksheetName]);
2946 if (isset($this->calculationCache[$fromWorksheetName])) {
2947 $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName];
2948 unset($this->calculationCache[$fromWorksheetName]);
2959 $this->branchPruningEnabled = $enabled;
2974 $this->branchStoreKeyCounter = 0;
2984 return self::$localeLanguage;
2989 $localeFileName = $localeDir . str_replace(
'_', DIRECTORY_SEPARATOR, $locale) .
2990 DIRECTORY_SEPARATOR . $file;
2991 if (!file_exists($localeFileName)) {
2993 $localeFileName = $localeDir . $language . DIRECTORY_SEPARATOR . $file;
2994 if (!file_exists($localeFileName)) {
2995 throw new Exception(
'Locale file not found');
2999 return $localeFileName;
3012 $language = $locale = strtolower($locale);
3013 if (strpos($locale,
'_') !==
false) {
3016 if (count(self::$validLocaleLanguages) == 1) {
3017 self::loadLocales();
3021 if (in_array(
$language, self::$validLocaleLanguages)) {
3023 self::$localeFunctions = [];
3024 self::$localeArgumentSeparator =
',';
3025 self::$localeBoolean = [
'TRUE' =>
'TRUE',
'FALSE' =>
'FALSE',
'NULL' =>
'NULL'];
3028 if ($locale !==
'en_us') {
3029 $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__,
'locale', null]);
3038 $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
3040 [$localeFunction] = explode(
'##', $localeFunction);
3041 if (strpos($localeFunction,
'=') !==
false) {
3042 [$fName, $lfName] = array_map(
'trim', explode(
'=', $localeFunction));
3043 if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName !=
'') && ($fName != $lfName)) {
3044 self::$localeFunctions[$fName] = $lfName;
3049 if (isset(self::$localeFunctions[
'TRUE'])) {
3050 self::$localeBoolean[
'TRUE'] = self::$localeFunctions[
'TRUE'];
3052 if (isset(self::$localeFunctions[
'FALSE'])) {
3053 self::$localeBoolean[
'FALSE'] = self::$localeFunctions[
'FALSE'];
3062 $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
3063 foreach ($localeSettings as $localeSetting) {
3064 [$localeSetting] = explode(
'##', $localeSetting);
3065 if (strpos($localeSetting,
'=') !==
false) {
3066 [$settingName, $settingValue] = array_map(
'trim', explode(
'=', $localeSetting));
3067 $settingName = strtoupper($settingName);
3068 if ($settingValue !==
'') {
3069 switch ($settingName) {
3070 case 'ARGUMENTSEPARATOR':
3071 self::$localeArgumentSeparator = $settingValue;
3080 self::$functionReplaceFromExcel = self::$functionReplaceToExcel =
3081 self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
3082 self::$localeLanguage = $locale;
3100 $strlen = mb_strlen($formula);
3101 for (
$i = 0;
$i < $strlen; ++
$i) {
3102 $chr = mb_substr($formula,
$i, 1);
3104 case self::FORMULA_OPEN_FUNCTION_BRACE:
3108 case self::FORMULA_CLOSE_FUNCTION_BRACE:
3112 case $fromSeparator:
3114 $formula = mb_substr($formula, 0,
$i) . $toSeparator . mb_substr($formula,
$i + 1);
3134 if (self::$localeLanguage !==
'en_us') {
3137 if (strpos($formula, self::FORMULA_STRING_QUOTE) !==
false) {
3140 $temp = explode(self::FORMULA_STRING_QUOTE, $formula);
3142 foreach ($temp as &$value) {
3145 $value = preg_replace($from, $to, $value);
3146 $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
3151 $formula = implode(self::FORMULA_STRING_QUOTE, $temp);
3154 $formula = preg_replace($from, $to, $formula);
3155 $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
3168 if (self::$functionReplaceFromExcel === null) {
3169 self::$functionReplaceFromExcel = [];
3170 foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
3171 self::$functionReplaceFromExcel[] =
'/(@?[^\w\.])' . preg_quote($excelFunctionName,
'/') .
'([\s]*\()/Ui';
3173 foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
3174 self::$functionReplaceFromExcel[] =
'/(@?[^\w\.])' . preg_quote($excelBoolean,
'/') .
'([^\w\.])/Ui';
3178 if (self::$functionReplaceToLocale === null) {
3179 self::$functionReplaceToLocale = [];
3180 foreach (self::$localeFunctions as $localeFunctionName) {
3181 self::$functionReplaceToLocale[] =
'$1' . trim($localeFunctionName) .
'$2';
3184 self::$functionReplaceToLocale[] =
'$1' . trim($localeBoolean) .
'$2';
3188 return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula,
',', self::$localeArgumentSeparator);
3197 if (self::$functionReplaceFromLocale === null) {
3198 self::$functionReplaceFromLocale = [];
3199 foreach (self::$localeFunctions as $localeFunctionName) {
3200 self::$functionReplaceFromLocale[] =
'/(@?[^\w\.])' . preg_quote($localeFunctionName,
'/') .
'([\s]*\()/Ui';
3202 foreach (self::$localeBoolean as $excelBoolean) {
3203 self::$functionReplaceFromLocale[] =
'/(@?[^\w\.])' . preg_quote($excelBoolean,
'/') .
'([^\w\.])/Ui';
3207 if (self::$functionReplaceToExcel === null) {
3208 self::$functionReplaceToExcel = [];
3209 foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
3210 self::$functionReplaceToExcel[] =
'$1' . trim($excelFunctionName) .
'$2';
3212 foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
3213 self::$functionReplaceToExcel[] =
'$1' . trim($excelBoolean) .
'$2';
3217 return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator,
',');
3222 if (self::$localeLanguage !==
'en_us') {
3223 $functionName = trim($function,
'(');
3224 if (isset(self::$localeFunctions[$functionName])) {
3225 $brace = ($functionName != $function);
3226 $function = self::$localeFunctions[$functionName];
3245 if (is_string($value)) {
3247 if (preg_match(
'/^' . self::CALCULATION_REGEXP_ERROR .
'$/i', $value, $match)) {
3253 return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE;
3254 } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
3271 if (is_string($value)) {
3272 if ((isset($value[0])) && ($value[0] == self::FORMULA_STRING_QUOTE) && (substr($value, -1) == self::FORMULA_STRING_QUOTE)) {
3273 return substr($value, 1, -1);
3276 } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
3310 if ($pCell === null) {
3317 $this->formulaError = null;
3318 $this->debugLog->clearLog();
3319 $this->cyclicReferenceStack->clear();
3320 $this->cyclicFormulaCounter = 1;
3322 self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
3326 $this->cellStack[] = [
3327 'sheet' => $pCell->getWorksheet()->getTitle(),
3328 'cell' => $pCell->getCoordinate(),
3333 $cellAddress = array_pop($this->cellStack);
3334 $this->spreadsheet->getSheetByName($cellAddress[
'sheet'])->getCell($cellAddress[
'cell']);
3336 $cellAddress = array_pop($this->cellStack);
3337 $this->spreadsheet->getSheetByName($cellAddress[
'sheet'])->getCell($cellAddress[
'cell']);
3342 if ((is_array(
$result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
3345 if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
3349 if (count($testResult) != 1) {
3352 $r = array_shift(
$r);
3353 if (!is_numeric(
$r)) {
3358 $c = array_shift(
$c);
3359 if (!is_numeric(
$c)) {
3364 $result = array_shift($testResult);
3368 if (
$result === null && $pCell->getWorksheet()->getSheetView()->getShowZeros()) {
3388 $formula = trim($formula);
3389 if ((!isset($formula[0])) || ($formula[0] !=
'=')) {
3392 $formula = ltrim(substr($formula, 1));
3393 if (!isset($formula[0])) {
3413 $this->formulaError = null;
3414 $this->debugLog->clearLog();
3415 $this->cyclicReferenceStack->clear();
3418 if ($this->spreadsheet !== null && $cellID === null && $pCell === null) {
3420 $pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
3424 $this->calculationCacheEnabled =
false;
3434 if ($this->spreadsheet === null) {
3436 $this->calculationCacheEnabled = $resetCache;
3447 $this->debugLog->writeDebugLog(
"Testing cache value for cell {$cellReference}");
3450 if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
3451 $this->debugLog->writeDebugLog(
"Retrieving value for cell {$cellReference} from cache");
3454 $cellValue = $this->calculationCache[$cellReference];
3468 if ($this->calculationCacheEnabled) {
3469 $this->calculationCache[$cellReference] = $cellValue;
3487 if ($pCell !== null && $pCell->getStyle()->getQuotePrefix() ===
true) {
3488 return self::wrapResult((
string) $formula);
3491 if (preg_match(
'/^=\s*cmd\s*\|/miu', $formula) !== 0) {
3492 return self::wrapResult($formula);
3497 $formula = trim($formula);
3498 if ($formula[0] !=
'=') {
3499 return self::wrapResult($formula);
3501 $formula = ltrim(substr($formula, 1));
3502 if (!isset($formula[0])) {
3503 return self::wrapResult($formula);
3506 $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
3507 $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() :
"\x00Wrk";
3508 $wsCellReference = $wsTitle .
'!' . $cellID;
3510 if (($cellID !== null) && ($this->
getValueFromCache($wsCellReference, $cellValue))) {
3513 $this->debugLog->writeDebugLog(
"Evaluating formula for cell {$wsCellReference}");
3515 if (($wsTitle[0] !==
"\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) {
3516 if ($this->cyclicFormulaCount <= 0) {
3517 $this->cyclicFormulaCell =
'';
3520 } elseif ($this->cyclicFormulaCell === $wsCellReference) {
3522 if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
3523 $this->cyclicFormulaCell =
'';
3527 } elseif ($this->cyclicFormulaCell ==
'') {
3528 if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
3531 $this->cyclicFormulaCell = $wsCellReference;
3535 $this->debugLog->writeDebugLog(
"Formula for cell {$wsCellReference} is {$formula}");
3537 $this->cyclicReferenceStack->push($wsCellReference);
3540 $this->cyclicReferenceStack->pop();
3543 if ($cellID !== null) {
3569 if (!is_array($operand1)) {
3570 [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand2);
3571 $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
3573 } elseif (!is_array($operand2)) {
3574 [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand1);
3575 $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
3579 [$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1);
3580 [$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2);
3581 if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
3587 self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
3588 } elseif ($resize == 1) {
3590 self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
3593 return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns];
3605 $matrixRows = count($matrix);
3607 foreach ($matrix as $rowKey => $rowValue) {
3608 if (!is_array($rowValue)) {
3609 $matrix[$rowKey] = [$rowValue];
3610 $matrixColumns = max(1, $matrixColumns);
3612 $matrix[$rowKey] = array_values($rowValue);
3613 $matrixColumns = max(count($rowValue), $matrixColumns);
3616 $matrix = array_values($matrix);
3618 return [$matrixRows, $matrixColumns];
3631 private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns): void
3633 if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
3634 if ($matrix2Rows < $matrix1Rows) {
3635 for (
$i = $matrix2Rows;
$i < $matrix1Rows; ++
$i) {
3636 unset($matrix1[
$i]);
3639 if ($matrix2Columns < $matrix1Columns) {
3640 for (
$i = 0;
$i < $matrix1Rows; ++
$i) {
3641 for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
3642 unset($matrix1[
$i][$j]);
3648 if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
3649 if ($matrix1Rows < $matrix2Rows) {
3650 for (
$i = $matrix1Rows;
$i < $matrix2Rows; ++
$i) {
3651 unset($matrix2[
$i]);
3654 if ($matrix1Columns < $matrix2Columns) {
3655 for (
$i = 0;
$i < $matrix2Rows; ++
$i) {
3656 for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
3657 unset($matrix2[
$i][$j]);
3674 private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns): void
3676 if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
3677 if ($matrix2Columns < $matrix1Columns) {
3678 for (
$i = 0;
$i < $matrix2Rows; ++
$i) {
3679 $x = $matrix2[
$i][$matrix2Columns - 1];
3680 for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
3681 $matrix2[
$i][$j] =
$x;
3685 if ($matrix2Rows < $matrix1Rows) {
3686 $x = $matrix2[$matrix2Rows - 1];
3687 for (
$i = 0;
$i < $matrix1Rows; ++
$i) {
3693 if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
3694 if ($matrix1Columns < $matrix2Columns) {
3695 for (
$i = 0;
$i < $matrix1Rows; ++
$i) {
3696 $x = $matrix1[
$i][$matrix1Columns - 1];
3697 for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
3698 $matrix1[
$i][$j] =
$x;
3702 if ($matrix1Rows < $matrix2Rows) {
3703 $x = $matrix1[$matrix1Rows - 1];
3704 for (
$i = 0;
$i < $matrix2Rows; ++
$i) {
3720 if ($this->debugLog->getWriteDebugLog()) {
3722 if (count($testArray) == 1) {
3723 $value = array_pop($testArray);
3726 if (is_array($value)) {
3728 $pad = $rpad =
', ';
3729 foreach ($value as
$row) {
3730 if (is_array($row)) {
3731 $returnMatrix[] = implode($pad, array_map([$this,
'showValue'], $row));
3734 $returnMatrix[] = $this->
showValue($row);
3738 return '{ ' . implode($rpad, $returnMatrix) .
' }';
3739 } elseif (is_string($value) && (trim($value, self::FORMULA_STRING_QUOTE) == $value)) {
3740 return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE;
3741 } elseif (is_bool($value)) {
3742 return ($value) ? self::$localeBoolean[
'TRUE'] : self::$localeBoolean[
'FALSE'];
3758 if ($this->debugLog->getWriteDebugLog()) {
3760 if (count($testArray) == 1) {
3761 $value = array_pop($testArray);
3764 if ($value === null) {
3765 return 'a NULL value';
3766 } elseif (is_float($value)) {
3767 $typeString =
'a floating point number';
3768 } elseif (is_int($value)) {
3769 $typeString =
'an integer number';
3770 } elseif (is_bool($value)) {
3771 $typeString =
'a boolean';
3772 } elseif (is_array($value)) {
3773 $typeString =
'a matrix';
3776 return 'an empty string';
3777 } elseif ($value[0] ==
'#') {
3778 return 'a ' . $value .
' error';
3780 $typeString =
'a string';
3783 return $typeString .
' with a value of ' . $this->
showValue($value);
3796 static $matrixReplaceFrom = [self::FORMULA_OPEN_FUNCTION_BRACE,
';', self::FORMULA_CLOSE_FUNCTION_BRACE];
3797 static $matrixReplaceTo = [
'MKMATRIX(MKMATRIX(',
'),MKMATRIX(',
'))'];
3800 if (strpos($formula, self::FORMULA_OPEN_FUNCTION_BRACE) !==
false) {
3802 if (strpos($formula, self::FORMULA_STRING_QUOTE) !==
false) {
3805 $temp = explode(self::FORMULA_STRING_QUOTE, $formula);
3807 $openCount = $closeCount = 0;
3809 foreach ($temp as &$value) {
3812 $openCount += substr_count($value, self::FORMULA_OPEN_FUNCTION_BRACE);
3813 $closeCount += substr_count($value, self::FORMULA_CLOSE_FUNCTION_BRACE);
3814 $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
3819 $formula = implode(self::FORMULA_STRING_QUOTE, $temp);
3822 $openCount = substr_count($formula, self::FORMULA_OPEN_FUNCTION_BRACE);
3823 $closeCount = substr_count($formula, self::FORMULA_CLOSE_FUNCTION_BRACE);
3824 $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
3827 if ($openCount < $closeCount) {
3828 if ($openCount > 0) {
3833 } elseif ($openCount > $closeCount) {
3834 if ($closeCount > 0) {
3854 '>' => 0,
'<' => 0,
'=' => 0,
'>=' => 0,
'<=' => 0,
'<>' => 0,
3859 private static $comparisonOperators = [
'>' =>
true,
'<' =>
true,
'=' =>
true,
'>=' =>
true,
'<=' =>
true,
'<>' =>
true];
3873 '>' => 0,
'<' => 0,
'=' => 0,
'>=' => 0,
'<=' => 0,
'<>' => 0,
3891 $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
3893 $regexpMatchString =
'/^(' . self::CALCULATION_REGEXP_FUNCTION .
3894 '|' . self::CALCULATION_REGEXP_CELLREF .
3895 '|' . self::CALCULATION_REGEXP_COLUMN_RANGE .
3896 '|' . self::CALCULATION_REGEXP_ROW_RANGE .
3897 '|' . self::CALCULATION_REGEXP_NUMBER .
3898 '|' . self::CALCULATION_REGEXP_STRING .
3899 '|' . self::CALCULATION_REGEXP_OPENBRACE .
3900 '|' . self::CALCULATION_REGEXP_DEFINEDNAME .
3901 '|' . self::CALCULATION_REGEXP_ERROR .
3906 $stack =
new Stack();
3908 $expectingOperator =
false;
3910 $expectingOperand =
false;
3915 $pendingStoreKey = null;
3917 $pendingStoreKeysStack = [];
3918 $expectingConditionMap = [];
3919 $expectingThenMap = [];
3920 $expectingElseMap = [];
3921 $parenthesisDepthMap = [];
3928 $currentCondition = null;
3929 $currentOnlyIf = null;
3930 $currentOnlyIfNot = null;
3931 $previousStoreKey = null;
3932 $pendingStoreKey = end($pendingStoreKeysStack);
3934 if ($this->branchPruningEnabled) {
3936 if (isset($expectingConditionMap[$pendingStoreKey]) && $expectingConditionMap[$pendingStoreKey]) {
3937 $currentCondition = $pendingStoreKey;
3938 $stackDepth = count($pendingStoreKeysStack);
3939 if ($stackDepth > 1) {
3940 $previousStoreKey = $pendingStoreKeysStack[$stackDepth - 2];
3943 if (isset($expectingThenMap[$pendingStoreKey]) && $expectingThenMap[$pendingStoreKey]) {
3944 $currentOnlyIf = $pendingStoreKey;
3945 } elseif (isset($previousStoreKey)) {
3946 if (isset($expectingThenMap[$previousStoreKey]) && $expectingThenMap[$previousStoreKey]) {
3947 $currentOnlyIf = $previousStoreKey;
3950 if (isset($expectingElseMap[$pendingStoreKey]) && $expectingElseMap[$pendingStoreKey]) {
3951 $currentOnlyIfNot = $pendingStoreKey;
3952 } elseif (isset($previousStoreKey)) {
3953 if (isset($expectingElseMap[$previousStoreKey]) && $expectingElseMap[$previousStoreKey]) {
3954 $currentOnlyIfNot = $previousStoreKey;
3959 $opCharacter = $formula[
$index];
3961 if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) >
$index) && (isset(self::$comparisonOperators[$formula[
$index + 1]]))) {
3962 $opCharacter .= $formula[++
$index];
3965 $isOperandOrFunction = (bool) preg_match($regexpMatchString, substr($formula,
$index), $match);
3967 if ($opCharacter ==
'-' && !$expectingOperator) {
3969 $stack->push(
'Unary Operator',
'~', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
3971 } elseif ($opCharacter ==
'%' && $expectingOperator) {
3973 $stack->push(
'Unary Operator',
'%', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
3975 } elseif ($opCharacter ==
'+' && !$expectingOperator) {
3977 } elseif ((($opCharacter ==
'~') || ($opCharacter ==
'|')) && (!$isOperandOrFunction)) {
3979 } elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) {
3981 $stack->count() > 0 &&
3982 ($o2 = $stack->last()) &&
3983 isset(self::$operators[$o2[
'value']]) &&
3984 @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2[
'value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2[
'value']])
3990 $stack->push(
'Binary Operator', $opCharacter, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
3993 $expectingOperator =
false;
3994 } elseif ($opCharacter ==
')' && $expectingOperator) {
3995 $expectingOperand =
false;
3996 while (($o2 = $stack->pop()) && $o2[
'value'] !=
'(') {
4002 $d = $stack->last(2);
4006 if (!empty($pendingStoreKey)) {
4007 --$parenthesisDepthMap[$pendingStoreKey];
4010 if (is_array(
$d) && preg_match(
'/^' . self::CALCULATION_REGEXP_FUNCTION .
'$/miu',
$d[
'value'], $matches)) {
4011 if (!empty($pendingStoreKey) && $parenthesisDepthMap[$pendingStoreKey] == -1) {
4013 if (
$d[
'value'] !=
'IF(') {
4016 if ($expectingConditionMap[$pendingStoreKey]) {
4019 $expectingThenMap[$pendingStoreKey] =
false;
4020 $expectingElseMap[$pendingStoreKey] =
false;
4021 --$parenthesisDepthMap[$pendingStoreKey];
4022 array_pop($pendingStoreKeysStack);
4023 unset($pendingStoreKey);
4026 $functionName = $matches[1];
4028 $argumentCount =
$d[
'value'];
4031 if (isset(self::$controlFunctions[$functionName])) {
4032 $expectedArgumentCount = self::$controlFunctions[$functionName][
'argumentCount'];
4033 $functionCall = self::$controlFunctions[$functionName][
'functionCall'];
4034 } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
4035 $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName][
'argumentCount'];
4036 $functionCall = self::$phpSpreadsheetFunctions[$functionName][
'functionCall'];
4038 return $this->
raiseFormulaError(
'Formula Error: Internal error, non-function on stack');
4041 $argumentCountError =
false;
4042 $expectedArgumentCountString = null;
4043 if (is_numeric($expectedArgumentCount)) {
4044 if ($expectedArgumentCount < 0) {
4045 if ($argumentCount > abs($expectedArgumentCount)) {
4046 $argumentCountError =
true;
4047 $expectedArgumentCountString =
'no more than ' . abs($expectedArgumentCount);
4050 if ($argumentCount != $expectedArgumentCount) {
4051 $argumentCountError =
true;
4052 $expectedArgumentCountString = $expectedArgumentCount;
4055 } elseif ($expectedArgumentCount !=
'*') {
4056 $isOperandOrFunction = preg_match(
'/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
4057 switch ($argMatch[2]) {
4059 if ($argumentCount < $argMatch[1]) {
4060 $argumentCountError =
true;
4061 $expectedArgumentCountString = $argMatch[1] .
' or more ';
4066 if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
4067 $argumentCountError =
true;
4068 $expectedArgumentCountString =
'between ' . $argMatch[1] .
' and ' . $argMatch[3];
4073 if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
4074 $argumentCountError =
true;
4075 $expectedArgumentCountString =
'either ' . $argMatch[1] .
' or ' . $argMatch[3];
4081 if ($argumentCountError) {
4082 return $this->
raiseFormulaError(
"Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString .
' expected');
4086 } elseif ($opCharacter ==
',') {
4088 !empty($pendingStoreKey) &&
4089 $parenthesisDepthMap[$pendingStoreKey] == 0
4092 if ($expectingConditionMap[$pendingStoreKey]) {
4093 $expectingConditionMap[$pendingStoreKey] =
false;
4094 $expectingThenMap[$pendingStoreKey] =
true;
4095 } elseif ($expectingThenMap[$pendingStoreKey]) {
4096 $expectingThenMap[$pendingStoreKey] =
false;
4097 $expectingElseMap[$pendingStoreKey] =
true;
4098 } elseif ($expectingElseMap[$pendingStoreKey]) {
4102 while (($o2 = $stack->pop()) && $o2[
'value'] !=
'(') {
4110 if (($expectingOperand) || (!$expectingOperator)) {
4111 $output[] = [
'type' =>
'NULL Value',
'value' => self::$excelConstants[
'NULL'],
'reference' => null];
4114 $d = $stack->last(2);
4115 if (!preg_match(
'/^' . self::CALCULATION_REGEXP_FUNCTION .
'$/miu',
$d[
'value'], $matches)) {
4119 $itemStoreKey =
$d[
'storeKey'] ?? null;
4120 $itemOnlyIf =
$d[
'onlyIf'] ?? null;
4121 $itemOnlyIfNot =
$d[
'onlyIfNot'] ?? null;
4122 $stack->push(
$d[
'type'], ++
$d[
'value'],
$d[
'reference'], $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot);
4123 $stack->push(
'Brace',
'(', null, $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot);
4124 $expectingOperator =
false;
4125 $expectingOperand =
true;
4127 } elseif ($opCharacter ==
'(' && !$expectingOperator) {
4128 if (!empty($pendingStoreKey)) {
4129 ++$parenthesisDepthMap[$pendingStoreKey];
4131 $stack->push(
'Brace',
'(', null, $currentCondition, $currentOnlyIf, $currentOnlyIf);
4133 } elseif ($isOperandOrFunction && !$expectingOperator) {
4134 $expectingOperator =
true;
4135 $expectingOperand =
false;
4137 $length = strlen($val);
4139 if (preg_match(
'/^' . self::CALCULATION_REGEXP_FUNCTION .
'$/miu', $val, $matches)) {
4140 $val = preg_replace(
'/\s/u',
'', $val);
4141 if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) {
4142 $valToUpper = strtoupper($val);
4144 $valToUpper =
'NAME.ERROR(';
4148 if ($this->branchPruningEnabled && ($valToUpper ==
'IF(')) {
4150 $pendingStoreKeysStack[] = $pendingStoreKey;
4151 $expectingConditionMap[$pendingStoreKey] =
true;
4152 $parenthesisDepthMap[$pendingStoreKey] = 0;
4154 if (!empty($pendingStoreKey) && array_key_exists($pendingStoreKey, $parenthesisDepthMap)) {
4155 ++$parenthesisDepthMap[$pendingStoreKey];
4159 $stack->push(
'Function', $valToUpper, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4161 $ax = preg_match(
'/^\s*\)/u', substr($formula,
$index + $length));
4163 $stack->push(
'Operand Count for Function ' . $valToUpper .
')', 0, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4164 $expectingOperator =
true;
4166 $stack->push(
'Operand Count for Function ' . $valToUpper .
')', 1, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4167 $expectingOperator =
false;
4169 $stack->push(
'Brace',
'(');
4170 } elseif (preg_match(
'/^' . self::CALCULATION_REGEXP_CELLREF .
'$/i', $val, $matches)) {
4174 $testPrevOp = $stack->last(1);
4175 if ($testPrevOp !== null && $testPrevOp[
'value'] ===
':') {
4177 if ($matches[2] ==
'') {
4181 preg_match(
'/^' . self::CALCULATION_REGEXP_CELLREF .
'$/i', $rangeStartCellRef, $rangeStartMatches);
4182 if ($rangeStartMatches[2] >
'') {
4183 $val = $rangeStartMatches[2] .
'!' . $val;
4187 preg_match(
'/^' . self::CALCULATION_REGEXP_CELLREF .
'$/i', $rangeStartCellRef, $rangeStartMatches);
4188 if ($rangeStartMatches[2] !== $matches[2]) {
4192 } elseif (strpos($val,
'!') ===
false && $pCellParent !== null) {
4193 $worksheet = $pCellParent->getTitle();
4194 $val =
"'{$worksheet}'!{$val}";
4197 $outputItem = $stack->getStackItem(
'Cell Reference', $val, $val, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4201 $localeConstant =
false;
4202 $stackItemType =
'Value';
4203 $stackItemReference = null;
4206 $testPrevOp = $stack->last(1);
4207 if ($testPrevOp !== null && $testPrevOp[
'value'] ===
':') {
4208 $stackItemType =
'Cell Reference';
4211 $rangeSheetRef = $rangeWS1;
4212 if ($rangeWS1 !==
'') {
4215 $rangeSheetRef = trim($rangeSheetRef,
"'");
4217 if ($rangeWS2 !==
'') {
4220 $rangeWS2 = $rangeWS1;
4223 $refSheet = $pCellParent;
4224 if ($pCellParent !== null && $rangeSheetRef !==
'' && $rangeSheetRef !== $pCellParent->getTitle()) {
4225 $refSheet = $pCellParent->getParent()->getSheetByName($rangeSheetRef);
4228 if (ctype_digit($val) && $val <= 1048576) {
4230 $stackItemType =
'Row Reference';
4231 $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataColumn($val) :
'XFD';
4232 $val =
"{$rangeWS2}{$endRowColRef}{$val}";
4233 } elseif (ctype_alpha($val) && strlen($val) <= 3) {
4235 $stackItemType =
'Column Reference';
4236 $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataRow($val) : 1048576;
4237 $val =
"{$rangeWS2}{$val}{$endRowColRef}";
4239 $stackItemReference = $val;
4240 } elseif ($opCharacter == self::FORMULA_STRING_QUOTE) {
4242 $val = self::wrapResult(str_replace(
'""', self::FORMULA_STRING_QUOTE, self::unwrapResult($val)));
4243 } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) {
4244 $stackItemType =
'Constant';
4245 $excelConstant = trim(strtoupper($val));
4246 $val = self::$excelConstants[$excelConstant];
4247 } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !==
false) {
4248 $stackItemType =
'Constant';
4249 $val = self::$excelConstants[$localeConstant];
4251 preg_match(
'/^' . self::CALCULATION_REGEXP_ROW_RANGE .
'/miu', substr($formula,
$index), $rowRangeReference)
4253 $val = $rowRangeReference[1];
4254 $length = strlen($rowRangeReference[1]);
4255 $stackItemType =
'Row Reference';
4257 if (($testPrevOp !== null && $testPrevOp[
'value'] ===
':') && $pCellParent !== null) {
4258 $column = $pCellParent->getHighestDataColumn($val);
4260 $val =
"{$rowRangeReference[2]}{$column}{$rowRangeReference[7]}";
4261 $stackItemReference = $val;
4263 preg_match(
'/^' . self::CALCULATION_REGEXP_COLUMN_RANGE .
'/miu', substr($formula,
$index), $columnRangeReference)
4265 $val = $columnRangeReference[1];
4266 $length = strlen($val);
4267 $stackItemType =
'Column Reference';
4269 if (($testPrevOp !== null && $testPrevOp[
'value'] ===
':') && $pCellParent !== null) {
4270 $row = $pCellParent->getHighestDataRow($val);
4272 $val =
"{$val}{$row}";
4273 $stackItemReference = $val;
4274 } elseif (preg_match(
'/^' . self::CALCULATION_REGEXP_DEFINEDNAME .
'.*/miu', $val, $match)) {
4275 $stackItemType =
'Defined Name';
4276 $stackItemReference = $val;
4277 } elseif (is_numeric($val)) {
4278 if ((strpos($val,
'.') !==
false) || (stripos($val,
'e') !==
false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
4279 $val = (float) $val;
4285 $details = $stack->getStackItem($stackItemType, $val, $stackItemReference, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4286 if ($localeConstant) {
4287 $details[
'localeValue'] = $localeConstant;
4292 } elseif ($opCharacter ==
'$') {
4294 } elseif ($opCharacter ==
')') {
4295 if ($expectingOperand) {
4296 $output[] = [
'type' =>
'NULL Value',
'value' => self::$excelConstants[
'NULL'],
'reference' => null];
4297 $expectingOperand =
false;
4298 $expectingOperator =
true;
4302 } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
4303 return $this->
raiseFormulaError(
"Formula Error: Unexpected operator '$opCharacter'");
4308 if (
$index == strlen($formula)) {
4311 if ((isset(self::$operators[$opCharacter])) && ($opCharacter !=
'%')) {
4312 return $this->
raiseFormulaError(
"Formula Error: Operator '$opCharacter' has no operands");
4318 while (($formula[
$index] ==
"\n") || ($formula[
$index] ==
"\r")) {
4322 if ($formula[
$index] ==
' ') {
4323 while ($formula[
$index] ==
' ') {
4330 ($expectingOperator) &&
4331 ((preg_match(
'/^' . self::CALCULATION_REGEXP_CELLREF .
'.*/Ui', substr($formula,
$index), $match)) &&
4333 (preg_match(
'/^' . self::CALCULATION_REGEXP_DEFINEDNAME .
'.*/miu', substr($formula,
$index), $match)) &&
4338 $stack->count() > 0 &&
4339 ($o2 = $stack->last()) &&
4340 isset(self::$operators[$o2[
'value']]) &&
4341 @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2[
'value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2[
'value']])
4345 $stack->push(
'Binary Operator',
'|');
4346 $expectingOperator =
false;
4351 while (($op = $stack->pop()) !== null) {
4352 if ((is_array($op) && $op[
'value'] ==
'(') || ($op ===
'(')) {
4363 $operand = $operandData[
'value'];
4364 if (($operandData[
'reference'] === null) && (is_array($operand))) {
4365 $rKeys = array_keys($operand);
4366 $rowKey = array_shift($rKeys);
4367 $cKeys = array_keys(array_keys($operand[$rowKey]));
4368 $colKey = array_shift($cKeys);
4369 if (ctype_upper($colKey)) {
4370 $operandData[
'reference'] = $colKey . $rowKey;
4387 if ($tokens ==
false) {
4393 $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
4394 $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
4395 $stack =
new Stack();
4398 $fakedForBranchPruning = [];
4402 foreach ($tokens as $tokenData) {
4403 $token = $tokenData[
'value'];
4406 $storeKey = $tokenData[
'storeKey'] ?? null;
4407 if ($this->branchPruningEnabled && isset($tokenData[
'onlyIf'])) {
4408 $onlyIfStoreKey = $tokenData[
'onlyIf'];
4409 $storeValue = $branchStore[$onlyIfStoreKey] ?? null;
4410 $storeValueAsBool = ($storeValue === null) ?
4412 if (is_array($storeValue)) {
4413 $wrappedItem = end($storeValue);
4414 $storeValue = end($wrappedItem);
4422 || ($storeValue ===
'Pruned branch')
4426 if (!isset($fakedForBranchPruning[
'onlyIf-' . $onlyIfStoreKey])) {
4427 $stack->push(
'Value',
'Pruned branch (only if ' . $onlyIfStoreKey .
') ' .
$token);
4428 $fakedForBranchPruning[
'onlyIf-' . $onlyIfStoreKey] =
true;
4431 if (isset($storeKey)) {
4434 $branchStore[$storeKey] =
'Pruned branch';
4435 $fakedForBranchPruning[
'onlyIfNot-' . $storeKey] =
true;
4436 $fakedForBranchPruning[
'onlyIf-' . $storeKey] =
true;
4443 if ($this->branchPruningEnabled && isset($tokenData[
'onlyIfNot'])) {
4444 $onlyIfNotStoreKey = $tokenData[
'onlyIfNot'];
4445 $storeValue = $branchStore[$onlyIfNotStoreKey] ?? null;
4446 $storeValueAsBool = ($storeValue === null) ?
4448 if (is_array($storeValue)) {
4449 $wrappedItem = end($storeValue);
4450 $storeValue = end($wrappedItem);
4457 || ($storeValue ===
'Pruned branch'))
4460 if (!isset($fakedForBranchPruning[
'onlyIfNot-' . $onlyIfNotStoreKey])) {
4461 $stack->push(
'Value',
'Pruned branch (only if not ' . $onlyIfNotStoreKey .
') ' .
$token);
4462 $fakedForBranchPruning[
'onlyIfNot-' . $onlyIfNotStoreKey] =
true;
4465 if (isset($storeKey)) {
4468 $branchStore[$storeKey] =
'Pruned branch';
4469 $fakedForBranchPruning[
'onlyIfNot-' . $storeKey] =
true;
4470 $fakedForBranchPruning[
'onlyIf-' . $storeKey] =
true;
4478 if (isset(self::$binaryOperators[
$token])) {
4480 if (($operand2Data = $stack->pop()) === null) {
4481 return $this->
raiseFormulaError(
'Internal error - Operand value missing from stack');
4483 if (($operand1Data = $stack->pop()) === null) {
4484 return $this->
raiseFormulaError(
'Internal error - Operand value missing from stack');
4487 $operand1 = self::dataTestReference($operand1Data);
4488 $operand2 = self::dataTestReference($operand2Data);
4491 if ($token ==
':') {
4492 $this->debugLog->writeDebugLog(
'Evaluating Range ', $this->
showValue($operand1Data[
'reference']),
' ', $token,
' ', $this->
showValue($operand2Data[
'reference']));
4494 $this->debugLog->writeDebugLog(
'Evaluating ', $this->
showValue($operand1),
' ', $token,
' ', $this->
showValue($operand2));
4507 if (isset($storeKey)) {
4508 $branchStore[$storeKey] =
$result;
4514 if (strpos($operand1Data[
'reference'],
'!') !==
false) {
4517 $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() :
'';
4521 if (empty($sheet2)) {
4525 if ($sheet1 == $sheet2) {
4526 if ($operand1Data[
'reference'] === null) {
4527 if ((trim($operand1Data[
'value']) !=
'') && (is_numeric($operand1Data[
'value']))) {
4528 $operand1Data[
'reference'] = $pCell->getColumn() . $operand1Data[
'value'];
4529 } elseif (trim($operand1Data[
'reference']) ==
'') {
4530 $operand1Data[
'reference'] = $pCell->getCoordinate();
4532 $operand1Data[
'reference'] = $operand1Data[
'value'] . $pCell->getRow();
4535 if ($operand2Data[
'reference'] === null) {
4536 if ((trim($operand2Data[
'value']) !=
'') && (is_numeric($operand2Data[
'value']))) {
4537 $operand2Data[
'reference'] = $pCell->getColumn() . $operand2Data[
'value'];
4538 } elseif (trim($operand2Data[
'reference']) ==
'') {
4539 $operand2Data[
'reference'] = $pCell->getCoordinate();
4541 $operand2Data[
'reference'] = $operand2Data[
'value'] . $pCell->getRow();
4545 $oData = array_merge(explode(
':', $operand1Data[
'reference']), explode(
':', $operand2Data[
'reference']));
4547 foreach ($oData as $oDatum) {
4553 if ($pCellParent !== null) {
4554 $cellValue = $this->
extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1),
false);
4559 $stack->push(
'Cell Reference', $cellValue, $cellRef);
4567 if (isset($storeKey)) {
4568 $branchStore[$storeKey] =
$result;
4574 if (isset($storeKey)) {
4575 $branchStore[$storeKey] =
$result;
4581 if (isset($storeKey)) {
4582 $branchStore[$storeKey] =
$result;
4588 if (isset($storeKey)) {
4589 $branchStore[$storeKey] =
$result;
4595 if (isset($storeKey)) {
4596 $branchStore[$storeKey] =
$result;
4604 if (is_bool($operand1)) {
4605 $operand1 = ($operand1) ? self::$localeBoolean[
'TRUE'] : self::$localeBoolean[
'FALSE'];
4607 if (is_bool($operand2)) {
4608 $operand2 = ($operand2) ? self::$localeBoolean[
'TRUE'] : self::$localeBoolean[
'FALSE'];
4610 if ((is_array($operand1)) || (is_array($operand2))) {
4612 self::checkMatrixOperands($operand1, $operand2, 2);
4618 $matrixResult =
$matrix->concat($operand2);
4619 $result = $matrixResult->getArray();
4621 $this->debugLog->writeDebugLog(
'JAMA Matrix Exception: ', $ex->getMessage());
4625 $result = self::FORMULA_STRING_QUOTE . str_replace(
'""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE;
4628 $stack->push(
'Value',
$result);
4630 if (isset($storeKey)) {
4631 $branchStore[$storeKey] =
$result;
4636 $rowIntersect = array_intersect_key($operand1, $operand2);
4637 $cellIntersect = $oCol = $oRow = [];
4638 foreach (array_keys($rowIntersect) as
$row) {
4640 foreach ($rowIntersect[$row] as $col =>
$data) {
4642 $cellIntersect[
$row] = array_intersect_key($operand1[$row], $operand2[$row]);
4646 $this->debugLog->writeDebugLog(
'Evaluation Result is ', $this->
showTypeDetails($cellIntersect));
4651 $this->debugLog->writeDebugLog(
'Evaluation Result is ', $this->
showTypeDetails($cellIntersect));
4652 $stack->push(
'Value', $cellIntersect, $cellRef);
4659 } elseif (($token ===
'~') || ($token ===
'%')) {
4660 if (($arg = $stack->pop()) === null) {
4661 return $this->
raiseFormulaError(
'Internal error - Operand value missing from stack');
4663 $arg = $arg[
'value'];
4664 if ($token ===
'~') {
4665 $this->debugLog->writeDebugLog(
'Evaluating Negation of ', $this->
showValue($arg));
4668 $this->debugLog->writeDebugLog(
'Evaluating Percentile of ', $this->
showValue($arg));
4671 if (is_array($arg)) {
4672 self::checkMatrixOperands($arg, $multiplier, 2);
4676 $matrixResult = $matrix1->arrayTimesEquals($multiplier);
4677 $result = $matrixResult->getArray();
4679 $this->debugLog->writeDebugLog(
'JAMA Matrix Exception: ', $ex->getMessage());
4683 $stack->push(
'Value',
$result);
4684 if (isset($storeKey)) {
4685 $branchStore[$storeKey] =
$result;
4690 } elseif (preg_match(
'/^' . self::CALCULATION_REGEXP_CELLREF .
'$/i', $token ??
'', $matches)) {
4693 if (isset($matches[8])) {
4694 if ($pCell === null) {
4698 $cellRef = $matches[6] . $matches[7] .
':' . $matches[9] . $matches[10];
4699 if ($matches[2] >
'') {
4700 $matches[2] = trim($matches[2],
"\"'");
4701 if ((strpos($matches[2],
'[') !==
false) || (strpos($matches[2],
']') !==
false)) {
4705 $matches[2] = trim($matches[2],
"\"'");
4706 $this->debugLog->writeDebugLog(
'Evaluating Cell Range ', $cellRef,
' in worksheet ', $matches[2]);
4707 if ($pCellParent !== null) {
4708 $cellValue = $this->
extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]),
false);
4712 $this->debugLog->writeDebugLog(
'Evaluation Result for cells ', $cellRef,
' in worksheet ', $matches[2],
' is ', $this->
showTypeDetails($cellValue));
4714 $this->debugLog->writeDebugLog(
'Evaluating Cell Range ', $cellRef,
' in current worksheet');
4715 if ($pCellParent !== null) {
4720 $this->debugLog->writeDebugLog(
'Evaluation Result for cells ', $cellRef,
' is ', $this->
showTypeDetails($cellValue));
4724 if ($pCell === null) {
4728 $cellRef = $matches[6] . $matches[7];
4729 if ($matches[2] >
'') {
4730 $matches[2] = trim($matches[2],
"\"'");
4731 if ((strpos($matches[2],
'[') !==
false) || (strpos($matches[2],
']') !==
false)) {
4735 $this->debugLog->writeDebugLog(
'Evaluating Cell ', $cellRef,
' in worksheet ', $matches[2]);
4736 if ($pCellParent !== null) {
4737 $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
4738 if ($cellSheet && $cellSheet->cellExists($cellRef)) {
4739 $cellValue = $this->
extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]),
false);
4740 $pCell->attach($pCellParent);
4742 $cellRef = ($cellSheet !== null) ?
"{$matches[2]}!{$cellRef}" : $cellRef;
4748 $this->debugLog->writeDebugLog(
'Evaluation Result for cell ', $cellRef,
' in worksheet ', $matches[2],
' is ', $this->
showTypeDetails($cellValue));
4750 $this->debugLog->writeDebugLog(
'Evaluating Cell ', $cellRef,
' in current worksheet');
4751 if ($pCellParent->has($cellRef)) {
4753 $pCell->attach($pCellParent);
4757 $this->debugLog->writeDebugLog(
'Evaluation Result for cell ', $cellRef,
' is ', $this->
showTypeDetails($cellValue));
4762 $stack->push(
'Cell Value', $cellValue, $cellRef);
4763 if (isset($storeKey)) {
4764 $branchStore[$storeKey] = $cellValue;
4768 } elseif (preg_match(
'/^' . self::CALCULATION_REGEXP_FUNCTION .
'$/miu', $token ??
'', $matches)) {
4770 $pCell->attach($pCellParent);
4773 $functionName = $matches[1];
4774 $argCount = $stack->pop();
4775 $argCount = $argCount[
'value'];
4776 if ($functionName !=
'MKMATRIX') {
4777 $this->debugLog->writeDebugLog(
'Evaluating Function ', self::localeFunc($functionName),
'() with ', (($argCount == 0) ?
'no' : $argCount),
' argument', (($argCount == 1) ?
'' :
's'));
4779 if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) {
4780 $passByReference =
false;
4781 $passCellReference =
false;
4782 $functionCall = null;
4783 if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
4784 $functionCall = self::$phpSpreadsheetFunctions[$functionName][
'functionCall'];
4785 $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName][
'passByReference']);
4786 $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName][
'passCellReference']);
4787 } elseif (isset(self::$controlFunctions[$functionName])) {
4788 $functionCall = self::$controlFunctions[$functionName][
'functionCall'];
4789 $passByReference = isset(self::$controlFunctions[$functionName][
'passByReference']);
4790 $passCellReference = isset(self::$controlFunctions[$functionName][
'passCellReference']);
4793 $args = $argArrayVals = [];
4794 for (
$i = 0;
$i < $argCount; ++
$i) {
4795 $arg = $stack->pop();
4796 $a = $argCount -
$i - 1;
4798 ($passByReference) &&
4799 (isset(self::$phpSpreadsheetFunctions[$functionName][
'passByReference'][$a])) &&
4800 (self::$phpSpreadsheetFunctions[$functionName][
'passByReference'][$a])
4802 if ($arg[
'reference'] === null) {
4804 if ($functionName !=
'MKMATRIX') {
4805 $argArrayVals[] = $this->
showValue($cellID);
4808 $args[] = $arg[
'reference'];
4809 if ($functionName !=
'MKMATRIX') {
4810 $argArrayVals[] = $this->
showValue($arg[
'reference']);
4814 $args[] = self::unwrapResult($arg[
'value']);
4815 if ($functionName !=
'MKMATRIX') {
4816 $argArrayVals[] = $this->
showValue($arg[
'value']);
4824 if (($passByReference) && ($argCount == 0)) {
4826 $argArrayVals[] = $this->
showValue($cellID);
4829 if ($functionName !=
'MKMATRIX') {
4830 if ($this->debugLog->getWriteDebugLog()) {
4831 krsort($argArrayVals);
4832 $this->debugLog->writeDebugLog(
'Evaluating ', self::localeFunc($functionName),
'( ', implode(self::$localeArgumentSeparator .
' ',
Functions::flattenArray($argArrayVals)),
' )');
4837 $args = $this->
addCellReference($args, $passCellReference, $functionCall, $pCell);
4839 if (!is_array($functionCall)) {
4840 foreach ($args as &$arg) {
4846 $result = call_user_func_array($functionCall, $args);
4848 if ($functionName !=
'MKMATRIX') {
4849 $this->debugLog->writeDebugLog(
'Evaluation Result for ', self::localeFunc($functionName),
'() function call is ', $this->
showTypeDetails(
$result));
4851 $stack->push(
'Value', self::wrapResult(
$result));
4852 if (isset($storeKey)) {
4853 $branchStore[$storeKey] =
$result;
4858 if (isset(self::$excelConstants[strtoupper($token ??
'')])) {
4859 $excelConstant = strtoupper($token);
4860 $stack->push(
'Constant Value', self::$excelConstants[$excelConstant]);
4861 if (isset($storeKey)) {
4862 $branchStore[$storeKey] = self::$excelConstants[$excelConstant];
4864 $this->debugLog->writeDebugLog(
'Evaluating Constant ', $excelConstant,
' as ', $this->
showTypeDetails(self::$excelConstants[$excelConstant]));
4865 } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token ==
'') || ($token[0] == self::FORMULA_STRING_QUOTE) || ($token[0] ==
'#')) {
4866 $stack->push(
'Value', $token);
4867 if (isset($storeKey)) {
4868 $branchStore[$storeKey] =
$token;
4871 } elseif (preg_match(
'/^' . self::CALCULATION_REGEXP_DEFINEDNAME .
'$/miu', $token, $matches)) {
4872 $definedName = $matches[6];
4873 if ($pCell === null || $pCellWorksheet === null) {
4877 $this->debugLog->writeDebugLog(
'Evaluating Defined Name ', $definedName);
4879 if ($namedRange === null) {
4884 if (isset($storeKey)) {
4885 $branchStore[$storeKey] =
$result;
4893 if ($stack->count() != 1) {
4904 if (is_array($operand)) {
4905 if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) {
4907 $operand = array_pop($operand);
4908 }
while (is_array($operand));
4912 if (is_string($operand)) {
4915 if ($operand >
'' && $operand[0] == self::FORMULA_STRING_QUOTE) {
4916 $operand = self::unwrapResult($operand);
4919 if (!is_numeric($operand)) {
4921 if ($operand >
'' && $operand[0] ==
'#') {
4922 $stack->push(
'Value', $operand);
4923 $this->debugLog->writeDebugLog(
'Evaluation Result is ', $this->
showTypeDetails($operand));
4928 $stack->push(
'Error',
'#VALUE!');
4929 $this->debugLog->writeDebugLog(
'Evaluation Result is a ', $this->
showTypeDetails(
'#VALUE!'));
4951 if (!is_array($operand2)) {
4953 foreach ($operand1 as
$x => $operandData) {
4954 $this->debugLog->writeDebugLog(
'Evaluating Comparison ', $this->
showValue($operandData),
' ', $operation,
' ', $this->
showValue($operand2));
4959 } elseif (!is_array($operand1)) {
4961 foreach ($operand2 as
$x => $operandData) {
4962 $this->debugLog->writeDebugLog(
'Evaluating Comparison ', $this->
showValue($operand1),
' ', $operation,
' ', $this->
showValue($operandData));
4969 if (!$recursingArrays) {
4970 self::checkMatrixOperands($operand1, $operand2, 2);
4972 foreach ($operand1 as
$x => $operandData) {
4973 $this->debugLog->writeDebugLog(
'Evaluating Comparison ', $this->
showValue($operandData),
' ', $operation,
' ', $this->
showValue($operand2[
$x]));
4999 if ((is_array($operand1)) || (is_array($operand2))) {
5000 return $this->
executeArrayComparison($cellID, $operand1, $operand2, $operation, $stack, $recursingArrays);
5004 if (is_string($operand1) && $operand1 >
'' && $operand1[0] == self::FORMULA_STRING_QUOTE) {
5005 $operand1 = self::unwrapResult($operand1);
5007 if (is_string($operand2) && $operand2 >
'' && $operand2[0] == self::FORMULA_STRING_QUOTE) {
5008 $operand2 = self::unwrapResult($operand2);
5013 if (is_string($operand1)) {
5016 if (is_string($operand2)) {
5024 switch ($operation) {
5027 if ($useLowercaseFirstComparison) {
5030 $result = ($operand1 > $operand2);
5036 if ($useLowercaseFirstComparison) {
5039 $result = ($operand1 < $operand2);
5045 if (is_numeric($operand1) && is_numeric($operand2)) {
5054 if (is_numeric($operand1) && is_numeric($operand2)) {
5056 } elseif ($useLowercaseFirstComparison) {
5065 if (is_numeric($operand1) && is_numeric($operand2)) {
5067 } elseif ($useLowercaseFirstComparison) {
5076 if (is_numeric($operand1) && is_numeric($operand2)) {
5077 $result = (abs($operand1 - $operand2) > 1E-14);
5085 throw new Exception(
'Unsupported binary comparison operation');
5109 return strcmp($inversedStr1 ??
'', $inversedStr2 ??
'');
5122 return strcmp($str1 ??
'', $str2 ??
'');
5147 if ((is_array($operand1)) || (is_array($operand2))) {
5149 self::checkMatrixOperands($operand1, $operand2, 2);
5155 $matrixResult =
$matrix->$matrixFunction($operand2);
5156 $result = $matrixResult->getArray();
5158 $this->debugLog->writeDebugLog(
'JAMA Matrix Exception: ', $ex->getMessage());
5164 ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
5165 (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))
5170 switch ($operation) {
5173 $result = $operand1 + $operand2;
5178 $result = $operand1 - $operand2;
5183 $result = $operand1 * $operand2;
5188 if ($operand2 == 0) {
5190 $stack->push(
'Error',
'#DIV/0!');
5191 $this->debugLog->writeDebugLog(
'Evaluation Result is ', $this->
showTypeDetails(
'#DIV/0!'));
5195 $result = $operand1 / $operand2;
5200 $result = $operand1 ** $operand2;
5205 throw new Exception(
'Unsupported numeric binary operation');
5213 $stack->push(
'Value',
$result);
5221 $this->formulaError = $errorMessage;
5222 $this->cyclicReferenceStack->clear();
5223 if (!$this->suppressFormulaErrors) {
5226 trigger_error($errorMessage, E_USER_ERROR);
5245 if ($pSheet !== null) {
5246 $pSheetName = $pSheet->getTitle();
5248 if (strpos($pRange,
'!') !==
false) {
5250 $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
5255 $pRange = $pSheetName .
'!' . $pRange;
5256 if (!isset($aReferences[1])) {
5260 sscanf($aReferences[0],
'%[A-Z]%d', $currentCol, $currentRow);
5261 if ($pSheet->cellExists($aReferences[0])) {
5262 $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
5264 $returnValue[$currentRow][$currentCol] = null;
5268 foreach ($aReferences as $reference) {
5272 sscanf($reference,
'%[A-Z]%d', $currentCol, $currentRow);
5273 if ($pSheet->cellExists($reference)) {
5274 $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
5276 $returnValue[$currentRow][$currentCol] = null;
5282 return $returnValue;
5299 if ($pSheet !== null) {
5300 $pSheetName = $pSheet->getTitle();
5301 if (strpos($pRange,
'!') !==
false) {
5303 $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
5308 if ($namedRange === null) {
5312 $pSheet = $namedRange->getWorksheet();
5313 $pRange = $namedRange->getValue();
5316 if (ctype_alpha($splitRange[0][0])) {
5317 $pRange = $splitRange[0][0] .
'1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
5318 } elseif (ctype_digit($splitRange[0][0])) {
5319 $pRange =
'A' . $splitRange[0][0] .
':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
5324 if (!isset($aReferences[1])) {
5327 if ($pSheet->cellExists($aReferences[0])) {
5328 $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
5330 $returnValue[$currentRow][$currentCol] = null;
5334 foreach ($aReferences as $reference) {
5337 if ($pSheet->cellExists($reference)) {
5338 $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
5340 $returnValue[$currentRow][$currentCol] = null;
5346 return $returnValue;
5358 $pFunction = strtoupper($pFunction);
5359 $notImplemented = !isset(self::$phpSpreadsheetFunctions[$pFunction]) || (is_array(self::$phpSpreadsheetFunctions[$pFunction][
'functionCall']) && self::$phpSpreadsheetFunctions[$pFunction][
'functionCall'][1] ===
'DUMMY');
5361 return !$notImplemented;
5369 return self::$phpSpreadsheetFunctions;
5380 foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
5382 $returnValue[] = $functionName;
5386 return $returnValue;
5399 if ($passCellReference) {
5400 if (is_array($functionCall)) {
5401 $className = $functionCall[0];
5402 $methodName = $functionCall[1];
5405 $argumentCount = count($reflectionMethod->getParameters());
5406 while (count($args) < $argumentCount - 1) {
5422 return $storeKeyValue;
5427 $tokensStr = array_map(
function (
$token) {
5428 $value =
$token[
'value'] ??
'no value';
5429 while (is_array($value)) {
5430 $value = array_pop($value);
5436 return '[ ' . implode(
' | ', $tokensStr) .
' ]';
5444 $definedNameScope = $namedRange->
getScope();
5445 if ($definedNameScope !== null && $definedNameScope !== $pCellWorksheet) {
5453 $definedNameValue = $namedRange->
getValue();
5454 $definedNameType = $namedRange->
isFormula() ?
'Formula' :
'Range';
5457 if ($definedNameValue[0] !==
'=') {
5458 $definedNameValue =
'=' . $definedNameValue;
5461 $this->debugLog->writeDebugLog(
"Defined Name is a {$definedNameType} with a value of {$definedNameValue}");
5463 $recursiveCalculationCell = ($definedNameWorksheet !== null && $definedNameWorksheet !== $pCellWorksheet)
5464 ? $definedNameWorksheet->getCell(
'A1')
5466 $recursiveCalculationCellAddress = $recursiveCalculationCell->
getCoordinate();
5469 $definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet(
5475 $this->debugLog->writeDebugLog(
"Value adjusted for relative references is {$definedNameValue}");
5478 $recursiveCalculator->getDebugLog()->setWriteDebugLog($this->
getDebugLog()->getWriteDebugLog());
5479 $recursiveCalculator->getDebugLog()->setEchoDebugLog($this->
getDebugLog()->getEchoDebugLog());
5480 $result = $recursiveCalculator->_calculateFormulaValue($definedNameValue, $recursiveCalculationCellAddress, $recursiveCalculationCell);
5483 $this->debugLog->mergeDebugLog(array_slice($recursiveCalculator->getDebugLog()->getLog(), 3));
5484 $this->debugLog->writeDebugLog(
"Evaluation Result for Named {$definedNameType} {$namedRange->getName()} is {$this->showTypeDetails($result)}");
getFunctions()
Get a list of all implemented functions as an array of function objects.
static splitRange($pRange)
Split range into coordinate strings.
parseFormula($formula)
Validate and parse a formula string.
static static static $operatorPrecedence
static $returnArrayAsType
const CALCULATION_REGEXP_ERROR
static localeFunc($function)
getCoordinate()
Get cell coordinate.
static getArrayReturnType()
Return the Array Return Type (Array or Value of first element in the array).
getWorksheet()
Get worksheet.
getLocaleFile(string $localeDir, string $locale, string $language, string $file)
static $functionReplaceToExcel
const CATEGORY_TEXT_AND_DATA
strcmpAllowNull($str1, $str2)
PHP8.1 deprecates passing null to strcmp.
static convertToNumberIfFraction(&$operand)
Identify whether a string contains a fractional numeric value, and convert it to a numeric if it is...
push( $type, $value, $reference=null, $storeKey=null, $onlyIf=null, $onlyIfNot=null)
Push a new entry onto the stack.
static resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
Ensure that paired matrix operands are both matrices of the same size.
static extractSheetTitle($pRange, $returnRange=false)
Extract worksheet title from range.
isImplemented($pFunction)
Is a specific function implemented?
const CALCULATION_REGEXP_ROWRANGE_RELATIVE
static wrapResult($value)
Wrap string values in quotes.
getValue()
Get range or formula value.
clearCalculationCacheForWorksheet($worksheetName)
Clear calculation cache for a specified worksheet.
showValue($value)
Format details of an operand for display in the log (based on operand type).
const FORMULA_OPEN_FUNCTION_BRACE
pop()
Pop the last entry from the stack.
clearCalculationCache()
Clear calculation cache.
static $localeArgumentSeparator
getImplementedFunctionNames()
Get a list of implemented Excel function names.
executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays=false)
static flattenArray($array)
Convert a multi-dimensional array to a simple 1-dimensional array.
const CATEGORY_STATISTICAL
saveValueToCache($cellReference, $cellValue)
const CALCULATION_REGEXP_FUNCTION
executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack)
const FORMULA_STRING_QUOTE
calculateCellValue(?Cell $pCell=null, $resetLog=true)
Calculate the value of a cell formula.
const CALCULATION_REGEXP_COLUMN_RANGE
addCellReference(array $args, $passCellReference, $functionCall, ?Cell $pCell=null)
Add cell reference if needed while making sure that it is the last argument.
static isError($value='')
IS_ERROR.
convertMatrixReferences($formula)
static strToUpper($pValue)
Convert a UTF-8 encoded string to upper case.
static getFALSE()
Return the locale-specific translation of FALSE.
const CATEGORY_INFORMATION
__clone()
__clone implementation.
getUnusedBranchStoreKey()
strcmpLowercaseFirst($str1, $str2)
Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters...
const RETURN_ARRAY_AS_VALUE
const RETURN_ARRAY_AS_ERROR
constants
const CATEGORY_MATH_AND_TRIG
const COMPATIBILITY_OPENOFFICE
static $functionReplaceFromLocale
setBranchPruningEnabled($enabled)
Enable/disable calculation cache.
getCalculationCacheEnabled()
Is calculation caching enabled?
disableCalculationCache()
Disable calculation cache.
static getTRUE()
Return the locale-specific translation of TRUE.
static setArrayReturnType($returnType)
Set the Array Return Type (Array or Value of first element in the array).
static checkMatrixOperands(&$operand1, &$operand2, $resize=1)
Ensure that paired matrix operands are both matrices and of the same size.
_translateFormulaToEnglish($formula)
executeArrayComparison($cellID, $operand1, $operand2, $operation, Stack &$stack, bool $recursingArrays)
enableCalculationCache()
Enable calculation cache.
static dataTestReference(&$operandData)
static resolveName(string $pDefinedName, Worksheet $pSheet, string $sheetName='')
Resolve a named range to a regular cell range or formula.
const CALCULATION_REGEXP_ROW_RANGE
static strCaseReverse($pValue)
Reverse the case of a string, so that all uppercase characters become lowercase and all lowercase cha...
isFormula()
Identify whether this is a named range or a named formula.
const FORMULA_CLOSE_FUNCTION_BRACE
static static static __construct(?Spreadsheet $spreadsheet=null)
extractCellRange(&$pRange='A1', ?Worksheet $pSheet=null, $resetLog=true)
Extract range values.
static getInstance()
Get an instance of this class.
_translateFormulaToLocale($formula)
static $validLocaleLanguages
setCalculationCacheEnabled($pValue)
Enable/disable calculation cache.
static translateFormula(array $from, array $to, $formula, $fromSeparator, $toSeparator)
const CALCULATION_REGEXP_CELLREF
static unwrapResult($value)
Remove quotes used as a wrapper to identify string values.
const CATEGORY_DATE_AND_TIME
static $functionReplaceFromExcel
calculate(?Cell $pCell=null)
Calculate cell value (using formula from a cell ID) Retained for backward compatibility.
static getInstance(?Spreadsheet $spreadsheet=null)
Get an instance of this class.
static $operatorAssociativity
static $functionReplaceToLocale
getDebugLog()
Get the Logger for this calculation engine instance.
renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName)
Rename calculation cache for a specified worksheet.
extractNamedRange(&$pRange='A1', ?Worksheet $pSheet=null, $resetLog=true)
Extract range values.
static static $comparisonOperators
static coordinateFromString($pCoordinateString)
Coordinate from string.
const CALCULATION_REGEXP_STRING
setLocale(string $locale)
Set the locale code.
const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE
flushInstance()
Flush the calculation cache for any existing instance of this class but only if a Calculation instanc...
validateBinaryOperand(&$operand, &$stack)
const CATEGORY_LOOKUP_AND_REFERENCE
const CALCULATION_REGEXP_NUMBER
Constants.
processTokenStack($tokens, $cellID=null, ?Cell $pCell=null)
evaluateDefinedName(Cell $pCell, DefinedName $namedRange, Worksheet $pCellWorksheet, Stack $stack)
static extractAllCellReferencesInRange($cellRange)
Extract all cell references in range, which may be comprised of multiple cell ranges.
static static static internalParseFormula($formula, ?Cell $pCell=null)
static translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
static flattenSingleValue($value='')
Convert an array to a single scalar value by extracting the first element.
raiseFormulaError($errorMessage)
static static $phpSpreadsheetFunctions
showTypeDetails($value)
Format type and details of an operand for display in the log (based on operand type).
static static static $controlFunctions
getRow()
Get cell coordinate row.
_calculateFormulaValue($formula, $cellID=null, ?Cell $pCell=null)
Parse a cell formula and calculate its value.
calculateFormula($formula, $cellID=null, ?Cell $pCell=null)
Calculate the value of a formula.
getLocale()
Get the currently defined locale code.
static getMatrixDimensions(array &$matrix)
Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0...
getTokensAsString($tokens)
static columnIndexFromString($pString)
Column index from string.
const CALCULATION_REGEXP_CELLREF_RELATIVE
static resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
Ensure that paired matrix operands are both matrices of the same size.
getValueFromCache(string $cellReference, &$cellValue)
const CATEGORY_ENGINEERING
const RETURN_ARRAY_AS_ARRAY
static stringFromColumnIndex($columnIndex)
String from column index.
for($i=6; $i< 13; $i++) for($i=1; $i< 13; $i++) $d
const CALCULATION_REGEXP_DEFINEDNAME
static getCompatibilityMode()
Return the current Compatibility Mode.
const CALCULATION_REGEXP_OPENBRACE
getColumn()
Get cell coordinate column.