/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php

https://github.com/sbourget/moodle · PHP · 162 lines · 110 code · 24 blank · 28 comment · 22 complexity · e7580532af4915232bdb862f344b7e31 MD5 · raw file

  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
  3. use PhpOffice\PhpSpreadsheet\Style\Color;
  4. use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
  5. class Formatter
  6. {
  7. private static function splitFormatCompare($value, $cond, $val, $dfcond, $dfval)
  8. {
  9. if (!$cond) {
  10. $cond = $dfcond;
  11. $val = $dfval;
  12. }
  13. switch ($cond) {
  14. case '>':
  15. return $value > $val;
  16. case '<':
  17. return $value < $val;
  18. case '<=':
  19. return $value <= $val;
  20. case '<>':
  21. return $value != $val;
  22. case '=':
  23. return $value == $val;
  24. }
  25. return $value >= $val;
  26. }
  27. private static function splitFormat($sections, $value)
  28. {
  29. // Extract the relevant section depending on whether number is positive, negative, or zero?
  30. // Text not supported yet.
  31. // Here is how the sections apply to various values in Excel:
  32. // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT]
  33. // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE]
  34. // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO]
  35. // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT]
  36. $cnt = count($sections);
  37. $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/mui';
  38. $cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/';
  39. $colors = ['', '', '', '', ''];
  40. $condops = ['', '', '', '', ''];
  41. $condvals = [0, 0, 0, 0, 0];
  42. for ($idx = 0; $idx < $cnt; ++$idx) {
  43. if (preg_match($color_regex, $sections[$idx], $matches)) {
  44. $colors[$idx] = $matches[0];
  45. $sections[$idx] = preg_replace($color_regex, '', $sections[$idx]);
  46. }
  47. if (preg_match($cond_regex, $sections[$idx], $matches)) {
  48. $condops[$idx] = $matches[1];
  49. $condvals[$idx] = $matches[2];
  50. $sections[$idx] = preg_replace($cond_regex, '', $sections[$idx]);
  51. }
  52. }
  53. $color = $colors[0];
  54. $format = $sections[0];
  55. $absval = $value;
  56. switch ($cnt) {
  57. case 2:
  58. $absval = abs($value);
  59. if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>=', 0)) {
  60. $color = $colors[1];
  61. $format = $sections[1];
  62. }
  63. break;
  64. case 3:
  65. case 4:
  66. $absval = abs($value);
  67. if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>', 0)) {
  68. if (self::splitFormatCompare($value, $condops[1], $condvals[1], '<', 0)) {
  69. $color = $colors[1];
  70. $format = $sections[1];
  71. } else {
  72. $color = $colors[2];
  73. $format = $sections[2];
  74. }
  75. }
  76. break;
  77. }
  78. return [$color, $format, $absval];
  79. }
  80. /**
  81. * Convert a value in a pre-defined format to a PHP string.
  82. *
  83. * @param mixed $value Value to format
  84. * @param string $format Format code, see = NumberFormat::FORMAT_*
  85. * @param array $callBack Callback function for additional formatting of string
  86. *
  87. * @return string Formatted string
  88. */
  89. public static function toFormattedString($value, $format, $callBack = null)
  90. {
  91. // For now we do not treat strings although section 4 of a format code affects strings
  92. if (!is_numeric($value)) {
  93. return $value;
  94. }
  95. // For 'General' format code, we just pass the value although this is not entirely the way Excel does it,
  96. // it seems to round numbers to a total of 10 digits.
  97. if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) {
  98. return $value;
  99. }
  100. $format = preg_replace_callback(
  101. '/(["])(?:(?=(\\\\?))\\2.)*?\\1/u',
  102. function ($matches) {
  103. return str_replace('.', chr(0x00), $matches[0]);
  104. },
  105. $format
  106. );
  107. // Convert any other escaped characters to quoted strings, e.g. (\T to "T")
  108. $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format);
  109. // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
  110. $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);
  111. [$colors, $format, $value] = self::splitFormat($sections, $value);
  112. // In Excel formats, "_" is used to add spacing,
  113. // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space
  114. $format = preg_replace('/_.?/ui', ' ', $format);
  115. // Let's begin inspecting the format and converting the value to a formatted string
  116. // Check for date/time characters (not inside quotes)
  117. if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) {
  118. // datetime format
  119. $value = DateFormatter::format($value, $format);
  120. } else {
  121. if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"' && substr_count($format, '"') === 2) {
  122. $value = substr($format, 1, -1);
  123. } elseif (preg_match('/[0#, ]%/', $format)) {
  124. // % number format
  125. $value = PercentageFormatter::format($value, $format);
  126. } else {
  127. $value = NumberFormatter::format($value, $format);
  128. }
  129. }
  130. // Additional formatting provided by callback function
  131. if ($callBack !== null) {
  132. [$writerInstance, $function] = $callBack;
  133. $value = $writerInstance->$function($value, $colors);
  134. }
  135. $value = str_replace(chr(0x00), '.', $value);
  136. return $value;
  137. }
  138. }