ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
NumberFormatter.php
Go to the documentation of this file.
1<?php
2
4
7
9{
10 private const NUMBER_REGEX = '/(0+)(\\.?)(0*)/';
11
12 private static function mergeComplexNumberFormatMasks($numbers, $masks): array
13 {
14 $decimalCount = strlen($numbers[1]);
15 $postDecimalMasks = [];
16
17 do {
18 $tempMask = array_pop($masks);
19 if ($tempMask !== null) {
20 $postDecimalMasks[] = $tempMask;
21 $decimalCount -= strlen($tempMask);
22 }
23 } while ($tempMask !== null && $decimalCount > 0);
24
25 return [
26 implode('.', $masks),
27 implode('.', array_reverse($postDecimalMasks)),
28 ];
29 }
30
31 private static function processComplexNumberFormatMask($number, $mask): string
32 {
33 $result = $number;
34 $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE);
35
36 if ($maskingBlockCount > 1) {
37 $maskingBlocks = array_reverse($maskingBlocks[0]);
38
39 $offset = 0;
40 foreach ($maskingBlocks as $block) {
41 $size = strlen($block[0]);
42 $divisor = 10 ** $size;
43 $offset = $block[1];
44
45 $blockValue = sprintf("%0{$size}d", fmod($number, $divisor));
46 $number = floor($number / $divisor);
47 $mask = substr_replace($mask, $blockValue, $offset, $size);
48 }
49 if ($number > 0) {
50 $mask = substr_replace($mask, $number, $offset, 0);
51 }
52 $result = $mask;
53 }
54
55 return $result;
56 }
57
58 private static function complexNumberFormatMask($number, $mask, $splitOnPoint = true): string
59 {
60 $sign = ($number < 0.0) ? '-' : '';
61 $number = abs($number);
62
63 if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) {
64 $numbers = explode('.', $number);
65 $masks = explode('.', $mask);
66 if (count($masks) > 2) {
67 $masks = self::mergeComplexNumberFormatMasks($numbers, $masks);
68 }
69 $integerPart = self::complexNumberFormatMask($numbers[0], $masks[0], false);
70 $decimalPart = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false));
71
72 return "{$sign}{$integerPart}.{$decimalPart}";
73 }
74
76
77 return "{$sign}{$result}";
78 }
79
80 private static function formatStraightNumericValue($value, $format, array $matches, $useThousands): string
81 {
82 $left = $matches[1];
83 $dec = $matches[2];
84 $right = $matches[3];
85
86 // minimun width of formatted number (including dot)
87 $minWidth = strlen($left) + strlen($dec) + strlen($right);
88 if ($useThousands) {
89 $value = number_format(
90 $value,
91 strlen($right),
94 );
95
96 return preg_replace(self::NUMBER_REGEX, $value, $format);
97 }
98
99 if (preg_match('/[0#]E[+-]0/i', $format)) {
100 // Scientific format
101 return sprintf('%5.2E', $value);
102 } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) {
103 if ($value == (int) $value && substr_count($format, '.') === 1) {
104 $value *= 10 ** strlen(explode('.', $format)[1]);
105 }
106
108 }
109
110 $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
111 $value = sprintf($sprintf_pattern, $value);
112
113 return preg_replace(self::NUMBER_REGEX, $value, $format);
114 }
115
116 public static function format($value, $format): string
117 {
118 // The "_" in this string has already been stripped out,
119 // so this test is never true. Furthermore, testing
120 // on Excel shows this format uses Euro symbol, not "EUR".
121 //if ($format === NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE) {
122 // return 'EUR ' . sprintf('%1.2f', $value);
123 //}
124
125 // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
126 $format = str_replace(['"', '*'], '', $format);
127
128 // Find out if we need thousands separator
129 // This is indicated by a comma enclosed by a digit placeholder:
130 // #,# or 0,0
131 $useThousands = preg_match('/(#,#|0,0)/', $format);
132 if ($useThousands) {
133 $format = preg_replace('/0,0/', '00', $format);
134 $format = preg_replace('/#,#/', '##', $format);
135 }
136
137 // Scale thousands, millions,...
138 // This is indicated by a number of commas after a digit placeholder:
139 // #, or 0.0,,
140 $scale = 1; // same as no scale
141 $matches = [];
142 if (preg_match('/(#|0)(,+)/', $format, $matches)) {
143 $scale = 1000 ** strlen($matches[2]);
144
145 // strip the commas
146 $format = preg_replace('/0,+/', '0', $format);
147 $format = preg_replace('/#,+/', '#', $format);
148 }
149 if (preg_match('/#?.*\?\/\?/', $format, $m)) {
150 if ($value != (int) $value) {
151 $value = FractionFormatter::format($value, $format);
152 }
153 } else {
154 // Handle the number itself
155
156 // scale number
157 $value = $value / $scale;
158 // Strip #
159 $format = preg_replace('/\\#/', '0', $format);
160 // Remove locale code [$-###]
161 $format = preg_replace('/\[\$\-.*\]/', '', $format);
162
163 $n = '/\\[[^\\]]+\\]/';
164 $m = preg_replace($n, '', $format);
165 if (preg_match(self::NUMBER_REGEX, $m, $matches)) {
166 // There are placeholders for digits, so inject digits from the value into the mask
167 $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands);
169 // Yes, I know that this is basically just a hack;
170 // if there's no placeholders for digits, just return the format mask "as is"
171 $value = str_replace('?', '', $format ?? '');
172 }
173 }
174
175 if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
176 // Currency or Accounting
177 $currencyCode = $m[1];
178 [$currencyCode] = explode('-', $currencyCode);
179 if ($currencyCode == '') {
180 $currencyCode = StringHelper::getCurrencyCode();
181 }
182 $value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value);
183 }
184
185 return $value;
186 }
187}
$result
$n
Definition: RandomTest.php:85
$size
Definition: RandomTest.php:84
An exception for terminatinating execution or to throw for unit testing.
static getThousandsSeparator()
Get the thousands separator.
static getDecimalSeparator()
Get the decimal separator.
static getCurrencyCode()
Get the currency code.
static complexNumberFormatMask($number, $mask, $splitOnPoint=true)
static formatStraightNumericValue($value, $format, array $matches, $useThousands)
$mask
Definition: example_042.php:90
$format
Definition: metadata.php:141