Passed
Push — develop ( 50a9bc...5e5be1 )
by Adrien
30:18
created

Calculation   F

Complexity

Total Complexity 524

Size/Duplication

Total Lines 4418
Duplicated Lines 0 %

Test Coverage

Coverage 63.03%

Importance

Changes 0
Metric Value
eloc 2762
dl 0
loc 4418
ccs 694
cts 1101
cp 0.6303
rs 0.8
c 0
b 0
f 0
wmc 524

54 Methods

Rating   Name   Duplication   Size   Complexity  
B convertMatrixReferences() 0 49 9
A getLocale() 0 3 1
A getDebugLog() 0 3 1
A disableCalculationCache() 0 3 1
D setLocale() 0 79 19
A translateFormula() 0 29 5
A getArrayReturnType() 0 3 1
B _translateFormulaToEnglish() 0 23 7
A translateSeparator() 0 22 6
A localeFunc() 0 14 4
A getMatrixDimensions() 0 16 3
A calculate() 0 6 2
A calculateFormula() 0 30 6
A getValueFromCache() 0 14 3
A parseFormula() 0 15 4
A setArrayReturnType() 0 11 4
B checkMatrixOperands() 0 29 7
A flushInstance() 0 3 1
A getInstance() 0 14 5
C resizeMatricesExtend() 0 32 15
C _calculateFormulaValue() 0 55 15
A __clone() 0 3 1
A __construct() 0 7 1
A clearCalculationCacheForWorksheet() 0 4 2
C calculateCellValue() 0 67 15
A getTRUE() 0 3 1
A clearCalculationCache() 0 3 1
A getCalculationCacheEnabled() 0 3 1
C resizeMatricesShrink() 0 27 15
A getFALSE() 0 3 1
A wrapResult() 0 16 6
A saveValueToCache() 0 4 2
A setCalculationCacheEnabled() 0 4 1
B _translateFormulaToLocale() 0 23 7
A enableCalculationCache() 0 3 1
B showTypeDetails() 0 28 10
A loadLocales() 0 7 3
A renameCalculationCacheForWorksheet() 0 5 2
B showValue() 0 29 10
A mkMatrix() 0 3 1
B unwrapResult() 0 12 8
F _parseFormula() 0 307 111
A dataTestReference() 0 14 4
B extractCellRange() 0 38 7
F executeBinaryComparisonOperation() 0 126 42
A raiseFormulaError() 0 8 2
B extractNamedRange() 0 53 10
A getFunctions() 0 3 1
B validateBinaryOperand() 0 36 11
D executeNumericBinaryOperation() 0 77 19
A strcmpLowercaseFirst() 0 6 1
F processTokenStack() 0 374 102
A getImplementedFunctionNames() 0 10 3
A isImplemented() 0 6 3

How to fix   Complexity   

Complex Class

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
2
3
namespace PhpOffice\PhpSpreadsheet\Calculation;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Engine\CyclicReferenceStack;
6
use PhpOffice\PhpSpreadsheet\Calculation\Engine\Logger;
7
use PhpOffice\PhpSpreadsheet\Calculation\Token\Stack;
8
use PhpOffice\PhpSpreadsheet\Cell\Cell;
9
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
10
use PhpOffice\PhpSpreadsheet\NamedRange;
11
use PhpOffice\PhpSpreadsheet\Shared;
12
use PhpOffice\PhpSpreadsheet\Spreadsheet;
13
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
14
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,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?([a-z]{1,3})\$?(\d{1,7})';
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
     * List of operators that can be used within formulae
71
     * The true/false value indicates whether it is a binary operator or a unary operator.
72
     *
73
     * @var array
74
     */
75
    private static $operators = [
76
        '+' => true, '-' => true, '*' => true, '/' => true,
77
        '^' => true, '&' => true, '%' => false, '~' => false,
78
        '>' => true, '<' => true, '=' => true, '>=' => true,
79
        '<=' => true, '<>' => true, '|' => true, ':' => true,
80
    ];
81
82
    /**
83
     * List of binary operators (those that expect two operands).
84
     *
85
     * @var array
86
     */
87
    private static $binaryOperators = [
88
        '+' => true, '-' => true, '*' => true, '/' => true,
89
        '^' => true, '&' => true, '>' => true, '<' => true,
90
        '=' => true, '>=' => true, '<=' => true, '<>' => true,
91
        '|' => true, ':' => true,
92
    ];
93
94
    /**
95
     * The debug log generated by the calculation engine.
96
     *
97
     * @var Logger
98
     */
99
    private $debugLog;
100
101
    /**
102
     * Flag to determine how formula errors should be handled
103
     *        If true, then a user error will be triggered
104
     *        If false, then an exception will be thrown.
105
     *
106
     * @var bool
107
     */
108
    public $suppressFormulaErrors = false;
109
110
    /**
111
     * Error message for any error that was raised/thrown by the calculation engine.
112
     *
113
     * @var string
114
     */
115
    public $formulaError;
116
117
    /**
118
     * An array of the nested cell references accessed by the calculation engine, used for the debug log.
119
     *
120
     * @var array of string
121
     */
122
    private $cyclicReferenceStack;
123
124
    private $cellStack = [];
125
126
    /**
127
     * Current iteration counter for cyclic formulae
128
     * If the value is 0 (or less) then cyclic formulae will throw an exception,
129
     * otherwise they will iterate to the limit defined here before returning a result.
130
     *
131
     * @var int
132
     */
133
    private $cyclicFormulaCounter = 1;
134
135
    private $cyclicFormulaCell = '';
136
137
    /**
138
     * Number of iterations for cyclic formulae.
139
     *
140
     * @var int
141
     */
142
    public $cyclicFormulaCount = 1;
143
144
    /**
145
     * Epsilon Precision used for comparisons in calculations.
146
     *
147
     * @var float
148
     */
149
    private $delta = 0.1e-12;
150
151
    /**
152
     * The current locale setting.
153
     *
154
     * @var string
155
     */
156
    private static $localeLanguage = 'en_us'; //    US English    (default locale)
157
158
    /**
159
     * List of available locale settings
160
     * Note that this is read for the locale subdirectory only when requested.
161
     *
162
     * @var string[]
163
     */
164
    private static $validLocaleLanguages = [
165
        'en', //    English        (default language)
166
    ];
167
168
    /**
169
     * Locale-specific argument separator for function arguments.
170
     *
171
     * @var string
172
     */
173
    private static $localeArgumentSeparator = ',';
174
175
    private static $localeFunctions = [];
176
177
    /**
178
     * Locale-specific translations for Excel constants (True, False and Null).
179
     *
180
     * @var string[]
181
     */
182
    public static $localeBoolean = [
183
        'TRUE' => 'TRUE',
184
        'FALSE' => 'FALSE',
185
        'NULL' => 'NULL',
186
    ];
187
188
    /**
189
     * Excel constant string translations to their PHP equivalents
190
     * Constant conversion from text name/value to actual (datatyped) value.
191
     *
192
     * @var string[]
193
     */
194
    private static $excelConstants = [
195
        'TRUE' => true,
196
        'FALSE' => false,
197
        'NULL' => null,
198
    ];
199
200
    // PhpSpreadsheet functions
201
    private static $phpSpreadsheetFunctions = [
202
        'ABS' => [
203
            'category' => Category::CATEGORY_MATH_AND_TRIG,
204
            'functionCall' => 'abs',
205
            'argumentCount' => '1',
206
        ],
207
        'ACCRINT' => [
208
            'category' => Category::CATEGORY_FINANCIAL,
209
            'functionCall' => [Financial::class, 'ACCRINT'],
210
            'argumentCount' => '4-7',
211
        ],
212
        'ACCRINTM' => [
213
            'category' => Category::CATEGORY_FINANCIAL,
214
            'functionCall' => [Financial::class, 'ACCRINTM'],
215
            'argumentCount' => '3-5',
216
        ],
217
        'ACOS' => [
218
            'category' => Category::CATEGORY_MATH_AND_TRIG,
219
            'functionCall' => 'acos',
220
            'argumentCount' => '1',
221
        ],
222
        'ACOSH' => [
223
            'category' => Category::CATEGORY_MATH_AND_TRIG,
224
            'functionCall' => 'acosh',
225
            'argumentCount' => '1',
226
        ],
227
        'ACOT' => [
228
            'category' => Category::CATEGORY_MATH_AND_TRIG,
229
            'functionCall' => [MathTrig::class, 'ACOT'],
230
            'argumentCount' => '1',
231
        ],
232
        'ACOTH' => [
233
            'category' => Category::CATEGORY_MATH_AND_TRIG,
234
            'functionCall' => [MathTrig::class, 'ACOTH'],
235
            'argumentCount' => '1',
236
        ],
237
        'ADDRESS' => [
238
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
239
            'functionCall' => [LookupRef::class, 'cellAddress'],
240
            'argumentCount' => '2-5',
241
        ],
242
        'AMORDEGRC' => [
243
            'category' => Category::CATEGORY_FINANCIAL,
244
            'functionCall' => [Financial::class, 'AMORDEGRC'],
245
            'argumentCount' => '6,7',
246
        ],
247
        'AMORLINC' => [
248
            'category' => Category::CATEGORY_FINANCIAL,
249
            'functionCall' => [Financial::class, 'AMORLINC'],
250
            'argumentCount' => '6,7',
251
        ],
252
        'AND' => [
253
            'category' => Category::CATEGORY_LOGICAL,
254
            'functionCall' => [Logical::class, 'logicalAnd'],
255
            'argumentCount' => '1+',
256
        ],
257
        'AREAS' => [
258
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
259
            'functionCall' => [Functions::class, 'DUMMY'],
260
            'argumentCount' => '1',
261
        ],
262
        'ASC' => [
263
            'category' => Category::CATEGORY_TEXT_AND_DATA,
264
            'functionCall' => [Functions::class, 'DUMMY'],
265
            'argumentCount' => '1',
266
        ],
267
        'ASIN' => [
268
            'category' => Category::CATEGORY_MATH_AND_TRIG,
269
            'functionCall' => 'asin',
270
            'argumentCount' => '1',
271
        ],
272
        'ASINH' => [
273
            'category' => Category::CATEGORY_MATH_AND_TRIG,
274
            'functionCall' => 'asinh',
275
            'argumentCount' => '1',
276
        ],
277
        'ATAN' => [
278
            'category' => Category::CATEGORY_MATH_AND_TRIG,
279
            'functionCall' => 'atan',
280
            'argumentCount' => '1',
281
        ],
282
        'ATAN2' => [
283
            'category' => Category::CATEGORY_MATH_AND_TRIG,
284
            'functionCall' => [MathTrig::class, 'ATAN2'],
285
            'argumentCount' => '2',
286
        ],
287
        'ATANH' => [
288
            'category' => Category::CATEGORY_MATH_AND_TRIG,
289
            'functionCall' => 'atanh',
290
            'argumentCount' => '1',
291
        ],
292
        'AVEDEV' => [
293
            'category' => Category::CATEGORY_STATISTICAL,
294
            'functionCall' => [Statistical::class, 'AVEDEV'],
295
            'argumentCount' => '1+',
296
        ],
297
        'AVERAGE' => [
298
            'category' => Category::CATEGORY_STATISTICAL,
299
            'functionCall' => [Statistical::class, 'AVERAGE'],
300
            'argumentCount' => '1+',
301
        ],
302
        'AVERAGEA' => [
303
            'category' => Category::CATEGORY_STATISTICAL,
304
            'functionCall' => [Statistical::class, 'AVERAGEA'],
305
            'argumentCount' => '1+',
306
        ],
307
        'AVERAGEIF' => [
308
            'category' => Category::CATEGORY_STATISTICAL,
309
            'functionCall' => [Statistical::class, 'AVERAGEIF'],
310
            'argumentCount' => '2,3',
311
        ],
312
        'AVERAGEIFS' => [
313
            'category' => Category::CATEGORY_STATISTICAL,
314
            'functionCall' => [Functions::class, 'DUMMY'],
315
            'argumentCount' => '3+',
316
        ],
317
        'BAHTTEXT' => [
318
            'category' => Category::CATEGORY_TEXT_AND_DATA,
319
            'functionCall' => [Functions::class, 'DUMMY'],
320
            'argumentCount' => '1',
321
        ],
322
        'BESSELI' => [
323
            'category' => Category::CATEGORY_ENGINEERING,
324
            'functionCall' => [Engineering::class, 'BESSELI'],
325
            'argumentCount' => '2',
326
        ],
327
        'BESSELJ' => [
328
            'category' => Category::CATEGORY_ENGINEERING,
329
            'functionCall' => [Engineering::class, 'BESSELJ'],
330
            'argumentCount' => '2',
331
        ],
332
        'BESSELK' => [
333
            'category' => Category::CATEGORY_ENGINEERING,
334
            'functionCall' => [Engineering::class, 'BESSELK'],
335
            'argumentCount' => '2',
336
        ],
337
        'BESSELY' => [
338
            'category' => Category::CATEGORY_ENGINEERING,
339
            'functionCall' => [Engineering::class, 'BESSELY'],
340
            'argumentCount' => '2',
341
        ],
342
        'BETADIST' => [
343
            'category' => Category::CATEGORY_STATISTICAL,
344
            'functionCall' => [Statistical::class, 'BETADIST'],
345
            'argumentCount' => '3-5',
346
        ],
347
        'BETAINV' => [
348
            'category' => Category::CATEGORY_STATISTICAL,
349
            'functionCall' => [Statistical::class, 'BETAINV'],
350
            'argumentCount' => '3-5',
351
        ],
352
        'BIN2DEC' => [
353
            'category' => Category::CATEGORY_ENGINEERING,
354
            'functionCall' => [Engineering::class, 'BINTODEC'],
355
            'argumentCount' => '1',
356
        ],
357
        'BIN2HEX' => [
358
            'category' => Category::CATEGORY_ENGINEERING,
359
            'functionCall' => [Engineering::class, 'BINTOHEX'],
360
            'argumentCount' => '1,2',
361
        ],
362
        'BIN2OCT' => [
363
            'category' => Category::CATEGORY_ENGINEERING,
364
            'functionCall' => [Engineering::class, 'BINTOOCT'],
365
            'argumentCount' => '1,2',
366
        ],
367
        'BINOMDIST' => [
368
            'category' => Category::CATEGORY_STATISTICAL,
369
            'functionCall' => [Statistical::class, 'BINOMDIST'],
370
            'argumentCount' => '4',
371
        ],
372
        'BITAND' => [
373
            'category' => Category::CATEGORY_ENGINEERING,
374
            'functionCall' => [Engineering::class, 'BITAND'],
375
            'argumentCount' => '2',
376
        ],
377
        'BITOR' => [
378
            'category' => Category::CATEGORY_ENGINEERING,
379
            'functionCall' => [Engineering::class, 'BITOR'],
380
            'argumentCount' => '2',
381
        ],
382
        'BITXOR' => [
383
            'category' => Category::CATEGORY_ENGINEERING,
384
            'functionCall' => [Engineering::class, 'BITOR'],
385
            'argumentCount' => '2',
386
        ],
387
        'BITLSHIFT' => [
388
            'category' => Category::CATEGORY_ENGINEERING,
389
            'functionCall' => [Engineering::class, 'BITLSHIFT'],
390
            'argumentCount' => '2',
391
        ],
392
        'BITRSHIFT' => [
393
            'category' => Category::CATEGORY_ENGINEERING,
394
            'functionCall' => [Engineering::class, 'BITRSHIFT'],
395
            'argumentCount' => '2',
396
        ],
397
        'CEILING' => [
398
            'category' => Category::CATEGORY_MATH_AND_TRIG,
399
            'functionCall' => [MathTrig::class, 'CEILING'],
400
            'argumentCount' => '2',
401
        ],
402
        'CELL' => [
403
            'category' => Category::CATEGORY_INFORMATION,
404
            'functionCall' => [Functions::class, 'DUMMY'],
405
            'argumentCount' => '1,2',
406
        ],
407
        'CHAR' => [
408
            'category' => Category::CATEGORY_TEXT_AND_DATA,
409
            'functionCall' => [TextData::class, 'CHARACTER'],
410
            'argumentCount' => '1',
411
        ],
412
        'CHIDIST' => [
413
            'category' => Category::CATEGORY_STATISTICAL,
414
            'functionCall' => [Statistical::class, 'CHIDIST'],
415
            'argumentCount' => '2',
416
        ],
417
        'CHIINV' => [
418
            'category' => Category::CATEGORY_STATISTICAL,
419
            'functionCall' => [Statistical::class, 'CHIINV'],
420
            'argumentCount' => '2',
421
        ],
422
        'CHITEST' => [
423
            'category' => Category::CATEGORY_STATISTICAL,
424
            'functionCall' => [Functions::class, 'DUMMY'],
425
            'argumentCount' => '2',
426
        ],
427
        'CHOOSE' => [
428
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
429
            'functionCall' => [LookupRef::class, 'CHOOSE'],
430
            'argumentCount' => '2+',
431
        ],
432
        'CLEAN' => [
433
            'category' => Category::CATEGORY_TEXT_AND_DATA,
434
            'functionCall' => [TextData::class, 'TRIMNONPRINTABLE'],
435
            'argumentCount' => '1',
436
        ],
437
        'CODE' => [
438
            'category' => Category::CATEGORY_TEXT_AND_DATA,
439
            'functionCall' => [TextData::class, 'ASCIICODE'],
440
            'argumentCount' => '1',
441
        ],
442
        'COLUMN' => [
443
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
444
            'functionCall' => [LookupRef::class, 'COLUMN'],
445
            'argumentCount' => '-1',
446
            'passByReference' => [true],
447
        ],
448
        'COLUMNS' => [
449
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
450
            'functionCall' => [LookupRef::class, 'COLUMNS'],
451
            'argumentCount' => '1',
452
        ],
453
        'COMBIN' => [
454
            'category' => Category::CATEGORY_MATH_AND_TRIG,
455
            'functionCall' => [MathTrig::class, 'COMBIN'],
456
            'argumentCount' => '2',
457
        ],
458
        'COMPLEX' => [
459
            'category' => Category::CATEGORY_ENGINEERING,
460
            'functionCall' => [Engineering::class, 'COMPLEX'],
461
            'argumentCount' => '2,3',
462
        ],
463
        'CONCAT' => [
464
            'category' => Category::CATEGORY_TEXT_AND_DATA,
465
            'functionCall' => [TextData::class, 'CONCATENATE'],
466
            'argumentCount' => '1+',
467
        ],
468
        'CONCATENATE' => [
469
            'category' => Category::CATEGORY_TEXT_AND_DATA,
470
            'functionCall' => [TextData::class, 'CONCATENATE'],
471
            'argumentCount' => '1+',
472
        ],
473
        'CONFIDENCE' => [
474
            'category' => Category::CATEGORY_STATISTICAL,
475
            'functionCall' => [Statistical::class, 'CONFIDENCE'],
476
            'argumentCount' => '3',
477
        ],
478
        'CONVERT' => [
479
            'category' => Category::CATEGORY_ENGINEERING,
480
            'functionCall' => [Engineering::class, 'CONVERTUOM'],
481
            'argumentCount' => '3',
482
        ],
483
        'CORREL' => [
484
            'category' => Category::CATEGORY_STATISTICAL,
485
            'functionCall' => [Statistical::class, 'CORREL'],
486
            'argumentCount' => '2',
487
        ],
488
        'COS' => [
489
            'category' => Category::CATEGORY_MATH_AND_TRIG,
490
            'functionCall' => 'cos',
491
            'argumentCount' => '1',
492
        ],
493
        'COSH' => [
494
            'category' => Category::CATEGORY_MATH_AND_TRIG,
495
            'functionCall' => 'cosh',
496
            'argumentCount' => '1',
497
        ],
498
        'COT' => [
499
            'category' => Category::CATEGORY_MATH_AND_TRIG,
500
            'functionCall' => [MathTrig::class, 'COT'],
501
            'argumentCount' => '1',
502
        ],
503
        'COTH' => [
504
            'category' => Category::CATEGORY_MATH_AND_TRIG,
505
            'functionCall' => [MathTrig::class, 'COTH'],
506
            'argumentCount' => '1',
507
        ],
508
        'COUNT' => [
509
            'category' => Category::CATEGORY_STATISTICAL,
510
            'functionCall' => [Statistical::class, 'COUNT'],
511
            'argumentCount' => '1+',
512
        ],
513
        'COUNTA' => [
514
            'category' => Category::CATEGORY_STATISTICAL,
515
            'functionCall' => [Statistical::class, 'COUNTA'],
516
            'argumentCount' => '1+',
517
        ],
518
        'COUNTBLANK' => [
519
            'category' => Category::CATEGORY_STATISTICAL,
520
            'functionCall' => [Statistical::class, 'COUNTBLANK'],
521
            'argumentCount' => '1',
522
        ],
523
        'COUNTIF' => [
524
            'category' => Category::CATEGORY_STATISTICAL,
525
            'functionCall' => [Statistical::class, 'COUNTIF'],
526
            'argumentCount' => '2',
527
        ],
528
        'COUNTIFS' => [
529
            'category' => Category::CATEGORY_STATISTICAL,
530
            'functionCall' => [Functions::class, 'DUMMY'],
531
            'argumentCount' => '2',
532
        ],
533
        'COUPDAYBS' => [
534
            'category' => Category::CATEGORY_FINANCIAL,
535
            'functionCall' => [Financial::class, 'COUPDAYBS'],
536
            'argumentCount' => '3,4',
537
        ],
538
        'COUPDAYS' => [
539
            'category' => Category::CATEGORY_FINANCIAL,
540
            'functionCall' => [Financial::class, 'COUPDAYS'],
541
            'argumentCount' => '3,4',
542
        ],
543
        'COUPDAYSNC' => [
544
            'category' => Category::CATEGORY_FINANCIAL,
545
            'functionCall' => [Financial::class, 'COUPDAYSNC'],
546
            'argumentCount' => '3,4',
547
        ],
548
        'COUPNCD' => [
549
            'category' => Category::CATEGORY_FINANCIAL,
550
            'functionCall' => [Financial::class, 'COUPNCD'],
551
            'argumentCount' => '3,4',
552
        ],
553
        'COUPNUM' => [
554
            'category' => Category::CATEGORY_FINANCIAL,
555
            'functionCall' => [Financial::class, 'COUPNUM'],
556
            'argumentCount' => '3,4',
557
        ],
558
        'COUPPCD' => [
559
            'category' => Category::CATEGORY_FINANCIAL,
560
            'functionCall' => [Financial::class, 'COUPPCD'],
561
            'argumentCount' => '3,4',
562
        ],
563
        'COVAR' => [
564
            'category' => Category::CATEGORY_STATISTICAL,
565
            'functionCall' => [Statistical::class, 'COVAR'],
566
            'argumentCount' => '2',
567
        ],
568
        'CRITBINOM' => [
569
            'category' => Category::CATEGORY_STATISTICAL,
570
            'functionCall' => [Statistical::class, 'CRITBINOM'],
571
            'argumentCount' => '3',
572
        ],
573
        'CSC' => [
574
            'category' => Category::CATEGORY_MATH_AND_TRIG,
575
            'functionCall' => [MathTrig::class, 'CSC'],
576
            'argumentCount' => '1',
577
        ],
578
        'CSCH' => [
579
            'category' => Category::CATEGORY_MATH_AND_TRIG,
580
            'functionCall' => [MathTrig::class, 'CSCH'],
581
            'argumentCount' => '1',
582
        ],
583
        'CUBEKPIMEMBER' => [
584
            'category' => Category::CATEGORY_CUBE,
585
            'functionCall' => [Functions::class, 'DUMMY'],
586
            'argumentCount' => '?',
587
        ],
588
        'CUBEMEMBER' => [
589
            'category' => Category::CATEGORY_CUBE,
590
            'functionCall' => [Functions::class, 'DUMMY'],
591
            'argumentCount' => '?',
592
        ],
593
        'CUBEMEMBERPROPERTY' => [
594
            'category' => Category::CATEGORY_CUBE,
595
            'functionCall' => [Functions::class, 'DUMMY'],
596
            'argumentCount' => '?',
597
        ],
598
        'CUBERANKEDMEMBER' => [
599
            'category' => Category::CATEGORY_CUBE,
600
            'functionCall' => [Functions::class, 'DUMMY'],
601
            'argumentCount' => '?',
602
        ],
603
        'CUBESET' => [
604
            'category' => Category::CATEGORY_CUBE,
605
            'functionCall' => [Functions::class, 'DUMMY'],
606
            'argumentCount' => '?',
607
        ],
608
        'CUBESETCOUNT' => [
609
            'category' => Category::CATEGORY_CUBE,
610
            'functionCall' => [Functions::class, 'DUMMY'],
611
            'argumentCount' => '?',
612
        ],
613
        'CUBEVALUE' => [
614
            'category' => Category::CATEGORY_CUBE,
615
            'functionCall' => [Functions::class, 'DUMMY'],
616
            'argumentCount' => '?',
617
        ],
618
        'CUMIPMT' => [
619
            'category' => Category::CATEGORY_FINANCIAL,
620
            'functionCall' => [Financial::class, 'CUMIPMT'],
621
            'argumentCount' => '6',
622
        ],
623
        'CUMPRINC' => [
624
            'category' => Category::CATEGORY_FINANCIAL,
625
            'functionCall' => [Financial::class, 'CUMPRINC'],
626
            'argumentCount' => '6',
627
        ],
628
        'DATE' => [
629
            'category' => Category::CATEGORY_DATE_AND_TIME,
630
            'functionCall' => [DateTime::class, 'DATE'],
631
            'argumentCount' => '3',
632
        ],
633
        'DATEDIF' => [
634
            'category' => Category::CATEGORY_DATE_AND_TIME,
635
            'functionCall' => [DateTime::class, 'DATEDIF'],
636
            'argumentCount' => '2,3',
637
        ],
638
        'DATEVALUE' => [
639
            'category' => Category::CATEGORY_DATE_AND_TIME,
640
            'functionCall' => [DateTime::class, 'DATEVALUE'],
641
            'argumentCount' => '1',
642
        ],
643
        'DAVERAGE' => [
644
            'category' => Category::CATEGORY_DATABASE,
645
            'functionCall' => [Database::class, 'DAVERAGE'],
646
            'argumentCount' => '3',
647
        ],
648
        'DAY' => [
649
            'category' => Category::CATEGORY_DATE_AND_TIME,
650
            'functionCall' => [DateTime::class, 'DAYOFMONTH'],
651
            'argumentCount' => '1',
652
        ],
653
        'DAYS360' => [
654
            'category' => Category::CATEGORY_DATE_AND_TIME,
655
            'functionCall' => [DateTime::class, 'DAYS360'],
656
            'argumentCount' => '2,3',
657
        ],
658
        'DB' => [
659
            'category' => Category::CATEGORY_FINANCIAL,
660
            'functionCall' => [Financial::class, 'DB'],
661
            'argumentCount' => '4,5',
662
        ],
663
        'DCOUNT' => [
664
            'category' => Category::CATEGORY_DATABASE,
665
            'functionCall' => [Database::class, 'DCOUNT'],
666
            'argumentCount' => '3',
667
        ],
668
        'DCOUNTA' => [
669
            'category' => Category::CATEGORY_DATABASE,
670
            'functionCall' => [Database::class, 'DCOUNTA'],
671
            'argumentCount' => '3',
672
        ],
673
        'DDB' => [
674
            'category' => Category::CATEGORY_FINANCIAL,
675
            'functionCall' => [Financial::class, 'DDB'],
676
            'argumentCount' => '4,5',
677
        ],
678
        'DEC2BIN' => [
679
            'category' => Category::CATEGORY_ENGINEERING,
680
            'functionCall' => [Engineering::class, 'DECTOBIN'],
681
            'argumentCount' => '1,2',
682
        ],
683
        'DEC2HEX' => [
684
            'category' => Category::CATEGORY_ENGINEERING,
685
            'functionCall' => [Engineering::class, 'DECTOHEX'],
686
            'argumentCount' => '1,2',
687
        ],
688
        'DEC2OCT' => [
689
            'category' => Category::CATEGORY_ENGINEERING,
690
            'functionCall' => [Engineering::class, 'DECTOOCT'],
691
            'argumentCount' => '1,2',
692
        ],
693
        'DEGREES' => [
694
            'category' => Category::CATEGORY_MATH_AND_TRIG,
695
            'functionCall' => 'rad2deg',
696
            'argumentCount' => '1',
697
        ],
698
        'DELTA' => [
699
            'category' => Category::CATEGORY_ENGINEERING,
700
            'functionCall' => [Engineering::class, 'DELTA'],
701
            'argumentCount' => '1,2',
702
        ],
703
        'DEVSQ' => [
704
            'category' => Category::CATEGORY_STATISTICAL,
705
            'functionCall' => [Statistical::class, 'DEVSQ'],
706
            'argumentCount' => '1+',
707
        ],
708
        'DGET' => [
709
            'category' => Category::CATEGORY_DATABASE,
710
            'functionCall' => [Database::class, 'DGET'],
711
            'argumentCount' => '3',
712
        ],
713
        'DISC' => [
714
            'category' => Category::CATEGORY_FINANCIAL,
715
            'functionCall' => [Financial::class, 'DISC'],
716
            'argumentCount' => '4,5',
717
        ],
718
        'DMAX' => [
719
            'category' => Category::CATEGORY_DATABASE,
720
            'functionCall' => [Database::class, 'DMAX'],
721
            'argumentCount' => '3',
722
        ],
723
        'DMIN' => [
724
            'category' => Category::CATEGORY_DATABASE,
725
            'functionCall' => [Database::class, 'DMIN'],
726
            'argumentCount' => '3',
727
        ],
728
        'DOLLAR' => [
729
            'category' => Category::CATEGORY_TEXT_AND_DATA,
730
            'functionCall' => [TextData::class, 'DOLLAR'],
731
            'argumentCount' => '1,2',
732
        ],
733
        'DOLLARDE' => [
734
            'category' => Category::CATEGORY_FINANCIAL,
735
            'functionCall' => [Financial::class, 'DOLLARDE'],
736
            'argumentCount' => '2',
737
        ],
738
        'DOLLARFR' => [
739
            'category' => Category::CATEGORY_FINANCIAL,
740
            'functionCall' => [Financial::class, 'DOLLARFR'],
741
            'argumentCount' => '2',
742
        ],
743
        'DPRODUCT' => [
744
            'category' => Category::CATEGORY_DATABASE,
745
            'functionCall' => [Database::class, 'DPRODUCT'],
746
            'argumentCount' => '3',
747
        ],
748
        'DSTDEV' => [
749
            'category' => Category::CATEGORY_DATABASE,
750
            'functionCall' => [Database::class, 'DSTDEV'],
751
            'argumentCount' => '3',
752
        ],
753
        'DSTDEVP' => [
754
            'category' => Category::CATEGORY_DATABASE,
755
            'functionCall' => [Database::class, 'DSTDEVP'],
756
            'argumentCount' => '3',
757
        ],
758
        'DSUM' => [
759
            'category' => Category::CATEGORY_DATABASE,
760
            'functionCall' => [Database::class, 'DSUM'],
761
            'argumentCount' => '3',
762
        ],
763
        'DURATION' => [
764
            'category' => Category::CATEGORY_FINANCIAL,
765
            'functionCall' => [Functions::class, 'DUMMY'],
766
            'argumentCount' => '5,6',
767
        ],
768
        'DVAR' => [
769
            'category' => Category::CATEGORY_DATABASE,
770
            'functionCall' => [Database::class, 'DVAR'],
771
            'argumentCount' => '3',
772
        ],
773
        'DVARP' => [
774
            'category' => Category::CATEGORY_DATABASE,
775
            'functionCall' => [Database::class, 'DVARP'],
776
            'argumentCount' => '3',
777
        ],
778
        'EDATE' => [
779
            'category' => Category::CATEGORY_DATE_AND_TIME,
780
            'functionCall' => [DateTime::class, 'EDATE'],
781
            'argumentCount' => '2',
782
        ],
783
        'EFFECT' => [
784
            'category' => Category::CATEGORY_FINANCIAL,
785
            'functionCall' => [Financial::class, 'EFFECT'],
786
            'argumentCount' => '2',
787
        ],
788
        'EOMONTH' => [
789
            'category' => Category::CATEGORY_DATE_AND_TIME,
790
            'functionCall' => [DateTime::class, 'EOMONTH'],
791
            'argumentCount' => '2',
792
        ],
793
        'ERF' => [
794
            'category' => Category::CATEGORY_ENGINEERING,
795
            'functionCall' => [Engineering::class, 'ERF'],
796
            'argumentCount' => '1,2',
797
        ],
798
        'ERF.PRECISE' => [
799
            'category' => Category::CATEGORY_ENGINEERING,
800
            'functionCall' => [Engineering::class, 'ERFPRECISE'],
801
            'argumentCount' => '1',
802
        ],
803
        'ERFC' => [
804
            'category' => Category::CATEGORY_ENGINEERING,
805
            'functionCall' => [Engineering::class, 'ERFC'],
806
            'argumentCount' => '1',
807
        ],
808
        'ERFC.PRECISE' => [
809
            'category' => Category::CATEGORY_ENGINEERING,
810
            'functionCall' => [Engineering::class, 'ERFC'],
811
            'argumentCount' => '1',
812
        ],
813
        'ERROR.TYPE' => [
814
            'category' => Category::CATEGORY_INFORMATION,
815
            'functionCall' => [Functions::class, 'errorType'],
816
            'argumentCount' => '1',
817
        ],
818
        'EVEN' => [
819
            'category' => Category::CATEGORY_MATH_AND_TRIG,
820
            'functionCall' => [MathTrig::class, 'EVEN'],
821
            'argumentCount' => '1',
822
        ],
823
        'EXACT' => [
824
            'category' => Category::CATEGORY_TEXT_AND_DATA,
825
            'functionCall' => [TextData::class, 'EXACT'],
826
            'argumentCount' => '2',
827
        ],
828
        'EXP' => [
829
            'category' => Category::CATEGORY_MATH_AND_TRIG,
830
            'functionCall' => 'exp',
831
            'argumentCount' => '1',
832
        ],
833
        'EXPONDIST' => [
834
            'category' => Category::CATEGORY_STATISTICAL,
835
            'functionCall' => [Statistical::class, 'EXPONDIST'],
836
            'argumentCount' => '3',
837
        ],
838
        'FACT' => [
839
            'category' => Category::CATEGORY_MATH_AND_TRIG,
840
            'functionCall' => [MathTrig::class, 'FACT'],
841
            'argumentCount' => '1',
842
        ],
843
        'FACTDOUBLE' => [
844
            'category' => Category::CATEGORY_MATH_AND_TRIG,
845
            'functionCall' => [MathTrig::class, 'FACTDOUBLE'],
846
            'argumentCount' => '1',
847
        ],
848
        'FALSE' => [
849
            'category' => Category::CATEGORY_LOGICAL,
850
            'functionCall' => [Logical::class, 'FALSE'],
851
            'argumentCount' => '0',
852
        ],
853
        'FDIST' => [
854
            'category' => Category::CATEGORY_STATISTICAL,
855
            'functionCall' => [Functions::class, 'DUMMY'],
856
            'argumentCount' => '3',
857
        ],
858
        'FIND' => [
859
            'category' => Category::CATEGORY_TEXT_AND_DATA,
860
            'functionCall' => [TextData::class, 'SEARCHSENSITIVE'],
861
            'argumentCount' => '2,3',
862
        ],
863
        'FINDB' => [
864
            'category' => Category::CATEGORY_TEXT_AND_DATA,
865
            'functionCall' => [TextData::class, 'SEARCHSENSITIVE'],
866
            'argumentCount' => '2,3',
867
        ],
868
        'FINV' => [
869
            'category' => Category::CATEGORY_STATISTICAL,
870
            'functionCall' => [Functions::class, 'DUMMY'],
871
            'argumentCount' => '3',
872
        ],
873
        'FISHER' => [
874
            'category' => Category::CATEGORY_STATISTICAL,
875
            'functionCall' => [Statistical::class, 'FISHER'],
876
            'argumentCount' => '1',
877
        ],
878
        'FISHERINV' => [
879
            'category' => Category::CATEGORY_STATISTICAL,
880
            'functionCall' => [Statistical::class, 'FISHERINV'],
881
            'argumentCount' => '1',
882
        ],
883
        'FIXED' => [
884
            'category' => Category::CATEGORY_TEXT_AND_DATA,
885
            'functionCall' => [TextData::class, 'FIXEDFORMAT'],
886
            'argumentCount' => '1-3',
887
        ],
888
        'FLOOR' => [
889
            'category' => Category::CATEGORY_MATH_AND_TRIG,
890
            'functionCall' => [MathTrig::class, 'FLOOR'],
891
            'argumentCount' => '2',
892
        ],
893
        'FORECAST' => [
894
            'category' => Category::CATEGORY_STATISTICAL,
895
            'functionCall' => [Statistical::class, 'FORECAST'],
896
            'argumentCount' => '3',
897
        ],
898
        'FORMULATEXT' => [
899
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
900
            'functionCall' => [LookupRef::class, 'FORMULATEXT'],
901
            'argumentCount' => '1',
902
            'passCellReference' => true,
903
            'passByReference' => [true],
904
        ],
905
        'FREQUENCY' => [
906
            'category' => Category::CATEGORY_STATISTICAL,
907
            'functionCall' => [Functions::class, 'DUMMY'],
908
            'argumentCount' => '2',
909
        ],
910
        'FTEST' => [
911
            'category' => Category::CATEGORY_STATISTICAL,
912
            'functionCall' => [Functions::class, 'DUMMY'],
913
            'argumentCount' => '2',
914
        ],
915
        'FV' => [
916
            'category' => Category::CATEGORY_FINANCIAL,
917
            'functionCall' => [Financial::class, 'FV'],
918
            'argumentCount' => '3-5',
919
        ],
920
        'FVSCHEDULE' => [
921
            'category' => Category::CATEGORY_FINANCIAL,
922
            'functionCall' => [Financial::class, 'FVSCHEDULE'],
923
            'argumentCount' => '2',
924
        ],
925
        'GAMMADIST' => [
926
            'category' => Category::CATEGORY_STATISTICAL,
927
            'functionCall' => [Statistical::class, 'GAMMADIST'],
928
            'argumentCount' => '4',
929
        ],
930
        'GAMMAINV' => [
931
            'category' => Category::CATEGORY_STATISTICAL,
932
            'functionCall' => [Statistical::class, 'GAMMAINV'],
933
            'argumentCount' => '3',
934
        ],
935
        'GAMMALN' => [
936
            'category' => Category::CATEGORY_STATISTICAL,
937
            'functionCall' => [Statistical::class, 'GAMMALN'],
938
            'argumentCount' => '1',
939
        ],
940
        'GCD' => [
941
            'category' => Category::CATEGORY_MATH_AND_TRIG,
942
            'functionCall' => [MathTrig::class, 'GCD'],
943
            'argumentCount' => '1+',
944
        ],
945
        'GEOMEAN' => [
946
            'category' => Category::CATEGORY_STATISTICAL,
947
            'functionCall' => [Statistical::class, 'GEOMEAN'],
948
            'argumentCount' => '1+',
949
        ],
950
        'GESTEP' => [
951
            'category' => Category::CATEGORY_ENGINEERING,
952
            'functionCall' => [Engineering::class, 'GESTEP'],
953
            'argumentCount' => '1,2',
954
        ],
955
        'GETPIVOTDATA' => [
956
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
957
            'functionCall' => [Functions::class, 'DUMMY'],
958
            'argumentCount' => '2+',
959
        ],
960
        'GROWTH' => [
961
            'category' => Category::CATEGORY_STATISTICAL,
962
            'functionCall' => [Statistical::class, 'GROWTH'],
963
            'argumentCount' => '1-4',
964
        ],
965
        'HARMEAN' => [
966
            'category' => Category::CATEGORY_STATISTICAL,
967
            'functionCall' => [Statistical::class, 'HARMEAN'],
968
            'argumentCount' => '1+',
969
        ],
970
        'HEX2BIN' => [
971
            'category' => Category::CATEGORY_ENGINEERING,
972
            'functionCall' => [Engineering::class, 'HEXTOBIN'],
973
            'argumentCount' => '1,2',
974
        ],
975
        'HEX2DEC' => [
976
            'category' => Category::CATEGORY_ENGINEERING,
977
            'functionCall' => [Engineering::class, 'HEXTODEC'],
978
            'argumentCount' => '1',
979
        ],
980
        'HEX2OCT' => [
981
            'category' => Category::CATEGORY_ENGINEERING,
982
            'functionCall' => [Engineering::class, 'HEXTOOCT'],
983
            'argumentCount' => '1,2',
984
        ],
985
        'HLOOKUP' => [
986
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
987
            'functionCall' => [LookupRef::class, 'HLOOKUP'],
988
            'argumentCount' => '3,4',
989
        ],
990
        'HOUR' => [
991
            'category' => Category::CATEGORY_DATE_AND_TIME,
992
            'functionCall' => [DateTime::class, 'HOUROFDAY'],
993
            'argumentCount' => '1',
994
        ],
995
        'HYPERLINK' => [
996
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
997
            'functionCall' => [LookupRef::class, 'HYPERLINK'],
998
            'argumentCount' => '1,2',
999
            'passCellReference' => true,
1000
        ],
1001
        'HYPGEOMDIST' => [
1002
            'category' => Category::CATEGORY_STATISTICAL,
1003
            'functionCall' => [Statistical::class, 'HYPGEOMDIST'],
1004
            'argumentCount' => '4',
1005
        ],
1006
        'IF' => [
1007
            'category' => Category::CATEGORY_LOGICAL,
1008
            'functionCall' => [Logical::class, 'statementIf'],
1009
            'argumentCount' => '1-3',
1010
        ],
1011
        'IFERROR' => [
1012
            'category' => Category::CATEGORY_LOGICAL,
1013
            'functionCall' => [Logical::class, 'IFERROR'],
1014
            'argumentCount' => '2',
1015
        ],
1016
        'IMABS' => [
1017
            'category' => Category::CATEGORY_ENGINEERING,
1018
            'functionCall' => [Engineering::class, 'IMABS'],
1019
            'argumentCount' => '1',
1020
        ],
1021
        'IMAGINARY' => [
1022
            'category' => Category::CATEGORY_ENGINEERING,
1023
            'functionCall' => [Engineering::class, 'IMAGINARY'],
1024
            'argumentCount' => '1',
1025
        ],
1026
        'IMARGUMENT' => [
1027
            'category' => Category::CATEGORY_ENGINEERING,
1028
            'functionCall' => [Engineering::class, 'IMARGUMENT'],
1029
            'argumentCount' => '1',
1030
        ],
1031
        'IMCONJUGATE' => [
1032
            'category' => Category::CATEGORY_ENGINEERING,
1033
            'functionCall' => [Engineering::class, 'IMCONJUGATE'],
1034
            'argumentCount' => '1',
1035
        ],
1036
        'IMCOS' => [
1037
            'category' => Category::CATEGORY_ENGINEERING,
1038
            'functionCall' => [Engineering::class, 'IMCOS'],
1039
            'argumentCount' => '1',
1040
        ],
1041
        'IMCOSH' => [
1042
            'category' => Category::CATEGORY_ENGINEERING,
1043
            'functionCall' => [Engineering::class, 'IMCOSH'],
1044
            'argumentCount' => '1',
1045
        ],
1046
        'IMCOT' => [
1047
            'category' => Category::CATEGORY_ENGINEERING,
1048
            'functionCall' => [Engineering::class, 'IMCOT'],
1049
            'argumentCount' => '1',
1050
        ],
1051
        'IMCSC' => [
1052
            'category' => Category::CATEGORY_ENGINEERING,
1053
            'functionCall' => [Engineering::class, 'IMCSC'],
1054
            'argumentCount' => '1',
1055
        ],
1056
        'IMCSCH' => [
1057
            'category' => Category::CATEGORY_ENGINEERING,
1058
            'functionCall' => [Engineering::class, 'IMCSCH'],
1059
            'argumentCount' => '1',
1060
        ],
1061
        'IMDIV' => [
1062
            'category' => Category::CATEGORY_ENGINEERING,
1063
            'functionCall' => [Engineering::class, 'IMDIV'],
1064
            'argumentCount' => '2',
1065
        ],
1066
        'IMEXP' => [
1067
            'category' => Category::CATEGORY_ENGINEERING,
1068
            'functionCall' => [Engineering::class, 'IMEXP'],
1069
            'argumentCount' => '1',
1070
        ],
1071
        'IMLN' => [
1072
            'category' => Category::CATEGORY_ENGINEERING,
1073
            'functionCall' => [Engineering::class, 'IMLN'],
1074
            'argumentCount' => '1',
1075
        ],
1076
        'IMLOG10' => [
1077
            'category' => Category::CATEGORY_ENGINEERING,
1078
            'functionCall' => [Engineering::class, 'IMLOG10'],
1079
            'argumentCount' => '1',
1080
        ],
1081
        'IMLOG2' => [
1082
            'category' => Category::CATEGORY_ENGINEERING,
1083
            'functionCall' => [Engineering::class, 'IMLOG2'],
1084
            'argumentCount' => '1',
1085
        ],
1086
        'IMPOWER' => [
1087
            'category' => Category::CATEGORY_ENGINEERING,
1088
            'functionCall' => [Engineering::class, 'IMPOWER'],
1089
            'argumentCount' => '2',
1090
        ],
1091
        'IMPRODUCT' => [
1092
            'category' => Category::CATEGORY_ENGINEERING,
1093
            'functionCall' => [Engineering::class, 'IMPRODUCT'],
1094
            'argumentCount' => '1+',
1095
        ],
1096
        'IMREAL' => [
1097
            'category' => Category::CATEGORY_ENGINEERING,
1098
            'functionCall' => [Engineering::class, 'IMREAL'],
1099
            'argumentCount' => '1',
1100
        ],
1101
        'IMSEC' => [
1102
            'category' => Category::CATEGORY_ENGINEERING,
1103
            'functionCall' => [Engineering::class, 'IMSEC'],
1104
            'argumentCount' => '1',
1105
        ],
1106
        'IMSECH' => [
1107
            'category' => Category::CATEGORY_ENGINEERING,
1108
            'functionCall' => [Engineering::class, 'IMSECH'],
1109
            'argumentCount' => '1',
1110
        ],
1111
        'IMSIN' => [
1112
            'category' => Category::CATEGORY_ENGINEERING,
1113
            'functionCall' => [Engineering::class, 'IMSIN'],
1114
            'argumentCount' => '1',
1115
        ],
1116
        'IMSINH' => [
1117
            'category' => Category::CATEGORY_ENGINEERING,
1118
            'functionCall' => [Engineering::class, 'IMSINH'],
1119
            'argumentCount' => '1',
1120
        ],
1121
        'IMSQRT' => [
1122
            'category' => Category::CATEGORY_ENGINEERING,
1123
            'functionCall' => [Engineering::class, 'IMSQRT'],
1124
            'argumentCount' => '1',
1125
        ],
1126
        'IMSUB' => [
1127
            'category' => Category::CATEGORY_ENGINEERING,
1128
            'functionCall' => [Engineering::class, 'IMSUB'],
1129
            'argumentCount' => '2',
1130
        ],
1131
        'IMSUM' => [
1132
            'category' => Category::CATEGORY_ENGINEERING,
1133
            'functionCall' => [Engineering::class, 'IMSUM'],
1134
            'argumentCount' => '1+',
1135
        ],
1136
        'IMTAN' => [
1137
            'category' => Category::CATEGORY_ENGINEERING,
1138
            'functionCall' => [Engineering::class, 'IMTAN'],
1139
            'argumentCount' => '1',
1140
        ],
1141
        'INDEX' => [
1142
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1143
            'functionCall' => [LookupRef::class, 'INDEX'],
1144
            'argumentCount' => '1-4',
1145
        ],
1146
        'INDIRECT' => [
1147
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1148
            'functionCall' => [LookupRef::class, 'INDIRECT'],
1149
            'argumentCount' => '1,2',
1150
            'passCellReference' => true,
1151
        ],
1152
        'INFO' => [
1153
            'category' => Category::CATEGORY_INFORMATION,
1154
            'functionCall' => [Functions::class, 'DUMMY'],
1155
            'argumentCount' => '1',
1156
        ],
1157
        'INT' => [
1158
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1159
            'functionCall' => [MathTrig::class, 'INT'],
1160
            'argumentCount' => '1',
1161
        ],
1162
        'INTERCEPT' => [
1163
            'category' => Category::CATEGORY_STATISTICAL,
1164
            'functionCall' => [Statistical::class, 'INTERCEPT'],
1165
            'argumentCount' => '2',
1166
        ],
1167
        'INTRATE' => [
1168
            'category' => Category::CATEGORY_FINANCIAL,
1169
            'functionCall' => [Financial::class, 'INTRATE'],
1170
            'argumentCount' => '4,5',
1171
        ],
1172
        'IPMT' => [
1173
            'category' => Category::CATEGORY_FINANCIAL,
1174
            'functionCall' => [Financial::class, 'IPMT'],
1175
            'argumentCount' => '4-6',
1176
        ],
1177
        'IRR' => [
1178
            'category' => Category::CATEGORY_FINANCIAL,
1179
            'functionCall' => [Financial::class, 'IRR'],
1180
            'argumentCount' => '1,2',
1181
        ],
1182
        'ISBLANK' => [
1183
            'category' => Category::CATEGORY_INFORMATION,
1184
            'functionCall' => [Functions::class, 'isBlank'],
1185
            'argumentCount' => '1',
1186
        ],
1187
        'ISERR' => [
1188
            'category' => Category::CATEGORY_INFORMATION,
1189
            'functionCall' => [Functions::class, 'isErr'],
1190
            'argumentCount' => '1',
1191
        ],
1192
        'ISERROR' => [
1193
            'category' => Category::CATEGORY_INFORMATION,
1194
            'functionCall' => [Functions::class, 'isError'],
1195
            'argumentCount' => '1',
1196
        ],
1197
        'ISEVEN' => [
1198
            'category' => Category::CATEGORY_INFORMATION,
1199
            'functionCall' => [Functions::class, 'isEven'],
1200
            'argumentCount' => '1',
1201
        ],
1202
        'ISFORMULA' => [
1203
            'category' => Category::CATEGORY_INFORMATION,
1204
            'functionCall' => [Functions::class, 'isFormula'],
1205
            'argumentCount' => '1',
1206
            'passCellReference' => true,
1207
            'passByReference' => [true],
1208
        ],
1209
        'ISLOGICAL' => [
1210
            'category' => Category::CATEGORY_INFORMATION,
1211
            'functionCall' => [Functions::class, 'isLogical'],
1212
            'argumentCount' => '1',
1213
        ],
1214
        'ISNA' => [
1215
            'category' => Category::CATEGORY_INFORMATION,
1216
            'functionCall' => [Functions::class, 'isNa'],
1217
            'argumentCount' => '1',
1218
        ],
1219
        'ISNONTEXT' => [
1220
            'category' => Category::CATEGORY_INFORMATION,
1221
            'functionCall' => [Functions::class, 'isNonText'],
1222
            'argumentCount' => '1',
1223
        ],
1224
        'ISNUMBER' => [
1225
            'category' => Category::CATEGORY_INFORMATION,
1226
            'functionCall' => [Functions::class, 'isNumber'],
1227
            'argumentCount' => '1',
1228
        ],
1229
        'ISODD' => [
1230
            'category' => Category::CATEGORY_INFORMATION,
1231
            'functionCall' => [Functions::class, 'isOdd'],
1232
            'argumentCount' => '1',
1233
        ],
1234
        'ISOWEEKNUM' => [
1235
            'category' => Category::CATEGORY_DATE_AND_TIME,
1236
            'functionCall' => [DateTime::class, 'ISOWEEKNUM'],
1237
            'argumentCount' => '1',
1238
        ],
1239
        'ISPMT' => [
1240
            'category' => Category::CATEGORY_FINANCIAL,
1241
            'functionCall' => [Financial::class, 'ISPMT'],
1242
            'argumentCount' => '4',
1243
        ],
1244
        'ISREF' => [
1245
            'category' => Category::CATEGORY_INFORMATION,
1246
            'functionCall' => [Functions::class, 'DUMMY'],
1247
            'argumentCount' => '1',
1248
        ],
1249
        'ISTEXT' => [
1250
            'category' => Category::CATEGORY_INFORMATION,
1251
            'functionCall' => [Functions::class, 'isText'],
1252
            'argumentCount' => '1',
1253
        ],
1254
        'JIS' => [
1255
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1256
            'functionCall' => [Functions::class, 'DUMMY'],
1257
            'argumentCount' => '1',
1258
        ],
1259
        'KURT' => [
1260
            'category' => Category::CATEGORY_STATISTICAL,
1261
            'functionCall' => [Statistical::class, 'KURT'],
1262
            'argumentCount' => '1+',
1263
        ],
1264
        'LARGE' => [
1265
            'category' => Category::CATEGORY_STATISTICAL,
1266
            'functionCall' => [Statistical::class, 'LARGE'],
1267
            'argumentCount' => '2',
1268
        ],
1269
        'LCM' => [
1270
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1271
            'functionCall' => [MathTrig::class, 'LCM'],
1272
            'argumentCount' => '1+',
1273
        ],
1274
        'LEFT' => [
1275
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1276
            'functionCall' => [TextData::class, 'LEFT'],
1277
            'argumentCount' => '1,2',
1278
        ],
1279
        'LEFTB' => [
1280
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1281
            'functionCall' => [TextData::class, 'LEFT'],
1282
            'argumentCount' => '1,2',
1283
        ],
1284
        'LEN' => [
1285
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1286
            'functionCall' => [TextData::class, 'STRINGLENGTH'],
1287
            'argumentCount' => '1',
1288
        ],
1289
        'LENB' => [
1290
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1291
            'functionCall' => [TextData::class, 'STRINGLENGTH'],
1292
            'argumentCount' => '1',
1293
        ],
1294
        'LINEST' => [
1295
            'category' => Category::CATEGORY_STATISTICAL,
1296
            'functionCall' => [Statistical::class, 'LINEST'],
1297
            'argumentCount' => '1-4',
1298
        ],
1299
        'LN' => [
1300
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1301
            'functionCall' => 'log',
1302
            'argumentCount' => '1',
1303
        ],
1304
        'LOG' => [
1305
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1306
            'functionCall' => [MathTrig::class, 'logBase'],
1307
            'argumentCount' => '1,2',
1308
        ],
1309
        'LOG10' => [
1310
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1311
            'functionCall' => 'log10',
1312
            'argumentCount' => '1',
1313
        ],
1314
        'LOGEST' => [
1315
            'category' => Category::CATEGORY_STATISTICAL,
1316
            'functionCall' => [Statistical::class, 'LOGEST'],
1317
            'argumentCount' => '1-4',
1318
        ],
1319
        'LOGINV' => [
1320
            'category' => Category::CATEGORY_STATISTICAL,
1321
            'functionCall' => [Statistical::class, 'LOGINV'],
1322
            'argumentCount' => '3',
1323
        ],
1324
        'LOGNORMDIST' => [
1325
            'category' => Category::CATEGORY_STATISTICAL,
1326
            'functionCall' => [Statistical::class, 'LOGNORMDIST'],
1327
            'argumentCount' => '3',
1328
        ],
1329
        'LOOKUP' => [
1330
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1331
            'functionCall' => [LookupRef::class, 'LOOKUP'],
1332
            'argumentCount' => '2,3',
1333
        ],
1334
        'LOWER' => [
1335
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1336
            'functionCall' => [TextData::class, 'LOWERCASE'],
1337
            'argumentCount' => '1',
1338
        ],
1339
        'MATCH' => [
1340
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1341
            'functionCall' => [LookupRef::class, 'MATCH'],
1342
            'argumentCount' => '2,3',
1343
        ],
1344
        'MAX' => [
1345
            'category' => Category::CATEGORY_STATISTICAL,
1346
            'functionCall' => [Statistical::class, 'MAX'],
1347
            'argumentCount' => '1+',
1348
        ],
1349
        'MAXA' => [
1350
            'category' => Category::CATEGORY_STATISTICAL,
1351
            'functionCall' => [Statistical::class, 'MAXA'],
1352
            'argumentCount' => '1+',
1353
        ],
1354
        'MAXIF' => [
1355
            'category' => Category::CATEGORY_STATISTICAL,
1356
            'functionCall' => [Statistical::class, 'MAXIF'],
1357
            'argumentCount' => '2+',
1358
        ],
1359
        'MDETERM' => [
1360
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1361
            'functionCall' => [MathTrig::class, 'MDETERM'],
1362
            'argumentCount' => '1',
1363
        ],
1364
        'MDURATION' => [
1365
            'category' => Category::CATEGORY_FINANCIAL,
1366
            'functionCall' => [Functions::class, 'DUMMY'],
1367
            'argumentCount' => '5,6',
1368
        ],
1369
        'MEDIAN' => [
1370
            'category' => Category::CATEGORY_STATISTICAL,
1371
            'functionCall' => [Statistical::class, 'MEDIAN'],
1372
            'argumentCount' => '1+',
1373
        ],
1374
        'MEDIANIF' => [
1375
            'category' => Category::CATEGORY_STATISTICAL,
1376
            'functionCall' => [Functions::class, 'DUMMY'],
1377
            'argumentCount' => '2+',
1378
        ],
1379
        'MID' => [
1380
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1381
            'functionCall' => [TextData::class, 'MID'],
1382
            'argumentCount' => '3',
1383
        ],
1384
        'MIDB' => [
1385
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1386
            'functionCall' => [TextData::class, 'MID'],
1387
            'argumentCount' => '3',
1388
        ],
1389
        'MIN' => [
1390
            'category' => Category::CATEGORY_STATISTICAL,
1391
            'functionCall' => [Statistical::class, 'MIN'],
1392
            'argumentCount' => '1+',
1393
        ],
1394
        'MINA' => [
1395
            'category' => Category::CATEGORY_STATISTICAL,
1396
            'functionCall' => [Statistical::class, 'MINA'],
1397
            'argumentCount' => '1+',
1398
        ],
1399
        'MINIF' => [
1400
            'category' => Category::CATEGORY_STATISTICAL,
1401
            'functionCall' => [Statistical::class, 'MINIF'],
1402
            'argumentCount' => '2+',
1403
        ],
1404
        'MINUTE' => [
1405
            'category' => Category::CATEGORY_DATE_AND_TIME,
1406
            'functionCall' => [DateTime::class, 'MINUTE'],
1407
            'argumentCount' => '1',
1408
        ],
1409
        'MINVERSE' => [
1410
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1411
            'functionCall' => [MathTrig::class, 'MINVERSE'],
1412
            'argumentCount' => '1',
1413
        ],
1414
        'MIRR' => [
1415
            'category' => Category::CATEGORY_FINANCIAL,
1416
            'functionCall' => [Financial::class, 'MIRR'],
1417
            'argumentCount' => '3',
1418
        ],
1419
        'MMULT' => [
1420
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1421
            'functionCall' => [MathTrig::class, 'MMULT'],
1422
            'argumentCount' => '2',
1423
        ],
1424
        'MOD' => [
1425
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1426
            'functionCall' => [MathTrig::class, 'MOD'],
1427
            'argumentCount' => '2',
1428
        ],
1429
        'MODE' => [
1430
            'category' => Category::CATEGORY_STATISTICAL,
1431
            'functionCall' => [Statistical::class, 'MODE'],
1432
            'argumentCount' => '1+',
1433
        ],
1434
        'MODE.SNGL' => [
1435
            'category' => Category::CATEGORY_STATISTICAL,
1436
            'functionCall' => [Statistical::class, 'MODE'],
1437
            'argumentCount' => '1+',
1438
        ],
1439
        'MONTH' => [
1440
            'category' => Category::CATEGORY_DATE_AND_TIME,
1441
            'functionCall' => [DateTime::class, 'MONTHOFYEAR'],
1442
            'argumentCount' => '1',
1443
        ],
1444
        'MROUND' => [
1445
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1446
            'functionCall' => [MathTrig::class, 'MROUND'],
1447
            'argumentCount' => '2',
1448
        ],
1449
        'MULTINOMIAL' => [
1450
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1451
            'functionCall' => [MathTrig::class, 'MULTINOMIAL'],
1452
            'argumentCount' => '1+',
1453
        ],
1454
        'N' => [
1455
            'category' => Category::CATEGORY_INFORMATION,
1456
            'functionCall' => [Functions::class, 'n'],
1457
            'argumentCount' => '1',
1458
        ],
1459
        'NA' => [
1460
            'category' => Category::CATEGORY_INFORMATION,
1461
            'functionCall' => [Functions::class, 'NA'],
1462
            'argumentCount' => '0',
1463
        ],
1464
        'NEGBINOMDIST' => [
1465
            'category' => Category::CATEGORY_STATISTICAL,
1466
            'functionCall' => [Statistical::class, 'NEGBINOMDIST'],
1467
            'argumentCount' => '3',
1468
        ],
1469
        'NETWORKDAYS' => [
1470
            'category' => Category::CATEGORY_DATE_AND_TIME,
1471
            'functionCall' => [DateTime::class, 'NETWORKDAYS'],
1472
            'argumentCount' => '2+',
1473
        ],
1474
        'NOMINAL' => [
1475
            'category' => Category::CATEGORY_FINANCIAL,
1476
            'functionCall' => [Financial::class, 'NOMINAL'],
1477
            'argumentCount' => '2',
1478
        ],
1479
        'NORMDIST' => [
1480
            'category' => Category::CATEGORY_STATISTICAL,
1481
            'functionCall' => [Statistical::class, 'NORMDIST'],
1482
            'argumentCount' => '4',
1483
        ],
1484
        'NORMINV' => [
1485
            'category' => Category::CATEGORY_STATISTICAL,
1486
            'functionCall' => [Statistical::class, 'NORMINV'],
1487
            'argumentCount' => '3',
1488
        ],
1489
        'NORMSDIST' => [
1490
            'category' => Category::CATEGORY_STATISTICAL,
1491
            'functionCall' => [Statistical::class, 'NORMSDIST'],
1492
            'argumentCount' => '1',
1493
        ],
1494
        'NORMSINV' => [
1495
            'category' => Category::CATEGORY_STATISTICAL,
1496
            'functionCall' => [Statistical::class, 'NORMSINV'],
1497
            'argumentCount' => '1',
1498
        ],
1499
        'NOT' => [
1500
            'category' => Category::CATEGORY_LOGICAL,
1501
            'functionCall' => [Logical::class, 'NOT'],
1502
            'argumentCount' => '1',
1503
        ],
1504
        'NOW' => [
1505
            'category' => Category::CATEGORY_DATE_AND_TIME,
1506
            'functionCall' => [DateTime::class, 'DATETIMENOW'],
1507
            'argumentCount' => '0',
1508
        ],
1509
        'NPER' => [
1510
            'category' => Category::CATEGORY_FINANCIAL,
1511
            'functionCall' => [Financial::class, 'NPER'],
1512
            'argumentCount' => '3-5',
1513
        ],
1514
        'NPV' => [
1515
            'category' => Category::CATEGORY_FINANCIAL,
1516
            'functionCall' => [Financial::class, 'NPV'],
1517
            'argumentCount' => '2+',
1518
        ],
1519
        'NUMBERVALUE' => [
1520
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1521
            'functionCall' => [TextData::class, 'NUMBERVALUE'],
1522
            'argumentCount' => '1+',
1523
        ],
1524
        'OCT2BIN' => [
1525
            'category' => Category::CATEGORY_ENGINEERING,
1526
            'functionCall' => [Engineering::class, 'OCTTOBIN'],
1527
            'argumentCount' => '1,2',
1528
        ],
1529
        'OCT2DEC' => [
1530
            'category' => Category::CATEGORY_ENGINEERING,
1531
            'functionCall' => [Engineering::class, 'OCTTODEC'],
1532
            'argumentCount' => '1',
1533
        ],
1534
        'OCT2HEX' => [
1535
            'category' => Category::CATEGORY_ENGINEERING,
1536
            'functionCall' => [Engineering::class, 'OCTTOHEX'],
1537
            'argumentCount' => '1,2',
1538
        ],
1539
        'ODD' => [
1540
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1541
            'functionCall' => [MathTrig::class, 'ODD'],
1542
            'argumentCount' => '1',
1543
        ],
1544
        'ODDFPRICE' => [
1545
            'category' => Category::CATEGORY_FINANCIAL,
1546
            'functionCall' => [Functions::class, 'DUMMY'],
1547
            'argumentCount' => '8,9',
1548
        ],
1549
        'ODDFYIELD' => [
1550
            'category' => Category::CATEGORY_FINANCIAL,
1551
            'functionCall' => [Functions::class, 'DUMMY'],
1552
            'argumentCount' => '8,9',
1553
        ],
1554
        'ODDLPRICE' => [
1555
            'category' => Category::CATEGORY_FINANCIAL,
1556
            'functionCall' => [Functions::class, 'DUMMY'],
1557
            'argumentCount' => '7,8',
1558
        ],
1559
        'ODDLYIELD' => [
1560
            'category' => Category::CATEGORY_FINANCIAL,
1561
            'functionCall' => [Functions::class, 'DUMMY'],
1562
            'argumentCount' => '7,8',
1563
        ],
1564
        'OFFSET' => [
1565
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1566
            'functionCall' => [LookupRef::class, 'OFFSET'],
1567
            'argumentCount' => '3-5',
1568
            'passCellReference' => true,
1569
            'passByReference' => [true],
1570
        ],
1571
        'OR' => [
1572
            'category' => Category::CATEGORY_LOGICAL,
1573
            'functionCall' => [Logical::class, 'logicalOr'],
1574
            'argumentCount' => '1+',
1575
        ],
1576
        'PDURATION' => [
1577
            'category' => Category::CATEGORY_FINANCIAL,
1578
            'functionCall' => [Financial::class, 'PDURATION'],
1579
            'argumentCount' => '3',
1580
        ],
1581
        'PEARSON' => [
1582
            'category' => Category::CATEGORY_STATISTICAL,
1583
            'functionCall' => [Statistical::class, 'CORREL'],
1584
            'argumentCount' => '2',
1585
        ],
1586
        'PERCENTILE' => [
1587
            'category' => Category::CATEGORY_STATISTICAL,
1588
            'functionCall' => [Statistical::class, 'PERCENTILE'],
1589
            'argumentCount' => '2',
1590
        ],
1591
        'PERCENTRANK' => [
1592
            'category' => Category::CATEGORY_STATISTICAL,
1593
            'functionCall' => [Statistical::class, 'PERCENTRANK'],
1594
            'argumentCount' => '2,3',
1595
        ],
1596
        'PERMUT' => [
1597
            'category' => Category::CATEGORY_STATISTICAL,
1598
            'functionCall' => [Statistical::class, 'PERMUT'],
1599
            'argumentCount' => '2',
1600
        ],
1601
        'PHONETIC' => [
1602
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1603
            'functionCall' => [Functions::class, 'DUMMY'],
1604
            'argumentCount' => '1',
1605
        ],
1606
        'PI' => [
1607
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1608
            'functionCall' => 'pi',
1609
            'argumentCount' => '0',
1610
        ],
1611
        'PMT' => [
1612
            'category' => Category::CATEGORY_FINANCIAL,
1613
            'functionCall' => [Financial::class, 'PMT'],
1614
            'argumentCount' => '3-5',
1615
        ],
1616
        'POISSON' => [
1617
            'category' => Category::CATEGORY_STATISTICAL,
1618
            'functionCall' => [Statistical::class, 'POISSON'],
1619
            'argumentCount' => '3',
1620
        ],
1621
        'POWER' => [
1622
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1623
            'functionCall' => [MathTrig::class, 'POWER'],
1624
            'argumentCount' => '2',
1625
        ],
1626
        'PPMT' => [
1627
            'category' => Category::CATEGORY_FINANCIAL,
1628
            'functionCall' => [Financial::class, 'PPMT'],
1629
            'argumentCount' => '4-6',
1630
        ],
1631
        'PRICE' => [
1632
            'category' => Category::CATEGORY_FINANCIAL,
1633
            'functionCall' => [Financial::class, 'PRICE'],
1634
            'argumentCount' => '6,7',
1635
        ],
1636
        'PRICEDISC' => [
1637
            'category' => Category::CATEGORY_FINANCIAL,
1638
            'functionCall' => [Financial::class, 'PRICEDISC'],
1639
            'argumentCount' => '4,5',
1640
        ],
1641
        'PRICEMAT' => [
1642
            'category' => Category::CATEGORY_FINANCIAL,
1643
            'functionCall' => [Financial::class, 'PRICEMAT'],
1644
            'argumentCount' => '5,6',
1645
        ],
1646
        'PROB' => [
1647
            'category' => Category::CATEGORY_STATISTICAL,
1648
            'functionCall' => [Functions::class, 'DUMMY'],
1649
            'argumentCount' => '3,4',
1650
        ],
1651
        'PRODUCT' => [
1652
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1653
            'functionCall' => [MathTrig::class, 'PRODUCT'],
1654
            'argumentCount' => '1+',
1655
        ],
1656
        'PROPER' => [
1657
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1658
            'functionCall' => [TextData::class, 'PROPERCASE'],
1659
            'argumentCount' => '1',
1660
        ],
1661
        'PV' => [
1662
            'category' => Category::CATEGORY_FINANCIAL,
1663
            'functionCall' => [Financial::class, 'PV'],
1664
            'argumentCount' => '3-5',
1665
        ],
1666
        'QUARTILE' => [
1667
            'category' => Category::CATEGORY_STATISTICAL,
1668
            'functionCall' => [Statistical::class, 'QUARTILE'],
1669
            'argumentCount' => '2',
1670
        ],
1671
        'QUOTIENT' => [
1672
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1673
            'functionCall' => [MathTrig::class, 'QUOTIENT'],
1674
            'argumentCount' => '2',
1675
        ],
1676
        'RADIANS' => [
1677
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1678
            'functionCall' => 'deg2rad',
1679
            'argumentCount' => '1',
1680
        ],
1681
        'RAND' => [
1682
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1683
            'functionCall' => [MathTrig::class, 'RAND'],
1684
            'argumentCount' => '0',
1685
        ],
1686
        'RANDBETWEEN' => [
1687
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1688
            'functionCall' => [MathTrig::class, 'RAND'],
1689
            'argumentCount' => '2',
1690
        ],
1691
        'RANK' => [
1692
            'category' => Category::CATEGORY_STATISTICAL,
1693
            'functionCall' => [Statistical::class, 'RANK'],
1694
            'argumentCount' => '2,3',
1695
        ],
1696
        'RATE' => [
1697
            'category' => Category::CATEGORY_FINANCIAL,
1698
            'functionCall' => [Financial::class, 'RATE'],
1699
            'argumentCount' => '3-6',
1700
        ],
1701
        'RECEIVED' => [
1702
            'category' => Category::CATEGORY_FINANCIAL,
1703
            'functionCall' => [Financial::class, 'RECEIVED'],
1704
            'argumentCount' => '4-5',
1705
        ],
1706
        'REPLACE' => [
1707
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1708
            'functionCall' => [TextData::class, 'REPLACE'],
1709
            'argumentCount' => '4',
1710
        ],
1711
        'REPLACEB' => [
1712
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1713
            'functionCall' => [TextData::class, 'REPLACE'],
1714
            'argumentCount' => '4',
1715
        ],
1716
        'REPT' => [
1717
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1718
            'functionCall' => 'str_repeat',
1719
            'argumentCount' => '2',
1720
        ],
1721
        'RIGHT' => [
1722
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1723
            'functionCall' => [TextData::class, 'RIGHT'],
1724
            'argumentCount' => '1,2',
1725
        ],
1726
        'RIGHTB' => [
1727
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1728
            'functionCall' => [TextData::class, 'RIGHT'],
1729
            'argumentCount' => '1,2',
1730
        ],
1731
        'ROMAN' => [
1732
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1733
            'functionCall' => [MathTrig::class, 'ROMAN'],
1734
            'argumentCount' => '1,2',
1735
        ],
1736
        'ROUND' => [
1737
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1738
            'functionCall' => 'round',
1739
            'argumentCount' => '2',
1740
        ],
1741
        'ROUNDDOWN' => [
1742
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1743
            'functionCall' => [MathTrig::class, 'ROUNDDOWN'],
1744
            'argumentCount' => '2',
1745
        ],
1746
        'ROUNDUP' => [
1747
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1748
            'functionCall' => [MathTrig::class, 'ROUNDUP'],
1749
            'argumentCount' => '2',
1750
        ],
1751
        'ROW' => [
1752
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1753
            'functionCall' => [LookupRef::class, 'ROW'],
1754
            'argumentCount' => '-1',
1755
            'passByReference' => [true],
1756
        ],
1757
        'ROWS' => [
1758
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1759
            'functionCall' => [LookupRef::class, 'ROWS'],
1760
            'argumentCount' => '1',
1761
        ],
1762
        'RRI' => [
1763
            'category' => Category::CATEGORY_FINANCIAL,
1764
            'functionCall' => [Financial::class, 'RRI'],
1765
            'argumentCount' => '3',
1766
        ],
1767
        'RSQ' => [
1768
            'category' => Category::CATEGORY_STATISTICAL,
1769
            'functionCall' => [Statistical::class, 'RSQ'],
1770
            'argumentCount' => '2',
1771
        ],
1772
        'RTD' => [
1773
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1774
            'functionCall' => [Functions::class, 'DUMMY'],
1775
            'argumentCount' => '1+',
1776
        ],
1777
        'SEARCH' => [
1778
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1779
            'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'],
1780
            'argumentCount' => '2,3',
1781
        ],
1782
        'SEARCHB' => [
1783
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1784
            'functionCall' => [TextData::class, 'SEARCHINSENSITIVE'],
1785
            'argumentCount' => '2,3',
1786
        ],
1787
        'SEC' => [
1788
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1789
            'functionCall' => [MathTrig::class, 'SEC'],
1790
            'argumentCount' => '1',
1791
        ],
1792
        'SECH' => [
1793
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1794
            'functionCall' => [MathTrig::class, 'SECH'],
1795
            'argumentCount' => '1',
1796
        ],
1797
        'SECOND' => [
1798
            'category' => Category::CATEGORY_DATE_AND_TIME,
1799
            'functionCall' => [DateTime::class, 'SECOND'],
1800
            'argumentCount' => '1',
1801
        ],
1802
        'SERIESSUM' => [
1803
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1804
            'functionCall' => [MathTrig::class, 'SERIESSUM'],
1805
            'argumentCount' => '4',
1806
        ],
1807
        'SIGN' => [
1808
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1809
            'functionCall' => [MathTrig::class, 'SIGN'],
1810
            'argumentCount' => '1',
1811
        ],
1812
        'SIN' => [
1813
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1814
            'functionCall' => 'sin',
1815
            'argumentCount' => '1',
1816
        ],
1817
        'SINH' => [
1818
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1819
            'functionCall' => 'sinh',
1820
            'argumentCount' => '1',
1821
        ],
1822
        'SKEW' => [
1823
            'category' => Category::CATEGORY_STATISTICAL,
1824
            'functionCall' => [Statistical::class, 'SKEW'],
1825
            'argumentCount' => '1+',
1826
        ],
1827
        'SLN' => [
1828
            'category' => Category::CATEGORY_FINANCIAL,
1829
            'functionCall' => [Financial::class, 'SLN'],
1830
            'argumentCount' => '3',
1831
        ],
1832
        'SLOPE' => [
1833
            'category' => Category::CATEGORY_STATISTICAL,
1834
            'functionCall' => [Statistical::class, 'SLOPE'],
1835
            'argumentCount' => '2',
1836
        ],
1837
        'SMALL' => [
1838
            'category' => Category::CATEGORY_STATISTICAL,
1839
            'functionCall' => [Statistical::class, 'SMALL'],
1840
            'argumentCount' => '2',
1841
        ],
1842
        'SQRT' => [
1843
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1844
            'functionCall' => 'sqrt',
1845
            'argumentCount' => '1',
1846
        ],
1847
        'SQRTPI' => [
1848
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1849
            'functionCall' => [MathTrig::class, 'SQRTPI'],
1850
            'argumentCount' => '1',
1851
        ],
1852
        'STANDARDIZE' => [
1853
            'category' => Category::CATEGORY_STATISTICAL,
1854
            'functionCall' => [Statistical::class, 'STANDARDIZE'],
1855
            'argumentCount' => '3',
1856
        ],
1857
        'STDEV' => [
1858
            'category' => Category::CATEGORY_STATISTICAL,
1859
            'functionCall' => [Statistical::class, 'STDEV'],
1860
            'argumentCount' => '1+',
1861
        ],
1862
        'STDEV.S' => [
1863
            'category' => Category::CATEGORY_STATISTICAL,
1864
            'functionCall' => [Statistical::class, 'STDEV'],
1865
            'argumentCount' => '1+',
1866
        ],
1867
        'STDEV.P' => [
1868
            'category' => Category::CATEGORY_STATISTICAL,
1869
            'functionCall' => [Statistical::class, 'STDEVP'],
1870
            'argumentCount' => '1+',
1871
        ],
1872
        'STDEVA' => [
1873
            'category' => Category::CATEGORY_STATISTICAL,
1874
            'functionCall' => [Statistical::class, 'STDEVA'],
1875
            'argumentCount' => '1+',
1876
        ],
1877
        'STDEVP' => [
1878
            'category' => Category::CATEGORY_STATISTICAL,
1879
            'functionCall' => [Statistical::class, 'STDEVP'],
1880
            'argumentCount' => '1+',
1881
        ],
1882
        'STDEVPA' => [
1883
            'category' => Category::CATEGORY_STATISTICAL,
1884
            'functionCall' => [Statistical::class, 'STDEVPA'],
1885
            'argumentCount' => '1+',
1886
        ],
1887
        'STEYX' => [
1888
            'category' => Category::CATEGORY_STATISTICAL,
1889
            'functionCall' => [Statistical::class, 'STEYX'],
1890
            'argumentCount' => '2',
1891
        ],
1892
        'SUBSTITUTE' => [
1893
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1894
            'functionCall' => [TextData::class, 'SUBSTITUTE'],
1895
            'argumentCount' => '3,4',
1896
        ],
1897
        'SUBTOTAL' => [
1898
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1899
            'functionCall' => [MathTrig::class, 'SUBTOTAL'],
1900
            'argumentCount' => '2+',
1901
            'passCellReference' => true,
1902
        ],
1903
        'SUM' => [
1904
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1905
            'functionCall' => [MathTrig::class, 'SUM'],
1906
            'argumentCount' => '1+',
1907
        ],
1908
        'SUMIF' => [
1909
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1910
            'functionCall' => [MathTrig::class, 'SUMIF'],
1911
            'argumentCount' => '2,3',
1912
        ],
1913
        'SUMIFS' => [
1914
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1915
            'functionCall' => [MathTrig::class, 'SUMIFS'],
1916
            'argumentCount' => '3+',
1917
        ],
1918
        'SUMPRODUCT' => [
1919
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1920
            'functionCall' => [MathTrig::class, 'SUMPRODUCT'],
1921
            'argumentCount' => '1+',
1922
        ],
1923
        'SUMSQ' => [
1924
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1925
            'functionCall' => [MathTrig::class, 'SUMSQ'],
1926
            'argumentCount' => '1+',
1927
        ],
1928
        'SUMX2MY2' => [
1929
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1930
            'functionCall' => [MathTrig::class, 'SUMX2MY2'],
1931
            'argumentCount' => '2',
1932
        ],
1933
        'SUMX2PY2' => [
1934
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1935
            'functionCall' => [MathTrig::class, 'SUMX2PY2'],
1936
            'argumentCount' => '2',
1937
        ],
1938
        'SUMXMY2' => [
1939
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1940
            'functionCall' => [MathTrig::class, 'SUMXMY2'],
1941
            'argumentCount' => '2',
1942
        ],
1943
        'SYD' => [
1944
            'category' => Category::CATEGORY_FINANCIAL,
1945
            'functionCall' => [Financial::class, 'SYD'],
1946
            'argumentCount' => '4',
1947
        ],
1948
        'T' => [
1949
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1950
            'functionCall' => [TextData::class, 'RETURNSTRING'],
1951
            'argumentCount' => '1',
1952
        ],
1953
        'TAN' => [
1954
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1955
            'functionCall' => 'tan',
1956
            'argumentCount' => '1',
1957
        ],
1958
        'TANH' => [
1959
            'category' => Category::CATEGORY_MATH_AND_TRIG,
1960
            'functionCall' => 'tanh',
1961
            'argumentCount' => '1',
1962
        ],
1963
        'TBILLEQ' => [
1964
            'category' => Category::CATEGORY_FINANCIAL,
1965
            'functionCall' => [Financial::class, 'TBILLEQ'],
1966
            'argumentCount' => '3',
1967
        ],
1968
        'TBILLPRICE' => [
1969
            'category' => Category::CATEGORY_FINANCIAL,
1970
            'functionCall' => [Financial::class, 'TBILLPRICE'],
1971
            'argumentCount' => '3',
1972
        ],
1973
        'TBILLYIELD' => [
1974
            'category' => Category::CATEGORY_FINANCIAL,
1975
            'functionCall' => [Financial::class, 'TBILLYIELD'],
1976
            'argumentCount' => '3',
1977
        ],
1978
        'TDIST' => [
1979
            'category' => Category::CATEGORY_STATISTICAL,
1980
            'functionCall' => [Statistical::class, 'TDIST'],
1981
            'argumentCount' => '3',
1982
        ],
1983
        'TEXT' => [
1984
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1985
            'functionCall' => [TextData::class, 'TEXTFORMAT'],
1986
            'argumentCount' => '2',
1987
        ],
1988
        'TEXTJOIN' => [
1989
            'category' => Category::CATEGORY_TEXT_AND_DATA,
1990
            'functionCall' => [TextData::class, 'TEXTJOIN'],
1991
            'argumentCount' => '3+',
1992
        ],
1993
        'TIME' => [
1994
            'category' => Category::CATEGORY_DATE_AND_TIME,
1995
            'functionCall' => [DateTime::class, 'TIME'],
1996
            'argumentCount' => '3',
1997
        ],
1998
        'TIMEVALUE' => [
1999
            'category' => Category::CATEGORY_DATE_AND_TIME,
2000
            'functionCall' => [DateTime::class, 'TIMEVALUE'],
2001
            'argumentCount' => '1',
2002
        ],
2003
        'TINV' => [
2004
            'category' => Category::CATEGORY_STATISTICAL,
2005
            'functionCall' => [Statistical::class, 'TINV'],
2006
            'argumentCount' => '2',
2007
        ],
2008
        'TODAY' => [
2009
            'category' => Category::CATEGORY_DATE_AND_TIME,
2010
            'functionCall' => [DateTime::class, 'DATENOW'],
2011
            'argumentCount' => '0',
2012
        ],
2013
        'TRANSPOSE' => [
2014
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
2015
            'functionCall' => [LookupRef::class, 'TRANSPOSE'],
2016
            'argumentCount' => '1',
2017
        ],
2018
        'TREND' => [
2019
            'category' => Category::CATEGORY_STATISTICAL,
2020
            'functionCall' => [Statistical::class, 'TREND'],
2021
            'argumentCount' => '1-4',
2022
        ],
2023
        'TRIM' => [
2024
            'category' => Category::CATEGORY_TEXT_AND_DATA,
2025
            'functionCall' => [TextData::class, 'TRIMSPACES'],
2026
            'argumentCount' => '1',
2027
        ],
2028
        'TRIMMEAN' => [
2029
            'category' => Category::CATEGORY_STATISTICAL,
2030
            'functionCall' => [Statistical::class, 'TRIMMEAN'],
2031
            'argumentCount' => '2',
2032
        ],
2033
        'TRUE' => [
2034
            'category' => Category::CATEGORY_LOGICAL,
2035
            'functionCall' => [Logical::class, 'TRUE'],
2036
            'argumentCount' => '0',
2037
        ],
2038
        'TRUNC' => [
2039
            'category' => Category::CATEGORY_MATH_AND_TRIG,
2040
            'functionCall' => [MathTrig::class, 'TRUNC'],
2041
            'argumentCount' => '1,2',
2042
        ],
2043
        'TTEST' => [
2044
            'category' => Category::CATEGORY_STATISTICAL,
2045
            'functionCall' => [Functions::class, 'DUMMY'],
2046
            'argumentCount' => '4',
2047
        ],
2048
        'TYPE' => [
2049
            'category' => Category::CATEGORY_INFORMATION,
2050
            'functionCall' => [Functions::class, 'TYPE'],
2051
            'argumentCount' => '1',
2052
        ],
2053
        'UNICHAR' => [
2054
            'category' => Category::CATEGORY_TEXT_AND_DATA,
2055
            'functionCall' => [TextData::class, 'CHARACTER'],
2056
            'argumentCount' => '1',
2057
        ],
2058
        'UNICODE' => [
2059
            'category' => Category::CATEGORY_TEXT_AND_DATA,
2060
            'functionCall' => [TextData::class, 'ASCIICODE'],
2061
            'argumentCount' => '1',
2062
        ],
2063
        'UPPER' => [
2064
            'category' => Category::CATEGORY_TEXT_AND_DATA,
2065
            'functionCall' => [TextData::class, 'UPPERCASE'],
2066
            'argumentCount' => '1',
2067
        ],
2068
        'USDOLLAR' => [
2069
            'category' => Category::CATEGORY_FINANCIAL,
2070
            'functionCall' => [Functions::class, 'DUMMY'],
2071
            'argumentCount' => '2',
2072
        ],
2073
        'VALUE' => [
2074
            'category' => Category::CATEGORY_TEXT_AND_DATA,
2075
            'functionCall' => [TextData::class, 'VALUE'],
2076
            'argumentCount' => '1',
2077
        ],
2078
        'VAR' => [
2079
            'category' => Category::CATEGORY_STATISTICAL,
2080
            'functionCall' => [Statistical::class, 'VARFunc'],
2081
            'argumentCount' => '1+',
2082
        ],
2083
        'VAR.P' => [
2084
            'category' => Category::CATEGORY_STATISTICAL,
2085
            'functionCall' => [Statistical::class, 'VARP'],
2086
            'argumentCount' => '1+',
2087
        ],
2088
        'VAR.S' => [
2089
            'category' => Category::CATEGORY_STATISTICAL,
2090
            'functionCall' => [Statistical::class, 'VARFunc'],
2091
            'argumentCount' => '1+',
2092
        ],
2093
        'VARA' => [
2094
            'category' => Category::CATEGORY_STATISTICAL,
2095
            'functionCall' => [Statistical::class, 'VARA'],
2096
            'argumentCount' => '1+',
2097
        ],
2098
        'VARP' => [
2099
            'category' => Category::CATEGORY_STATISTICAL,
2100
            'functionCall' => [Statistical::class, 'VARP'],
2101
            'argumentCount' => '1+',
2102
        ],
2103
        'VARPA' => [
2104
            'category' => Category::CATEGORY_STATISTICAL,
2105
            'functionCall' => [Statistical::class, 'VARPA'],
2106
            'argumentCount' => '1+',
2107
        ],
2108
        'VDB' => [
2109
            'category' => Category::CATEGORY_FINANCIAL,
2110
            'functionCall' => [Functions::class, 'DUMMY'],
2111
            'argumentCount' => '5-7',
2112
        ],
2113
        'VLOOKUP' => [
2114
            'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
2115
            'functionCall' => [LookupRef::class, 'VLOOKUP'],
2116
            'argumentCount' => '3,4',
2117
        ],
2118
        'WEEKDAY' => [
2119
            'category' => Category::CATEGORY_DATE_AND_TIME,
2120
            'functionCall' => [DateTime::class, 'WEEKDAY'],
2121
            'argumentCount' => '1,2',
2122
        ],
2123
        'WEEKNUM' => [
2124
            'category' => Category::CATEGORY_DATE_AND_TIME,
2125
            'functionCall' => [DateTime::class, 'WEEKNUM'],
2126
            'argumentCount' => '1,2',
2127
        ],
2128
        'WEIBULL' => [
2129
            'category' => Category::CATEGORY_STATISTICAL,
2130
            'functionCall' => [Statistical::class, 'WEIBULL'],
2131
            'argumentCount' => '4',
2132
        ],
2133
        'WORKDAY' => [
2134
            'category' => Category::CATEGORY_DATE_AND_TIME,
2135
            'functionCall' => [DateTime::class, 'WORKDAY'],
2136
            'argumentCount' => '2+',
2137
        ],
2138
        'XIRR' => [
2139
            'category' => Category::CATEGORY_FINANCIAL,
2140
            'functionCall' => [Financial::class, 'XIRR'],
2141
            'argumentCount' => '2,3',
2142
        ],
2143
        'XNPV' => [
2144
            'category' => Category::CATEGORY_FINANCIAL,
2145
            'functionCall' => [Financial::class, 'XNPV'],
2146
            'argumentCount' => '3',
2147
        ],
2148
        'XOR' => [
2149
            'category' => Category::CATEGORY_LOGICAL,
2150
            'functionCall' => [Logical::class, 'logicalXor'],
2151
            'argumentCount' => '1+',
2152
        ],
2153
        'YEAR' => [
2154
            'category' => Category::CATEGORY_DATE_AND_TIME,
2155
            'functionCall' => [DateTime::class, 'YEAR'],
2156
            'argumentCount' => '1',
2157
        ],
2158
        'YEARFRAC' => [
2159
            'category' => Category::CATEGORY_DATE_AND_TIME,
2160
            'functionCall' => [DateTime::class, 'YEARFRAC'],
2161
            'argumentCount' => '2,3',
2162
        ],
2163
        'YIELD' => [
2164
            'category' => Category::CATEGORY_FINANCIAL,
2165
            'functionCall' => [Functions::class, 'DUMMY'],
2166
            'argumentCount' => '6,7',
2167
        ],
2168
        'YIELDDISC' => [
2169
            'category' => Category::CATEGORY_FINANCIAL,
2170
            'functionCall' => [Financial::class, 'YIELDDISC'],
2171
            'argumentCount' => '4,5',
2172
        ],
2173
        'YIELDMAT' => [
2174
            'category' => Category::CATEGORY_FINANCIAL,
2175
            'functionCall' => [Financial::class, 'YIELDMAT'],
2176
            'argumentCount' => '5,6',
2177
        ],
2178
        'ZTEST' => [
2179
            'category' => Category::CATEGORY_STATISTICAL,
2180
            'functionCall' => [Statistical::class, 'ZTEST'],
2181
            'argumentCount' => '2-3',
2182
        ],
2183
    ];
2184
2185
    //    Internal functions used for special control purposes
2186
    private static $controlFunctions = [
2187
        'MKMATRIX' => [
2188
            'argumentCount' => '*',
2189
            'functionCall' => 'self::mkMatrix',
2190
        ],
2191
    ];
2192
2193 190
    public function __construct(Spreadsheet $spreadsheet = null)
2194
    {
2195 190
        $this->delta = 1 * pow(10, 0 - ini_get('precision'));
2196
2197 190
        $this->spreadsheet = $spreadsheet;
2198 190
        $this->cyclicReferenceStack = new CyclicReferenceStack();
0 ignored issues
show
Documentation Bug introduced by
It seems like new PhpOffice\PhpSpreads...\CyclicReferenceStack() of type PhpOffice\PhpSpreadsheet...ne\CyclicReferenceStack is incompatible with the declared type array of property $cyclicReferenceStack.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2199 190
        $this->debugLog = new Logger($this->cyclicReferenceStack);
2200 190
    }
2201
2202 1
    private static function loadLocales()
2203
    {
2204 1
        $localeFileDirectory = __DIR__ . '/locale/';
2205 1
        foreach (glob($localeFileDirectory . '/*', GLOB_ONLYDIR) as $filename) {
2206 1
            $filename = substr($filename, strlen($localeFileDirectory) + 1);
2207 1
            if ($filename != 'en') {
2208 1
                self::$validLocaleLanguages[] = $filename;
2209
            }
2210
        }
2211 1
    }
2212
2213
    /**
2214
     * Get an instance of this class.
2215
     *
2216
     * @param Spreadsheet $spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object,
2217
     *                                    or NULL to create a standalone claculation engine
2218
     *
2219
     * @return Calculation
2220
     */
2221 621
    public static function getInstance(Spreadsheet $spreadsheet = null)
2222
    {
2223 621
        if ($spreadsheet !== null) {
2224 127
            $instance = $spreadsheet->getCalculationEngine();
2225 127
            if (isset($instance)) {
2226 127
                return $instance;
2227
            }
2228
        }
2229
2230 508
        if (!isset(self::$instance) || (self::$instance === null)) {
2231 14
            self::$instance = new self();
2232
        }
2233
2234 508
        return self::$instance;
2235
    }
2236
2237
    /**
2238
     * Flush the calculation cache for any existing instance of this class
2239
     *        but only if a Calculation instance exists.
2240
     */
2241
    public function flushInstance()
2242
    {
2243
        $this->clearCalculationCache();
2244
    }
2245
2246
    /**
2247
     * Get the Logger for this calculation engine instance.
2248
     *
2249
     * @return Logger
2250
     */
2251 98
    public function getDebugLog()
2252
    {
2253 98
        return $this->debugLog;
2254
    }
2255
2256
    /**
2257
     * __clone implementation. Cloning should not be allowed in a Singleton!
2258
     *
2259
     * @throws Exception
2260
     */
2261
    final public function __clone()
2262
    {
2263
        throw new Exception('Cloning the calculation engine is not allowed!');
2264
    }
2265
2266
    /**
2267
     * Return the locale-specific translation of TRUE.
2268
     *
2269
     * @return string locale-specific translation of TRUE
2270
     */
2271 42
    public static function getTRUE()
2272
    {
2273 42
        return self::$localeBoolean['TRUE'];
2274
    }
2275
2276
    /**
2277
     * Return the locale-specific translation of FALSE.
2278
     *
2279
     * @return string locale-specific translation of FALSE
2280
     */
2281 31
    public static function getFALSE()
2282
    {
2283 31
        return self::$localeBoolean['FALSE'];
2284
    }
2285
2286
    /**
2287
     * Set the Array Return Type (Array or Value of first element in the array).
2288
     *
2289
     * @param string $returnType Array return type
2290
     *
2291
     * @return bool Success or failure
2292
     */
2293 27
    public static function setArrayReturnType($returnType)
2294
    {
2295 27
        if (($returnType == self::RETURN_ARRAY_AS_VALUE) ||
2296 20
            ($returnType == self::RETURN_ARRAY_AS_ERROR) ||
2297 27
            ($returnType == self::RETURN_ARRAY_AS_ARRAY)) {
2298 27
            self::$returnArrayAsType = $returnType;
2299
2300 27
            return true;
2301
        }
2302
2303
        return false;
2304
    }
2305
2306
    /**
2307
     * Return the Array Return Type (Array or Value of first element in the array).
2308
     *
2309
     * @return string $returnType Array return type
2310
     */
2311 16
    public static function getArrayReturnType()
2312
    {
2313 16
        return self::$returnArrayAsType;
2314
    }
2315
2316
    /**
2317
     * Is calculation caching enabled?
2318
     *
2319
     * @return bool
2320
     */
2321 1
    public function getCalculationCacheEnabled()
2322
    {
2323 1
        return $this->calculationCacheEnabled;
2324
    }
2325
2326
    /**
2327
     * Enable/disable calculation cache.
2328
     *
2329
     * @param bool $pValue
2330
     */
2331
    public function setCalculationCacheEnabled($pValue)
2332
    {
2333
        $this->calculationCacheEnabled = $pValue;
2334
        $this->clearCalculationCache();
2335
    }
2336
2337
    /**
2338
     * Enable calculation cache.
2339
     */
2340
    public function enableCalculationCache()
2341
    {
2342
        $this->setCalculationCacheEnabled(true);
2343
    }
2344
2345
    /**
2346
     * Disable calculation cache.
2347
     */
2348
    public function disableCalculationCache()
2349
    {
2350
        $this->setCalculationCacheEnabled(false);
2351
    }
2352
2353
    /**
2354
     * Clear calculation cache.
2355
     */
2356
    public function clearCalculationCache()
2357
    {
2358
        $this->calculationCache = [];
2359
    }
2360
2361
    /**
2362
     * Clear calculation cache for a specified worksheet.
2363
     *
2364
     * @param string $worksheetName
2365
     */
2366 27
    public function clearCalculationCacheForWorksheet($worksheetName)
2367
    {
2368 27
        if (isset($this->calculationCache[$worksheetName])) {
2369
            unset($this->calculationCache[$worksheetName]);
2370
        }
2371 27
    }
2372
2373
    /**
2374
     * Rename calculation cache for a specified worksheet.
2375
     *
2376
     * @param string $fromWorksheetName
2377
     * @param string $toWorksheetName
2378
     */
2379 190
    public function renameCalculationCacheForWorksheet($fromWorksheetName, $toWorksheetName)
2380
    {
2381 190
        if (isset($this->calculationCache[$fromWorksheetName])) {
2382
            $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName];
2383
            unset($this->calculationCache[$fromWorksheetName]);
2384
        }
2385 190
    }
2386
2387
    /**
2388
     * Get the currently defined locale code.
2389
     *
2390
     * @return string
2391
     */
2392
    public function getLocale()
2393
    {
2394
        return self::$localeLanguage;
2395
    }
2396
2397
    /**
2398
     * Set the locale code.
2399
     *
2400
     * @param string $locale The locale to use for formula translation, eg: 'en_us'
2401
     *
2402
     * @return bool
2403
     */
2404 466
    public function setLocale($locale)
2405
    {
2406
        //    Identify our locale and language
2407 466
        $language = $locale = strtolower($locale);
2408 466
        if (strpos($locale, '_') !== false) {
2409 466
            list($language) = explode('_', $locale);
2410
        }
2411
2412 466
        if (count(self::$validLocaleLanguages) == 1) {
2413 1
            self::loadLocales();
2414
        }
2415
        //    Test whether we have any language data for this language (any locale)
2416 466
        if (in_array($language, self::$validLocaleLanguages)) {
2417
            //    initialise language/locale settings
2418 466
            self::$localeFunctions = [];
2419 466
            self::$localeArgumentSeparator = ',';
2420 466
            self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL'];
2421
            //    Default is English, if user isn't requesting english, then read the necessary data from the locale files
2422 466
            if ($locale != 'en_us') {
2423
                //    Search for a file with a list of function names for locale
2424 17
                $functionNamesFile = __DIR__ . '/locale/' . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'functions';
2425 17
                if (!file_exists($functionNamesFile)) {
2426
                    //    If there isn't a locale specific function file, look for a language specific function file
2427
                    $functionNamesFile = __DIR__ . '/locale/' . $language . DIRECTORY_SEPARATOR . 'functions';
2428
                    if (!file_exists($functionNamesFile)) {
2429
                        return false;
2430
                    }
2431
                }
2432
                //    Retrieve the list of locale or language specific function names
2433 17
                $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2434 17
                foreach ($localeFunctions as $localeFunction) {
2435 17
                    list($localeFunction) = explode('##', $localeFunction); //    Strip out comments
2436 17
                    if (strpos($localeFunction, '=') !== false) {
2437 17
                        list($fName, $lfName) = explode('=', $localeFunction);
2438 17
                        $fName = trim($fName);
2439 17
                        $lfName = trim($lfName);
2440 17
                        if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
2441 17
                            self::$localeFunctions[$fName] = $lfName;
2442
                        }
2443
                    }
2444
                }
2445
                //    Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
2446 17
                if (isset(self::$localeFunctions['TRUE'])) {
2447 17
                    self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE'];
2448
                }
2449 17
                if (isset(self::$localeFunctions['FALSE'])) {
2450 17
                    self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE'];
2451
                }
2452
2453 17
                $configFile = __DIR__ . '/locale/' . str_replace('_', DIRECTORY_SEPARATOR, $locale) . DIRECTORY_SEPARATOR . 'config';
2454 17
                if (!file_exists($configFile)) {
2455
                    $configFile = __DIR__ . '/locale/' . $language . DIRECTORY_SEPARATOR . 'config';
2456
                }
2457 17
                if (file_exists($configFile)) {
2458 17
                    $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
2459 17
                    foreach ($localeSettings as $localeSetting) {
2460 17
                        list($localeSetting) = explode('##', $localeSetting); //    Strip out comments
2461 17
                        if (strpos($localeSetting, '=') !== false) {
2462 17
                            list($settingName, $settingValue) = explode('=', $localeSetting);
2463 17
                            $settingName = strtoupper(trim($settingName));
2464
                            switch ($settingName) {
2465 17
                                case 'ARGUMENTSEPARATOR':
2466 17
                                    self::$localeArgumentSeparator = trim($settingValue);
2467
2468 17
                                    break;
2469
                            }
2470
                        }
2471
                    }
2472
                }
2473
            }
2474
2475
            self::$functionReplaceFromExcel = self::$functionReplaceToExcel =
2476 466
            self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
2477 466
            self::$localeLanguage = $locale;
2478
2479 466
            return true;
2480
        }
2481
2482
        return false;
2483
    }
2484
2485
    /**
2486
     * @param string $fromSeparator
2487
     * @param string $toSeparator
2488
     * @param string $formula
2489
     * @param bool $inBraces
2490
     *
2491
     * @return string
2492
     */
2493 5
    public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
2494
    {
2495 5
        $strlen = mb_strlen($formula);
2496 5
        for ($i = 0; $i < $strlen; ++$i) {
2497 5
            $chr = mb_substr($formula, $i, 1);
2498
            switch ($chr) {
2499 5
                case '{':
2500
                    $inBraces = true;
2501
2502
                    break;
2503 5
                case '}':
2504
                    $inBraces = false;
2505
2506
                    break;
2507 5
                case $fromSeparator:
2508
                    if (!$inBraces) {
2509
                        $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
2510
                    }
2511
            }
2512
        }
2513
2514 5
        return $formula;
2515
    }
2516
2517
    /**
2518
     * @param string[] $from
2519
     * @param string[] $to
2520
     * @param string $formula
2521
     * @param string $fromSeparator
2522
     * @param string $toSeparator
2523
     *
2524
     * @return string
2525
     */
2526
    private static function translateFormula(array $from, array $to, $formula, $fromSeparator, $toSeparator)
2527
    {
2528
        //    Convert any Excel function names to the required language
2529
        if (self::$localeLanguage !== 'en_us') {
2530
            $inBraces = false;
2531
            //    If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
2532
            if (strpos($formula, '"') !== false) {
2533
                //    So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
2534
                //        the formula
2535
                $temp = explode('"', $formula);
2536
                $i = false;
2537
                foreach ($temp as &$value) {
2538
                    //    Only count/replace in alternating array entries
2539
                    if ($i = !$i) {
0 ignored issues
show
introduced by
The condition $i is always false.
Loading history...
2540
                        $value = preg_replace($from, $to, $value);
2541
                        $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
2542
                    }
2543
                }
2544
                unset($value);
2545
                //    Then rebuild the formula string
2546
                $formula = implode('"', $temp);
2547
            } else {
2548
                //    If there's no quoted strings, then we do a simple count/replace
2549
                $formula = preg_replace($from, $to, $formula);
2550
                $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
2551
            }
2552
        }
2553
2554
        return $formula;
2555
    }
2556
2557
    private static $functionReplaceFromExcel = null;
2558
2559
    private static $functionReplaceToLocale = null;
2560
2561
    public function _translateFormulaToLocale($formula)
2562
    {
2563
        if (self::$functionReplaceFromExcel === null) {
2564
            self::$functionReplaceFromExcel = [];
2565
            foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
2566
                self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/Ui';
2567
            }
2568
            foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
2569
                self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui';
2570
            }
2571
        }
2572
2573
        if (self::$functionReplaceToLocale === null) {
2574
            self::$functionReplaceToLocale = [];
2575
            foreach (self::$localeFunctions as $localeFunctionName) {
2576
                self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
2577
            }
2578
            foreach (self::$localeBoolean as $localeBoolean) {
2579
                self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
2580
            }
2581
        }
2582
2583
        return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator);
2584
    }
2585
2586
    private static $functionReplaceFromLocale = null;
2587
2588
    private static $functionReplaceToExcel = null;
2589
2590
    public function _translateFormulaToEnglish($formula)
2591
    {
2592
        if (self::$functionReplaceFromLocale === null) {
2593
            self::$functionReplaceFromLocale = [];
2594
            foreach (self::$localeFunctions as $localeFunctionName) {
2595
                self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/Ui';
2596
            }
2597
            foreach (self::$localeBoolean as $excelBoolean) {
2598
                self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui';
2599
            }
2600
        }
2601
2602
        if (self::$functionReplaceToExcel === null) {
2603
            self::$functionReplaceToExcel = [];
2604
            foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
2605
                self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2';
2606
            }
2607
            foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
2608
                self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2';
2609
            }
2610
        }
2611
2612
        return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ',');
2613
    }
2614
2615 49
    public static function localeFunc($function)
2616
    {
2617 49
        if (self::$localeLanguage !== 'en_us') {
2618
            $functionName = trim($function, '(');
2619
            if (isset(self::$localeFunctions[$functionName])) {
2620
                $brace = ($functionName != $function);
2621
                $function = self::$localeFunctions[$functionName];
2622
                if ($brace) {
2623
                    $function .= '(';
2624
                }
2625
            }
2626
        }
2627
2628 49
        return $function;
2629
    }
2630
2631
    /**
2632
     * Wrap string values in quotes.
2633
     *
2634
     * @param mixed $value
2635
     *
2636
     * @return mixed
2637
     */
2638 91
    public static function wrapResult($value)
2639
    {
2640 91
        if (is_string($value)) {
2641
            //    Error values cannot be "wrapped"
2642 77
            if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) {
2643
                //    Return Excel errors "as is"
2644 10
                return $value;
2645
            }
2646
            //    Return strings wrapped in quotes
2647 68
            return '"' . $value . '"';
2648
        //    Convert numeric errors to NaN error
2649 46
        } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
2650
            return Functions::NAN();
2651
        }
2652
2653 46
        return $value;
2654
    }
2655
2656
    /**
2657
     * Remove quotes used as a wrapper to identify string values.
2658
     *
2659
     * @param mixed $value
2660
     *
2661
     * @return mixed
2662
     */
2663 97
    public static function unwrapResult($value)
2664
    {
2665 97
        if (is_string($value)) {
2666 74
            if ((isset($value[0])) && ($value[0] == '"') && (substr($value, -1) == '"')) {
2667 74
                return substr($value, 1, -1);
2668
            }
2669
            //    Convert numeric errors to NAN error
2670 61
        } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
2671
            return Functions::NAN();
2672
        }
2673
2674 62
        return $value;
2675
    }
2676
2677
    /**
2678
     * Calculate cell value (using formula from a cell ID)
2679
     * Retained for backward compatibility.
2680
     *
2681
     * @param Cell $pCell Cell to calculate
2682
     *
2683
     * @throws Exception
2684
     *
2685
     * @return mixed
2686
     */
2687
    public function calculate(Cell $pCell = null)
2688
    {
2689
        try {
2690
            return $this->calculateCellValue($pCell);
2691
        } catch (\Exception $e) {
2692
            throw new Exception($e->getMessage());
2693
        }
2694
    }
2695
2696
    /**
2697
     * Calculate the value of a cell formula.
2698
     *
2699
     * @param Cell $pCell Cell to calculate
2700
     * @param bool $resetLog Flag indicating whether the debug log should be reset or not
2701
     *
2702
     * @throws Exception
2703
     *
2704
     * @return mixed
2705
     */
2706 51
    public function calculateCellValue(Cell $pCell = null, $resetLog = true)
2707
    {
2708 51
        if ($pCell === null) {
2709
            return null;
2710
        }
2711
2712 51
        $returnArrayAsType = self::$returnArrayAsType;
2713 51
        if ($resetLog) {
2714
            //    Initialise the logging settings if requested
2715 50
            $this->formulaError = null;
2716 50
            $this->debugLog->clearLog();
2717 50
            $this->cyclicReferenceStack->clear();
2718 50
            $this->cyclicFormulaCounter = 1;
2719
2720 50
            self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
2721
        }
2722
2723
        //    Execute the calculation for the cell formula
2724 51
        $this->cellStack[] = [
2725 51
            'sheet' => $pCell->getWorksheet()->getTitle(),
2726 51
            'cell' => $pCell->getCoordinate(),
2727
        ];
2728
2729
        try {
2730 51
            $result = self::unwrapResult($this->_calculateFormulaValue($pCell->getValue(), $pCell->getCoordinate(), $pCell));
2731 51
            $cellAddress = array_pop($this->cellStack);
2732 51
            $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
2733
        } catch (\Exception $e) {
2734
            $cellAddress = array_pop($this->cellStack);
2735
            $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
2736
2737
            throw new Exception($e->getMessage());
2738
        }
2739
2740 51
        if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
2741 2
            self::$returnArrayAsType = $returnArrayAsType;
2742 2
            $testResult = Functions::flattenArray($result);
2743 2
            if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
2744
                return Functions::VALUE();
2745
            }
2746
            //    If there's only a single cell in the array, then we allow it
2747 2
            if (count($testResult) != 1) {
2748
                //    If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it
2749
                $r = array_keys($result);
2750
                $r = array_shift($r);
2751
                if (!is_numeric($r)) {
2752
                    return Functions::VALUE();
2753
                }
2754
                if (is_array($result[$r])) {
2755
                    $c = array_keys($result[$r]);
2756
                    $c = array_shift($c);
2757
                    if (!is_numeric($c)) {
2758
                        return Functions::VALUE();
2759
                    }
2760
                }
2761
            }
2762 2
            $result = array_shift($testResult);
2763
        }
2764 51
        self::$returnArrayAsType = $returnArrayAsType;
2765
2766 51
        if ($result === null) {
2767
            return 0;
2768 51
        } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
2769
            return Functions::NAN();
2770
        }
2771
2772 51
        return $result;
2773
    }
2774
2775
    /**
2776
     * Validate and parse a formula string.
2777
     *
2778
     * @param string $formula Formula to parse
2779
     *
2780
     * @return array
2781
     */
2782 1
    public function parseFormula($formula)
2783
    {
2784
        //    Basic validation that this is indeed a formula
2785
        //    We return an empty array if not
2786 1
        $formula = trim($formula);
2787 1
        if ((!isset($formula[0])) || ($formula[0] != '=')) {
2788
            return [];
2789
        }
2790 1
        $formula = ltrim(substr($formula, 1));
2791 1
        if (!isset($formula[0])) {
2792
            return [];
2793
        }
2794
2795
        //    Parse the formula and return the token stack
2796 1
        return $this->_parseFormula($formula);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_parseFormula($formula) returns the type boolean which is incompatible with the documented return type array.
Loading history...
2797
    }
2798
2799
    /**
2800
     * Calculate the value of a formula.
2801
     *
2802
     * @param string $formula Formula to parse
2803
     * @param string $cellID Address of the cell to calculate
2804
     * @param Cell $pCell Cell to calculate
2805
     *
2806
     * @throws Exception
2807
     *
2808
     * @return mixed
2809
     */
2810 1
    public function calculateFormula($formula, $cellID = null, Cell $pCell = null)
2811
    {
2812
        //    Initialise the logging settings
2813 1
        $this->formulaError = null;
2814 1
        $this->debugLog->clearLog();
2815 1
        $this->cyclicReferenceStack->clear();
2816
2817 1
        if ($this->spreadsheet !== null && $cellID === null && $pCell === null) {
2818
            $cellID = 'A1';
2819
            $pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
2820
        } else {
2821
            //    Disable calculation cacheing because it only applies to cell calculations, not straight formulae
2822
            //    But don't actually flush any cache
2823 1
            $resetCache = $this->getCalculationCacheEnabled();
2824 1
            $this->calculationCacheEnabled = false;
2825
        }
2826
2827
        //    Execute the calculation
2828
        try {
2829 1
            $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $pCell));
2830 1
        } catch (\Exception $e) {
2831 1
            throw new Exception($e->getMessage());
2832
        }
2833
2834 1
        if ($this->spreadsheet === null) {
2835
            //    Reset calculation cacheing to its previous state
2836
            $this->calculationCacheEnabled = $resetCache;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $resetCache does not seem to be defined for all execution paths leading up to this point.
Loading history...
2837
        }
2838
2839 1
        return $result;
2840
    }
2841
2842
    /**
2843
     * @param string $cellReference
2844
     * @param mixed $cellValue
2845
     *
2846
     * @return bool
2847
     */
2848 52
    public function getValueFromCache($cellReference, &$cellValue)
2849
    {
2850
        // Is calculation cacheing enabled?
2851
        // Is the value present in calculation cache?
2852 52
        $this->debugLog->writeDebugLog('Testing cache value for cell ', $cellReference);
2853 52
        if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
2854 43
            $this->debugLog->writeDebugLog('Retrieving value for cell ', $cellReference, ' from cache');
2855
            // Return the cached result
2856 43
            $cellValue = $this->calculationCache[$cellReference];
2857
2858 43
            return true;
2859
        }
2860
2861 52
        return false;
2862
    }
2863
2864
    /**
2865
     * @param string $cellReference
2866
     * @param mixed $cellValue
2867
     */
2868 52
    public function saveValueToCache($cellReference, $cellValue)
2869
    {
2870 52
        if ($this->calculationCacheEnabled) {
2871 51
            $this->calculationCache[$cellReference] = $cellValue;
2872
        }
2873 52
    }
2874
2875
    /**
2876
     * Parse a cell formula and calculate its value.
2877
     *
2878
     * @param string $formula The formula to parse and calculate
2879
     * @param string $cellID The ID (e.g. A3) of the cell that we are calculating
2880
     * @param Cell $pCell Cell to calculate
2881
     *
2882
     * @throws Exception
2883
     *
2884
     * @return mixed
2885
     */
2886 121
    public function _calculateFormulaValue($formula, $cellID = null, Cell $pCell = null)
2887
    {
2888 121
        $cellValue = null;
2889
2890
        //    Basic validation that this is indeed a formula
2891
        //    We simply return the cell value if not
2892 121
        $formula = trim($formula);
2893 121
        if ($formula[0] != '=') {
2894
            return self::wrapResult($formula);
2895
        }
2896 121
        $formula = ltrim(substr($formula, 1));
2897 121
        if (!isset($formula[0])) {
2898
            return self::wrapResult($formula);
2899
        }
2900
2901 121
        $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
2902 121
        $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk";
2903 121
        $wsCellReference = $wsTitle . '!' . $cellID;
2904
2905 121
        if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) {
2906 43
            return $cellValue;
2907
        }
2908
2909 121
        if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) {
2910
            if ($this->cyclicFormulaCount <= 0) {
2911
                $this->cyclicFormulaCell = '';
2912
2913
                return $this->raiseFormulaError('Cyclic Reference in Formula');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError... Reference in Formula') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
2914
            } elseif ($this->cyclicFormulaCell === $wsCellReference) {
2915
                ++$this->cyclicFormulaCounter;
2916
                if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
2917
                    $this->cyclicFormulaCell = '';
2918
2919
                    return $cellValue;
2920
                }
2921
            } elseif ($this->cyclicFormulaCell == '') {
2922
                if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
2923
                    return $cellValue;
2924
                }
2925
                $this->cyclicFormulaCell = $wsCellReference;
2926
            }
2927
        }
2928
2929
        //    Parse the formula onto the token stack and calculate the value
2930 121
        $this->cyclicReferenceStack->push($wsCellReference);
2931 121
        $cellValue = $this->processTokenStack($this->_parseFormula($formula, $pCell), $cellID, $pCell);
2932 121
        $this->cyclicReferenceStack->pop();
2933
2934
        // Save to calculation cache
2935 121
        if ($cellID !== null) {
2936 52
            $this->saveValueToCache($wsCellReference, $cellValue);
2937
        }
2938
2939
        //    Return the calculated value
2940 121
        return $cellValue;
2941
    }
2942
2943
    /**
2944
     * Ensure that paired matrix operands are both matrices and of the same size.
2945
     *
2946
     * @param mixed &$operand1 First matrix operand
2947
     * @param mixed &$operand2 Second matrix operand
2948
     * @param int $resize Flag indicating whether the matrices should be resized to match
2949
     *                                        and (if so), whether the smaller dimension should grow or the
2950
     *                                        larger should shrink.
2951
     *                                            0 = no resize
2952
     *                                            1 = shrink to fit
2953
     *                                            2 = extend to fit
2954
     *
2955
     * @return array
2956
     */
2957 5
    private static function checkMatrixOperands(&$operand1, &$operand2, $resize = 1)
2958
    {
2959
        //    Examine each of the two operands, and turn them into an array if they aren't one already
2960
        //    Note that this function should only be called if one or both of the operand is already an array
2961 5
        if (!is_array($operand1)) {
2962
            list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand2);
2963
            $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
2964
            $resize = 0;
2965 5
        } elseif (!is_array($operand2)) {
2966 2
            list($matrixRows, $matrixColumns) = self::getMatrixDimensions($operand1);
2967 2
            $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
2968 2
            $resize = 0;
2969
        }
2970
2971 5
        list($matrix1Rows, $matrix1Columns) = self::getMatrixDimensions($operand1);
2972 5
        list($matrix2Rows, $matrix2Columns) = self::getMatrixDimensions($operand2);
2973 5
        if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
2974 5
            $resize = 1;
2975
        }
2976
2977 5
        if ($resize == 2) {
2978
            //    Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger
2979
            self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
2980 5
        } elseif ($resize == 1) {
2981
            //    Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller
2982 5
            self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
2983
        }
2984
2985 5
        return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns];
2986
    }
2987
2988
    /**
2989
     * Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0.
2990
     *
2991
     * @param array &$matrix matrix operand
2992
     *
2993
     * @return int[] An array comprising the number of rows, and number of columns
2994
     */
2995 15
    public static function getMatrixDimensions(array &$matrix)
2996
    {
2997 15
        $matrixRows = count($matrix);
2998 15
        $matrixColumns = 0;
2999 15
        foreach ($matrix as $rowKey => $rowValue) {
3000 13
            if (!is_array($rowValue)) {
3001 4
                $matrix[$rowKey] = [$rowValue];
3002 4
                $matrixColumns = max(1, $matrixColumns);
3003
            } else {
3004 9
                $matrix[$rowKey] = array_values($rowValue);
3005 13
                $matrixColumns = max(count($rowValue), $matrixColumns);
3006
            }
3007
        }
3008 15
        $matrix = array_values($matrix);
3009
3010 15
        return [$matrixRows, $matrixColumns];
3011
    }
3012
3013
    /**
3014
     * Ensure that paired matrix operands are both matrices of the same size.
3015
     *
3016
     * @param mixed &$matrix1 First matrix operand
3017
     * @param mixed &$matrix2 Second matrix operand
3018
     * @param int $matrix1Rows Row size of first matrix operand
3019
     * @param int $matrix1Columns Column size of first matrix operand
3020
     * @param int $matrix2Rows Row size of second matrix operand
3021
     * @param int $matrix2Columns Column size of second matrix operand
3022
     */
3023 5
    private static function resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
3024
    {
3025 5
        if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
3026
            if ($matrix2Rows < $matrix1Rows) {
3027
                for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
3028
                    unset($matrix1[$i]);
3029
                }
3030
            }
3031
            if ($matrix2Columns < $matrix1Columns) {
3032
                for ($i = 0; $i < $matrix1Rows; ++$i) {
3033
                    for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
3034
                        unset($matrix1[$i][$j]);
3035
                    }
3036
                }
3037
            }
3038
        }
3039
3040 5
        if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
3041
            if ($matrix1Rows < $matrix2Rows) {
3042
                for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
3043
                    unset($matrix2[$i]);
3044
                }
3045
            }
3046
            if ($matrix1Columns < $matrix2Columns) {
3047
                for ($i = 0; $i < $matrix2Rows; ++$i) {
3048
                    for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
3049
                        unset($matrix2[$i][$j]);
3050
                    }
3051
                }
3052
            }
3053
        }
3054 5
    }
3055
3056
    /**
3057
     * Ensure that paired matrix operands are both matrices of the same size.
3058
     *
3059
     * @param mixed &$matrix1 First matrix operand
3060
     * @param mixed &$matrix2 Second matrix operand
3061
     * @param int $matrix1Rows Row size of first matrix operand
3062
     * @param int $matrix1Columns Column size of first matrix operand
3063
     * @param int $matrix2Rows Row size of second matrix operand
3064
     * @param int $matrix2Columns Column size of second matrix operand
3065
     */
3066
    private static function resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
3067
    {
3068
        if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
3069
            if ($matrix2Columns < $matrix1Columns) {
3070
                for ($i = 0; $i < $matrix2Rows; ++$i) {
3071
                    $x = $matrix2[$i][$matrix2Columns - 1];
3072
                    for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
3073
                        $matrix2[$i][$j] = $x;
3074
                    }
3075
                }
3076
            }
3077
            if ($matrix2Rows < $matrix1Rows) {
3078
                $x = $matrix2[$matrix2Rows - 1];
3079
                for ($i = 0; $i < $matrix1Rows; ++$i) {
3080
                    $matrix2[$i] = $x;
3081
                }
3082
            }
3083
        }
3084
3085
        if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
3086
            if ($matrix1Columns < $matrix2Columns) {
3087
                for ($i = 0; $i < $matrix1Rows; ++$i) {
3088
                    $x = $matrix1[$i][$matrix1Columns - 1];
3089
                    for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
3090
                        $matrix1[$i][$j] = $x;
3091
                    }
3092
                }
3093
            }
3094
            if ($matrix1Rows < $matrix2Rows) {
3095
                $x = $matrix1[$matrix1Rows - 1];
3096
                for ($i = 0; $i < $matrix2Rows; ++$i) {
3097
                    $matrix1[$i] = $x;
3098
                }
3099
            }
3100
        }
3101
    }
3102
3103
    /**
3104
     * Format details of an operand for display in the log (based on operand type).
3105
     *
3106
     * @param mixed $value First matrix operand
3107
     *
3108
     * @return mixed
3109
     */
3110 118
    private function showValue($value)
3111
    {
3112 118
        if ($this->debugLog->getWriteDebugLog()) {
3113
            $testArray = Functions::flattenArray($value);
3114
            if (count($testArray) == 1) {
3115
                $value = array_pop($testArray);
3116
            }
3117
3118
            if (is_array($value)) {
3119
                $returnMatrix = [];
3120
                $pad = $rpad = ', ';
3121
                foreach ($value as $row) {
3122
                    if (is_array($row)) {
3123
                        $returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row));
3124
                        $rpad = '; ';
3125
                    } else {
3126
                        $returnMatrix[] = $this->showValue($row);
3127
                    }
3128
                }
3129
3130
                return '{ ' . implode($rpad, $returnMatrix) . ' }';
3131
            } elseif (is_string($value) && (trim($value, '"') == $value)) {
3132
                return '"' . $value . '"';
3133
            } elseif (is_bool($value)) {
3134
                return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3135
            }
3136
        }
3137
3138 118
        return Functions::flattenSingleValue($value);
3139
    }
3140
3141
    /**
3142
     * Format type and details of an operand for display in the log (based on operand type).
3143
     *
3144
     * @param mixed $value First matrix operand
3145
     *
3146
     * @return null|string
3147
     */
3148 119
    private function showTypeDetails($value)
3149
    {
3150 119
        if ($this->debugLog->getWriteDebugLog()) {
3151
            $testArray = Functions::flattenArray($value);
3152
            if (count($testArray) == 1) {
3153
                $value = array_pop($testArray);
3154
            }
3155
3156
            if ($value === null) {
3157
                return 'a NULL value';
3158
            } elseif (is_float($value)) {
3159
                $typeString = 'a floating point number';
3160
            } elseif (is_int($value)) {
3161
                $typeString = 'an integer number';
3162
            } elseif (is_bool($value)) {
3163
                $typeString = 'a boolean';
3164
            } elseif (is_array($value)) {
3165
                $typeString = 'a matrix';
3166
            } else {
3167
                if ($value == '') {
3168
                    return 'an empty string';
3169
                } elseif ($value[0] == '#') {
3170
                    return 'a ' . $value . ' error';
3171
                }
3172
                $typeString = 'a string';
3173
            }
3174
3175
            return $typeString . ' with a value of ' . $this->showValue($value);
3176
        }
3177 119
    }
3178
3179
    /**
3180
     * @param string $formula
3181
     *
3182
     * @return string
3183
     */
3184 122
    private function convertMatrixReferences($formula)
3185
    {
3186 122
        static $matrixReplaceFrom = ['{', ';', '}'];
3187 122
        static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
3188
3189
        //    Convert any Excel matrix references to the MKMATRIX() function
3190 122
        if (strpos($formula, '{') !== false) {
3191
            //    If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
3192
            if (strpos($formula, '"') !== false) {
3193
                //    So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
3194
                //        the formula
3195
                $temp = explode('"', $formula);
3196
                //    Open and Closed counts used for trapping mismatched braces in the formula
3197
                $openCount = $closeCount = 0;
3198
                $i = false;
3199
                foreach ($temp as &$value) {
3200
                    //    Only count/replace in alternating array entries
3201
                    if ($i = !$i) {
0 ignored issues
show
introduced by
The condition $i is always false.
Loading history...
3202
                        $openCount += substr_count($value, '{');
3203
                        $closeCount += substr_count($value, '}');
3204
                        $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
3205
                    }
3206
                }
3207
                unset($value);
3208
                //    Then rebuild the formula string
3209
                $formula = implode('"', $temp);
3210
            } else {
3211
                //    If there's no quoted strings, then we do a simple count/replace
3212
                $openCount = substr_count($formula, '{');
3213
                $closeCount = substr_count($formula, '}');
3214
                $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
3215
            }
3216
            //    Trap for mismatched braces and trigger an appropriate error
3217
            if ($openCount < $closeCount) {
3218
                if ($openCount > 0) {
3219
                    return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...hed matrix braces '}'') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3220
                }
3221
3222
                return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ected '}' encountered') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3223
            } elseif ($openCount > $closeCount) {
3224
                if ($closeCount > 0) {
3225
                    return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...hed matrix braces '{'') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3226
                }
3227
3228
                return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ected '{' encountered') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3229
            }
3230
        }
3231
3232 122
        return $formula;
3233
    }
3234
3235
    private static function mkMatrix(...$args)
3236
    {
3237
        return $args;
3238
    }
3239
3240
    //    Binary Operators
3241
    //    These operators always work on two values
3242
    //    Array key is the operator, the value indicates whether this is a left or right associative operator
3243
    private static $operatorAssociativity = [
3244
        '^' => 0, //    Exponentiation
3245
        '*' => 0, '/' => 0, //    Multiplication and Division
3246
        '+' => 0, '-' => 0, //    Addition and Subtraction
3247
        '&' => 0, //    Concatenation
3248
        '|' => 0, ':' => 0, //    Intersect and Range
3249
        '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, //    Comparison
3250
    ];
3251
3252
    //    Comparison (Boolean) Operators
3253
    //    These operators work on two values, but always return a boolean result
3254
    private static $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true];
3255
3256
    //    Operator Precedence
3257
    //    This list includes all valid operators, whether binary (including boolean) or unary (such as %)
3258
    //    Array key is the operator, the value is its precedence
3259
    private static $operatorPrecedence = [
3260
        ':' => 8, //    Range
3261
        '|' => 7, //    Intersect
3262
        '~' => 6, //    Negation
3263
        '%' => 5, //    Percentage
3264
        '^' => 4, //    Exponentiation
3265
        '*' => 3, '/' => 3, //    Multiplication and Division
3266
        '+' => 2, '-' => 2, //    Addition and Subtraction
3267
        '&' => 1, //    Concatenation
3268
        '>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0, //    Comparison
3269
    ];
3270
3271
    // Convert infix to postfix notation
3272
3273
    /**
3274
     * @param string $formula
3275
     * @param null|\PhpOffice\PhpSpreadsheet\Cell\Cell $pCell
3276
     *
3277
     * @return bool
3278
     */
3279 122
    private function _parseFormula($formula, Cell $pCell = null)
3280
    {
3281 122
        if (($formula = $this->convertMatrixReferences(trim($formula))) === false) {
0 ignored issues
show
introduced by
The condition $formula = $this->conver...im($formula)) === false is always false.
Loading history...
3282
            return false;
3283
        }
3284
3285
        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
3286
        //        so we store the parent worksheet so that we can re-attach it when necessary
3287 122
        $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
3288
3289 122
        $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION .
3290 122
                                '|' . self::CALCULATION_REGEXP_CELLREF .
3291 122
                                '|' . self::CALCULATION_REGEXP_NUMBER .
3292 122
                                '|' . self::CALCULATION_REGEXP_STRING .
3293 122
                                '|' . self::CALCULATION_REGEXP_OPENBRACE .
3294 122
                                '|' . self::CALCULATION_REGEXP_NAMEDRANGE .
3295 122
                                '|' . self::CALCULATION_REGEXP_ERROR .
3296 122
                                ')/si';
3297
3298
        //    Start with initialisation
3299 122
        $index = 0;
3300 122
        $stack = new Stack();
3301 122
        $output = [];
3302 122
        $expectingOperator = false; //    We use this test in syntax-checking the expression to determine when a
3303
                                                    //        - is a negation or + is a positive operator rather than an operation
3304 122
        $expectingOperand = false; //    We use this test in syntax-checking the expression to determine whether an operand
3305
                                                    //        should be null in a function call
3306
        //    The guts of the lexical parser
3307
        //    Loop through the formula extracting each operator and operand in turn
3308 122
        while (true) {
3309 122
            $opCharacter = $formula[$index]; //    Get the first character of the value at the current index position
3310 122
            if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) {
3311 34
                $opCharacter .= $formula[++$index];
3312
            }
3313
3314
            //    Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand
3315 122
            $isOperandOrFunction = preg_match($regexpMatchString, substr($formula, $index), $match);
3316
3317 122
            if ($opCharacter == '-' && !$expectingOperator) {                //    Is it a negation instead of a minus?
3318 2
                $stack->push('Unary Operator', '~'); //    Put a negation on the stack
3319 2
                ++$index; //        and drop the negation symbol
3320 122
            } elseif ($opCharacter == '%' && $expectingOperator) {
3321
                $stack->push('Unary Operator', '%'); //    Put a percentage on the stack
3322
                ++$index;
3323 122
            } elseif ($opCharacter == '+' && !$expectingOperator) {            //    Positive (unary plus rather than binary operator plus) can be discarded?
3324
                ++$index; //    Drop the redundant plus symbol
3325 122
            } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) {    //    We have to explicitly deny a tilde or pipe, because they are legal
3326
                return $this->raiseFormulaError("Formula Error: Illegal character '~'"); //        on the stack but not in the input expression
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...Illegal character '~'') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3327 122
            } elseif ((isset(self::$operators[$opCharacter]) or $isOperandOrFunction) && $expectingOperator) {    //    Are we putting an operator on the stack?
3328 113
                while ($stack->count() > 0 &&
3329 113
                    ($o2 = $stack->last()) &&
3330 113
                    isset(self::$operators[$o2['value']]) &&
3331 113
                    @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
3332 2
                    $output[] = $stack->pop(); //    Swap operands and higher precedence operators from the stack to the output
3333
                }
3334 113
                $stack->push('Binary Operator', $opCharacter); //    Finally put our current operator onto the stack
3335 113
                ++$index;
3336 113
                $expectingOperator = false;
3337 122
            } elseif ($opCharacter == ')' && $expectingOperator) {            //    Are we expecting to close a parenthesis?
3338 49
                $expectingOperand = false;
3339 49
                while (($o2 = $stack->pop()) && $o2['value'] != '(') {        //    Pop off the stack back to the last (
3340 40
                    if ($o2 === null) {
3341
                        return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ted closing brace ")"') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3342
                    }
3343 40
                    $output[] = $o2;
3344
                }
3345 49
                $d = $stack->last(2);
3346 49
                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {    //    Did this parenthesis just close a function?
3347 49
                    $functionName = $matches[1]; //    Get the function name
3348 49
                    $d = $stack->pop();
3349 49
                    $argumentCount = $d['value']; //    See how many arguments there were (argument count is the next value stored on the stack)
3350 49
                    $output[] = $d; //    Dump the argument count on the output
3351 49
                    $output[] = $stack->pop(); //    Pop the function and push onto the output
3352 49
                    if (isset(self::$controlFunctions[$functionName])) {
3353
                        $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
3354
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
0 ignored issues
show
Unused Code introduced by
The assignment to $functionCall is dead and can be removed.
Loading history...
3355 49
                    } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
3356 49
                        $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
3357 49
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
3358
                    } else {    // did we somehow push a non-function on the stack? this should never happen
3359
                        return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...non-function on stack') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3360
                    }
3361
                    //    Check the argument count
3362 49
                    $argumentCountError = false;
3363 49
                    if (is_numeric($expectedArgumentCount)) {
3364 28
                        if ($expectedArgumentCount < 0) {
3365
                            if ($argumentCount > abs($expectedArgumentCount)) {
3366
                                $argumentCountError = true;
3367
                                $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount);
3368
                            }
3369
                        } else {
3370 28
                            if ($argumentCount != $expectedArgumentCount) {
3371
                                $argumentCountError = true;
3372 28
                                $expectedArgumentCountString = $expectedArgumentCount;
3373
                            }
3374
                        }
3375 41
                    } elseif ($expectedArgumentCount != '*') {
3376 41
                        $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
0 ignored issues
show
Unused Code introduced by
The assignment to $isOperandOrFunction is dead and can be removed.
Loading history...
3377 41
                        switch ($argMatch[2]) {
3378 41
                            case '+':
3379 39
                                if ($argumentCount < $argMatch[1]) {
3380
                                    $argumentCountError = true;
3381
                                    $expectedArgumentCountString = $argMatch[1] . ' or more ';
3382
                                }
3383
3384 39
                                break;
3385 16
                            case '-':
3386 13
                                if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
3387
                                    $argumentCountError = true;
3388
                                    $expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3];
3389
                                }
3390
3391 13
                                break;
3392 3
                            case ',':
3393 3
                                if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
3394
                                    $argumentCountError = true;
3395
                                    $expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3];
3396
                                }
3397
3398 3
                                break;
3399
                        }
3400
                    }
3401 49
                    if ($argumentCountError) {
3402
                        return $this->raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString . ' expected');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $expectedArgumentCountString does not seem to be defined for all execution paths leading up to this point.
Loading history...
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ntString . ' expected') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3403
                    }
3404
                }
3405 49
                ++$index;
3406 122
            } elseif ($opCharacter == ',') {            //    Is this the separator for function arguments?
3407 30
                while (($o2 = $stack->pop()) && $o2['value'] != '(') {        //    Pop off the stack back to the last (
3408 25
                    if ($o2 === null) {
3409
                        return $this->raiseFormulaError('Formula Error: Unexpected ,');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...a Error: Unexpected ,') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3410
                    }
3411 25
                    $output[] = $o2; // pop the argument expression stuff and push onto the output
3412
                }
3413
                //    If we've a comma when we're expecting an operand, then what we actually have is a null operand;
3414
                //        so push a null onto the stack
3415 30
                if (($expectingOperand) || (!$expectingOperator)) {
3416
                    $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
3417
                }
3418
                // make sure there was a function
3419 30
                $d = $stack->last(2);
3420 30
                if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {
3421
                    return $this->raiseFormulaError('Formula Error: Unexpected ,');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...a Error: Unexpected ,') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3422
                }
3423 30
                $d = $stack->pop();
3424 30
                $stack->push($d['type'], ++$d['value'], $d['reference']); // increment the argument count
3425 30
                $stack->push('Brace', '('); // put the ( back on, we'll need to pop back to it again
3426 30
                $expectingOperator = false;
3427 30
                $expectingOperand = true;
3428 30
                ++$index;
3429 122
            } elseif ($opCharacter == '(' && !$expectingOperator) {
3430 3
                $stack->push('Brace', '(');
3431 3
                ++$index;
3432 122
            } elseif ($isOperandOrFunction && !$expectingOperator) {    // do we now have a function/variable/number?
3433 122
                $expectingOperator = true;
3434 122
                $expectingOperand = false;
3435 122
                $val = $match[1];
3436 122
                $length = strlen($val);
3437
3438 122
                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $val, $matches)) {
3439 49
                    $val = preg_replace('/\s/u', '', $val);
3440 49
                    if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) {    // it's a function
3441 49
                        $stack->push('Function', strtoupper($val));
3442 49
                        $ax = preg_match('/^\s*(\s*\))/ui', substr($formula, $index + $length), $amatch);
3443 49
                        if ($ax) {
3444 10
                            $stack->push('Operand Count for Function ' . strtoupper($val) . ')', 0);
3445 10
                            $expectingOperator = true;
3446
                        } else {
3447 48
                            $stack->push('Operand Count for Function ' . strtoupper($val) . ')', 1);
3448 48
                            $expectingOperator = false;
3449
                        }
3450 49
                        $stack->push('Brace', '(');
3451
                    } else {    // it's a var w/ implicit multiplication
3452 49
                        $output[] = ['type' => 'Value', 'value' => $matches[1], 'reference' => null];
3453
                    }
3454 121
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) {
3455
                    //    Watch for this case-change when modifying to allow cell references in different worksheets...
3456
                    //    Should only be applied to the actual cell column, not the worksheet name
3457
3458
                    //    If the last entry on the stack was a : operator, then we have a cell range reference
3459 62
                    $testPrevOp = $stack->last(1);
3460 62
                    if ($testPrevOp['value'] == ':') {
3461
                        //    If we have a worksheet reference, then we're playing with a 3D reference
3462 53
                        if ($matches[2] == '') {
3463
                            //    Otherwise, we 'inherit' the worksheet reference from the start cell reference
3464
                            //    The start of the cell range reference should be the last entry in $output
3465 53
                            $startCellRef = $output[count($output) - 1]['value'];
3466 53
                            preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $startCellRef, $startMatches);
3467 53
                            if ($startMatches[2] > '') {
3468 53
                                $val = $startMatches[2] . '!' . $val;
3469
                            }
3470
                        } else {
3471
                            return $this->raiseFormulaError('3D Range references are not yet supported');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...are not yet supported') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3472
                        }
3473
                    }
3474
3475 62
                    $output[] = ['type' => 'Cell Reference', 'value' => $val, 'reference' => $val];
3476
                } else {    // it's a variable, constant, string, number or boolean
3477
                    //    If the last entry on the stack was a : operator, then we may have a row or column range reference
3478 97
                    $testPrevOp = $stack->last(1);
3479 97
                    if ($testPrevOp['value'] == ':') {
3480 1
                        $startRowColRef = $output[count($output) - 1]['value'];
3481 1
                        list($rangeWS1, $startRowColRef) = Worksheet::extractSheetTitle($startRowColRef, true);
3482 1
                        if ($rangeWS1 != '') {
3483
                            $rangeWS1 .= '!';
3484
                        }
3485 1
                        list($rangeWS2, $val) = Worksheet::extractSheetTitle($val, true);
3486 1
                        if ($rangeWS2 != '') {
3487
                            $rangeWS2 .= '!';
3488
                        } else {
3489 1
                            $rangeWS2 = $rangeWS1;
3490
                        }
3491 1
                        if ((is_int($startRowColRef)) && (ctype_digit($val)) &&
3492 1
                            ($startRowColRef <= 1048576) && ($val <= 1048576)) {
3493
                            //    Row range
3494
                            $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestColumn() : 'XFD'; //    Max 16,384 columns for Excel2007
3495
                            $output[count($output) - 1]['value'] = $rangeWS1 . 'A' . $startRowColRef;
3496
                            $val = $rangeWS2 . $endRowColRef . $val;
3497 1
                        } elseif ((ctype_alpha($startRowColRef)) && (ctype_alpha($val)) &&
3498 1
                            (strlen($startRowColRef) <= 3) && (strlen($val) <= 3)) {
3499
                            //    Column range
3500
                            $endRowColRef = ($pCellParent !== null) ? $pCellParent->getHighestRow() : 1048576; //    Max 1,048,576 rows for Excel2007
3501
                            $output[count($output) - 1]['value'] = $rangeWS1 . strtoupper($startRowColRef) . '1';
3502
                            $val = $rangeWS2 . $val . $endRowColRef;
3503
                        }
3504
                    }
3505
3506 97
                    $localeConstant = false;
3507 97
                    if ($opCharacter == '"') {
3508
                        //    UnEscape any quotes within the string
3509 60
                        $val = self::wrapResult(str_replace('""', '"', self::unwrapResult($val)));
3510 63
                    } elseif (is_numeric($val)) {
3511 61
                        if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
3512 21
                            $val = (float) $val;
3513
                        } else {
3514 61
                            $val = (int) $val;
3515
                        }
3516 6
                    } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) {
3517 1
                        $excelConstant = trim(strtoupper($val));
3518 1
                        $val = self::$excelConstants[$excelConstant];
3519 5
                    } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
3520
                        $val = self::$excelConstants[$localeConstant];
3521
                    }
3522 97
                    $details = ['type' => 'Value', 'value' => $val, 'reference' => null];
3523 97
                    if ($localeConstant) {
3524
                        $details['localeValue'] = $localeConstant;
3525
                    }
3526 97
                    $output[] = $details;
3527
                }
3528 122
                $index += $length;
3529
            } elseif ($opCharacter == '$') {    // absolute row or column range
3530
                ++$index;
3531
            } elseif ($opCharacter == ')') {    // miscellaneous error checking
3532
                if ($expectingOperand) {
3533
                    $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
3534
                    $expectingOperand = false;
3535
                    $expectingOperator = true;
3536
                } else {
3537
                    return $this->raiseFormulaError("Formula Error: Unexpected ')'");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...Error: Unexpected ')'') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3538
                }
3539
            } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
3540
                return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...or ''.$opCharacter.''') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3541
            } else {    // I don't even want to know what you did to get here
3542
                return $this->raiseFormulaError('Formula Error: An unexpected error occured');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...xpected error occured') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3543
            }
3544
            //    Test for end of formula string
3545 122
            if ($index == strlen($formula)) {
3546
                //    Did we end with an operator?.
3547
                //    Only valid for the % unary operator
3548 122
                if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) {
3549
                    return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...er.'' has no operands') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3550
                }
3551
3552 122
                break;
3553
            }
3554
            //    Ignore white space
3555 120
            while (($formula[$index] == "\n") || ($formula[$index] == "\r")) {
3556
                ++$index;
3557
            }
3558 120
            if ($formula[$index] == ' ') {
3559 55
                while ($formula[$index] == ' ') {
3560 55
                    ++$index;
3561
                }
3562
                //    If we're expecting an operator, but only have a space between the previous and next operands (and both are
3563
                //        Cell References) then we have an INTERSECTION operator
3564 55
                if (($expectingOperator) && (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) &&
3565 55
                    ($output[count($output) - 1]['type'] == 'Cell Reference')) {
3566
                    while ($stack->count() > 0 &&
3567
                        ($o2 = $stack->last()) &&
3568
                        isset(self::$operators[$o2['value']]) &&
3569
                        @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])) {
3570
                        $output[] = $stack->pop(); //    Swap operands and higher precedence operators from the stack to the output
3571
                    }
3572
                    $stack->push('Binary Operator', '|'); //    Put an Intersect Operator on the stack
3573
                    $expectingOperator = false;
3574
                }
3575
            }
3576
        }
3577
3578 122
        while (($op = $stack->pop()) !== null) {    // pop everything off the stack and push onto output
3579 99
            if ((is_array($op) && $op['value'] == '(') || ($op === '(')) {
3580
                return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError... Error: Expecting ')'') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3581
            }
3582 99
            $output[] = $op;
3583
        }
3584
3585 122
        return $output;
3586
    }
3587
3588 112
    private static function dataTestReference(&$operandData)
3589
    {
3590 112
        $operand = $operandData['value'];
3591 112
        if (($operandData['reference'] === null) && (is_array($operand))) {
3592 1
            $rKeys = array_keys($operand);
3593 1
            $rowKey = array_shift($rKeys);
3594 1
            $cKeys = array_keys(array_keys($operand[$rowKey]));
3595 1
            $colKey = array_shift($cKeys);
3596 1
            if (ctype_upper($colKey)) {
3597
                $operandData['reference'] = $colKey . $rowKey;
3598
            }
3599
        }
3600
3601 112
        return $operand;
3602
    }
3603
3604
    // evaluate postfix notation
3605
3606
    /**
3607
     * @param mixed $tokens
3608
     * @param null|string $cellID
3609
     * @param null|Cell $pCell
3610
     *
3611
     * @return bool
3612
     */
3613 121
    private function processTokenStack($tokens, $cellID = null, Cell $pCell = null)
3614
    {
3615 121
        if ($tokens == false) {
3616
            return false;
3617
        }
3618
3619
        //    If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
3620
        //        so we store the parent cell collection so that we can re-attach it when necessary
3621 121
        $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
3622 121
        $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
3623 121
        $stack = new Stack();
3624
3625
        //    Loop through each token in turn
3626 121
        foreach ($tokens as $tokenData) {
3627 121
            $token = $tokenData['value'];
3628
            // 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
3629 121
            if (isset(self::$binaryOperators[$token])) {
3630
                //    We must have two operands, error if we don't
3631 112
                if (($operand2Data = $stack->pop()) === null) {
3632
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ue missing from stack') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3633
                }
3634 112
                if (($operand1Data = $stack->pop()) === null) {
3635
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ue missing from stack') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3636
                }
3637
3638 112
                $operand1 = self::dataTestReference($operand1Data);
3639 112
                $operand2 = self::dataTestReference($operand2Data);
3640
3641
                //    Log what we're doing
3642 112
                if ($token == ':') {
3643 52
                    $this->debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference']));
3644
                } else {
3645 95
                    $this->debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2));
3646
                }
3647
3648
                //    Process the operation in the appropriate manner
3649
                switch ($token) {
3650
                    //    Comparison (Boolean) Operators
3651 112
                    case '>':            //    Greater than
3652 98
                    case '<':            //    Less than
3653 91
                    case '>=':            //    Greater than or Equal to
3654 83
                    case '<=':            //    Less than or Equal to
3655 76
                    case '=':            //    Equality
3656 67
                    case '<>':            //    Inequality
3657 74
                        $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
3658
3659 74
                        break;
3660
                    //    Binary Operators
3661 60
                    case ':':            //    Range
3662 52
                        if (strpos($operand1Data['reference'], '!') !== false) {
3663 52
                            list($sheet1, $operand1Data['reference']) = Worksheet::extractSheetTitle($operand1Data['reference'], true);
3664
                        } else {
3665 1
                            $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
1 ignored issue
show
Bug introduced by
The method getTitle() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

3665
                            $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->/** @scrutinizer ignore-call */ getTitle() : '';

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
3666
                        }
3667
3668 52
                        list($sheet2, $operand2Data['reference']) = Worksheet::extractSheetTitle($operand2Data['reference'], true);
3669 52
                        if (empty($sheet2)) {
3670 10
                            $sheet2 = $sheet1;
3671
                        }
3672
3673 52
                        if ($sheet1 == $sheet2) {
3674 52
                            if ($operand1Data['reference'] === null) {
3675
                                if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
3676
                                    $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value'];
1 ignored issue
show
Bug introduced by
The method getColumn() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

3676
                                    $operand1Data['reference'] = $pCell->/** @scrutinizer ignore-call */ getColumn() . $operand1Data['value'];

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
3677
                                } elseif (trim($operand1Data['reference']) == '') {
3678
                                    $operand1Data['reference'] = $pCell->getCoordinate();
3679
                                } else {
3680
                                    $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
3681
                                }
3682
                            }
3683 52
                            if ($operand2Data['reference'] === null) {
3684
                                if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
3685
                                    $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
3686
                                } elseif (trim($operand2Data['reference']) == '') {
3687
                                    $operand2Data['reference'] = $pCell->getCoordinate();
3688
                                } else {
3689
                                    $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
3690
                                }
3691
                            }
3692
3693 52
                            $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
3694 52
                            $oCol = $oRow = [];
3695 52
                            foreach ($oData as $oDatum) {
3696 52
                                $oCR = Coordinate::coordinateFromString($oDatum);
3697 52
                                $oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
3698 52
                                $oRow[] = $oCR[1];
3699
                            }
3700 52
                            $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
3701 52
                            if ($pCellParent !== null) {
3702 52
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
3703
                            } else {
3704
                                return $this->raiseFormulaError('Unable to access Cell Reference');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...access Cell Reference') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3705
                            }
3706 52
                            $stack->push('Cell Reference', $cellValue, $cellRef);
3707
                        } else {
3708
                            $stack->push('Error', Functions::REF(), null);
3709
                        }
3710
3711 52
                        break;
3712 33
                    case '+':            //    Addition
3713 24
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack);
3714
3715 24
                        break;
3716 28
                    case '-':            //    Subtraction
3717 7
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack);
3718
3719 7
                        break;
3720 26
                    case '*':            //    Multiplication
3721 19
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack);
3722
3723 19
                        break;
3724 11
                    case '/':            //    Division
3725 8
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack);
3726
3727 8
                        break;
3728 6
                    case '^':            //    Exponential
3729
                        $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack);
3730
3731
                        break;
3732 6
                    case '&':            //    Concatenation
3733
                        //    If either of the operands is a matrix, we need to treat them both as matrices
3734
                        //        (converting the other operand to a matrix if need be); then perform the required
3735
                        //        matrix operation
3736 6
                        if (is_bool($operand1)) {
3737
                            $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3738
                        }
3739 6
                        if (is_bool($operand2)) {
3740
                            $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3741
                        }
3742 6
                        if ((is_array($operand1)) || (is_array($operand2))) {
3743
                            //    Ensure that both operands are arrays/matrices
3744 5
                            self::checkMatrixOperands($operand1, $operand2, 2);
3745
3746
                            try {
3747
                                //    Convert operand 1 from a PHP array to a matrix
3748 5
                                $matrix = new Shared\JAMA\Matrix($operand1);
3749
                                //    Perform the required operation against the operand 1 matrix, passing in operand 2
3750 5
                                $matrixResult = $matrix->concat($operand2);
3751 5
                                $result = $matrixResult->getArray();
3752
                            } catch (\Exception $ex) {
3753
                                $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
3754 5
                                $result = '#VALUE!';
3755
                            }
3756
                        } else {
3757 1
                            $result = '"' . str_replace('""', '"', self::unwrapResult($operand1) . self::unwrapResult($operand2)) . '"';
3758
                        }
3759 6
                        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
3760 6
                        $stack->push('Value', $result);
3761
3762 6
                        break;
3763
                    case '|':            //    Intersect
3764
                        $rowIntersect = array_intersect_key($operand1, $operand2);
3765
                        $cellIntersect = $oCol = $oRow = [];
3766
                        foreach (array_keys($rowIntersect) as $row) {
3767
                            $oRow[] = $row;
3768
                            foreach ($rowIntersect[$row] as $col => $data) {
3769
                                $oCol[] = Coordinate::columnIndexFromString($col) - 1;
3770
                                $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
3771
                            }
3772
                        }
3773
                        $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
3774
                        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
3775
                        $stack->push('Value', $cellIntersect, $cellRef);
3776
3777 112
                        break;
3778
                }
3779
3780
                // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
3781 121
            } elseif (($token === '~') || ($token === '%')) {
3782 2
                if (($arg = $stack->pop()) === null) {
3783
                    return $this->raiseFormulaError('Internal error - Operand value missing from stack');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ue missing from stack') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3784
                }
3785 2
                $arg = $arg['value'];
3786 2
                if ($token === '~') {
3787 2
                    $this->debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg));
3788 2
                    $multiplier = -1;
3789
                } else {
3790
                    $this->debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg));
3791
                    $multiplier = 0.01;
3792
                }
3793 2
                if (is_array($arg)) {
3794
                    self::checkMatrixOperands($arg, $multiplier, 2);
3795
3796
                    try {
3797
                        $matrix1 = new Shared\JAMA\Matrix($arg);
3798
                        $matrixResult = $matrix1->arrayTimesEquals($multiplier);
3799
                        $result = $matrixResult->getArray();
3800
                    } catch (\Exception $ex) {
3801
                        $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
3802
                        $result = '#VALUE!';
3803
                    }
3804
                    $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
3805
                    $stack->push('Value', $result);
3806
                } else {
3807 2
                    $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack);
3808
                }
3809 121
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) {
3810 61
                $cellRef = null;
3811 61
                if (isset($matches[8])) {
3812
                    if ($pCell === null) {
3813
                        //                        We can't access the range, so return a REF error
3814
                        $cellValue = Functions::REF();
3815
                    } else {
3816
                        $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
3817
                        if ($matches[2] > '') {
3818
                            $matches[2] = trim($matches[2], "\"'");
3819
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
3820
                                //    It's a Reference to an external spreadsheet (not currently supported)
3821
                                return $this->raiseFormulaError('Unable to access External Workbook');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ess External Workbook') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3822
                            }
3823
                            $matches[2] = trim($matches[2], "\"'");
3824
                            $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]);
3825
                            if ($pCellParent !== null) {
3826
                                $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
3827
                            } else {
3828
                                return $this->raiseFormulaError('Unable to access Cell Reference');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...access Cell Reference') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3829
                            }
3830
                            $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
3831
                        } else {
3832
                            $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet');
3833
                            if ($pCellParent !== null) {
3834
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
3835
                            } else {
3836
                                return $this->raiseFormulaError('Unable to access Cell Reference');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...access Cell Reference') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3837
                            }
3838
                            $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
3839
                        }
3840
                    }
3841
                } else {
3842 61
                    if ($pCell === null) {
3843
                        //                        We can't access the cell, so return a REF error
3844
                        $cellValue = Functions::REF();
3845
                    } else {
3846 61
                        $cellRef = $matches[6] . $matches[7];
3847 61
                        if ($matches[2] > '') {
3848 14
                            $matches[2] = trim($matches[2], "\"'");
3849 14
                            if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
3850
                                //    It's a Reference to an external spreadsheet (not currently supported)
3851
                                return $this->raiseFormulaError('Unable to access External Workbook');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...ess External Workbook') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3852
                            }
3853 14
                            $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]);
3854 14
                            if ($pCellParent !== null) {
3855 14
                                $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
3856 14
                                if ($cellSheet && $cellSheet->cellExists($cellRef)) {
3857 14
                                    $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
3858 14
                                    $pCell->attach($pCellParent);
3859
                                } else {
3860 14
                                    $cellValue = null;
3861
                                }
3862
                            } else {
3863
                                return $this->raiseFormulaError('Unable to access Cell Reference');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...access Cell Reference') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3864
                            }
3865 14
                            $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
3866
                        } else {
3867 49
                            $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet');
3868 49
                            if ($pCellParent->has($cellRef)) {
1 ignored issue
show
Bug introduced by
The method has() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

3868
                            if ($pCellParent->/** @scrutinizer ignore-call */ has($cellRef)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
3869 49
                                $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
3870 49
                                $pCell->attach($pCellParent);
1 ignored issue
show
Bug introduced by
It seems like $pCellParent can also be of type null; however, parameter $parent of PhpOffice\PhpSpreadsheet\Cell\Cell::attach() does only seem to accept PhpOffice\PhpSpreadsheet\Collection\Cells, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3870
                                $pCell->attach(/** @scrutinizer ignore-type */ $pCellParent);
Loading history...
3871
                            } else {
3872 2
                                $cellValue = null;
3873
                            }
3874 49
                            $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
3875
                        }
3876
                    }
3877
                }
3878 61
                $stack->push('Value', $cellValue, $cellRef);
3879
3880
            // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
3881 107
            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
3882 48
                $functionName = $matches[1];
3883 48
                $argCount = $stack->pop();
3884 48
                $argCount = $argCount['value'];
3885 48
                if ($functionName != 'MKMATRIX') {
3886 48
                    $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's'));
3887
                }
3888 48
                if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) {    // function
3889 48
                    if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
3890 48
                        $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
3891 48
                        $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
3892 48
                        $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
3893
                    } elseif (isset(self::$controlFunctions[$functionName])) {
3894
                        $functionCall = self::$controlFunctions[$functionName]['functionCall'];
3895
                        $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
3896
                        $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
3897
                    }
3898
                    // get the arguments for this function
3899 48
                    $args = $argArrayVals = [];
3900 48
                    for ($i = 0; $i < $argCount; ++$i) {
3901 47
                        $arg = $stack->pop();
3902 47
                        $a = $argCount - $i - 1;
3903 47
                        if (($passByReference) &&
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $passByReference does not seem to be defined for all execution paths leading up to this point.
Loading history...
3904 47
                            (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) &&
3905 47
                            (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) {
3906 1
                            if ($arg['reference'] === null) {
3907
                                $args[] = $cellID;
3908
                                if ($functionName != 'MKMATRIX') {
3909
                                    $argArrayVals[] = $this->showValue($cellID);
3910
                                }
3911
                            } else {
3912 1
                                $args[] = $arg['reference'];
3913 1
                                if ($functionName != 'MKMATRIX') {
3914 1
                                    $argArrayVals[] = $this->showValue($arg['reference']);
3915
                                }
3916
                            }
3917
                        } else {
3918 46
                            $args[] = self::unwrapResult($arg['value']);
3919 46
                            if ($functionName != 'MKMATRIX') {
3920 46
                                $argArrayVals[] = $this->showValue($arg['value']);
3921
                            }
3922
                        }
3923
                    }
3924
                    //    Reverse the order of the arguments
3925 48
                    krsort($args);
3926
3927 48
                    if (($passByReference) && ($argCount == 0)) {
3928
                        $args[] = $cellID;
3929
                        $argArrayVals[] = $this->showValue($cellID);
3930
                    }
3931
3932 48
                    if ($functionName != 'MKMATRIX') {
3933 48
                        if ($this->debugLog->getWriteDebugLog()) {
3934
                            krsort($argArrayVals);
3935
                            $this->debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)), ' )');
3936
                        }
3937
                    }
3938
3939
                    //    Process the argument with the appropriate function call
3940 48
                    if ($passCellReference) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $passCellReference does not seem to be defined for all execution paths leading up to this point.
Loading history...
3941 2
                        $args[] = $pCell;
3942
                    }
3943
3944 48
                    if (!is_array($functionCall)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $functionCall does not seem to be defined for all execution paths leading up to this point.
Loading history...
3945 1
                        foreach ($args as &$arg) {
3946
                            $arg = Functions::flattenSingleValue($arg);
3947
                        }
3948 1
                        unset($arg);
3949
                    }
3950 48
                    $result = call_user_func_array($functionCall, $args);
3951
3952 48
                    if ($functionName != 'MKMATRIX') {
3953 48
                        $this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result));
3954
                    }
3955 48
                    $stack->push('Value', self::wrapResult($result));
3956
                }
3957
            } else {
3958
                // if the token is a number, boolean, string or an Excel error, push it onto the stack
3959 107
                if (isset(self::$excelConstants[strtoupper($token)])) {
3960
                    $excelConstant = strtoupper($token);
3961
                    $stack->push('Constant Value', self::$excelConstants[$excelConstant]);
3962
                    $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant]));
3963 107
                } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == '"') || ($token[0] == '#')) {
3964 107
                    $stack->push('Value', $token);
3965
                // if the token is a named range, push the named range name onto the stack
3966 5
                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
3967 5
                    $namedRange = $matches[6];
3968 5
                    $this->debugLog->writeDebugLog('Evaluating Named Range ', $namedRange);
3969
3970 5
                    $cellValue = $this->extractNamedRange($namedRange, ((null !== $pCell) ? $pCellWorksheet : null), false);
3971 5
                    $pCell->attach($pCellParent);
3972 5
                    $this->debugLog->writeDebugLog('Evaluation Result for named range ', $namedRange, ' is ', $this->showTypeDetails($cellValue));
3973 5
                    $stack->push('Named Range', $cellValue, $namedRange);
3974
                } else {
3975 121
                    return $this->raiseFormulaError("undefined variable '$token'");
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError...variable ''.$token.''') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3976
                }
3977
            }
3978
        }
3979
        // when we're out of tokens, the stack should have a single element, the final result
3980 121
        if ($stack->count() != 1) {
3981
            return $this->raiseFormulaError('internal error');
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->raiseFormulaError('internal error') targeting PhpOffice\PhpSpreadsheet...on::raiseFormulaError() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
3982
        }
3983 121
        $output = $stack->pop();
3984 121
        $output = $output['value'];
3985
3986 121
        return $output;
3987
    }
3988
3989 32
    private function validateBinaryOperand(&$operand, &$stack)
3990
    {
3991 32
        if (is_array($operand)) {
3992 22
            if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) {
3993
                do {
3994 22
                    $operand = array_pop($operand);
3995 22
                } while (is_array($operand));
3996
            }
3997
        }
3998
        //    Numbers, matrices and booleans can pass straight through, as they're already valid
3999 32
        if (is_string($operand)) {
4000
            //    We only need special validations for the operand if it is a string
4001
            //    Start by stripping off the quotation marks we use to identify true excel string values internally
4002 2
            if ($operand > '' && $operand[0] == '"') {
4003
                $operand = self::unwrapResult($operand);
4004
            }
4005
            //    If the string is a numeric value, we treat it as a numeric, so no further testing
4006 2
            if (!is_numeric($operand)) {
4007
                //    If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations
4008 2
                if ($operand > '' && $operand[0] == '#') {
4009
                    $stack->push('Value', $operand);
4010
                    $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($operand));
4011
4012
                    return false;
4013 2
                } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) {
4014
                    //    If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations
4015 2
                    $stack->push('Value', '#VALUE!');
4016 2
                    $this->debugLog->writeDebugLog('Evaluation Result is a ', $this->showTypeDetails('#VALUE!'));
4017
4018 2
                    return false;
4019
                }
4020
            }
4021
        }
4022
4023
        //    return a true if the value of the operand is one that we can use in normal binary operations
4024 31
        return true;
4025
    }
4026
4027
    /**
4028
     * @param null|string $cellID
4029
     * @param mixed $operand1
4030
     * @param mixed $operand2
4031
     * @param string $operation
4032
     * @param Stack $stack
4033
     * @param bool $recursingArrays
4034
     *
4035
     * @return bool
4036
     */
4037 74
    private function executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays = false)
4038
    {
4039
        //    If we're dealing with matrix operations, we want a matrix result
4040 74
        if ((is_array($operand1)) || (is_array($operand2))) {
4041 12
            $result = [];
4042 12
            if ((is_array($operand1)) && (!is_array($operand2))) {
4043 12
                foreach ($operand1 as $x => $operandData) {
4044 12
                    $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2));
4045 12
                    $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack);
4046 12
                    $r = $stack->pop();
4047 12
                    $result[$x] = $r['value'];
4048
                }
4049
            } elseif ((!is_array($operand1)) && (is_array($operand2))) {
4050
                foreach ($operand2 as $x => $operandData) {
4051
                    $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData));
4052
                    $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack);
4053
                    $r = $stack->pop();
4054
                    $result[$x] = $r['value'];
4055
                }
4056
            } else {
4057
                if (!$recursingArrays) {
4058
                    self::checkMatrixOperands($operand1, $operand2, 2);
4059
                }
4060
                foreach ($operand1 as $x => $operandData) {
4061
                    $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x]));
4062
                    $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true);
4063
                    $r = $stack->pop();
4064
                    $result[$x] = $r['value'];
4065
                }
4066
            }
4067
            //    Log the result details
4068 12
            $this->debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result));
4069
            //    And push the result onto the stack
4070 12
            $stack->push('Array', $result);
4071
4072 12
            return true;
4073
        }
4074
4075
        //    Simple validate the two operands if they are string values
4076 74
        if (is_string($operand1) && $operand1 > '' && $operand1[0] == '"') {
4077 43
            $operand1 = self::unwrapResult($operand1);
4078
        }
4079 74
        if (is_string($operand2) && $operand2 > '' && $operand2[0] == '"') {
4080 55
            $operand2 = self::unwrapResult($operand2);
4081
        }
4082
4083
        // Use case insensitive comparaison if not OpenOffice mode
4084 74
        if (Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) {
4085 74
            if (is_string($operand1)) {
4086 43
                $operand1 = strtoupper($operand1);
4087
            }
4088 74
            if (is_string($operand2)) {
4089 55
                $operand2 = strtoupper($operand2);
4090
            }
4091
        }
4092
4093 74
        $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE;
4094
4095
        //    execute the necessary operation
4096
        switch ($operation) {
4097
            //    Greater than
4098 74
            case '>':
4099 18
                if ($useLowercaseFirstComparison) {
4100 9
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0;
4101
                } else {
4102 18
                    $result = ($operand1 > $operand2);
4103
                }
4104
4105 18
                break;
4106
            //    Less than
4107 60
            case '<':
4108 8
                if ($useLowercaseFirstComparison) {
4109 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0;
4110
                } else {
4111 8
                    $result = ($operand1 < $operand2);
4112
                }
4113
4114 8
                break;
4115
            //    Equality
4116 53
            case '=':
4117 19
                if (is_numeric($operand1) && is_numeric($operand2)) {
4118 3
                    $result = (abs($operand1 - $operand2) < $this->delta);
4119
                } else {
4120 16
                    $result = strcmp($operand1, $operand2) == 0;
4121
                }
4122
4123 19
                break;
4124
            //    Greater than or equal
4125 34
            case '>=':
4126 8
                if (is_numeric($operand1) && is_numeric($operand2)) {
4127 4
                    $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 > $operand2));
4128 4
                } elseif ($useLowercaseFirstComparison) {
4129 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0;
4130
                } else {
4131 4
                    $result = strcmp($operand1, $operand2) >= 0;
4132
                }
4133
4134 8
                break;
4135
            //    Less than or equal
4136 26
            case '<=':
4137 7
                if (is_numeric($operand1) && is_numeric($operand2)) {
4138 3
                    $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 < $operand2));
4139 4
                } elseif ($useLowercaseFirstComparison) {
4140 4
                    $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0;
4141
                } else {
4142 4
                    $result = strcmp($operand1, $operand2) <= 0;
4143
                }
4144
4145 7
                break;
4146
            //    Inequality
4147 19
            case '<>':
4148 19
                if (is_numeric($operand1) && is_numeric($operand2)) {
4149 3
                    $result = (abs($operand1 - $operand2) > 1E-14);
4150
                } else {
4151 16
                    $result = strcmp($operand1, $operand2) != 0;
4152
                }
4153
4154 19
                break;
4155
        }
4156
4157
        //    Log the result details
4158 74
        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
4159
        //    And push the result onto the stack
4160 74
        $stack->push('Value', $result);
4161
4162 74
        return true;
4163
    }
4164
4165
    /**
4166
     * Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.
4167
     *
4168
     * @param string $str1 First string value for the comparison
4169
     * @param string $str2 Second string value for the comparison
4170
     *
4171
     * @return int
4172
     */
4173 21
    private function strcmpLowercaseFirst($str1, $str2)
4174
    {
4175 21
        $inversedStr1 = Shared\StringHelper::strCaseReverse($str1);
4176 21
        $inversedStr2 = Shared\StringHelper::strCaseReverse($str2);
4177
4178 21
        return strcmp($inversedStr1, $inversedStr2);
4179
    }
4180
4181
    /**
4182
     * @param mixed $operand1
4183
     * @param mixed $operand2
4184
     * @param mixed $operation
4185
     * @param string $matrixFunction
4186
     * @param mixed $stack
4187
     *
4188
     * @return bool
4189
     */
4190 32
    private function executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack)
4191
    {
4192
        //    Validate the two operands
4193 32
        if (!$this->validateBinaryOperand($operand1, $stack)) {
4194 2
            return false;
4195
        }
4196 31
        if (!$this->validateBinaryOperand($operand2, $stack)) {
4197 1
            return false;
4198
        }
4199
4200
        //    If either of the operands is a matrix, we need to treat them both as matrices
4201
        //        (converting the other operand to a matrix if need be); then perform the required
4202
        //        matrix operation
4203 31
        if ((is_array($operand1)) || (is_array($operand2))) {
4204
            //    Ensure that both operands are arrays/matrices of the same size
4205
            self::checkMatrixOperands($operand1, $operand2, 2);
4206
4207
            try {
4208
                //    Convert operand 1 from a PHP array to a matrix
4209
                $matrix = new Shared\JAMA\Matrix($operand1);
4210
                //    Perform the required operation against the operand 1 matrix, passing in operand 2
4211
                $matrixResult = $matrix->$matrixFunction($operand2);
4212
                $result = $matrixResult->getArray();
4213
            } catch (\Exception $ex) {
4214
                $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
4215
                $result = '#VALUE!';
4216
            }
4217
        } else {
4218 31
            if ((Functions::getCompatibilityMode() != Functions::COMPATIBILITY_OPENOFFICE) &&
4219 31
                ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
4220 31
                 (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))) {
4221
                $result = Functions::VALUE();
4222
            } else {
4223
                //    If we're dealing with non-matrix operations, execute the necessary operation
4224
                switch ($operation) {
4225
                    //    Addition
4226 31
                    case '+':
4227 23
                        $result = $operand1 + $operand2;
4228
4229 23
                        break;
4230
                    //    Subtraction
4231 26
                    case '-':
4232 7
                        $result = $operand1 - $operand2;
4233
4234 7
                        break;
4235
                    //    Multiplication
4236 24
                    case '*':
4237 19
                        $result = $operand1 * $operand2;
4238
4239 19
                        break;
4240
                    //    Division
4241 8
                    case '/':
4242 8
                        if ($operand2 == 0) {
4243
                            //    Trap for Divide by Zero error
4244 8
                            $stack->push('Value', '#DIV/0!');
4245 8
                            $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails('#DIV/0!'));
4246
4247 8
                            return false;
4248
                        }
4249
                            $result = $operand1 / $operand2;
4250
4251
                        break;
4252
                    //    Power
4253
                    case '^':
4254
                        $result = pow($operand1, $operand2);
4255
4256
                        break;
4257
                }
4258
            }
4259
        }
4260
4261
        //    Log the result details
4262 26
        $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
4263
        //    And push the result onto the stack
4264 26
        $stack->push('Value', $result);
4265
4266 26
        return true;
4267
    }
4268
4269
    // trigger an error, but nicely, if need be
4270
    protected function raiseFormulaError($errorMessage)
4271
    {
4272
        $this->formulaError = $errorMessage;
4273
        $this->cyclicReferenceStack->clear();
4274
        if (!$this->suppressFormulaErrors) {
4275
            throw new Exception($errorMessage);
4276
        }
4277
        trigger_error($errorMessage, E_USER_ERROR);
4278
    }
4279
4280
    /**
4281
     * Extract range values.
4282
     *
4283
     * @param string &$pRange String based range representation
4284
     * @param Worksheet $pSheet Worksheet
4285
     * @param bool $resetLog Flag indicating whether calculation log should be reset or not
4286
     *
4287
     * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.
4288
     */
4289 61
    public function extractCellRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true)
4290
    {
4291
        // Return value
4292 61
        $returnValue = [];
4293
4294 61
        if ($pSheet !== null) {
4295 61
            $pSheetName = $pSheet->getTitle();
4296 61
            if (strpos($pRange, '!') !== false) {
4297
                list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
4298
                $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
4299
            }
4300
4301
            // Extract range
4302 61
            $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
4303 61
            $pRange = $pSheetName . '!' . $pRange;
4304 61
            if (!isset($aReferences[1])) {
4305
                //    Single cell in range
4306 61
                sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $currentRow seems to be never defined.
Loading history...
4307 61
                if ($pSheet->cellExists($aReferences[0])) {
4308 61
                    $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
4309
                } else {
4310 61
                    $returnValue[$currentRow][$currentCol] = null;
4311
                }
4312
            } else {
4313
                // Extract cell data for all cells in the range
4314 52
                foreach ($aReferences as $reference) {
4315
                    // Extract range
4316 52
                    sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
4317 52
                    if ($pSheet->cellExists($reference)) {
4318 52
                        $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
4319
                    } else {
4320 52
                        $returnValue[$currentRow][$currentCol] = null;
4321
                    }
4322
                }
4323
            }
4324
        }
4325
4326 61
        return $returnValue;
4327
    }
4328
4329
    /**
4330
     * Extract range values.
4331
     *
4332
     * @param string &$pRange String based range representation
4333
     * @param Worksheet $pSheet Worksheet
4334
     * @param bool $resetLog Flag indicating whether calculation log should be reset or not
4335
     *
4336
     * @return mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.
4337
     */
4338 5
    public function extractNamedRange(&$pRange = 'A1', Worksheet $pSheet = null, $resetLog = true)
4339
    {
4340
        // Return value
4341 5
        $returnValue = [];
4342
4343 5
        if ($pSheet !== null) {
4344 5
            $pSheetName = $pSheet->getTitle();
0 ignored issues
show
Unused Code introduced by
The assignment to $pSheetName is dead and can be removed.
Loading history...
4345 5
            if (strpos($pRange, '!') !== false) {
4346
                list($pSheetName, $pRange) = Worksheet::extractSheetTitle($pRange, true);
4347
                $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
4348
            }
4349
4350
            // Named range?
4351 5
            $namedRange = NamedRange::resolveRange($pRange, $pSheet);
4352 5
            if ($namedRange !== null) {
4353 2
                $pSheet = $namedRange->getWorksheet();
4354 2
                $pRange = $namedRange->getRange();
4355 2
                $splitRange = Coordinate::splitRange($pRange);
4356
                //    Convert row and column references
4357 2
                if (ctype_alpha($splitRange[0][0])) {
4358
                    $pRange = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
4359 2
                } elseif (ctype_digit($splitRange[0][0])) {
4360 2
                    $pRange = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
4361
                }
4362
            } else {
4363 3
                return Functions::REF();
4364
            }
4365
4366
            // Extract range
4367 2
            $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
4368 2
            if (!isset($aReferences[1])) {
4369
                //    Single cell (or single column or row) in range
4370 1
                list($currentCol, $currentRow) = Coordinate::coordinateFromString($aReferences[0]);
4371 1
                if ($pSheet->cellExists($aReferences[0])) {
4372 1
                    $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
4373
                } else {
4374 1
                    $returnValue[$currentRow][$currentCol] = null;
4375
                }
4376
            } else {
4377
                // Extract cell data for all cells in the range
4378 1
                foreach ($aReferences as $reference) {
4379
                    // Extract range
4380 1
                    list($currentCol, $currentRow) = Coordinate::coordinateFromString($reference);
4381 1
                    if ($pSheet->cellExists($reference)) {
4382 1
                        $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
4383
                    } else {
4384 1
                        $returnValue[$currentRow][$currentCol] = null;
4385
                    }
4386
                }
4387
            }
4388
        }
4389
4390 2
        return $returnValue;
4391
    }
4392
4393
    /**
4394
     * Is a specific function implemented?
4395
     *
4396
     * @param string $pFunction Function Name
4397
     *
4398
     * @return bool
4399
     */
4400 3
    public function isImplemented($pFunction)
4401
    {
4402 3
        $pFunction = strtoupper($pFunction);
4403 3
        $notImplemented = !isset(self::$phpSpreadsheetFunctions[$pFunction]) || (is_array(self::$phpSpreadsheetFunctions[$pFunction]['functionCall']) && self::$phpSpreadsheetFunctions[$pFunction]['functionCall'][1] === 'DUMMY');
4404
4405 3
        return !$notImplemented;
4406
    }
4407
4408
    /**
4409
     * Get a list of all implemented functions as an array of function objects.
4410
     *
4411
     * @return array of Category
4412
     */
4413
    public function getFunctions()
4414
    {
4415
        return self::$phpSpreadsheetFunctions;
4416
    }
4417
4418
    /**
4419
     * Get a list of implemented Excel function names.
4420
     *
4421
     * @return array
4422
     */
4423 2
    public function getImplementedFunctionNames()
4424
    {
4425 2
        $returnValue = [];
4426 2
        foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
4427 2
            if ($this->isImplemented($functionName)) {
4428 2
                $returnValue[] = $functionName;
4429
            }
4430
        }
4431
4432 2
        return $returnValue;
4433
    }
4434
}
4435