Improved masking for number format handling, particularly for datetime masks

This commit is contained in:
MarkBaker 2015-08-01 00:39:10 +01:00
parent f968a95ca5
commit d7ef6810a4
3 changed files with 81 additions and 17 deletions

View File

@ -425,26 +425,43 @@ class NumberFormat extends Supervisor implements \PHPExcel\IComparable
'h' => 'g' 'h' => 'g'
); );
private static function setLowercaseCallback($matches) {
return mb_strtolower($matches[0]);
}
private static function escapeQuotesCallback($matches) {
return '\\' . implode('\\', str_split($matches[1]));
}
private static function formatAsDate(&$value, &$format) 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] // strip off first part containing e.g. [$-F800] or [$USD-409]
// general syntax: [$<Currency string>-<language info>] // general syntax: [$<Currency string>-<language info>]
// language info is in hexadecimal // language info is in hexadecimal
$format = preg_replace('/^(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format); $format = preg_replace('/^(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format);
// OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case;
$format = strtolower($format); // but we don't want to change any quoted strings
$format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', ['self', 'setLowercaseCallback'], $format);
$format = strtr($format, self::$dateFormatReplacements); // Only process the non-quoted blocks for date format characters
if (!strpos($format, 'A')) { $blocks = explode('"', $format);
foreach($blocks as $key => &$block) {
if ($key % 2 == 0) {
$block = strtr($block, self::$dateFormatReplacements);
if (!strpos($block, 'A')) {
// 24-hour time format // 24-hour time format
$format = strtr($format, self::$dateFormatReplacements24); $block = strtr($block, self::$dateFormatReplacements24);
} else { } else {
// 12-hour time format // 12-hour time format
$format = strtr($format, self::$dateFormatReplacements12); $block = strtr($block, self::$dateFormatReplacements12);
} }
}
}
$format = implode('"', $blocks);
// escape any quoted characters so that DateTime format() will render them correctly
$format = preg_replace_callback('/"(.*)"/U', ['self', 'escapeQuotesCallback'], $format);
$dateObj = \PHPExcel\Shared\Date::ExcelToPHPObject($value); $dateObj = \PHPExcel\Shared\Date::ExcelToPHPObject($value);
$value = $dateObj->format($format); $value = $dateObj->format($format);
@ -553,10 +570,12 @@ class NumberFormat extends Supervisor implements \PHPExcel\IComparable
return $value; return $value;
} }
// Get the sections, there can be up to four sections // Convert any escaped characters to quoted strings, e.g. (\T to "T")
$sections = explode(';', $format); $format = preg_replace('/(\\\(.))(?=(?:[^"]|"[^"]*")*$)/u', '"${2}"', $format);
// Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
$sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);
// Fetch the relevant section depending on whether number is positive, negative, or zero? // Extract the relevant section depending on whether number is positive, negative, or zero?
// Text not supported yet. // Text not supported yet.
// Here is how the sections apply to various values in Excel: // Here is how the sections apply to various values in Excel:
// 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT] // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT]
@ -597,9 +616,13 @@ class NumberFormat extends Supervisor implements \PHPExcel\IComparable
$format = preg_replace($color_regex, '', $format); $format = preg_replace($color_regex, '', $format);
// 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
// Check for date/time characters (not inside quotes)
if (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format, $matches)) {
// datetime format
self::formatAsDate($value, $format); self::formatAsDate($value, $format);
} elseif (preg_match('/%$/', $format)) { // % number format } elseif (preg_match('/%$/', $format)) {
// % number format
self::formatAsPercentage($value, $format); self::formatAsPercentage($value, $format);
} else { } else {
if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) { if ($format === self::FORMAT_CURRENCY_EUR_SIMPLE) {

View File

@ -0,0 +1,35 @@
<?php
require_once 'testDataFileIterator.php';
class NumberFormatDateTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
if (!defined('PHPEXCEL_ROOT')) {
define('PHPEXCEL_ROOT', APPLICATION_PATH . '/');
}
require_once(PHPEXCEL_ROOT . 'PHPExcel/Autoloader.php');
PHPExcel_Shared_String::setDecimalSeparator('.');
PHPExcel_Shared_String::setThousandsSeparator(',');
}
/**
* @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/NumberFormatDates.data');
}
}

View File

@ -0,0 +1,6 @@
22269.0625, 'dd-mm-yyyy hh:mm:ss', "19-12-1960 01:30:00"
22269.0625, 'MM/DD/YYYY HH:MM:SS', "12/19/1960 01:30:00" // Oasis uses upper-case
22269.0625, 'yyyy-mm-dd\Thh:mm:ss', "1960-12-19T01:30:00" // Date with plaintext escaped with a \
22269.0625, 'yyyy-mm-dd"T"hh:mm:ss \Z', "1960-12-19T01:30:00 Z" // Date with plaintext in quotes
22269.0625, '"y-m-d" yyyy-mm-dd "h:m:s" hh:mm:ss', "y-m-d 1960-12-19 h:m:s 01:30:00" // Date with quoted formatting characters
22269.0625, '"y-m-d "yyyy-mm-dd" h:m:s "hh:mm:ss', "y-m-d 1960-12-19 h:m:s 01:30:00" // Date with quoted formatting characters