1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpOffice\PhpSpreadsheet\Calculation; |
4
|
|
|
|
5
|
|
|
use PhpOffice\PhpSpreadsheet\Cell\Cell; |
6
|
|
|
use PhpOffice\PhpSpreadsheet\Shared\Date; |
7
|
|
|
use PhpOffice\PhpSpreadsheet\Shared\StringHelper; |
8
|
|
|
|
9
|
|
|
class Functions |
10
|
|
|
{ |
11
|
|
|
const PRECISION = 8.88E-016; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* 2 / PI. |
15
|
|
|
*/ |
16
|
|
|
const M_2DIVPI = 0.63661977236758134307553505349006; |
17
|
|
|
|
18
|
|
|
const COMPATIBILITY_EXCEL = 'Excel'; |
19
|
|
|
const COMPATIBILITY_GNUMERIC = 'Gnumeric'; |
20
|
|
|
const COMPATIBILITY_OPENOFFICE = 'OpenOfficeCalc'; |
21
|
|
|
|
22
|
|
|
/** Use of RETURNDATE_PHP_NUMERIC is discouraged - not 32-bit Y2038-safe, no timezone. */ |
23
|
|
|
const RETURNDATE_PHP_NUMERIC = 'P'; |
24
|
|
|
/** Use of RETURNDATE_UNIX_TIMESTAMP is discouraged - not 32-bit Y2038-safe, no timezone. */ |
25
|
|
|
const RETURNDATE_UNIX_TIMESTAMP = 'P'; |
26
|
|
|
const RETURNDATE_PHP_OBJECT = 'O'; |
27
|
|
|
const RETURNDATE_PHP_DATETIME_OBJECT = 'O'; |
28
|
|
|
const RETURNDATE_EXCEL = 'E'; |
29
|
|
|
|
30
|
|
|
public const NOT_YET_IMPLEMENTED = '#Not Yet Implemented'; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* Compatibility mode to use for error checking and responses. |
34
|
|
|
*/ |
35
|
|
|
protected static string $compatibilityMode = self::COMPATIBILITY_EXCEL; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* Data Type to use when returning date values. |
39
|
|
|
*/ |
40
|
|
|
protected static string $returnDateType = self::RETURNDATE_EXCEL; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Set the Compatibility Mode. |
44
|
|
|
* |
45
|
|
|
* @param string $compatibilityMode Compatibility Mode |
46
|
|
|
* Permitted values are: |
47
|
|
|
* Functions::COMPATIBILITY_EXCEL 'Excel' |
48
|
|
|
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' |
49
|
|
|
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' |
50
|
|
|
* |
51
|
|
|
* @return bool (Success or Failure) |
52
|
|
|
*/ |
53
|
8861 |
|
public static function setCompatibilityMode(string $compatibilityMode): bool |
54
|
|
|
{ |
55
|
|
|
if ( |
56
|
8861 |
|
($compatibilityMode == self::COMPATIBILITY_EXCEL) |
57
|
8861 |
|
|| ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) |
58
|
8861 |
|
|| ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE) |
59
|
|
|
) { |
60
|
8861 |
|
self::$compatibilityMode = $compatibilityMode; |
61
|
|
|
|
62
|
8861 |
|
return true; |
63
|
|
|
} |
64
|
|
|
|
65
|
1 |
|
return false; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Return the current Compatibility Mode. |
70
|
|
|
* |
71
|
|
|
* @return string Compatibility Mode |
72
|
|
|
* Possible Return values are: |
73
|
|
|
* Functions::COMPATIBILITY_EXCEL 'Excel' |
74
|
|
|
* Functions::COMPATIBILITY_GNUMERIC 'Gnumeric' |
75
|
|
|
* Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc' |
76
|
|
|
*/ |
77
|
10115 |
|
public static function getCompatibilityMode(): string |
78
|
|
|
{ |
79
|
10115 |
|
return self::$compatibilityMode; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP DateTime Object). |
84
|
|
|
* |
85
|
|
|
* @param string $returnDateType Return Date Format |
86
|
|
|
* Permitted values are: |
87
|
|
|
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P' |
88
|
|
|
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' |
89
|
|
|
* Functions::RETURNDATE_EXCEL 'E' |
90
|
|
|
* |
91
|
|
|
* @return bool Success or failure |
92
|
|
|
*/ |
93
|
3261 |
|
public static function setReturnDateType(string $returnDateType): bool |
94
|
|
|
{ |
95
|
|
|
if ( |
96
|
3261 |
|
($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP) |
97
|
3261 |
|
|| ($returnDateType == self::RETURNDATE_PHP_DATETIME_OBJECT) |
98
|
3261 |
|
|| ($returnDateType == self::RETURNDATE_EXCEL) |
99
|
|
|
) { |
100
|
3261 |
|
self::$returnDateType = $returnDateType; |
101
|
|
|
|
102
|
3261 |
|
return true; |
103
|
|
|
} |
104
|
|
|
|
105
|
1 |
|
return false; |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
/** |
109
|
|
|
* Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object). |
110
|
|
|
* |
111
|
|
|
* @return string Return Date Format |
112
|
|
|
* Possible Return values are: |
113
|
|
|
* Functions::RETURNDATE_UNIX_TIMESTAMP 'P' |
114
|
|
|
* Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O' |
115
|
|
|
* Functions::RETURNDATE_EXCEL ' 'E' |
116
|
|
|
*/ |
117
|
3381 |
|
public static function getReturnDateType(): string |
118
|
|
|
{ |
119
|
3381 |
|
return self::$returnDateType; |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* DUMMY. |
124
|
|
|
* |
125
|
|
|
* @return string #Not Yet Implemented |
126
|
|
|
*/ |
127
|
15 |
|
public static function DUMMY(): string |
128
|
|
|
{ |
129
|
15 |
|
return self::NOT_YET_IMPLEMENTED; |
130
|
|
|
} |
131
|
|
|
|
132
|
5 |
|
public static function isMatrixValue(mixed $idx): bool |
133
|
|
|
{ |
134
|
5 |
|
$idx = StringHelper::convertToString($idx); |
135
|
|
|
|
136
|
5 |
|
return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0); |
137
|
|
|
} |
138
|
|
|
|
139
|
1 |
|
public static function isValue(mixed $idx): bool |
140
|
|
|
{ |
141
|
1 |
|
$idx = StringHelper::convertToString($idx); |
142
|
|
|
|
143
|
1 |
|
return substr_count($idx, '.') === 0; |
144
|
|
|
} |
145
|
|
|
|
146
|
102 |
|
public static function isCellValue(mixed $idx): bool |
147
|
|
|
{ |
148
|
102 |
|
$idx = StringHelper::convertToString($idx); |
149
|
|
|
|
150
|
102 |
|
return substr_count($idx, '.') > 1; |
151
|
|
|
} |
152
|
|
|
|
153
|
212 |
|
public static function ifCondition(mixed $condition): string |
154
|
|
|
{ |
155
|
212 |
|
$condition = self::flattenSingleValue($condition); |
156
|
|
|
|
157
|
212 |
|
if ($condition === '' || $condition === null) { |
158
|
3 |
|
return '=""'; |
159
|
|
|
} |
160
|
211 |
|
if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='], true)) { |
161
|
121 |
|
$condition = self::operandSpecialHandling($condition); |
162
|
121 |
|
if (is_bool($condition)) { |
163
|
3 |
|
return '=' . ($condition ? 'TRUE' : 'FALSE'); |
164
|
|
|
} |
165
|
118 |
|
if (!is_numeric($condition)) { |
166
|
98 |
|
if ($condition !== '""') { // Not an empty string |
167
|
|
|
// Escape any quotes in the string value |
168
|
97 |
|
$condition = (string) preg_replace('/"/ui', '""', $condition); |
169
|
|
|
} |
170
|
98 |
|
$condition = Calculation::wrapResult(strtoupper($condition)); |
171
|
|
|
} |
172
|
|
|
|
173
|
118 |
|
return str_replace('""""', '""', '=' . StringHelper::convertToString($condition)); |
174
|
|
|
} |
175
|
126 |
|
$operator = $operand = ''; |
176
|
126 |
|
if (1 === preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches)) { |
177
|
126 |
|
[, $operator, $operand] = $matches; |
178
|
|
|
} |
179
|
|
|
|
180
|
126 |
|
$operand = (string) self::operandSpecialHandling($operand); |
181
|
126 |
|
if (is_numeric(trim($operand, '"'))) { |
182
|
73 |
|
$operand = trim($operand, '"'); |
183
|
80 |
|
} elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') { |
184
|
76 |
|
$operand = str_replace('"', '""', $operand); |
185
|
76 |
|
$operand = Calculation::wrapResult(strtoupper($operand)); |
186
|
76 |
|
$operand = StringHelper::convertToString($operand); |
187
|
|
|
} |
188
|
|
|
|
189
|
126 |
|
return str_replace('""""', '""', $operator . $operand); |
190
|
|
|
} |
191
|
|
|
|
192
|
211 |
|
private static function operandSpecialHandling(mixed $operand): bool|float|int|string |
193
|
|
|
{ |
194
|
211 |
|
if (is_numeric($operand) || is_bool($operand)) { |
195
|
111 |
|
return $operand; |
196
|
|
|
} |
197
|
174 |
|
$operand = StringHelper::convertToString($operand); |
198
|
174 |
|
if (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) { |
199
|
4 |
|
return strtoupper($operand); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
// Check for percentage |
203
|
170 |
|
if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $operand)) { |
204
|
4 |
|
return ((float) rtrim($operand, '%')) / 100; |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
// Check for dates |
208
|
170 |
|
if (($dateValueOperand = Date::stringToExcel($operand)) !== false) { |
209
|
4 |
|
return $dateValueOperand; |
210
|
|
|
} |
211
|
|
|
|
212
|
170 |
|
return $operand; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Convert a multi-dimensional array to a simple 1-dimensional array. |
217
|
|
|
* |
218
|
|
|
* @param mixed $array Array to be flattened |
219
|
|
|
* |
220
|
|
|
* @return array<mixed> Flattened array |
221
|
|
|
*/ |
222
|
6060 |
|
public static function flattenArray(mixed $array): array |
223
|
|
|
{ |
224
|
6060 |
|
if (!is_array($array)) { |
225
|
24 |
|
return (array) $array; |
226
|
|
|
} |
227
|
|
|
|
228
|
6050 |
|
$flattened = []; |
229
|
6050 |
|
$stack = array_values($array); |
230
|
|
|
|
231
|
6050 |
|
while (!empty($stack)) { |
232
|
5945 |
|
$value = array_shift($stack); |
233
|
|
|
|
234
|
5945 |
|
if (is_array($value)) { |
235
|
2545 |
|
array_unshift($stack, ...array_values($value)); |
236
|
|
|
} else { |
237
|
5942 |
|
$flattened[] = $value; |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
|
241
|
6050 |
|
return $flattened; |
242
|
|
|
} |
243
|
|
|
|
244
|
|
|
/** |
245
|
|
|
* Convert a multi-dimensional array to a simple 1-dimensional array. |
246
|
|
|
* Same as above but argument is specified in ... format. |
247
|
|
|
* |
248
|
|
|
* @param mixed $array Array to be flattened |
249
|
|
|
* |
250
|
|
|
* @return array<mixed> Flattened array |
251
|
|
|
*/ |
252
|
33 |
|
public static function flattenArray2(mixed ...$array): array |
253
|
|
|
{ |
254
|
33 |
|
$flattened = []; |
255
|
33 |
|
$stack = array_values($array); |
256
|
|
|
|
257
|
33 |
|
while (!empty($stack)) { |
258
|
33 |
|
$value = array_shift($stack); |
259
|
|
|
|
260
|
33 |
|
if (is_array($value)) { |
261
|
10 |
|
array_unshift($stack, ...array_values($value)); |
262
|
|
|
} else { |
263
|
33 |
|
$flattened[] = $value; |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
|
267
|
33 |
|
return $flattened; |
268
|
|
|
} |
269
|
|
|
|
270
|
724 |
|
public static function scalar(mixed $value): mixed |
271
|
|
|
{ |
272
|
724 |
|
if (!is_array($value)) { |
273
|
723 |
|
return $value; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
do { |
277
|
1 |
|
$value = array_pop($value); |
278
|
1 |
|
} while (is_array($value)); |
279
|
|
|
|
280
|
1 |
|
return $value; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing. |
285
|
|
|
* |
286
|
|
|
* @param array|mixed $array Array to be flattened |
287
|
|
|
* |
288
|
|
|
* @return array<mixed> Flattened array |
289
|
|
|
*/ |
290
|
763 |
|
public static function flattenArrayIndexed($array): array |
291
|
|
|
{ |
292
|
763 |
|
if (!is_array($array)) { |
293
|
12 |
|
return (array) $array; |
294
|
|
|
} |
295
|
|
|
|
296
|
752 |
|
$arrayValues = []; |
297
|
752 |
|
foreach ($array as $k1 => $value) { |
298
|
752 |
|
if (is_array($value)) { |
299
|
554 |
|
foreach ($value as $k2 => $val) { |
300
|
550 |
|
if (is_array($val)) { |
301
|
502 |
|
foreach ($val as $k3 => $v) { |
302
|
495 |
|
$arrayValues[$k1 . '.' . $k2 . '.' . $k3] = $v; |
303
|
|
|
} |
304
|
|
|
} else { |
305
|
65 |
|
$arrayValues[$k1 . '.' . $k2] = $val; |
306
|
|
|
} |
307
|
|
|
} |
308
|
|
|
} else { |
309
|
314 |
|
$arrayValues[$k1] = $value; |
310
|
|
|
} |
311
|
|
|
} |
312
|
|
|
|
313
|
752 |
|
return $arrayValues; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Convert an array to a single scalar value by extracting the first element. |
318
|
|
|
* |
319
|
|
|
* @param mixed $value Array or scalar value |
320
|
|
|
*/ |
321
|
12563 |
|
public static function flattenSingleValue(mixed $value): mixed |
322
|
|
|
{ |
323
|
12563 |
|
while (is_array($value)) { |
324
|
7375 |
|
$value = array_shift($value); |
325
|
|
|
} |
326
|
|
|
|
327
|
12563 |
|
return $value; |
328
|
|
|
} |
329
|
|
|
|
330
|
12 |
|
public static function expandDefinedName(string $coordinate, Cell $cell): string |
331
|
|
|
{ |
332
|
12 |
|
$worksheet = $cell->getWorksheet(); |
333
|
12 |
|
$spreadsheet = $worksheet->getParentOrThrow(); |
334
|
|
|
// Uppercase coordinate |
335
|
12 |
|
$pCoordinatex = strtoupper($coordinate); |
336
|
|
|
// Eliminate leading equal sign |
337
|
12 |
|
$pCoordinatex = (string) preg_replace('/^=/', '', $pCoordinatex); |
338
|
12 |
|
$defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet); |
339
|
12 |
|
if ($defined !== null) { |
340
|
1 |
|
$worksheet2 = $defined->getWorkSheet(); |
341
|
1 |
|
if (!$defined->isFormula() && $worksheet2 !== null) { |
342
|
1 |
|
$coordinate = "'" . $worksheet2->getTitle() . "'!" |
343
|
1 |
|
. (string) preg_replace('/^=/', '', str_replace('$', '', $defined->getValue())); |
344
|
|
|
} |
345
|
|
|
} |
346
|
|
|
|
347
|
12 |
|
return $coordinate; |
348
|
|
|
} |
349
|
|
|
|
350
|
18 |
|
public static function trimTrailingRange(string $coordinate): string |
351
|
|
|
{ |
352
|
18 |
|
return (string) preg_replace('/:[\w\$]+$/', '', $coordinate); |
353
|
|
|
} |
354
|
|
|
|
355
|
10253 |
|
public static function trimSheetFromCellReference(string $coordinate): string |
356
|
|
|
{ |
357
|
10253 |
|
if (str_contains($coordinate, '!')) { |
358
|
12 |
|
$coordinate = substr($coordinate, strrpos($coordinate, '!') + 1); |
359
|
|
|
} |
360
|
|
|
|
361
|
10253 |
|
return $coordinate; |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
|