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