PluginProbe
TablePress – Tables in WordPress made easy / 3.4
TablePress – Tables in WordPress made easy v3.4
3.4 3.3.4 3.3.3 3.3.2 3.3.1 trunk 1.12 1.14 1.9.2 2.0.4 2.1.7 2.1.8 2.2 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3 2.3.1 2.3.2 2.4 2.4.1 2.4.2 2.4.3 All 45 releases
tablepress / libraries / vendor / PhpSpreadsheet / Calculation / CalculationLocale.php

CalculationLocale.php in TablePress – Tables in WordPress made easy 3.4, at libraries/vendor/PhpSpreadsheet/Calculation/CalculationLocale.php

430 lines 14.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TablePress\PhpOffice\PhpSpreadsheet\Calculation;
4
5 class CalculationLocale extends CalculationBase
6 {
7 public const FORMULA_OPEN_FUNCTION_BRACE = '(';
8 public const FORMULA_CLOSE_FUNCTION_BRACE = ')';
9 public const FORMULA_OPEN_MATRIX_BRACE = '{';
10 public const FORMULA_CLOSE_MATRIX_BRACE = '}';
11 public const FORMULA_STRING_QUOTE = '"';
12
13 // Strip xlfn and xlws prefixes from function name
14 public const CALCULATION_REGEXP_STRIP_XLFN_XLWS = '/(_xlfn[.])?(_xlws[.])?(?=[\p{L}][\p{L}\p{N}\.]*[\s]*[(])/';
15
16 /**
17 * The current locale setting.
18 */
19 protected static string $localeLanguage = 'en_us'; // US English (default locale)
20
21 /**
22 * List of available locale settings
23 * Note that this is read for the locale subdirectory only when requested.
24 *
25 * @var string[]
26 */
27 protected static array $validLocaleLanguages = [
28 'en', // English (default language)
29 ];
30
31 /**
32 * Locale-specific argument separator for function arguments.
33 */
34 protected static string $localeArgumentSeparator = ',';
35
36 /** @var string[] */
37 protected static array $localeFunctions = [];
38
39 /**
40 * Locale-specific translations for Excel constants (True, False and Null).
41 *
42 * @var array<string, string>
43 */
44 protected static array $localeBoolean = [
45 'TRUE' => 'TRUE',
46 'FALSE' => 'FALSE',
47 'NULL' => 'NULL',
48 ];
49
50 /** @var array<int, array<int, string>> */
51 protected static array $falseTrueArray = [];
52
53 public static function getLocaleBoolean(string $index): string
54 {
55 return self::$localeBoolean[$index];
56 }
57
58 protected static function loadLocales(): void
59 {
60 $localeFileDirectory = __DIR__ . '/locale/';
61 $localeFileNames = glob($localeFileDirectory . '*', GLOB_ONLYDIR) ?: [];
62 foreach ($localeFileNames as $filename) {
63 $filename = (string) substr($filename, strlen($localeFileDirectory));
64 if ($filename != 'en') {
65 self::$validLocaleLanguages[] = $filename;
66 $subdirs = glob("$localeFileDirectory$filename/*", GLOB_ONLYDIR) ?: [];
67 foreach ($subdirs as $subdir) {
68 $subdirx = basename($subdir);
69 self::$validLocaleLanguages[] = "{$filename}_{$subdirx}";
70 }
71 }
72 }
73 }
74
75 /**
76 * Return the locale-specific translation of TRUE.
77 *
78 * @return string locale-specific translation of TRUE
79 */
80 public static function getTRUE(): string
81 {
82 return self::$localeBoolean['TRUE'];
83 }
84
85 /**
86 * Return the locale-specific translation of FALSE.
87 *
88 * @return string locale-specific translation of FALSE
89 */
90 public static function getFALSE(): string
91 {
92 return self::$localeBoolean['FALSE'];
93 }
94
95 /**
96 * Get the currently defined locale code.
97 */
98 public function getLocale(): string
99 {
100 return self::$localeLanguage;
101 }
102
103 protected function getLocaleFile(string $localeDir, string $locale, string $language, string $file): string
104 {
105 $localeFileName = $localeDir . str_replace('_', DIRECTORY_SEPARATOR, $locale)
106 . DIRECTORY_SEPARATOR . $file;
107 if (!file_exists($localeFileName)) {
108 // If there isn't a locale specific file, look for a language specific file
109 $localeFileName = $localeDir . $language . DIRECTORY_SEPARATOR . $file;
110 if (!file_exists($localeFileName)) {
111 throw new Exception('Locale file not found');
112 }
113 }
114
115 return $localeFileName;
116 }
117
118 /** @return array<int, array<int, string>> */
119 public function getFalseTrueArray(): array
120 {
121 if (!empty(self::$falseTrueArray)) {
122 return self::$falseTrueArray;
123 }
124 if (count(self::$validLocaleLanguages) == 1) {
125 self::loadLocales();
126 }
127 $falseTrueArray = [['FALSE'], ['TRUE']];
128 foreach (self::$validLocaleLanguages as $language) {
129 if (str_starts_with($language, 'en')) {
130 continue;
131 }
132 $locale = $language;
133 if (str_contains($locale, '_')) {
134 [$language] = explode('_', $locale);
135 }
136 $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]);
137
138 try {
139 $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions');
140 } catch (Exception $e) {
141 continue;
142 }
143 // Retrieve the list of locale or language specific function names
144 $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
145 foreach ($localeFunctions as $localeFunction) {
146 [$localeFunction] = explode('##', $localeFunction); // Strip out comments
147 if (str_contains($localeFunction, '=')) {
148 [$fName, $lfName] = array_map('trim', explode('=', $localeFunction));
149 if ($fName === 'FALSE') {
150 $falseTrueArray[0][] = $lfName;
151 } elseif ($fName === 'TRUE') {
152 $falseTrueArray[1][] = $lfName;
153 }
154 }
155 }
156 }
157 self::$falseTrueArray = $falseTrueArray;
158
159 return $falseTrueArray;
160 }
161
162 /**
163 * Set the locale code.
164 *
165 * @param string $locale The locale to use for formula translation, eg: 'en_us'
166 */
167 public function setLocale(string $locale): bool
168 {
169 // Identify our locale and language
170 $language = $locale = strtolower($locale);
171 if (str_contains($locale, '_')) {
172 [$language] = explode('_', $locale);
173 }
174 if (count(self::$validLocaleLanguages) == 1) {
175 self::loadLocales();
176 }
177
178 // Test whether we have any language data for this language (any locale)
179 if (in_array($language, self::$validLocaleLanguages, true)) {
180 // initialise language/locale settings
181 self::$localeFunctions = [];
182 self::$localeArgumentSeparator = ',';
183 self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL'];
184
185 // Default is US English, if user isn't requesting US english, then read the necessary data from the locale files
186 if ($locale !== 'en_us') {
187 $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]);
188
189 // Search for a file with a list of function names for locale
190 try {
191 $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions');
192 } catch (Exception $e) {
193 return false;
194 }
195
196 // Retrieve the list of locale or language specific function names
197 $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
198 $phpSpreadsheetFunctions = &self::getFunctionsAddress();
199 foreach ($localeFunctions as $localeFunction) {
200 [$localeFunction] = explode('##', $localeFunction); // Strip out comments
201 if (str_contains($localeFunction, '=')) {
202 [$fName, $lfName] = array_map('trim', explode('=', $localeFunction));
203 if ((str_starts_with($fName, '*') || isset($phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
204 self::$localeFunctions[$fName] = $lfName;
205 }
206 }
207 }
208 // Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
209 if (isset(self::$localeFunctions['TRUE'])) {
210 self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE'];
211 }
212 if (isset(self::$localeFunctions['FALSE'])) {
213 self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE'];
214 }
215
216 try {
217 $configFile = $this->getLocaleFile($localeDir, $locale, $language, 'config');
218 } catch (Exception $exception) {
219 return false;
220 }
221
222 $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
223 foreach ($localeSettings as $localeSetting) {
224 [$localeSetting] = explode('##', $localeSetting); // Strip out comments
225 if (str_contains($localeSetting, '=')) {
226 [$settingName, $settingValue] = array_map('trim', explode('=', $localeSetting));
227 $settingName = strtoupper($settingName);
228 if ($settingValue !== '') {
229 switch ($settingName) {
230 case 'ARGUMENTSEPARATOR':
231 self::$localeArgumentSeparator = $settingValue;
232
233 break;
234 }
235 }
236 }
237 }
238 }
239
240 self::$functionReplaceFromExcel = self::$functionReplaceToExcel
241 = self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
242 self::$localeLanguage = $locale;
243
244 return true;
245 }
246
247 return false;
248 }
249
250 public static function translateSeparator(
251 string $fromSeparator,
252 string $toSeparator,
253 string $formula,
254 int &$inBracesLevel,
255 string $openBrace = self::FORMULA_OPEN_FUNCTION_BRACE,
256 string $closeBrace = self::FORMULA_CLOSE_FUNCTION_BRACE
257 ): string {
258 $strlen = mb_strlen($formula);
259 for ($i = 0; $i < $strlen; ++$i) {
260 $chr = mb_substr($formula, $i, 1);
261 switch ($chr) {
262 case $openBrace:
263 ++$inBracesLevel;
264
265 break;
266 case $closeBrace:
267 --$inBracesLevel;
268
269 break;
270 case $fromSeparator:
271 if ($inBracesLevel > 0) {
272 $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
273 }
274 }
275 }
276
277 return $formula;
278 }
279
280 /**
281 * @param string[] $from
282 * @param string[] $to
283 */
284 protected static function translateFormulaBlock(
285 array $from,
286 array $to,
287 string $formula,
288 int &$inFunctionBracesLevel,
289 int &$inMatrixBracesLevel,
290 string $fromSeparator,
291 string $toSeparator
292 ): string {
293 // Function Names
294 $formula = (string) preg_replace($from, $to, $formula);
295
296 // Temporarily adjust matrix separators so that they won't be confused with function arguments
297 $formula = self::translateSeparator(';', '|', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
298 $formula = self::translateSeparator(',', '!', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
299 // Function Argument Separators
300 $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inFunctionBracesLevel);
301 // Restore matrix separators
302 $formula = self::translateSeparator('|', ';', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
303 $formula = self::translateSeparator('!', ',', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
304
305 return $formula;
306 }
307
308 /**
309 * @param string[] $from
310 * @param string[] $to
311 */
312 protected static function translateFormula(array $from, array $to, string $formula, string $fromSeparator, string $toSeparator): string
313 {
314 // Convert any Excel function names and constant names to the required language;
315 // and adjust function argument separators
316 if (self::$localeLanguage !== 'en_us') {
317 $inFunctionBracesLevel = 0;
318 $inMatrixBracesLevel = 0;
319 // If there is the possibility of separators within a quoted string, then we treat them as literals
320 if (str_contains($formula, self::FORMULA_STRING_QUOTE)) {
321 // So instead we skip replacing in any quoted strings by only replacing in every other array element
322 // after we've exploded the formula
323 $temp = explode(self::FORMULA_STRING_QUOTE, $formula);
324 $notWithinQuotes = false;
325 foreach ($temp as &$value) {
326 // Only adjust in alternating array entries
327 $notWithinQuotes = $notWithinQuotes === false;
328 if ($notWithinQuotes === true) {
329 $value = self::translateFormulaBlock($from, $to, $value, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
330 }
331 }
332 unset($value);
333 // Then rebuild the formula string
334 $formula = implode(self::FORMULA_STRING_QUOTE, $temp);
335 } else {
336 // If there's no quoted strings, then we do a simple count/replace
337 $formula = self::translateFormulaBlock($from, $to, $formula, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
338 }
339 }
340
341 return $formula;
342 }
343
344 /** @var null|string[] */
345 private static ?array $functionReplaceFromExcel;
346
347 /** @var null|string[] */
348 private static ?array $functionReplaceToLocale;
349
350 public function translateFormulaToLocale(string $formula): string
351 {
352 $formula = preg_replace(self::CALCULATION_REGEXP_STRIP_XLFN_XLWS, '', $formula) ?? '';
353 // Build list of function names and constants for translation
354 if (self::$functionReplaceFromExcel === null) {
355 self::$functionReplaceFromExcel = [];
356 foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
357 self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/ui';
358 }
359 foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
360 self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/ui';
361 }
362 }
363
364 if (self::$functionReplaceToLocale === null) {
365 self::$functionReplaceToLocale = [];
366 foreach (self::$localeFunctions as $localeFunctionName) {
367 self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
368 }
369 foreach (self::$localeBoolean as $localeBoolean) {
370 self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
371 }
372 }
373
374 return self::translateFormula(
375 self::$functionReplaceFromExcel,
376 self::$functionReplaceToLocale,
377 $formula,
378 ',',
379 self::$localeArgumentSeparator
380 );
381 }
382
383 /** @var null|string[] */
384 protected static ?array $functionReplaceFromLocale;
385
386 /** @var null|string[] */
387 protected static ?array $functionReplaceToExcel;
388
389 public function translateFormulaToEnglish(string $formula): string
390 {
391 if (self::$functionReplaceFromLocale === null) {
392 self::$functionReplaceFromLocale = [];
393 foreach (self::$localeFunctions as $localeFunctionName) {
394 self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/ui';
395 }
396 foreach (self::$localeBoolean as $excelBoolean) {
397 self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/ui';
398 }
399 }
400
401 if (self::$functionReplaceToExcel === null) {
402 self::$functionReplaceToExcel = [];
403 foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
404 self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2';
405 }
406 foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
407 self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2';
408 }
409 }
410
411 return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ',');
412 }
413
414 public static function localeFunc(string $function): string
415 {
416 if (self::$localeLanguage !== 'en_us') {
417 $functionName = trim($function, '(');
418 if (isset(self::$localeFunctions[$functionName])) {
419 $brace = ($functionName != $function);
420 $function = self::$localeFunctions[$functionName];
421 if ($brace) {
422 $function .= '(';
423 }
424 }
425 }
426
427 return $function;
428 }
429 }
430