fix: display fraction for integer values with mandatory 0 placeholder (#3625)
This commit is contained in:
1 parent
49d947e039
commit
daff98be25
3 files changed
+59
-4
No files matched your search
@@ -6,8 +6,13 @@ use PhpOffice\PhpSpreadsheet\Calculation\MathTrig;
|
||||
|
||||
class FractionFormatter extends BaseFormatter
|
||||
{
|
||||
/** @param null|bool|float|int|string $value value to be formatted */
|
||||
public static function format(mixed $value, string $format): string
|
||||
/**
|
||||
* @param null|bool|float|int|string $value value to be formatted
|
||||
* @param string $format normalized fraction mask (digit placeholders reduced to ?)
|
||||
* @param ?string $originalFormat unmodified mask, needed to tell mandatory (0)
|
||||
* placeholders apart from optional (? #) ones
|
||||
*/
|
||||
public static function format(mixed $value, string $format, ?string $originalFormat = null): string
|
||||
{
|
||||
$format = self::stripQuotes($format);
|
||||
$value = (float) $value;
|
||||
@@ -19,7 +24,7 @@ class FractionFormatter extends BaseFormatter
|
||||
|
||||
$decimalPart = self::getDecimal((string) $absValue);
|
||||
if ($decimalPart === '0') {
|
||||
return "{$sign}{$integerPart}";
|
||||
return self::integerFraction($sign, (int) $integerPart, $originalFormat ?? $format);
|
||||
}
|
||||
$decimalLength = strlen($decimalPart);
|
||||
$decimalDivisor = 10 ** $decimalLength;
|
||||
@@ -58,6 +63,37 @@ class FractionFormatter extends BaseFormatter
|
||||
return "{$sign}{$adjustedDecimalPart}/{$adjustedDecimalDivisor}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a value with no fractional remainder. Excel still displays a
|
||||
* fraction when the numerator placeholder uses mandatory digits (0), so
|
||||
* format "0/0" yields "1/1" and "0 0/0" yields "1 0/1"; the "?" and "#"
|
||||
* placeholders suppress the fraction and leave only the integer.
|
||||
*/
|
||||
private static function integerFraction(string $sign, int $integerPart, string $format): string
|
||||
{
|
||||
if (preg_match('~(?:([#0?]+)\s+)?([#0?]+)/([#0?]+|\d+)~', $format, $matches) !== 1) {
|
||||
return "{$sign}{$integerPart}";
|
||||
}
|
||||
[, $integerFormat, $numeratorFormat, $denominatorFormat] = $matches;
|
||||
if (!str_contains($numeratorFormat, '0')) {
|
||||
return "{$sign}{$integerPart}";
|
||||
}
|
||||
|
||||
if (preg_match('/[1-9]/', $denominatorFormat) === 1) {
|
||||
$denominator = $denominatorFormat;
|
||||
} else {
|
||||
$denominator = str_pad('1', substr_count($denominatorFormat, '0'), '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
if ($integerFormat !== '') {
|
||||
return "{$sign}{$integerPart} 0/{$denominator}";
|
||||
}
|
||||
|
||||
$numerator = $integerPart * (int) $denominator;
|
||||
|
||||
return "{$sign}{$numerator}/{$denominator}";
|
||||
}
|
||||
|
||||
private static function getDecimal(string $value): string
|
||||
{
|
||||
$decimalPart = '0';
|
||||
|
||||
@@ -208,10 +208,11 @@ class NumberFormatter extends BaseFormatter
|
||||
$scale = self::scaleThousandsMillions($format);
|
||||
|
||||
if (preg_match('/[#\?0]?.*[#\?0]\/(\?+|\d+|#)/', $format)) {
|
||||
$originalFormat = $format;
|
||||
// It's a dirty hack; but replace # and 0 digit placeholders with ?
|
||||
$format = (string) preg_replace('/[#0]+\//', '?/', $format);
|
||||
$format = (string) preg_replace('/\/[#0]+/', '/?', $format);
|
||||
$value = FractionFormatter::format($value, $format);
|
||||
$value = FractionFormatter::format($value, $format, $originalFormat);
|
||||
} else {
|
||||
// Handle the number itself
|
||||
// scale number
|
||||
|
||||
Reference in new issue
Block a user