mirror of
https://github.com/retailcrm/PHPExcel.git
synced 2024-11-22 13:26:07 +03:00
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();
|
||||
self::$_thousandsSeparator = ($localeconv['thousands_sep'] != '')
|
||||
? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
|
||||
|
||||
if (self::$_thousandsSeparator == '') {
|
||||
// Default to .
|
||||
self::$_thousandsSeparator = ',';
|
||||
}
|
||||
}
|
||||
return self::$_thousandsSeparator;
|
||||
}
|
||||
|
@ -431,6 +431,108 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||
'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
|
||||
*
|
||||
@ -439,7 +541,7 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||
* @param array $callBack Callback function for additional formatting of 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
|
||||
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
|
||||
if (preg_match('/^(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy]/i', $format)) { // datetime 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);
|
||||
|
||||
self::_formatAsDate($value, $format);
|
||||
} else if (preg_match('/%$/', $format)) { // % number 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);
|
||||
}
|
||||
|
||||
self::_formatAsPercentage($value, $format);
|
||||
} else {
|
||||
if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {
|
||||
$value = 'EUR ' . sprintf('%1.2f', $value);
|
||||
|
||||
} else {
|
||||
// In Excel formats, "_" is used to add spacing, which we can't do in HTML
|
||||
$format = preg_replace('/_./', '', $format);
|
||||
@ -574,25 +642,7 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||
if (preg_match('/#?.*\?\/\?/', $format, $m)) {
|
||||
//echo 'Format mask is fractional '.$format.' <br />';
|
||||
if ($value != (int)$value) {
|
||||
$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";
|
||||
}
|
||||
self::_formatAsFraction($value, $format);
|
||||
}
|
||||
|
||||
} else {
|
||||
@ -602,7 +652,7 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||
$value = $value / $scale;
|
||||
|
||||
// Strip #
|
||||
$format = preg_replace('/\\#/', '', $format);
|
||||
$format = preg_replace('/\\#/', '0', $format);
|
||||
|
||||
$n = "/\[[^\]]+\]/";
|
||||
$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)
|
||||
$minWidth = strlen($left) + strlen($dec) + strlen($right);
|
||||
|
||||
if ($useThousands) {
|
||||
$value = number_format(
|
||||
$value
|
||||
@ -622,14 +671,18 @@ class PHPExcel_Style_NumberFormat extends PHPExcel_Style_Supervisor implements P
|
||||
, PHPExcel_Shared_String::getDecimalSeparator()
|
||||
, 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 {
|
||||
$sprintf_pattern = "%0$minWidth." . strlen($right) . "f";
|
||||
$value = sprintf($sprintf_pattern, $value);
|
||||
}
|
||||
|
||||
$value = preg_replace($number_regex, $value, $format);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
|
||||
// Currency or Accounting
|
||||
$currencyFormat = $m[0];
|
||||
|
33
unitTests/Classes/PHPExcel/Style/NumberFormatTest.php
Normal file
33
unitTests/Classes/PHPExcel/Style/NumberFormatTest.php
Normal file
@ -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');
|
||||
}
|
||||
|
||||
}
|
34
unitTests/rawTestData/Style/NumberFormat.data
Normal file
34
unitTests/rawTestData/Style/NumberFormat.data
Normal file
@ -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
Block a user