Improvements to formatting numbers with more complex masks
This commit is contained in:
parent
46982cf098
commit
c17a4a62a3
|
@ -687,6 +687,11 @@ class PHPExcel_Shared_String
|
||||||
$localeconv = localeconv();
|
$localeconv = localeconv();
|
||||||
self::$_thousandsSeparator = ($localeconv['thousands_sep'] != '')
|
self::$_thousandsSeparator = ($localeconv['thousands_sep'] != '')
|
||||||
? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
|
? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
|
||||||
|
|
||||||
|
if (self::$_thousandsSeparator == '') {
|
||||||
|
// Default to .
|
||||||
|
self::$_thousandsSeparator = ',';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return self::$_thousandsSeparator;
|
return self::$_thousandsSeparator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -431,6 +431,108 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||||
'h' => 'g'
|
'h' => 'g'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private static function _formatAsDate(&$value, &$format)
|
||||||
|
{
|
||||||
|
// dvc: convert Excel formats to PHP date formats
|
||||||
|
|
||||||
|
// strip off first part containing e.g. [$-F800] or [$USD-409]
|
||||||
|
// general syntax: [$<Currency string>-<language info>]
|
||||||
|
// language info is in hexadecimal
|
||||||
|
$format = preg_replace('/^(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format);
|
||||||
|
|
||||||
|
// OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case
|
||||||
|
$format = strtolower($format);
|
||||||
|
|
||||||
|
$format = strtr($format,self::$_dateFormatReplacements);
|
||||||
|
if (!strpos($format,'A')) { // 24-hour time format
|
||||||
|
$format = strtr($format,self::$_dateFormatReplacements24);
|
||||||
|
} else { // 12-hour time format
|
||||||
|
$format = strtr($format,self::$_dateFormatReplacements12);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dateObj = PHPExcel_Shared_Date::ExcelToPHPObject($value);
|
||||||
|
$value = $dateObj->format($format);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _formatAsPercentage(&$value, &$format)
|
||||||
|
{
|
||||||
|
if ($format === self::FORMAT_PERCENTAGE) {
|
||||||
|
$value = round( (100 * $value), 0) . '%';
|
||||||
|
} else {
|
||||||
|
if (preg_match('/\.[#0]+/i', $format, $m)) {
|
||||||
|
$s = substr($m[0], 0, 1) . (strlen($m[0]) - 1);
|
||||||
|
$format = str_replace($m[0], $s, $format);
|
||||||
|
}
|
||||||
|
if (preg_match('/^[#0]+/', $format, $m)) {
|
||||||
|
$format = str_replace($m[0], strlen($m[0]), $format);
|
||||||
|
}
|
||||||
|
$format = '%' . str_replace('%', 'f%%', $format);
|
||||||
|
|
||||||
|
$value = sprintf($format, 100 * $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _formatAsFraction(&$value, &$format)
|
||||||
|
{
|
||||||
|
$sign = ($value < 0) ? '-' : '';
|
||||||
|
|
||||||
|
$integerPart = floor(abs($value));
|
||||||
|
$decimalPart = trim(fmod(abs($value),1),'0.');
|
||||||
|
$decimalLength = strlen($decimalPart);
|
||||||
|
$decimalDivisor = pow(10,$decimalLength);
|
||||||
|
|
||||||
|
$GCD = PHPExcel_Calculation_MathTrig::GCD($decimalPart,$decimalDivisor);
|
||||||
|
|
||||||
|
$adjustedDecimalPart = $decimalPart/$GCD;
|
||||||
|
$adjustedDecimalDivisor = $decimalDivisor/$GCD;
|
||||||
|
|
||||||
|
if ((strpos($format,'0') !== false) || (strpos($format,'#') !== false) || (substr($format,0,3) == '? ?')) {
|
||||||
|
if ($integerPart == 0) {
|
||||||
|
$integerPart = '';
|
||||||
|
}
|
||||||
|
$value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
|
||||||
|
} else {
|
||||||
|
$adjustedDecimalPart += $integerPart * $adjustedDecimalDivisor;
|
||||||
|
$value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _complexNumberFormatMask($number, $mask) {
|
||||||
|
if (strpos($mask,'.') !== false) {
|
||||||
|
$numbers = explode('.', $number . '.0');
|
||||||
|
$masks = explode('.', $mask . '.0');
|
||||||
|
$result1 = self::_complexNumberFormatMask($numbers[0], $masks[0]);
|
||||||
|
$result2 = strrev(self::_complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1])));
|
||||||
|
return $result1 . '.' . $result2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$r = preg_match_all('/0+/', $mask, $result, PREG_OFFSET_CAPTURE);
|
||||||
|
if ($r > 1) {
|
||||||
|
$result = array_reverse($result[0]);
|
||||||
|
|
||||||
|
foreach($result as $block) {
|
||||||
|
$divisor = 1 . $block[0];
|
||||||
|
$size = strlen($block[0]);
|
||||||
|
$offset = $block[1];
|
||||||
|
|
||||||
|
$blockValue = sprintf(
|
||||||
|
'%0' . $size . 'd',
|
||||||
|
fmod($number, $divisor)
|
||||||
|
);
|
||||||
|
$number = floor($number / $divisor);
|
||||||
|
$mask = substr_replace($mask,$blockValue, $offset, $size);
|
||||||
|
}
|
||||||
|
if ($number > 0) {
|
||||||
|
$mask = substr_replace($mask, $number, $offset, 0);
|
||||||
|
}
|
||||||
|
$result = $mask;
|
||||||
|
} else {
|
||||||
|
$result = $number;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a value in a pre-defined format to a PHP string
|
* Convert a value in a pre-defined format to a PHP string
|
||||||
*
|
*
|
||||||
|
@ -439,7 +541,7 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||||
* @param array $callBack Callback function for additional formatting of string
|
* @param array $callBack Callback function for additional formatting of string
|
||||||
* @return string Formatted string
|
* @return string Formatted string
|
||||||
*/
|
*/
|
||||||
public static function toFormattedString($value = PHPExcel_Style_NumberFormat::FORMAT_GENERAL, $format = '', $callBack = null)
|
public static function toFormattedString($value = '0', $format = PHPExcel_Style_NumberFormat::FORMAT_GENERAL, $callBack = null)
|
||||||
{
|
{
|
||||||
// For now we do not treat strings although section 4 of a format code affects strings
|
// For now we do not treat strings although section 4 of a format code affects strings
|
||||||
if (!is_numeric($value)) return $value;
|
if (!is_numeric($value)) return $value;
|
||||||
|
@ -499,46 +601,12 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||||
|
|
||||||
// Let's begin inspecting the format and converting the value to a formatted string
|
// Let's begin inspecting the format and converting the value to a formatted string
|
||||||
if (preg_match('/^(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy]/i', $format)) { // datetime format
|
if (preg_match('/^(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy]/i', $format)) { // datetime format
|
||||||
// dvc: convert Excel formats to PHP date formats
|
self::_formatAsDate($value, $format);
|
||||||
|
|
||||||
// strip off first part containing e.g. [$-F800] or [$USD-409]
|
|
||||||
// general syntax: [$<Currency string>-<language info>]
|
|
||||||
// language info is in hexadecimal
|
|
||||||
$format = preg_replace('/^(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format);
|
|
||||||
|
|
||||||
// OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case
|
|
||||||
$format = strtolower($format);
|
|
||||||
|
|
||||||
$format = strtr($format,self::$_dateFormatReplacements);
|
|
||||||
if (!strpos($format,'A')) { // 24-hour time format
|
|
||||||
$format = strtr($format,self::$_dateFormatReplacements24);
|
|
||||||
} else { // 12-hour time format
|
|
||||||
$format = strtr($format,self::$_dateFormatReplacements12);
|
|
||||||
}
|
|
||||||
|
|
||||||
$dateObj = PHPExcel_Shared_Date::ExcelToPHPObject($value);
|
|
||||||
$value = $dateObj->format($format);
|
|
||||||
|
|
||||||
} else if (preg_match('/%$/', $format)) { // % number format
|
} else if (preg_match('/%$/', $format)) { // % number format
|
||||||
if ($format === self::FORMAT_PERCENTAGE) {
|
self::_formatAsPercentage($value, $format);
|
||||||
$value = round( (100 * $value), 0) . '%';
|
|
||||||
} else {
|
|
||||||
if (preg_match('/\.[#0]+/i', $format, $m)) {
|
|
||||||
$s = substr($m[0], 0, 1) . (strlen($m[0]) - 1);
|
|
||||||
$format = str_replace($m[0], $s, $format);
|
|
||||||
}
|
|
||||||
if (preg_match('/^[#0]+/', $format, $m)) {
|
|
||||||
$format = str_replace($m[0], strlen($m[0]), $format);
|
|
||||||
}
|
|
||||||
$format = '%' . str_replace('%', 'f%%', $format);
|
|
||||||
|
|
||||||
$value = sprintf($format, 100 * $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
|
if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
|
||||||
$value = 'EUR ' . sprintf('%1.2f', $value);
|
$value = 'EUR ' . sprintf('%1.2f', $value);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// In Excel formats, "_" is used to add spacing, which we can't do in HTML
|
// In Excel formats, "_" is used to add spacing, which we can't do in HTML
|
||||||
$format = preg_replace('/_./', '', $format);
|
$format = preg_replace('/_./', '', $format);
|
||||||
|
@ -574,25 +642,7 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||||
if (preg_match('/#?.*\?\/\?/', $format, $m)) {
|
if (preg_match('/#?.*\?\/\?/', $format, $m)) {
|
||||||
//echo 'Format mask is fractional '.$format.' <br />';
|
//echo 'Format mask is fractional '.$format.' <br />';
|
||||||
if ($value != (int)$value) {
|
if ($value != (int)$value) {
|
||||||
$sign = ($value < 0) ? '-' : '';
|
self::_formatAsFraction($value, $format);
|
||||||
|
|
||||||
$integerPart = floor(abs($value));
|
|
||||||
$decimalPart = trim(fmod(abs($value),1),'0.');
|
|
||||||
$decimalLength = strlen($decimalPart);
|
|
||||||
$decimalDivisor = pow(10,$decimalLength);
|
|
||||||
|
|
||||||
$GCD = PHPExcel_Calculation_MathTrig::GCD($decimalPart,$decimalDivisor);
|
|
||||||
|
|
||||||
$adjustedDecimalPart = $decimalPart/$GCD;
|
|
||||||
$adjustedDecimalDivisor = $decimalDivisor/$GCD;
|
|
||||||
|
|
||||||
if ((strpos($format,'0') !== false) || (strpos($format,'#') !== false) || (substr($format,0,3) == '? ?')) {
|
|
||||||
if ($integerPart == 0) { $integerPart = ''; }
|
|
||||||
$value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
|
|
||||||
} else {
|
|
||||||
$adjustedDecimalPart += $integerPart * $adjustedDecimalDivisor;
|
|
||||||
$value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
@ -602,7 +652,7 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||||
$value = $value / $scale;
|
$value = $value / $scale;
|
||||||
|
|
||||||
// Strip #
|
// Strip #
|
||||||
$format = preg_replace('/\\#/', '', $format);
|
$format = preg_replace('/\\#/', '0', $format);
|
||||||
|
|
||||||
$n = "/\[[^\]]+\]/";
|
$n = "/\[[^\]]+\]/";
|
||||||
$m = preg_replace($n, '', $format);
|
$m = preg_replace($n, '', $format);
|
||||||
|
@ -614,7 +664,6 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||||
|
|
||||||
// minimun width of formatted number (including dot)
|
// minimun width of formatted number (including dot)
|
||||||
$minWidth = strlen($left) + strlen($dec) + strlen($right);
|
$minWidth = strlen($left) + strlen($dec) + strlen($right);
|
||||||
|
|
||||||
if ($useThousands) {
|
if ($useThousands) {
|
||||||
$value = number_format(
|
$value = number_format(
|
||||||
$value
|
$value
|
||||||
|
@ -622,14 +671,18 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||||
, PHPExcel_Shared_String::getDecimalSeparator()
|
, PHPExcel_Shared_String::getDecimalSeparator()
|
||||||
, PHPExcel_Shared_String::getThousandsSeparator()
|
, PHPExcel_Shared_String::getThousandsSeparator()
|
||||||
);
|
);
|
||||||
|
$value = preg_replace($number_regex, $value, $format);
|
||||||
|
} else {
|
||||||
|
if (preg_match('/0([^\d\.]+)0/', $format, $matches)) {
|
||||||
|
$value = self::_complexNumberFormatMask($value, $format);
|
||||||
} else {
|
} else {
|
||||||
$sprintf_pattern = "%0$minWidth." . strlen($right) . "f";
|
$sprintf_pattern = "%0$minWidth." . strlen($right) . "f";
|
||||||
$value = sprintf($sprintf_pattern, $value);
|
$value = sprintf($sprintf_pattern, $value);
|
||||||
}
|
|
||||||
|
|
||||||
$value = preg_replace($number_regex, $value, $format);
|
$value = preg_replace($number_regex, $value, $format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
|
if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
|
||||||
// Currency or Accounting
|
// Currency or Accounting
|
||||||
$currencyFormat = $m[0];
|
$currencyFormat = $m[0];
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
require_once 'testDataFileIterator.php';
|
||||||
|
|
||||||
|
class NumberFormatTest extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
if (!defined('PHPEXCEL_ROOT')) {
|
||||||
|
define('PHPEXCEL_ROOT', APPLICATION_PATH . '/');
|
||||||
|
}
|
||||||
|
require_once(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider providerNumberFormat
|
||||||
|
*/
|
||||||
|
public function testFormatValueWithMask()
|
||||||
|
{
|
||||||
|
$args = func_get_args();
|
||||||
|
$expectedResult = array_pop($args);
|
||||||
|
$result = call_user_func_array(array('PHPExcel_Style_NumberFormat','toFormattedString'),$args);
|
||||||
|
$this->assertEquals($expectedResult, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function providerNumberFormat()
|
||||||
|
{
|
||||||
|
return new testDataFileIterator('rawTestData/Style/NumberFormat.data');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
# value format result
|
||||||
|
0.0, "0.0", "0.0"
|
||||||
|
0.0, "0", "0"
|
||||||
|
0, "0.0", "0.0"
|
||||||
|
0, "0", "0"
|
||||||
|
0, "##0", "000"
|
||||||
|
12, "#.0#", "12.0"
|
||||||
|
0.1, "0.0", "0.1"
|
||||||
|
0.1, "0", "0"
|
||||||
|
5.5555, "0.###", "5.556"
|
||||||
|
5.5555, "0.0##", "5.556"
|
||||||
|
5.5555, "0.00#", "5.556"
|
||||||
|
5.5555, "0.000", "5.556"
|
||||||
|
5.5555, "0.0000", "5.5555"
|
||||||
|
12345.6789, '"#,##0.00"', '"12,345.68"'
|
||||||
|
12345.6789, '"#,##0.000"', '"12,345.679"'
|
||||||
|
12345.6789, '"£ #,##0.00"', '"£ 12,345.68"'
|
||||||
|
12345.6789, '"$ #,##0.000"', '"$ 12,345.679"'
|
||||||
|
5.6789, '"#,##0.00"', '"5.68"'
|
||||||
|
12000, '"#,###"', '"12,000"'
|
||||||
|
12000, '"#,"', '12'
|
||||||
|
12200000, '"0.0,,"', '12.2' // Scaling test
|
||||||
|
0.08, "0%", "8%"
|
||||||
|
0.8, "0%", "80%"
|
||||||
|
2.8, "0%", "280%"
|
||||||
|
125.74, '$0.00" Surplus";$-0.00" Shortage"', "$125.74 Surplus"
|
||||||
|
-125.74, '$0.00" Surplus";$-0.00" Shortage"', "$-125.74 Shortage"
|
||||||
|
-125.74, '$0.00" Surplus";$0.00" Shortage"', "$125.74 Shortage"
|
||||||
|
5.25, '# ???/???', "5 1/4" // Fraction
|
||||||
|
5.3, '# ???/???', "5 3/10" // Vulgar Fraction
|
||||||
|
5.25, '???/???', "21/4"
|
||||||
|
123456789, '(000) 0-0000-000', "(001) 2-3456-789"
|
||||||
|
123456789, '0 (+00) 0000 00 00 00', "0 (+00) 0123 45 67 89"
|
||||||
|
123456789, '0000:00:00', "12345:67:89"
|
Loading…
Reference in New Issue