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