diff --git a/Classes/PHPExcel/Shared/String.php b/Classes/PHPExcel/Shared/String.php index f72ccc5..3ba1be2 100644 --- a/Classes/PHPExcel/Shared/String.php +++ b/Classes/PHPExcel/Shared/String.php @@ -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; } diff --git a/Classes/PHPExcel/Style/NumberFormat.php b/Classes/PHPExcel/Style/NumberFormat.php index 04d51a8..3d5b20e 100644 --- a/Classes/PHPExcel/Style/NumberFormat.php +++ b/Classes/PHPExcel/Style/NumberFormat.php @@ -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: [$-] + // 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: [$-] - // 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.'
'; 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,12 +671,16 @@ 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 { - $sprintf_pattern = "%0$minWidth." . strlen($right) . "f"; - $value = sprintf($sprintf_pattern, $value); + 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); + } } - - $value = preg_replace($number_regex, $value, $format); } } if (preg_match('/\[\$(.*)\]/u', $format, $m)) { diff --git a/unitTests/Classes/PHPExcel/Style/NumberFormatTest.php b/unitTests/Classes/PHPExcel/Style/NumberFormatTest.php new file mode 100644 index 0000000..10b4427 --- /dev/null +++ b/unitTests/Classes/PHPExcel/Style/NumberFormatTest.php @@ -0,0 +1,33 @@ +assertEquals($expectedResult, $result); + } + + public function providerNumberFormat() + { + return new testDataFileIterator('rawTestData/Style/NumberFormat.data'); + } + +} diff --git a/unitTests/rawTestData/Style/NumberFormat.data b/unitTests/rawTestData/Style/NumberFormat.data new file mode 100644 index 0000000..3f5901a --- /dev/null +++ b/unitTests/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"