Compare commits

..
20 Commits
Author SHA1 Message Date
Adrien Loison 582da8403d Merge pull request #139 from box/fix_phpdoc
Fix PHPDoc to work with Augmented Types
2015-11-05 15:52:56 -08:00
Adrien Loison 8b666fc6cd Fix PHPDoc to work with Augmented Types 2015-11-05 15:48:26 -08:00
Adrien Loison f8aab6eefd Merge pull request #138 from box/increase_entropy_uniqid
Increase entropy of uniqid
2015-11-05 10:44:47 -08:00
Adrien Loison c4c6dddb20 Increase entropy of uniqid
This is to avoid conflicts if two folders are being created at the exact same time.
2015-11-05 10:40:19 -08:00
Adrien Loison 6c97141679 Merge pull request #133 from philipbrown/patch-1
Update README.md
2015-10-27 11:44:01 -07:00
Philip Brown 9c8cc05364 Update README.md 2015-10-27 18:34:58 +00:00
Adrien Loison 2a9400dfca Merge pull request #131 from box/better_date_support_xlsx
Better date support
2015-10-23 16:26:00 -07:00
Adrien Loison 8ef6bdac62 Better date support
Although Excel has a Date type, older Excel versions use numeric values to store dates.
The value represents the number of days since Jan 1st, 1900.
The only way to tell if the value is a number or a date is to look at the styles.xml and check if the cell has date formatting.
2015-10-23 16:04:38 -07:00
Adrien Loison 46c246e6a4 Merge pull request #130 from box/highlight_xlsx_over_csv
Highlight XLSX over CSV in documentation
2015-10-23 11:00:04 -07:00
Adrien Loison 8fd606ae4f Highlight XLSX over CSV in documentation 2015-10-23 10:52:12 -07:00
Adrien Loison 6767386daf Merge pull request #128 from box/increase_max_read_bytes_for_csv
Increase max read bytes per line for CSV
2015-10-22 13:39:18 -07:00
Adrien Loison 3395d3abb3 Increase max read bytes per line for CSV
Specify a bigger value than the default one to support long lines.
2015-10-22 10:54:12 -07:00
Adrien Loison d1c4d563c1 Merge pull request #126 from box/fix_xmlreader_open_issue_windows
Fix "Cannot open file" issue with XMLReader::open on Windows
2015-10-15 10:41:12 -07:00
Adrien Loison 01cc8b3da0 Fix "Cannot open file" issue with XMLReader::open on Windows
This occurred when using relative paths. Using realpath() solves this issue.
2015-10-15 09:19:47 -07:00
Adrien Loison 45980195cd Merge pull request #121 from box/add_test_for_empty_csv
Fix infinite loop for CSV with all lines empty
2015-10-05 21:15:11 +02:00
Adrien Loison a1a1077677 Fix infinite loop for CSV with all lines empty
Only occured with multiline CSV files
2015-10-05 21:10:41 +02:00
Adrien Loison 16d0290a17 Merge pull request #115 from box/add_api_phpdoc
Added @api tag for documentation
2015-09-04 12:52:32 -07:00
Adrien Loison f8c39287ad Added @api tag for documentation 2015-09-04 11:43:01 -07:00
Adrien Loison 118810de22 Merge pull request #114 from box/remove_duplicate_method
Remove duplicated method
2015-09-04 11:42:52 -07:00
Adrien Loison 3720c9ea1c Remove duplicated method 2015-09-04 11:37:44 -07:00
52 changed files with 711 additions and 81 deletions

No files matched your search

+6 -13
View File
@@ -18,16 +18,9 @@ Join the community and come discuss about Spout: [![Gitter](https://badges.gitte
Spout can be installed directly from [Composer](https://getcomposer.org/).
Add "box/spout" as a dependency in your project's composer.json file:
```json
"require": {
"box/spout": "~2.0"
}
Run the following command:
```
Then run the install command from Composer:
```
php composer.phar install
$ composer require box/spout
```
### Manual installation
@@ -62,8 +55,8 @@ Regardless of the file type, the interface to read a file is always the same:
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\Common\Type;
$reader = ReaderFactory::create(Type::CSV); // for CSV files
//$reader = ReaderFactory::create(Type::XLSX); // for XLSX files
$reader = ReaderFactory::create(Type::XLSX); // for XLSX files
//$reader = ReaderFactory::create(Type::CSV); // for CSV files
//$reader = ReaderFactory::create(Type::ODS); // for ODS files
$reader->open($filePath);
@@ -87,8 +80,8 @@ As with the reader, there is one common interface to write data to a file:
use Box\Spout\Writer\WriterFactory;
use Box\Spout\Common\Type;
$writer = WriterFactory::create(Type::CSV); // for CSV files
//$writer = WriterFactory::create(Type::XLSX); // for XLSX files
$writer = WriterFactory::create(Type::XLSX); // for XLSX files
//$writer = WriterFactory::create(Type::CSV); // for CSV files
//$writer = WriterFactory::create(Type::ODS); // for ODS files
$writer->openToFile($filePath); // write data to a file or to a PHP stream
@@ -1,12 +0,0 @@
<?php
namespace Box\Spout\Common\Exception;
/**
* Class BadUsageException
*
* @package Box\Spout\Common\Exception
*/
class BadUsageException extends SpoutException
{
}
@@ -5,6 +5,7 @@ namespace Box\Spout\Common\Exception;
/**
* Class EncodingConversionException
*
* @api
* @package Box\Spout\Common\Exception
*/
class EncodingConversionException extends SpoutException
@@ -5,6 +5,7 @@ namespace Box\Spout\Common\Exception;
/**
* Class IOException
*
* @api
* @package Box\Spout\Common\Exception
*/
class IOException extends SpoutException
@@ -5,6 +5,7 @@ namespace Box\Spout\Common\Exception;
/**
* Class InvalidArgumentException
*
* @api
* @package Box\Spout\Common\Exception
*/
class InvalidArgumentException extends SpoutException
@@ -6,6 +6,7 @@ namespace Box\Spout\Common\Exception;
* Class SpoutException
*
* @package Box\Spout\Common\Exception
* @abstract
*/
abstract class SpoutException extends \Exception
{
@@ -5,6 +5,7 @@ namespace Box\Spout\Common\Exception;
/**
* Class UnsupportedTypeException
*
* @api
* @package Box\Spout\Common\Exception
*/
class UnsupportedTypeException extends SpoutException
@@ -164,7 +164,43 @@ class GlobalFunctionsHelper
*/
public function file_get_contents($filePath)
{
return file_get_contents($filePath);
$realFilePath = $this->convertToUseRealPath($filePath);
return file_get_contents($realFilePath);
}
/**
* Updates the given file path to use a real path.
* This is to avoid issues on some Windows setup.
*
* @param string $filePath File path
* @return string The file path using a real path
*/
protected function convertToUseRealPath($filePath)
{
$realFilePath = $filePath;
if ($this->isZipStream($filePath)) {
if (preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) {
$documentPath = $matches[1];
$documentInsideZipPath = $matches[2];
$realFilePath = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath;
}
} else {
$realFilePath = realpath($filePath);
}
return $realFilePath;
}
/**
* Returns whether the given path is a zip stream.
*
* @param string $path Path pointing to a document
* @return bool TRUE if path is a zip stream, FALSE otherwise
*/
protected function isZipStream($path)
{
return (strpos($path, 'zip://') === 0);
}
/**
+2
View File
@@ -5,6 +5,8 @@ namespace Box\Spout\Common;
/**
* Class Type
* This class references the supported types
*
* @api
*/
abstract class Type
{
+6 -1
View File
@@ -55,6 +55,7 @@ abstract class AbstractReader implements ReaderInterface
* Prepares the reader to read the given file. It also makes sure
* that the file exists and is readable.
*
* @api
* @param string $filePath Path of the file to be read
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the file at the given path does not exist, is not readable or is corrupted
@@ -71,7 +72,9 @@ abstract class AbstractReader implements ReaderInterface
}
try {
$this->openReader($filePath);
// Need to use realpath to fix "Can't open file" on some Windows setup
$fileRealPath = realpath($filePath);
$this->openReader($fileRealPath);
$this->isStreamOpened = true;
} catch (\Exception $exception) {
throw new IOException("Could not open $filePath for reading! ({$exception->getMessage()})");
@@ -92,6 +95,7 @@ abstract class AbstractReader implements ReaderInterface
/**
* Returns an iterator to iterate over sheets.
*
* @api
* @return \Iterator To iterate over sheets
* @throws \Box\Spout\Reader\Exception\ReaderNotOpenedException If called before opening the reader
*/
@@ -107,6 +111,7 @@ abstract class AbstractReader implements ReaderInterface
/**
* Closes the reader, preventing any additional reading
*
* @api
* @return void
*/
public function close()
+12 -4
View File
@@ -13,6 +13,12 @@ use Box\Spout\Common\Helper\EncodingHelper;
*/
class RowIterator implements IteratorInterface
{
/**
* If no value is given to stream_get_line(), it defaults to 8192 (which may be too low).
* Alignement with other functions like fgets() is discussed here: https://bugs.php.net/bug.php?id=48421
*/
const MAX_READ_BYTES_PER_LINE = 32768;
/** @var resource Pointer to the CSV file to read */
protected $filePointer;
@@ -111,11 +117,14 @@ class RowIterator implements IteratorInterface
*/
public function next()
{
$lineData = false;
$this->hasReachedEndOfFile = $this->globalFunctionsHelper->feof($this->filePointer);
if (!$this->hasReachedEndOfFile) {
if ($this->hasReachedEndOfFile) {
return;
}
do {
$lineData = false;
$utf8EncodedLineData = $this->getNextUTF8EncodedLine();
if ($utf8EncodedLineData !== false) {
$lineData = $this->globalFunctionsHelper->str_getcsv($utf8EncodedLineData, $this->fieldDelimiter, $this->fieldEnclosure);
@@ -132,7 +141,6 @@ class RowIterator implements IteratorInterface
$this->hasReachedEndOfFile = $hasNowReachedEndOfFile;
}
}
}
/**
* Returns the next line, converted if necessary to UTF-8.
@@ -145,7 +153,7 @@ class RowIterator implements IteratorInterface
{
// Read until the EOL delimiter or EOF is reached. The delimiter's encoding needs to match the CSV's encoding.
$encodedEOLDelimiter = $this->getEncodedEOLDelimiter();
$encodedLineData = $this->globalFunctionsHelper->stream_get_line($this->filePointer, 0, $encodedEOLDelimiter);
$encodedLineData = $this->globalFunctionsHelper->stream_get_line($this->filePointer, self::MAX_READ_BYTES_PER_LINE, $encodedEOLDelimiter);
// If the line could have been read, it can be converted to UTF-8
$utf8EncodedLineData = ($encodedLineData !== false) ?
+3 -2
View File
@@ -11,7 +11,7 @@ use Box\Spout\Reader\SheetInterface;
*/
class Sheet implements SheetInterface
{
/** @var RowIterator To iterate over the CSV's rows */
/** @var \Box\Spout\Reader\CSV\RowIterator To iterate over the CSV's rows */
protected $rowIterator;
/**
@@ -27,7 +27,8 @@ class Sheet implements SheetInterface
}
/**
* @return RowIterator
* @api
* @return \Box\Spout\Reader\CSV\RowIterator
*/
public function getRowIterator()
{
+2 -2
View File
@@ -12,7 +12,7 @@ use Box\Spout\Reader\IteratorInterface;
*/
class SheetIterator implements IteratorInterface
{
/** @var Sheet The CSV unique "sheet" */
/** @var \Box\Spout\Reader\CSV\Sheet The CSV unique "sheet" */
protected $sheet;
/** @var bool Whether the unique "sheet" has already been read */
@@ -67,7 +67,7 @@ class SheetIterator implements IteratorInterface
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
*
* @return Sheet
* @return \Box\Spout\Reader\CSV\Sheet
*/
public function current()
{
@@ -5,6 +5,7 @@ namespace Box\Spout\Reader\Exception;
/**
* Class IteratorNotRewindableException
*
* @api
* @package Box\Spout\Reader\Exception
*/
class IteratorNotRewindableException extends ReaderException
@@ -5,6 +5,7 @@ namespace Box\Spout\Reader\Exception;
/**
* Class NoSheetsFoundException
*
* @api
* @package Box\Spout\Reader\Exception
*/
class NoSheetsFoundException extends ReaderException
@@ -5,6 +5,7 @@ namespace Box\Spout\Reader\Exception;
/**
* Class ReaderNotOpenedException
*
* @api
* @package Box\Spout\Reader\Exception
*/
class ReaderNotOpenedException extends ReaderException
@@ -5,6 +5,7 @@ namespace Box\Spout\Reader\Exception;
/**
* Class SharedStringNotFoundException
*
* @api
* @package Box\Spout\Reader\Exception
*/
class SharedStringNotFoundException extends ReaderException
+5 -2
View File
@@ -13,7 +13,7 @@ use Box\Spout\Reader\Wrapper\XMLReader;
*/
class Sheet implements SheetInterface
{
/** @var RowIterator To iterate over sheet's rows */
/** @var \Box\Spout\Reader\ODS\RowIterator To iterate over sheet's rows */
protected $rowIterator;
/** @var int ID of the sheet */
@@ -38,7 +38,8 @@ class Sheet implements SheetInterface
}
/**
* @return RowIterator
* @api
* @return \Box\Spout\Reader\ODS\RowIterator
*/
public function getRowIterator()
{
@@ -46,6 +47,7 @@ class Sheet implements SheetInterface
}
/**
* @api
* @return int Index of the sheet, based on order of creation (zero-based)
*/
public function getIndex()
@@ -54,6 +56,7 @@ class Sheet implements SheetInterface
}
/**
* @api
* @return string Name of the sheet
*/
public function getName()
+1 -1
View File
@@ -102,7 +102,7 @@ class SheetIterator implements IteratorInterface
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
*
* @return Sheet
* @return \Box\Spout\Reader\ODS\Sheet
*/
public function current()
{
+1
View File
@@ -18,6 +18,7 @@ class ReaderFactory
/**
* This creates an instance of the appropriate reader, given the type of the file to be read
*
* @api
* @param string $readerType Type of the reader to instantiate
* @return ReaderInterface
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException
@@ -151,6 +151,22 @@ class SimpleXMLElement
return $doesElementExist ? $this->wrapSimpleXMLElement($realElement) : null;
}
/**
* Returns the immediate children.
*
* @return array The children
*/
public function children()
{
$children = [];
foreach ($this->simpleXMLElement->children() as $child) {
$children[] = $this->wrapSimpleXMLElement($child);
}
return $children;
}
/**
* @return string
*/
+29 -4
View File
@@ -26,20 +26,45 @@ class XMLReader extends \XMLReader
public function open($URI, $encoding = null, $options = 0)
{
$wasOpenSuccessful = false;
$realPathURI = $this->convertURIToUseRealPath($URI);
// HHVM does not check if file exists within zip file
// @link https://github.com/facebook/hhvm/issues/5779
if ($this->isRunningHHVM() && $this->isZipStream($URI)) {
if ($this->fileExistsWithinZip($URI)) {
$wasOpenSuccessful = parent::open($URI, $encoding, $options|LIBXML_NONET);
if ($this->isRunningHHVM() && $this->isZipStream($realPathURI)) {
if ($this->fileExistsWithinZip($realPathURI)) {
$wasOpenSuccessful = parent::open($realPathURI, $encoding, $options|LIBXML_NONET);
}
} else {
$wasOpenSuccessful = parent::open($URI, $encoding, $options|LIBXML_NONET);
$wasOpenSuccessful = parent::open($realPathURI, $encoding, $options|LIBXML_NONET);
}
return $wasOpenSuccessful;
}
/**
* Updates the given URI to use a real path.
* This is to avoid issues on some Windows setup.
*
* @param string $URI URI
* @return string The URI using a real path
*/
protected function convertURIToUseRealPath($URI)
{
$realPathURI = $URI;
if ($this->isZipStream($URI)) {
if (preg_match('/zip:\/\/(.*)#(.*)/', $URI, $matches)) {
$documentPath = $matches[1];
$documentInsideZipPath = $matches[2];
$realPathURI = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath;
}
} else {
$realPathURI = realpath($URI);
}
return $realPathURI;
}
/**
* Returns whether the given URI is a zip stream.
*
@@ -25,19 +25,35 @@ class CellValueFormatter
/** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_TYPE = 't';
const XML_ATTRIBUTE_STYLE_ID = 's';
/** Constants used for date formatting */
const NUM_DAYS_FROM_JAN_1_1900_TO_JAN_1_1970 = 25569; // actually 25567 but accommodating for an Excel bug with some bisextile years
const NUM_SECONDS_IN_ONE_DAY = 86400;
/**
* February 29th, 1900 is NOT a leap year but Excel thinks it is...
* @see https://en.wikipedia.org/wiki/Year_1900_problem#Microsoft_Excel
*/
const ERRONEOUS_EXCEL_LEAP_YEAR_DAY = 60;
/** @var SharedStringsHelper Helper to work with shared strings */
protected $sharedStringsHelper;
/** @var StyleHelper Helper to work with styles */
protected $styleHelper;
/** @var \Box\Spout\Common\Escaper\XLSX Used to unescape XML data */
protected $escaper;
/**
* @param SharedStringsHelper $sharedStringsHelper Helper to work with shared strings
* @param StyleHelper $styleHelper Helper to work with styles
*/
public function __construct($sharedStringsHelper)
public function __construct($sharedStringsHelper, $styleHelper)
{
$this->sharedStringsHelper = $sharedStringsHelper;
$this->styleHelper = $styleHelper;
/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
$this->escaper = new \Box\Spout\Common\Escaper\XLSX();
@@ -53,6 +69,7 @@ class CellValueFormatter
{
// Default cell type is "n"
$cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE) ?: self::CELL_TYPE_NUMERIC;
$cellStyleId = intval($node->getAttribute(self::XML_ATTRIBUTE_STYLE_ID));
$vNodeValue = $this->getVNodeValue($node);
if (($vNodeValue === '') && ($cellType !== self::CELL_TYPE_INLINE_STRING)) {
@@ -69,7 +86,7 @@ class CellValueFormatter
case self::CELL_TYPE_BOOLEAN:
return $this->formatBooleanCellValue($vNodeValue);
case self::CELL_TYPE_NUMERIC:
return $this->formatNumericCellValue($vNodeValue);
return $this->formatNumericCellValue($vNodeValue, $cellStyleId);
case self::CELL_TYPE_DATE:
return $this->formatDateCellValue($vNodeValue);
default:
@@ -138,14 +155,49 @@ class CellValueFormatter
/**
* Returns the cell Numeric value from string of nodeValue.
* The value can also represent a timestamp and a DateTime will be returned.
*
* @param string $nodeValue
* @return int|float The value associated with the cell
* @param int $cellStyleId 0 being the default style
* @return int|float|\DateTime|null The value associated with the cell
*/
protected function formatNumericCellValue($nodeValue)
protected function formatNumericCellValue($nodeValue, $cellStyleId)
{
$cellValue = is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
// Numeric values can represent numbers as well as timestamps.
// We need to look at the style of the cell to determine whether it is one or the other.
$shouldFormatAsDate = $this->styleHelper->shouldFormatNumericValueAsDate($cellStyleId);
if ($shouldFormatAsDate) {
return $this->formatExcelTimestampValue(floatval($nodeValue));
} else {
return is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
}
}
/**
* Returns a cell's PHP Date value, associated to the given timestamp.
* NOTE: The timestamp is a float representing the number of days since January 1st, 1900.
*
* @param float $nodeValue
* @return \DateTime|null The value associated with the cell or NULL if invalid date value
*/
protected function formatExcelTimestampValue($nodeValue)
{
$numDaysSince1970Jan1 = $nodeValue - self::NUM_DAYS_FROM_JAN_1_1900_TO_JAN_1_1970;
// Fix for the erroneous leap year in Excel
if ($nodeValue < self::ERRONEOUS_EXCEL_LEAP_YEAR_DAY) {
$numDaysSince1970Jan1++;
}
$unixTimestamp = round($numDaysSince1970Jan1 * self::NUM_SECONDS_IN_ONE_DAY);
try {
$cellValue = (new \DateTime())->setTimestamp($unixTimestamp);
return $cellValue;
} catch (\Exception $e) {
return null;
}
}
/**
@@ -0,0 +1,224 @@
<?php
namespace Box\Spout\Reader\XLSX\Helper;
use Box\Spout\Reader\Wrapper\SimpleXMLElement;
use Box\Spout\Reader\Wrapper\XMLReader;
/**
* Class StyleHelper
* This class provides helper functions related to XLSX styles
*
* @package Box\Spout\Reader\XLSX\Helper
*/
class StyleHelper
{
/** Paths of XML files relative to the XLSX file root */
const STYLES_XML_FILE_PATH = 'xl/styles.xml';
/** Nodes used to find relevant information in the styles XML file */
const XML_NODE_NUM_FMTS = 'numFmts';
const XML_NODE_NUM_FMT = 'numFmt';
const XML_NODE_CELL_XFS = 'cellXfs';
const XML_NODE_XF = 'xf';
/** Attributes used to find relevant information in the styles XML file */
const XML_ATTRIBUTE_NUM_FMT_ID = 'numFmtId';
const XML_ATTRIBUTE_FORMAT_CODE = 'formatCode';
const XML_ATTRIBUTE_APPLY_NUMBER_FORMAT = 'applyNumberFormat';
/** By convention, default style ID is 0 */
const DEFAULT_STYLE_ID = 0;
/** @var string Path of the XLSX file being read */
protected $filePath;
/** @var array Array containing a mapping NUM_FMT_ID => FORMAT_CODE */
protected $customNumberFormats;
/** @var array Array containing a mapping STYLE_ID => [STYLE_ATTRIBUTES] */
protected $stylesAttributes;
/**
* @param string $filePath Path of the XLSX file being read
*/
public function __construct($filePath)
{
$this->filePath = $filePath;
}
/**
* Reads the styles.xml file and extract the relevant information from the file.
*
* @return void
*/
protected function extractRelevantInfo()
{
$this->customNumberFormats = [];
$this->stylesAttributes = [];
$stylesXmlFilePath = $this->filePath .'#' . self::STYLES_XML_FILE_PATH;
$xmlReader = new XMLReader();
if ($xmlReader->open('zip://' . $stylesXmlFilePath)) {
while ($xmlReader->read()) {
if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_NUM_FMTS)) {
$numFmtsNode = new SimpleXMLElement($xmlReader->readOuterXml());
$this->extractNumberFormats($numFmtsNode);
} else if ($xmlReader->isPositionedOnStartingNode(self::XML_NODE_CELL_XFS)) {
$cellXfsNode = new SimpleXMLElement($xmlReader->readOuterXml());
$this->extractStyleAttributes($cellXfsNode);
}
}
$xmlReader->close();
}
}
/**
* Extracts number formats from the "numFmt" nodes.
* For simplicity, the styles attributes are kept in memory. This is possible thanks
* to the reuse of formats. So 1 million cells should not use 1 million formats.
*
* @param SimpleXMLElement $numFmtsNode The "numFmts" node
* @return void
*/
protected function extractNumberFormats($numFmtsNode)
{
foreach ($numFmtsNode->children() as $numFmtNode) {
$numFmtId = intval($numFmtNode->getAttribute(self::XML_ATTRIBUTE_NUM_FMT_ID));
$formatCode = $numFmtNode->getAttribute(self::XML_ATTRIBUTE_FORMAT_CODE);
$this->customNumberFormats[$numFmtId] = $formatCode;
}
}
/**
* Extracts style attributes from the "xf" nodes, inside the "cellXfs" section.
* For simplicity, the styles attributes are kept in memory. This is possible thanks
* to the reuse of styles. So 1 million cells should not use 1 million styles.
*
* @param SimpleXMLElement $cellXfsNode The "cellXfs" node
* @return void
*/
protected function extractStyleAttributes($cellXfsNode)
{
foreach ($cellXfsNode->children() as $xfNode) {
$this->stylesAttributes[] = [
self::XML_ATTRIBUTE_NUM_FMT_ID => intval($xfNode->getAttribute(self::XML_ATTRIBUTE_NUM_FMT_ID)),
self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT => !!($xfNode->getAttribute(self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT)),
];
}
}
/**
* @return array The custom number formats
*/
protected function getCustomNumberFormats()
{
if (!isset($this->customNumberFormats)) {
$this->extractRelevantInfo();
}
return $this->customNumberFormats;
}
/**
* @return array The styles attributes
*/
protected function getStylesAttributes()
{
if (!isset($this->stylesAttributes)) {
$this->extractRelevantInfo();
}
return $this->stylesAttributes;
}
/**
* Returns whether the style with the given ID should consider
* numeric values as timestamps and format the cell as a date.
*
* @param int $styleId Zero-based style ID
* @return bool Whether the cell with the given cell should display a date instead of a numeric value
*/
public function shouldFormatNumericValueAsDate($styleId)
{
$stylesAttributes = $this->getStylesAttributes();
// Default style (0) does not format numeric values as timestamps. Only custom styles do.
// Also if the style ID does not exist in the styles.xml file, format as numeric value.
if ($styleId === self::DEFAULT_STYLE_ID || !array_key_exists($styleId, $stylesAttributes)) {
return false;
}
$styleAttributes = $stylesAttributes[$styleId];
$applyNumberFormat = $styleAttributes[self::XML_ATTRIBUTE_APPLY_NUMBER_FORMAT];
if (!$applyNumberFormat) {
return false;
}
$numFmtId = $styleAttributes[self::XML_ATTRIBUTE_NUM_FMT_ID];
return $this->doesNumFmtIdIndicateDate($numFmtId);
}
/**
* @param int $numFmtId
* @return bool Whether the number format ID indicates that the number is a timestamp
*/
protected function doesNumFmtIdIndicateDate($numFmtId)
{
return (
$this->isNumFmtIdBuiltInDateFormat($numFmtId) ||
$this->isNumFmtIdCustomDateFormat($numFmtId)
);
}
/**
* @param int $numFmtId
* @return bool Whether the number format ID indicates that the number is a timestamp
*/
protected function isNumFmtIdBuiltInDateFormat($numFmtId)
{
$builtInDateFormatIds = [14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47];
return in_array($numFmtId, $builtInDateFormatIds);
}
/**
* @param int $numFmtId
* @return bool Whether the number format ID indicates that the number is a timestamp
*/
protected function isNumFmtIdCustomDateFormat($numFmtId)
{
$customNumberFormats = $this->getCustomNumberFormats();
if (!array_key_exists($numFmtId, $customNumberFormats)) {
return false;
}
$customNumberFormat = $customNumberFormats[$numFmtId];
// Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\")
$pattern = '((?<!\\\)\[.+?(?<!\\\)\])';
$customNumberFormat = preg_replace($pattern, '', $customNumberFormat);
// custom date formats contain specific characters to represent the date:
// e - yy - m - d - h - s
// and all of their variants (yyyy - mm - dd...)
$dateFormatCharacters = ['e', 'yy', 'm', 'd', 'h', 's'];
$hasFoundDateFormatCharacter = false;
foreach ($dateFormatCharacters as $dateFormatCharacter) {
// character not preceded by "\"
$pattern = '/(?<!\\\)' . $dateFormatCharacter . '/';
if (preg_match($pattern, $customNumberFormat)) {
$hasFoundDateFormatCharacter = true;
break;
}
}
return $hasFoundDateFormatCharacter;
}
}
+7 -1
View File
@@ -8,6 +8,7 @@ use Box\Spout\Reader\IteratorInterface;
use Box\Spout\Reader\Wrapper\XMLReader;
use Box\Spout\Reader\XLSX\Helper\CellHelper;
use Box\Spout\Reader\XLSX\Helper\CellValueFormatter;
use Box\Spout\Reader\XLSX\Helper\StyleHelper;
/**
* Class RowIterator
@@ -39,6 +40,9 @@ class RowIterator implements IteratorInterface
/** @var Helper\CellValueFormatter Helper to format cell values */
protected $cellValueFormatter;
/** @var Helper\StyleHelper $styleHelper Helper to work with styles */
protected $styleHelper;
/** @var int Number of read rows */
protected $numReadRows = 0;
@@ -62,7 +66,9 @@ class RowIterator implements IteratorInterface
$this->sheetDataXMLFilePath = $this->normalizeSheetDataXMLFilePath($sheetDataXMLFilePath);
$this->xmlReader = new XMLReader();
$this->cellValueFormatter = new CellValueFormatter($sharedStringsHelper);
$this->styleHelper = new StyleHelper($filePath);
$this->cellValueFormatter = new CellValueFormatter($sharedStringsHelper, $this->styleHelper);
}
/**
+5 -2
View File
@@ -12,7 +12,7 @@ use Box\Spout\Reader\SheetInterface;
*/
class Sheet implements SheetInterface
{
/** @var RowIterator To iterate over sheet's rows */
/** @var \Box\Spout\Reader\XLSX\RowIterator To iterate over sheet's rows */
protected $rowIterator;
/** @var int Index of the sheet, based on order of creation (zero-based) */
@@ -36,7 +36,8 @@ class Sheet implements SheetInterface
}
/**
* @return RowIterator
* @api
* @return \Box\Spout\Reader\XLSX\RowIterator
*/
public function getRowIterator()
{
@@ -44,6 +45,7 @@ class Sheet implements SheetInterface
}
/**
* @api
* @return int Index of the sheet, based on order of creation (zero-based)
*/
public function getIndex()
@@ -52,6 +54,7 @@ class Sheet implements SheetInterface
}
/**
* @api
* @return string Name of the sheet
*/
public function getName()
+2 -2
View File
@@ -14,7 +14,7 @@ use Box\Spout\Reader\Exception\NoSheetsFoundException;
*/
class SheetIterator implements IteratorInterface
{
/** @var Sheet[] The list of sheet present in the file */
/** @var \Box\Spout\Reader\XLSX\Sheet[] The list of sheet present in the file */
protected $sheets;
/** @var int The index of the sheet being read (zero-based) */
@@ -79,7 +79,7 @@ class SheetIterator implements IteratorInterface
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
*
* @return Sheet
* @return \Box\Spout\Reader\XLSX\Sheet
*/
public function current()
{
@@ -24,6 +24,7 @@ abstract class AbstractMultiSheetsWriter extends AbstractWriter
* Sets whether new sheets should be automatically created when the max rows limit per sheet is reached.
* This must be set before opening the writer.
*
* @api
* @param bool $shouldCreateNewSheetsAutomatically Whether new sheets should be automatically created when the max rows limit per sheet is reached
* @return AbstractMultiSheetsWriter
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
@@ -39,6 +40,7 @@ abstract class AbstractMultiSheetsWriter extends AbstractWriter
/**
* Returns all the workbook's sheets
*
* @api
* @return Common\Sheet[] All the workbook's sheets
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
*/
@@ -60,6 +62,7 @@ abstract class AbstractMultiSheetsWriter extends AbstractWriter
/**
* Creates a new sheet and make it the current sheet. The data will now be written to this sheet.
*
* @api
* @return Common\Sheet The created sheet
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
*/
@@ -74,6 +77,7 @@ abstract class AbstractMultiSheetsWriter extends AbstractWriter
/**
* Returns the current sheet
*
* @api
* @return Common\Sheet The current sheet
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
*/
@@ -87,6 +91,7 @@ abstract class AbstractMultiSheetsWriter extends AbstractWriter
* Sets the given sheet as the current one. New data will be written to this sheet.
* The writing will resume where it stopped (i.e. data won't be truncated).
*
* @api
* @param Common\Sheet $sheet The sheet to set as current
* @return void
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
+7 -2
View File
@@ -85,6 +85,7 @@ abstract class AbstractWriter implements WriterInterface
* Inits the writer and opens it to accept data.
* By using this method, the data will be written to a file.
*
* @api
* @param string $outputFilePath Path of the output file that will contain the data
* @return AbstractWriter
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable
@@ -108,6 +109,7 @@ abstract class AbstractWriter implements WriterInterface
*
* @codeCoverageIgnore
*
* @api
* @param string $outputFileName Name of the output file that will contain the data. If a path is passed in, only the file name will be kept
* @return AbstractWriter
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
@@ -174,7 +176,7 @@ abstract class AbstractWriter implements WriterInterface
* @param array $dataRow Array containing data to be streamed.
* If empty, no data is added (i.e. not even as a blank row)
* Example: $dataRow = ['data1', 1234, null, '', 'data5', false];
*
* @api
* @return AbstractWriter
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
* @throws \Box\Spout\Common\Exception\IOException If unable to write data
@@ -197,6 +199,7 @@ abstract class AbstractWriter implements WriterInterface
* Write given data to the output and apply the given style.
* @see addRow
*
* @api
* @param array $dataRow Array of array containing data to be streamed.
* @param Style\Style $style Style to be applied to the row.
* @return AbstractWriter
@@ -220,13 +223,13 @@ abstract class AbstractWriter implements WriterInterface
/**
* Write given data to the output. New data will be appended to end of stream.
*
* @api
* @param array $dataRows Array of array containing data to be streamed.
* If a row is empty, it won't be added (i.e. not even as a blank row)
* Example: $dataRows = [
* ['data11', 12, , '', 'data13'],
* ['data21', 'data22', null, false],
* ];
*
* @return AbstractWriter
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
@@ -251,6 +254,7 @@ abstract class AbstractWriter implements WriterInterface
* Write given data to the output and apply the given style.
* @see addRows
*
* @api
* @param array $dataRows Array of array containing data to be streamed.
* @param Style\Style $style Style to be applied to the rows.
* @return AbstractWriter
@@ -309,6 +313,7 @@ abstract class AbstractWriter implements WriterInterface
* Closes the writer. This will close the streamer as well, preventing new data
* to be written to the file.
*
* @api
* @return void
*/
public function close()
+2
View File
@@ -32,6 +32,7 @@ class Writer extends AbstractWriter
/**
* Sets the field delimiter for the CSV
*
* @api
* @param string $fieldDelimiter Character that delimits fields
* @return Writer
*/
@@ -44,6 +45,7 @@ class Writer extends AbstractWriter
/**
* Sets the field enclosure for the CSV
*
* @api
* @param string $fieldEnclosure Character that enclose fields
* @return Writer
*/
+3
View File
@@ -44,6 +44,7 @@ class Sheet
}
/**
* @api
* @return int Index of the sheet, based on order of creation (zero-based)
*/
public function getIndex()
@@ -52,6 +53,7 @@ class Sheet
}
/**
* @api
* @return string Name of the sheet
*/
public function getName()
@@ -66,6 +68,7 @@ class Sheet
* - it should not contain these characters: \ / ? * : [ or ]
* - it should be unique
*
* @api
* @param string $name Name of the sheet
* @return Sheet
* @throws \Box\Spout\Writer\Exception\InvalidSheetNameException If the sheet's name is invalid.
@@ -5,6 +5,7 @@ namespace Box\Spout\Writer\Exception;
/**
* Class InvalidColorException
*
* @api
* @package Box\Spout\Writer\Exception
*/
class InvalidColorException extends WriterException
@@ -5,6 +5,7 @@ namespace Box\Spout\Writer\Exception;
/**
* Class InvalidSheetNameException
*
* @api
* @package Box\Spout\Writer\Exception
*/
class InvalidSheetNameException extends WriterException
@@ -5,6 +5,7 @@ namespace Box\Spout\Writer\Exception;
/**
* Class SheetNotFoundException
*
* @api
* @package Box\Spout\Writer\Exception
*/
class SheetNotFoundException extends WriterException
@@ -5,6 +5,7 @@ namespace Box\Spout\Writer\Exception;
/**
* Class WriterAlreadyOpenedException
*
* @api
* @package Box\Spout\Writer\Exception
*/
class WriterAlreadyOpenedException extends WriterException
@@ -5,6 +5,7 @@ namespace Box\Spout\Writer\Exception;
/**
* Class WriterNotOpenedException
*
* @api
* @package Box\Spout\Writer\Exception
*/
class WriterNotOpenedException extends WriterException
+1
View File
@@ -27,6 +27,7 @@ class Writer extends AbstractMultiSheetsWriter
* Sets a custom temporary folder for creating intermediate files/folders.
* This must be set before opening the writer.
*
* @api
* @param string $tempFolder Temporary folder where the files to create the ODS will be stored
* @return Writer
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
+1
View File
@@ -29,6 +29,7 @@ class Color
/**
* Returns an RGB color from R, G and B values
*
* @api
* @param int $red Red component, 0 - 255
* @param int $green Green component, 0 - 255
* @param int $blue Blue component, 0 - 255
+9
View File
@@ -24,6 +24,7 @@ class StyleBuilder
/**
* Makes the font bold.
*
* @api
* @return StyleBuilder
*/
public function setFontBold()
@@ -35,6 +36,7 @@ class StyleBuilder
/**
* Makes the font italic.
*
* @api
* @return StyleBuilder
*/
public function setFontItalic()
@@ -46,6 +48,7 @@ class StyleBuilder
/**
* Makes the font underlined.
*
* @api
* @return StyleBuilder
*/
public function setFontUnderline()
@@ -57,6 +60,7 @@ class StyleBuilder
/**
* Makes the font struck through.
*
* @api
* @return StyleBuilder
*/
public function setFontStrikethrough()
@@ -68,6 +72,7 @@ class StyleBuilder
/**
* Sets the font size.
*
* @api
* @param int $fontSize Font size, in pixels
* @return StyleBuilder
*/
@@ -80,6 +85,7 @@ class StyleBuilder
/**
* Sets the font color.
*
* @api
* @param string $fontColor ARGB color (@see Color)
* @return StyleBuilder
*/
@@ -92,6 +98,7 @@ class StyleBuilder
/**
* Sets the font name.
*
* @api
* @param string $fontName Name of the font to use
* @return StyleBuilder
*/
@@ -105,6 +112,7 @@ class StyleBuilder
* Makes the text wrap in the cell if it's too long or
* on multiple lines.
*
* @api
* @return StyleBuilder
*/
public function setShouldWrapText()
@@ -116,6 +124,7 @@ class StyleBuilder
/**
* Returns the configured style. The style is cached and can be reused.
*
* @api
* @return Style
*/
public function build()
+1
View File
@@ -18,6 +18,7 @@ class WriterFactory
/**
* This creates an instance of the appropriate writer, given the type of the file to be read
*
* @api
* @param string $writerType Type of the writer to instantiate
* @return WriterInterface
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException
@@ -94,7 +94,7 @@ class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper
*/
protected function createRootFolder()
{
$this->rootFolder = $this->createFolder($this->baseFolderPath, uniqid('xlsx'));
$this->rootFolder = $this->createFolder($this->baseFolderPath, uniqid('xlsx', true));
return $this;
}
+2 -19
View File
@@ -27,9 +27,6 @@ class Writer extends AbstractMultiSheetsWriter
/** @var bool Whether inline or shared strings should be used - inline string is more memory efficient */
protected $shouldUseInlineStrings = true;
/** @var bool Whether new sheets should be automatically created when the max rows limit per sheet is reached */
protected $shouldCreateNewSheetsAutomatically = true;
/** @var Internal\Workbook The workbook for the XLSX file */
protected $book;
@@ -37,6 +34,7 @@ class Writer extends AbstractMultiSheetsWriter
* Sets a custom temporary folder for creating intermediate files/folders.
* This must be set before opening the writer.
*
* @api
* @param string $tempFolder Temporary folder where the files to create the XLSX will be stored
* @return Writer
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
@@ -53,6 +51,7 @@ class Writer extends AbstractMultiSheetsWriter
* Use inline string to be more memory efficient. If set to false, it will use shared strings.
* This must be set before opening the writer.
*
* @api
* @param bool $shouldUseInlineStrings Whether inline or shared strings should be used
* @return Writer
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
@@ -65,22 +64,6 @@ class Writer extends AbstractMultiSheetsWriter
return $this;
}
/**
* Sets whether new sheets should be automatically created when the max rows limit per sheet is reached.
* This must be set before opening the writer.
*
* @param bool $shouldCreateNewSheetsAutomatically Whether new sheets should be automatically created when the max rows limit per sheet is reached
* @return Writer
* @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
*/
public function setShouldCreateNewSheetsAutomatically($shouldCreateNewSheetsAutomatically)
{
$this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.');
$this->shouldCreateNewSheetsAutomatically = $shouldCreateNewSheetsAutomatically;
return $this;
}
/**
* Configures the write and sets the current sheet pointer to a new sheet.
*
+23
View File
@@ -142,6 +142,29 @@ class ReaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expectedRows, $allRows);
}
/**
* @return array
*/
public function dataProviderForTestReadShouldReadEmptyFile()
{
return [
['csv_empty.csv'],
['csv_all_lines_empty.csv'],
];
}
/**
* @dataProvider dataProviderForTestReadShouldReadEmptyFile
*
* @param string $fileName
* @return void
*/
public function testReadShouldReadEmptyFile($fileName)
{
$allRows = $this->getAllRowsForFile($fileName);
$this->assertEquals([], $allRows);
}
/**
* @return void
*/
@@ -163,4 +163,39 @@ class XMLReaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expectedResult, $isZipStream);
}
/**
* @return array
*/
public function dataProviderForTestConvertURIToUseRealPath()
{
$tempFolder = realpath(sys_get_temp_dir());
return [
['/../../../' . $tempFolder . '/test.xlsx', $tempFolder . '/test.xlsx'],
[$tempFolder . '/test.xlsx', $tempFolder . '/test.xlsx'],
['zip://' . $tempFolder . '/test.xlsx#test.xml', 'zip://' . $tempFolder . '/test.xlsx#test.xml'],
['zip:///../../../' . $tempFolder . '/test.xlsx#test.xml', 'zip://' . $tempFolder . '/test.xlsx#test.xml'],
];
}
/**
* @dataProvider dataProviderForTestConvertURIToUseRealPath
*
* @param string $URI
* @param string $expectedConvertedURI
* @return void
*/
public function testConvertURIToUseRealPath($URI, $expectedConvertedURI)
{
$tempFolder = sys_get_temp_dir();
touch($tempFolder . '/test.xlsx');
$xmlReader = new XMLReader();
$convertedURI = \ReflectionHelper::callMethodOnObject($xmlReader, 'convertURIToUseRealPath', $URI);
$this->assertEquals($expectedConvertedURI, $convertedURI);
unlink($tempFolder . '/test.xlsx');
}
}
@@ -0,0 +1,134 @@
<?php
namespace Box\Spout\Reader\XLSX\Helper;
/**
* Class StyleHelperTest
*
* @package Box\Spout\Reader\XLSX\Helper
*/
class StyleHelperTest extends \PHPUnit_Framework_TestCase
{
/**
* @param array $styleAttributes
* @param array|void $customNumberFormats
* @return StyleHelper
*/
private function getStyleHelperMock($styleAttributes, $customNumberFormats = [])
{
/** @var StyleHelper $styleHelper */
$styleHelper = $this->getMockBuilder('\Box\Spout\Reader\XLSX\Helper\StyleHelper')
->setMethods(['getCustomNumberFormats', 'getStylesAttributes'])
->disableOriginalConstructor()
->getMock();
$styleHelper->method('getStylesAttributes')->willReturn($styleAttributes);
$styleHelper->method('getCustomNumberFormats')->willReturn($customNumberFormats);
return $styleHelper;
}
/**
* @return void
*/
public function testShouldFormatNumericValueAsDateWithDefaultStyle()
{
$styleHelper = $this->getStyleHelperMock([]);
$shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(0);
$this->assertFalse($shouldFormatAsDate);
}
/**
* @return void
*/
public function testShouldFormatNumericValueAsDateWhenStyleIdNotListed()
{
$styleHelper = $this->getStyleHelperMock([['applyNumberFormat' => true]]);
$shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1);
$this->assertFalse($shouldFormatAsDate);
}
/**
* @return void
*/
public function testShouldFormatNumericValueAsDateWhenShouldNotApplyNumberFormat()
{
$styleHelper = $this->getStyleHelperMock([[], ['applyNumberFormat' => false]]);
$shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1);
$this->assertFalse($shouldFormatAsDate);
}
/**
* @return void
*/
public function testShouldFormatNumericValueAsDateWithBuiltinDateFormats()
{
$builtinNumFmtIdsForDate = [14, 15, 16, 17, 18, 19, 20, 21, 22, 45, 46, 47];
foreach ($builtinNumFmtIdsForDate as $builtinNumFmtIdForDate) {
$styleHelper = $this->getStyleHelperMock([[], ['applyNumberFormat' => true, 'numFmtId' => $builtinNumFmtIdForDate]]);
$shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1);
$this->assertTrue($shouldFormatAsDate);
}
}
/**
* @return void
*/
public function testShouldFormatNumericValueAsDateWhenCustomNumberFormatNotFound()
{
$styleHelper = $this->getStyleHelperMock([[], ['applyNumberFormat' => true, 'numFmtId' => 165]], [166 => []]);
$shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1);
$this->assertFalse($shouldFormatAsDate);
}
/**
* @return array
*/
public function dataProviderForCustomDateFormats()
{
return [
// number format, expectedResult
['[$-409]dddd\,\ mmmm\ d\,\ yy', true],
['[$-409]d\-mmm\-yy;@', true],
['[$-409]d\-mmm\-yyyy;@', true],
['mm/dd/yy;@', true],
['[$-F800]dddd\,\ mmmm\ dd\,\ yyyy', true],
['m/d;@', true],
['m/d/yy;@', true],
['[$-409]d\-mmm;@', true],
['[$-409]dd\-mmm\-yy;@', true],
['[$-409]mmm\-yy;@', true],
['[$-409]mmmm\-yy;@', true],
['[$-409]mmmm\ d\,\ yyyy;@', true],
['[$-409]m/d/yy\ h:mm\ AM/PM;@', true],
['m/d/yy\ h:mm;@', true],
['[$-409]mmmmm;@', true],
['[$-409]mmmmm\-yy;@', true],
['m/d/yyyy;@', true],
['[$-409]m/d/yy\--h:mm;@', true],
['GENERAL', false],
['\ma\yb\e', false],
['[Red]foo;', false],
];
}
/**
* @dataProvider dataProviderForCustomDateFormats
*
* @param string $numberFormat
* @param bool $expectedResult
* @return void
*/
public function testShouldFormatNumericValueAsDateWithCustomDateFormats($numberFormat, $expectedResult)
{
$numFmtId = 165;
$styleHelper = $this->getStyleHelperMock([[], ['applyNumberFormat' => true, 'numFmtId' => $numFmtId]], [$numFmtId => $numberFormat]);
$shouldFormatAsDate = $styleHelper->shouldFormatNumericValueAsDate(1);
$this->assertEquals($expectedResult, $shouldFormatAsDate);
}
}
+46
View File
@@ -113,6 +113,52 @@ class ReaderTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expectedRows, $allRows);
}
/**
* @return void
*/
public function testReadShouldSupportNumericTimestampFormattedDifferentlyAsDate()
{
// make sure dates are always created with the same timezone
date_default_timezone_set('UTC');
$allRows = $this->getAllRowsForFile('sheet_with_same_numeric_value_date_formatted_differently.xlsx');
$expectedDate = \DateTime::createFromFormat('Y-m-d H:i:s', '2015-01-01 00:00:00');
$expectedRows = [
array_fill(0, 10, $expectedDate),
array_fill(0, 10, $expectedDate),
array_fill(0, 10, $expectedDate),
array_merge(array_fill(0, 7, $expectedDate), ['', '', '']),
];
$this->assertEquals($expectedRows, $allRows);
}
/**
* @return void
*/
public function testReadShouldSupportDifferentDatesAsNumericTimestamp()
{
// make sure dates are always created with the same timezone
date_default_timezone_set('UTC');
$allRows = $this->getAllRowsForFile('sheet_with_different_numeric_value_dates.xlsx');
$expectedRows = [
[
\DateTime::createFromFormat('Y-m-d H:i:s', '2015-09-01 00:00:00'),
\DateTime::createFromFormat('Y-m-d H:i:s', '2015-09-02 00:00:00'),
\DateTime::createFromFormat('Y-m-d H:i:s', '2015-09-01 22:23:00'),
],
[
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-02-28 23:59:59'),
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-03-01 00:00:00'),
\DateTime::createFromFormat('Y-m-d H:i:s', '1900-02-28 11:00:00'), // 1900-02-29 should be converted to 1900-02-28
]
];
$this->assertEquals($expectedRows, $allRows);
}
/**
* @return void
*/
+7 -7
View File
@@ -10,13 +10,13 @@ namespace Box\Spout;
trait TestUsingResource {
/** @var string Path to the test resources folder */
private $resourcesPath = 'tests/resources/';
private $resourcesPath = 'tests/resources';
/** @var string Path to the test generated resources folder */
private $generatedResourcesPath = 'tests/resources/generated/';
private $generatedResourcesPath = 'tests/resources/generated';
/** @var string Path to the test resources folder, that does not have writing permissions */
private $generatedUnwritableResourcesPath = 'tests/resources/generated/unwritable/';
private $generatedUnwritableResourcesPath = 'tests/resources/generated/unwritable';
/**
* @param string $resourceName
@@ -25,7 +25,7 @@ trait TestUsingResource {
protected function getResourcePath($resourceName)
{
$resourceType = pathinfo($resourceName, PATHINFO_EXTENSION);
$resourcePath = $this->resourcesPath . strtolower($resourceType) . '/' . $resourceName;
$resourcePath = realpath($this->resourcesPath) . '/' . strtolower($resourceType) . '/' . $resourceName;
return (file_exists($resourcePath) ? $resourcePath : null);
}
@@ -37,7 +37,7 @@ trait TestUsingResource {
protected function getGeneratedResourcePath($resourceName)
{
$resourceType = pathinfo($resourceName, PATHINFO_EXTENSION);
$generatedResourcePath = $this->generatedResourcesPath . strtolower($resourceType) . '/' . $resourceName;
$generatedResourcePath = realpath($this->generatedResourcesPath) . '/' . strtolower($resourceType) . '/' . $resourceName;
return $generatedResourcePath;
}
@@ -49,7 +49,7 @@ trait TestUsingResource {
protected function createGeneratedFolderIfNeeded($resourceName)
{
$resourceType = pathinfo($resourceName, PATHINFO_EXTENSION);
$generatedResourcePathForType = $this->generatedResourcesPath . strtolower($resourceType);
$generatedResourcePathForType = $this->generatedResourcesPath . '/' . strtolower($resourceType);
if (!file_exists($generatedResourcePathForType)) {
mkdir($generatedResourcePathForType, 0777, true);
@@ -62,7 +62,7 @@ trait TestUsingResource {
*/
protected function getGeneratedUnwritableResourcePath($resourceName)
{
return $this->generatedUnwritableResourcesPath . $resourceName;
return realpath($this->generatedUnwritableResourcesPath) . '/' . $resourceName;
}
/**
+3
View File
@@ -3,3 +3,6 @@ require_once(dirname(__DIR__) . '/vendor/autoload.php');
require_once(dirname(__DIR__) . '/tests/Spout/TestUsingResource.php');
require_once(dirname(__DIR__) . '/tests/Spout/ReflectionHelper.php');
// Make sure a timezone is set to be able to work with dates
date_default_timezone_set('UTC');
@@ -0,0 +1,2 @@
View File
Whitespace-only changes.