PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.4.8
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.4.8
4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 3.10.4 All 148 releases
visualizer / vendor / phpoffice / phpspreadsheet / src / PhpSpreadsheet / Style / NumberFormat.php

NumberFormat.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.4.8, at vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php

776 lines 27.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace PhpOffice\PhpSpreadsheet\Style;
4
5 use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
6 use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
7 use PhpOffice\PhpSpreadsheet\Shared\Date;
8 use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
9
10 class NumberFormat extends Supervisor
11 {
12 // Pre-defined formats
13 const FORMAT_GENERAL = 'General';
14
15 const FORMAT_TEXT = '@';
16
17 const FORMAT_NUMBER = '0';
18 const FORMAT_NUMBER_00 = '0.00';
19 const FORMAT_NUMBER_COMMA_SEPARATED1 = '#,##0.00';
20 const FORMAT_NUMBER_COMMA_SEPARATED2 = '#,##0.00_-';
21
22 const FORMAT_PERCENTAGE = '0%';
23 const FORMAT_PERCENTAGE_00 = '0.00%';
24
25 const FORMAT_DATE_YYYYMMDD2 = 'yyyy-mm-dd';
26 const FORMAT_DATE_YYYYMMDD = 'yy-mm-dd';
27 const FORMAT_DATE_DDMMYYYY = 'dd/mm/yy';
28 const FORMAT_DATE_DMYSLASH = 'd/m/yy';
29 const FORMAT_DATE_DMYMINUS = 'd-m-yy';
30 const FORMAT_DATE_DMMINUS = 'd-m';
31 const FORMAT_DATE_MYMINUS = 'm-yy';
32 const FORMAT_DATE_XLSX14 = 'mm-dd-yy';
33 const FORMAT_DATE_XLSX15 = 'd-mmm-yy';
34 const FORMAT_DATE_XLSX16 = 'd-mmm';
35 const FORMAT_DATE_XLSX17 = 'mmm-yy';
36 const FORMAT_DATE_XLSX22 = 'm/d/yy h:mm';
37 const FORMAT_DATE_DATETIME = 'd/m/yy h:mm';
38 const FORMAT_DATE_TIME1 = 'h:mm AM/PM';
39 const FORMAT_DATE_TIME2 = 'h:mm:ss AM/PM';
40 const FORMAT_DATE_TIME3 = 'h:mm';
41 const FORMAT_DATE_TIME4 = 'h:mm:ss';
42 const FORMAT_DATE_TIME5 = 'mm:ss';
43 const FORMAT_DATE_TIME6 = 'h:mm:ss';
44 const FORMAT_DATE_TIME7 = 'i:s.S';
45 const FORMAT_DATE_TIME8 = 'h:mm:ss;@';
46 const FORMAT_DATE_YYYYMMDDSLASH = 'yy/mm/dd;@';
47
48 const FORMAT_CURRENCY_USD_SIMPLE = '"$"#,##0.00_-';
49 const FORMAT_CURRENCY_USD = '$#,##0_-';
50 const FORMAT_CURRENCY_EUR_SIMPLE = '#,##0.00_-"€"';
51 const FORMAT_CURRENCY_EUR = '#,##0_-"€"';
52 const FORMAT_ACCOUNTING_USD = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
53 const FORMAT_ACCOUNTING_EUR = '_("€"* #,##0.00_);_("€"* \(#,##0.00\);_("€"* "-"??_);_(@_)';
54
55 /**
56 * Excel built-in number formats.
57 *
58 * @var array
59 */
60 protected static $builtInFormats;
61
62 /**
63 * Excel built-in number formats (flipped, for faster lookups).
64 *
65 * @var array
66 */
67 protected static $flippedBuiltInFormats;
68
69 /**
70 * Format Code.
71 *
72 * @var string
73 */
74 protected $formatCode = self::FORMAT_GENERAL;
75
76 /**
77 * Built-in format Code.
78 *
79 * @var string
80 */
81 protected $builtInFormatCode = 0;
82
83 /**
84 * Create a new NumberFormat.
85 *
86 * @param bool $isSupervisor Flag indicating if this is a supervisor or not
87 * Leave this value at default unless you understand exactly what
88 * its ramifications are
89 * @param bool $isConditional Flag indicating if this is a conditional style or not
90 * Leave this value at default unless you understand exactly what
91 * its ramifications are
92 */
93 public function __construct($isSupervisor = false, $isConditional = false)
94 {
95 // Supervisor?
96 parent::__construct($isSupervisor);
97
98 if ($isConditional) {
99 $this->formatCode = null;
100 $this->builtInFormatCode = false;
101 }
102 }
103
104 /**
105 * Get the shared style component for the currently active cell in currently active sheet.
106 * Only used for style supervisor.
107 *
108 * @return NumberFormat
109 */
110 public function getSharedComponent()
111 {
112 return $this->parent->getSharedComponent()->getNumberFormat();
113 }
114
115 /**
116 * Build style array from subcomponents.
117 *
118 * @param array $array
119 *
120 * @return array
121 */
122 public function getStyleArray($array)
123 {
124 return ['numberFormat' => $array];
125 }
126
127 /**
128 * Apply styles from array.
129 *
130 * <code>
131 * $spreadsheet->getActiveSheet()->getStyle('B2')->getNumberFormat()->applyFromArray(
132 * [
133 * 'formatCode' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE
134 * ]
135 * );
136 * </code>
137 *
138 * @param array $pStyles Array containing style information
139 *
140 * @throws PhpSpreadsheetException
141 *
142 * @return NumberFormat
143 */
144 public function applyFromArray(array $pStyles)
145 {
146 if ($this->isSupervisor) {
147 $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
148 } else {
149 if (isset($pStyles['formatCode'])) {
150 $this->setFormatCode($pStyles['formatCode']);
151 }
152 }
153
154 return $this;
155 }
156
157 /**
158 * Get Format Code.
159 *
160 * @return string
161 */
162 public function getFormatCode()
163 {
164 if ($this->isSupervisor) {
165 return $this->getSharedComponent()->getFormatCode();
166 }
167 if ($this->builtInFormatCode !== false) {
168 return self::builtInFormatCode($this->builtInFormatCode);
169 }
170
171 return $this->formatCode;
172 }
173
174 /**
175 * Set Format Code.
176 *
177 * @param string $pValue see self::FORMAT_*
178 *
179 * @return NumberFormat
180 */
181 public function setFormatCode($pValue)
182 {
183 if ($pValue == '') {
184 $pValue = self::FORMAT_GENERAL;
185 }
186 if ($this->isSupervisor) {
187 $styleArray = $this->getStyleArray(['formatCode' => $pValue]);
188 $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
189 } else {
190 $this->formatCode = $pValue;
191 $this->builtInFormatCode = self::builtInFormatCodeIndex($pValue);
192 }
193
194 return $this;
195 }
196
197 /**
198 * Get Built-In Format Code.
199 *
200 * @return int
201 */
202 public function getBuiltInFormatCode()
203 {
204 if ($this->isSupervisor) {
205 return $this->getSharedComponent()->getBuiltInFormatCode();
206 }
207
208 return $this->builtInFormatCode;
209 }
210
211 /**
212 * Set Built-In Format Code.
213 *
214 * @param int $pValue
215 *
216 * @return NumberFormat
217 */
218 public function setBuiltInFormatCode($pValue)
219 {
220 if ($this->isSupervisor) {
221 $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($pValue)]);
222 $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
223 } else {
224 $this->builtInFormatCode = $pValue;
225 $this->formatCode = self::builtInFormatCode($pValue);
226 }
227
228 return $this;
229 }
230
231 /**
232 * Fill built-in format codes.
233 */
234 private static function fillBuiltInFormatCodes()
235 {
236 // [MS-OI29500: Microsoft Office Implementation Information for ISO/IEC-29500 Standard Compliance]
237 // 18.8.30. numFmt (Number Format)
238 //
239 // The ECMA standard defines built-in format IDs
240 // 14: "mm-dd-yy"
241 // 22: "m/d/yy h:mm"
242 // 37: "#,##0 ;(#,##0)"
243 // 38: "#,##0 ;[Red](#,##0)"
244 // 39: "#,##0.00;(#,##0.00)"
245 // 40: "#,##0.00;[Red](#,##0.00)"
246 // 47: "mmss.0"
247 // KOR fmt 55: "yyyy-mm-dd"
248 // Excel defines built-in format IDs
249 // 14: "m/d/yyyy"
250 // 22: "m/d/yyyy h:mm"
251 // 37: "#,##0_);(#,##0)"
252 // 38: "#,##0_);[Red](#,##0)"
253 // 39: "#,##0.00_);(#,##0.00)"
254 // 40: "#,##0.00_);[Red](#,##0.00)"
255 // 47: "mm:ss.0"
256 // KOR fmt 55: "yyyy/mm/dd"
257
258 // Built-in format codes
259 if (self::$builtInFormats === null) {
260 self::$builtInFormats = [];
261
262 // General
263 self::$builtInFormats[0] = self::FORMAT_GENERAL;
264 self::$builtInFormats[1] = '0';
265 self::$builtInFormats[2] = '0.00';
266 self::$builtInFormats[3] = '#,##0';
267 self::$builtInFormats[4] = '#,##0.00';
268
269 self::$builtInFormats[9] = '0%';
270 self::$builtInFormats[10] = '0.00%';
271 self::$builtInFormats[11] = '0.00E+00';
272 self::$builtInFormats[12] = '# ?/?';
273 self::$builtInFormats[13] = '# ??/??';
274 self::$builtInFormats[14] = 'm/d/yyyy'; // Despite ECMA 'mm-dd-yy';
275 self::$builtInFormats[15] = 'd-mmm-yy';
276 self::$builtInFormats[16] = 'd-mmm';
277 self::$builtInFormats[17] = 'mmm-yy';
278 self::$builtInFormats[18] = 'h:mm AM/PM';
279 self::$builtInFormats[19] = 'h:mm:ss AM/PM';
280 self::$builtInFormats[20] = 'h:mm';
281 self::$builtInFormats[21] = 'h:mm:ss';
282 self::$builtInFormats[22] = 'm/d/yyyy h:mm'; // Despite ECMA 'm/d/yy h:mm';
283
284 self::$builtInFormats[37] = '#,##0_);(#,##0)'; // Despite ECMA '#,##0 ;(#,##0)';
285 self::$builtInFormats[38] = '#,##0_);[Red](#,##0)'; // Despite ECMA '#,##0 ;[Red](#,##0)';
286 self::$builtInFormats[39] = '#,##0.00_);(#,##0.00)'; // Despite ECMA '#,##0.00;(#,##0.00)';
287 self::$builtInFormats[40] = '#,##0.00_);[Red](#,##0.00)'; // Despite ECMA '#,##0.00;[Red](#,##0.00)';
288
289 self::$builtInFormats[44] = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
290 self::$builtInFormats[45] = 'mm:ss';
291 self::$builtInFormats[46] = '[h]:mm:ss';
292 self::$builtInFormats[47] = 'mm:ss.0'; // Despite ECMA 'mmss.0';
293 self::$builtInFormats[48] = '##0.0E+0';
294 self::$builtInFormats[49] = '@';
295
296 // CHT
297 self::$builtInFormats[27] = '[$-404]e/m/d';
298 self::$builtInFormats[30] = 'm/d/yy';
299 self::$builtInFormats[36] = '[$-404]e/m/d';
300 self::$builtInFormats[50] = '[$-404]e/m/d';
301 self::$builtInFormats[57] = '[$-404]e/m/d';
302
303 // THA
304 self::$builtInFormats[59] = 't0';
305 self::$builtInFormats[60] = 't0.00';
306 self::$builtInFormats[61] = 't#,##0';
307 self::$builtInFormats[62] = 't#,##0.00';
308 self::$builtInFormats[67] = 't0%';
309 self::$builtInFormats[68] = 't0.00%';
310 self::$builtInFormats[69] = 't# ?/?';
311 self::$builtInFormats[70] = 't# ??/??';
312
313 // JPN
314 self::$builtInFormats[28] = '[$-411]ggge"年"m"月"d"日"';
315 self::$builtInFormats[29] = '[$-411]ggge"年"m"月"d"日"';
316 self::$builtInFormats[31] = 'yyyy"年"m"月"d"日"';
317 self::$builtInFormats[32] = 'h"時"mm"分"';
318 self::$builtInFormats[33] = 'h"時"mm"分"ss"秒"';
319 self::$builtInFormats[34] = 'yyyy"年"m"月"';
320 self::$builtInFormats[35] = 'm"月"d"日"';
321 self::$builtInFormats[51] = '[$-411]ggge"年"m"月"d"日"';
322 self::$builtInFormats[52] = 'yyyy"年"m"月"';
323 self::$builtInFormats[53] = 'm"月"d"日"';
324 self::$builtInFormats[54] = '[$-411]ggge"年"m"月"d"日"';
325 self::$builtInFormats[55] = 'yyyy"年"m"月"';
326 self::$builtInFormats[56] = 'm"月"d"日"';
327 self::$builtInFormats[58] = '[$-411]ggge"年"m"月"d"日"';
328
329 // Flip array (for faster lookups)
330 self::$flippedBuiltInFormats = array_flip(self::$builtInFormats);
331 }
332 }
333
334 /**
335 * Get built-in format code.
336 *
337 * @param int $pIndex
338 *
339 * @return string
340 */
341 public static function builtInFormatCode($pIndex)
342 {
343 // Clean parameter
344 $pIndex = (int) $pIndex;
345
346 // Ensure built-in format codes are available
347 self::fillBuiltInFormatCodes();
348
349 // Lookup format code
350 if (isset(self::$builtInFormats[$pIndex])) {
351 return self::$builtInFormats[$pIndex];
352 }
353
354 return '';
355 }
356
357 /**
358 * Get built-in format code index.
359 *
360 * @param string $formatCode
361 *
362 * @return bool|int
363 */
364 public static function builtInFormatCodeIndex($formatCode)
365 {
366 // Ensure built-in format codes are available
367 self::fillBuiltInFormatCodes();
368
369 // Lookup format code
370 if (isset(self::$flippedBuiltInFormats[$formatCode])) {
371 return self::$flippedBuiltInFormats[$formatCode];
372 }
373
374 return false;
375 }
376
377 /**
378 * Get hash code.
379 *
380 * @return string Hash code
381 */
382 public function getHashCode()
383 {
384 if ($this->isSupervisor) {
385 return $this->getSharedComponent()->getHashCode();
386 }
387
388 return md5(
389 $this->formatCode .
390 $this->builtInFormatCode .
391 __CLASS__
392 );
393 }
394
395 /**
396 * Search/replace values to convert Excel date/time format masks to PHP format masks.
397 *
398 * @var array
399 */
400 private static $dateFormatReplacements = [
401 // first remove escapes related to non-format characters
402 '\\' => '',
403 // 12-hour suffix
404 'am/pm' => 'A',
405 // 4-digit year
406 'e' => 'Y',
407 'yyyy' => 'Y',
408 // 2-digit year
409 'yy' => 'y',
410 // first letter of month - no php equivalent
411 'mmmmm' => 'M',
412 // full month name
413 'mmmm' => 'F',
414 // short month name
415 'mmm' => 'M',
416 // mm is minutes if time, but can also be month w/leading zero
417 // so we try to identify times be the inclusion of a : separator in the mask
418 // It isn't perfect, but the best way I know how
419 ':mm' => ':i',
420 'mm:' => 'i:',
421 // month leading zero
422 'mm' => 'm',
423 // month no leading zero
424 'm' => 'n',
425 // full day of week name
426 'dddd' => 'l',
427 // short day of week name
428 'ddd' => 'D',
429 // days leading zero
430 'dd' => 'd',
431 // days no leading zero
432 'd' => 'j',
433 // seconds
434 'ss' => 's',
435 // fractional seconds - no php equivalent
436 '.s' => '',
437 ];
438
439 /**
440 * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock).
441 *
442 * @var array
443 */
444 private static $dateFormatReplacements24 = [
445 'hh' => 'H',
446 'h' => 'G',
447 ];
448
449 /**
450 * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
451 *
452 * @var array
453 */
454 private static $dateFormatReplacements12 = [
455 'hh' => 'h',
456 'h' => 'g',
457 ];
458
459 private static function setLowercaseCallback($matches)
460 {
461 return mb_strtolower($matches[0]);
462 }
463
464 private static function escapeQuotesCallback($matches)
465 {
466 return '\\' . implode('\\', str_split($matches[1]));
467 }
468
469 private static function formatAsDate(&$value, &$format)
470 {
471 // strip off first part containing e.g. [$-F800] or [$USD-409]
472 // general syntax: [$<Currency string>-<language info>]
473 // language info is in hexadecimal
474 // strip off chinese part like [DBNum1][$-804]
475 $format = preg_replace('/^(\[[0-9A-Za-z]*\])*(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format);
476
477 // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case;
478 // but we don't want to change any quoted strings
479 $format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', ['self', 'setLowercaseCallback'], $format);
480
481 // Only process the non-quoted blocks for date format characters
482 $blocks = explode('"', $format);
483 foreach ($blocks as $key => &$block) {
484 if ($key % 2 == 0) {
485 $block = strtr($block, self::$dateFormatReplacements);
486 if (!strpos($block, 'A')) {
487 // 24-hour time format
488 // when [h]:mm format, the [h] should replace to the hours of the value * 24
489 if (false !== strpos($block, '[h]')) {
490 $hours = (int) ($value * 24);
491 $block = str_replace('[h]', $hours, $block);
492
493 continue;
494 }
495 $block = strtr($block, self::$dateFormatReplacements24);
496 } else {
497 // 12-hour time format
498 $block = strtr($block, self::$dateFormatReplacements12);
499 }
500 }
501 }
502 $format = implode('"', $blocks);
503
504 // escape any quoted characters so that DateTime format() will render them correctly
505 $format = preg_replace_callback('/"(.*)"/U', ['self', 'escapeQuotesCallback'], $format);
506
507 $dateObj = Date::excelToDateTimeObject($value);
508 $value = $dateObj->format($format);
509 }
510
511 private static function formatAsPercentage(&$value, &$format)
512 {
513 if ($format === self::FORMAT_PERCENTAGE) {
514 $value = round((100 * $value), 0) . '%';
515 } else {
516 if (preg_match('/\.[#0]+/', $format, $m)) {
517 $s = substr($m[0], 0, 1) . (strlen($m[0]) - 1);
518 $format = str_replace($m[0], $s, $format);
519 }
520 if (preg_match('/^[#0]+/', $format, $m)) {
521 $format = str_replace($m[0], strlen($m[0]), $format);
522 }
523 $format = '%' . str_replace('%', 'f%%', $format);
524
525 $value = sprintf($format, 100 * $value);
526 }
527 }
528
529 private static function formatAsFraction(&$value, &$format)
530 {
531 $sign = ($value < 0) ? '-' : '';
532
533 $integerPart = floor(abs($value));
534 $decimalPart = trim(fmod(abs($value), 1), '0.');
535 $decimalLength = strlen($decimalPart);
536 $decimalDivisor = pow(10, $decimalLength);
537
538 $GCD = MathTrig::GCD($decimalPart, $decimalDivisor);
539
540 $adjustedDecimalPart = $decimalPart / $GCD;
541 $adjustedDecimalDivisor = $decimalDivisor / $GCD;
542
543 if ((strpos($format, '0') !== false) || (strpos($format, '#') !== false) || (substr($format, 0, 3) == '? ?')) {
544 if ($integerPart == 0) {
545 $integerPart = '';
546 }
547 $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
548 } else {
549 $adjustedDecimalPart += $integerPart * $adjustedDecimalDivisor;
550 $value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor";
551 }
552 }
553
554 private static function complexNumberFormatMask($number, $mask)
555 {
556 $sign = ($number < 0.0);
557 $number = abs($number);
558 if (strpos($mask, '.') !== false) {
559 $numbers = explode('.', $number . '.0');
560 $masks = explode('.', $mask . '.0');
561 $result1 = self::complexNumberFormatMask($numbers[0], $masks[0]);
562 $result2 = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1])));
563
564 return (($sign) ? '-' : '') . $result1 . '.' . $result2;
565 }
566
567 $r = preg_match_all('/0+/', $mask, $result, PREG_OFFSET_CAPTURE);
568 if ($r > 1) {
569 $result = array_reverse($result[0]);
570
571 foreach ($result as $block) {
572 $divisor = 1 . $block[0];
573 $size = strlen($block[0]);
574 $offset = $block[1];
575
576 $blockValue = sprintf(
577 '%0' . $size . 'd',
578 fmod($number, $divisor)
579 );
580 $number = floor($number / $divisor);
581 $mask = substr_replace($mask, $blockValue, $offset, $size);
582 }
583 if ($number > 0) {
584 $mask = substr_replace($mask, $number, $offset, 0);
585 }
586 $result = $mask;
587 } else {
588 $result = $number;
589 }
590
591 return (($sign) ? '-' : '') . $result;
592 }
593
594 /**
595 * Convert a value in a pre-defined format to a PHP string.
596 *
597 * @param mixed $value Value to format
598 * @param string $format Format code, see = self::FORMAT_*
599 * @param array $callBack Callback function for additional formatting of string
600 *
601 * @return string Formatted string
602 */
603 public static function toFormattedString($value, $format, $callBack = null)
604 {
605 // For now we do not treat strings although section 4 of a format code affects strings
606 if (!is_numeric($value)) {
607 return $value;
608 }
609
610 // For 'General' format code, we just pass the value although this is not entirely the way Excel does it,
611 // it seems to round numbers to a total of 10 digits.
612 if (($format === self::FORMAT_GENERAL) || ($format === self::FORMAT_TEXT)) {
613 return $value;
614 }
615
616 // Convert any other escaped characters to quoted strings, e.g. (\T to "T")
617 $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format);
618
619 // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
620 $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);
621
622 // Extract the relevant section depending on whether number is positive, negative, or zero?
623 // Text not supported yet.
624 // Here is how the sections apply to various values in Excel:
625 // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT]
626 // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE]
627 // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO]
628 // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT]
629 switch (count($sections)) {
630 case 1:
631 $format = $sections[0];
632
633 break;
634 case 2:
635 $format = ($value >= 0) ? $sections[0] : $sections[1];
636 $value = abs($value); // Use the absolute value
637 break;
638 case 3:
639 $format = ($value > 0) ?
640 $sections[0] : (($value < 0) ?
641 $sections[1] : $sections[2]);
642 $value = abs($value); // Use the absolute value
643 break;
644 case 4:
645 $format = ($value > 0) ?
646 $sections[0] : (($value < 0) ?
647 $sections[1] : $sections[2]);
648 $value = abs($value); // Use the absolute value
649 break;
650 default:
651 // something is wrong, just use first section
652 $format = $sections[0];
653
654 break;
655 }
656
657 // In Excel formats, "_" is used to add spacing,
658 // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space
659 $format = preg_replace('/_./', ' ', $format);
660
661 // Save format with color information for later use below
662 $formatColor = $format;
663
664 // Let's begin inspecting the format and converting the value to a formatted string
665
666 // Check for date/time characters (not inside quotes)
667 if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) {
668 // datetime format
669 self::formatAsDate($value, $format);
670 } else {
671 // Strip color information
672 $color_regex = '/^\\[[a-zA-Z]+\\]/';
673 $format = preg_replace($color_regex, '', $format);
674 if (preg_match('/%$/', $format)) {
675 // % number format
676 self::formatAsPercentage($value, $format);
677 } else {
678 if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
679 $value = 'EUR ' . sprintf('%1.2f', $value);
680 } else {
681 // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
682 $format = str_replace(['"', '*'], '', $format);
683
684 // Find out if we need thousands separator
685 // This is indicated by a comma enclosed by a digit placeholder:
686 // #,# or 0,0
687 $useThousands = preg_match('/(#,#|0,0)/', $format);
688 if ($useThousands) {
689 $format = preg_replace('/0,0/', '00', $format);
690 $format = preg_replace('/#,#/', '##', $format);
691 }
692
693 // Scale thousands, millions,...
694 // This is indicated by a number of commas after a digit placeholder:
695 // #, or 0.0,,
696 $scale = 1; // same as no scale
697 $matches = [];
698 if (preg_match('/(#|0)(,+)/', $format, $matches)) {
699 $scale = pow(1000, strlen($matches[2]));
700
701 // strip the commas
702 $format = preg_replace('/0,+/', '0', $format);
703 $format = preg_replace('/#,+/', '#', $format);
704 }
705
706 if (preg_match('/#?.*\?\/\?/', $format, $m)) {
707 if ($value != (int) $value) {
708 self::formatAsFraction($value, $format);
709 }
710 } else {
711 // Handle the number itself
712
713 // scale number
714 $value = $value / $scale;
715
716 // Strip #
717 $format = preg_replace('/\\#/', '0', $format);
718
719 // Remove locale code [$-###]
720 $format = preg_replace('/\[\$\-.*\]/', '', $format);
721
722 $n = '/\\[[^\\]]+\\]/';
723 $m = preg_replace($n, '', $format);
724 $number_regex = '/(0+)(\\.?)(0*)/';
725 if (preg_match($number_regex, $m, $matches)) {
726 $left = $matches[1];
727 $dec = $matches[2];
728 $right = $matches[3];
729
730 // minimun width of formatted number (including dot)
731 $minWidth = strlen($left) + strlen($dec) + strlen($right);
732 if ($useThousands) {
733 $value = number_format(
734 $value,
735 strlen($right),
736 StringHelper::getDecimalSeparator(),
737 StringHelper::getThousandsSeparator()
738 );
739 $value = preg_replace($number_regex, $value, $format);
740 } else {
741 if (preg_match('/[0#]E[+-]0/i', $format)) {
742 // Scientific format
743 $value = sprintf('%5.2E', $value);
744 } elseif (preg_match('/0([^\d\.]+)0/', $format)) {
745 $value = self::complexNumberFormatMask($value, $format);
746 } else {
747 $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
748 $value = sprintf($sprintf_pattern, $value);
749 $value = preg_replace($number_regex, $value, $format);
750 }
751 }
752 }
753 }
754 if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
755 // Currency or Accounting
756 $currencyCode = $m[1];
757 list($currencyCode) = explode('-', $currencyCode);
758 if ($currencyCode == '') {
759 $currencyCode = StringHelper::getCurrencyCode();
760 }
761 $value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value);
762 }
763 }
764 }
765 }
766
767 // Additional formatting provided by callback function
768 if ($callBack !== null) {
769 list($writerInstance, $function) = $callBack;
770 $value = $writerInstance->$function($value, $formatColor);
771 }
772
773 return $value;
774 }
775 }
776