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
|
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);
|
$format = self::stripQuotes($format);
|
||||||
$value = (float) $value;
|
$value = (float) $value;
|
||||||
@@ -19,7 +24,7 @@ class FractionFormatter extends BaseFormatter
|
|||||||
|
|
||||||
$decimalPart = self::getDecimal((string) $absValue);
|
$decimalPart = self::getDecimal((string) $absValue);
|
||||||
if ($decimalPart === '0') {
|
if ($decimalPart === '0') {
|
||||||
return "{$sign}{$integerPart}";
|
return self::integerFraction($sign, (int) $integerPart, $originalFormat ?? $format);
|
||||||
}
|
}
|
||||||
$decimalLength = strlen($decimalPart);
|
$decimalLength = strlen($decimalPart);
|
||||||
$decimalDivisor = 10 ** $decimalLength;
|
$decimalDivisor = 10 ** $decimalLength;
|
||||||
@@ -58,6 +63,37 @@ class FractionFormatter extends BaseFormatter
|
|||||||
return "{$sign}{$adjustedDecimalPart}/{$adjustedDecimalDivisor}";
|
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
|
private static function getDecimal(string $value): string
|
||||||
{
|
{
|
||||||
$decimalPart = '0';
|
$decimalPart = '0';
|
||||||
|
|||||||
@@ -208,10 +208,11 @@ class NumberFormatter extends BaseFormatter
|
|||||||
$scale = self::scaleThousandsMillions($format);
|
$scale = self::scaleThousandsMillions($format);
|
||||||
|
|
||||||
if (preg_match('/[#\?0]?.*[#\?0]\/(\?+|\d+|#)/', $format)) {
|
if (preg_match('/[#\?0]?.*[#\?0]\/(\?+|\d+|#)/', $format)) {
|
||||||
|
$originalFormat = $format;
|
||||||
// It's a dirty hack; but replace # and 0 digit placeholders with ?
|
// It's a dirty hack; but replace # and 0 digit placeholders with ?
|
||||||
$format = (string) preg_replace('/[#0]+\//', '?/', $format);
|
$format = (string) preg_replace('/[#0]+\//', '?/', $format);
|
||||||
$format = (string) preg_replace('/\/[#0]+/', '/?', $format);
|
$format = (string) preg_replace('/\/[#0]+/', '/?', $format);
|
||||||
$value = FractionFormatter::format($value, $format);
|
$value = FractionFormatter::format($value, $format, $originalFormat);
|
||||||
} else {
|
} else {
|
||||||
// Handle the number itself
|
// Handle the number itself
|
||||||
// scale number
|
// scale number
|
||||||
|
|||||||
@@ -143,4 +143,22 @@ return [
|
|||||||
2.65,
|
2.65,
|
||||||
'# ???/12',
|
'# ???/12',
|
||||||
],
|
],
|
||||||
|
// Integer value, fraction format (issue #3625): a mandatory-digit (0)
|
||||||
|
// numerator still displays the fraction, while ? and # placeholders
|
||||||
|
// suppress it and leave only the integer.
|
||||||
|
[
|
||||||
|
'1/1',
|
||||||
|
1,
|
||||||
|
'0/0',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'1 0/1',
|
||||||
|
1,
|
||||||
|
'0 0/0',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'1',
|
||||||
|
1,
|
||||||
|
'? ??/???',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
Reference in new issue
Block a user