Merge pull request #73 from box/iterators

Moved readers to iterators
This commit is contained in:
Adrien Loison 2015-07-27 00:26:26 -07:00
commit be3932af18
53 changed files with 1350 additions and 949 deletions

View File

@ -64,10 +64,11 @@ use Box\Spout\Common\Type;
$reader = ReaderFactory::create(Type::CSV); $reader = ReaderFactory::create(Type::CSV);
$reader->open($filePath); $reader->open($filePath);
while ($reader->hasNextRow()) { foreach ($reader->getSheetIterator() as $sheet) {
$row = $reader->nextRow(); foreach ($reader->getRowIterator() as $row) {
// do stuff // do stuff
} }
}
$reader->close(); $reader->close();
``` ```
@ -81,11 +82,8 @@ use Box\Spout\Common\Type;
$reader = ReaderFactory::create(Type::XLSX); $reader = ReaderFactory::create(Type::XLSX);
$reader->open($filePath); $reader->open($filePath);
while ($reader->hasNextSheet()) { foreach ($reader->getSheetIterator() as $sheet) {
$reader->nextSheet(); foreach ($reader->getRowIterator() as $row) {
while ($reader->hasNextRow()) {
$row = $reader->nextRow();
// do stuff // do stuff
} }
} }
@ -202,8 +200,7 @@ $sheets = $writer->getSheets();
If you rely on the sheet's name in your application, you can access it and customize it this way: If you rely on the sheet's name in your application, you can access it and customize it this way:
```php ```php
// Accessing the sheet name when reading // Accessing the sheet name when reading
while ($reader->hasNextSheet()) { foreach ($reader->getSheetIterator() as $sheet) {
$sheet = $reader->nextSheet();
$sheetName = $sheet->getName(); $sheetName = $sheet->getName();
} }
@ -253,7 +250,7 @@ For information, the performance tests take about one hour to run (processing 2
When writing data, Spout is streaming the data to files, one or few lines at a time. That means that it only keeps in memory the few rows that it needs to write. Once written, the memory is freed. When writing data, Spout is streaming the data to files, one or few lines at a time. That means that it only keeps in memory the few rows that it needs to write. Once written, the memory is freed.
Same goes with reading. Only one row at a time is stored in memory. A special technique is used to handle shared strings in XLSX, storing them into several small temporary files that allows fast access. Same goes with reading. Only one row at a time is stored in memory. A special technique is used to handle shared strings in XLSX, storing them - if needed - into several small temporary files that allows fast access.
#### How long does it take to generate a file with X rows? #### How long does it take to generate a file with X rows?

View File

@ -5,7 +5,6 @@
colors="true" colors="true"
convertErrorsToExceptions="false" convertErrorsToExceptions="false"
convertWarningsToExceptions="false" convertWarningsToExceptions="false"
strict="false"
verbose="false"> verbose="false">
<testsuites> <testsuites>

View File

@ -63,7 +63,7 @@ class FileSystemHelper
$filePath = $parentFolderPath . '/' . $fileName; $filePath = $parentFolderPath . '/' . $fileName;
$wasCreationSuccessful = file_put_contents($filePath, $fileContents); $wasCreationSuccessful = file_put_contents($filePath, $fileContents);
if (!$wasCreationSuccessful) { if ($wasCreationSuccessful === false) {
throw new IOException('Unable to create file: ' . $filePath); throw new IOException('Unable to create file: ' . $filePath);
} }

View File

@ -160,7 +160,7 @@ class GlobalFunctionsHelper
* @see file_get_contents() * @see file_get_contents()
* *
* @param string $filePath * @param string $filePath
* @return bool * @return string
*/ */
public function file_get_contents($filePath) public function file_get_contents($filePath)
{ {

View File

@ -4,7 +4,6 @@ namespace Box\Spout\Reader;
use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\IOException;
use Box\Spout\Reader\Exception\ReaderNotOpenedException; use Box\Spout\Reader\Exception\ReaderNotOpenedException;
use Box\Spout\Reader\Exception\EndOfFileReachedException;
/** /**
* Class AbstractReader * Class AbstractReader
@ -14,18 +13,9 @@ use Box\Spout\Reader\Exception\EndOfFileReachedException;
*/ */
abstract class AbstractReader implements ReaderInterface abstract class AbstractReader implements ReaderInterface
{ {
/** @var int Used to keep track of the row index */
protected $currentRowIndex = 0;
/** @var bool Indicates whether the stream is currently open */ /** @var bool Indicates whether the stream is currently open */
protected $isStreamOpened = false; protected $isStreamOpened = false;
/** @var bool Indicates whether all rows have been read */
protected $hasReachedEndOfFile = false;
/** @var array Buffer used to store the row data, while checking if there are more rows to read */
protected $rowDataBuffer = null;
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */ /** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper; protected $globalFunctionsHelper;
@ -38,11 +28,11 @@ abstract class AbstractReader implements ReaderInterface
abstract protected function openReader($filePath); abstract protected function openReader($filePath);
/** /**
* Reads and returns next row if available. * Returns an iterator to iterate over sheets.
* *
* @return array|null Array that contains the data for the read row or null at the end of the file * @return \Iterator To iterate over sheets
*/ */
abstract protected function read(); abstract public function getConcreteSheetIterator();
/** /**
* Closes the reader. To be used after reading the file. * Closes the reader. To be used after reading the file.
@ -80,9 +70,6 @@ abstract class AbstractReader implements ReaderInterface
} }
} }
$this->currentRowIndex = 0;
$this->hasReachedEndOfFile = false;
try { try {
$this->openReader($filePath); $this->openReader($filePath);
$this->isStreamOpened = true; $this->isStreamOpened = true;
@ -103,82 +90,18 @@ abstract class AbstractReader implements ReaderInterface
} }
/** /**
* Returns whether all rows have been read (i.e. if we are at the end of the file). * Returns an iterator to iterate over sheets.
* To know if the end of file has been reached, it uses a buffer. If the buffer is
* empty (meaning, nothing has been read or previous read line has been consumed), then
* it reads the next line, store it in the buffer for the next time or flip a variable if
* the end of file has been reached.
* *
* @return bool Whether all rows have been read (i.e. if we are at the end of the file) * @return \Iterator To iterate over sheets
* @throws \Box\Spout\Reader\Exception\ReaderNotOpenedException If the stream was not opened first * @throws \Box\Spout\Reader\Exception\ReaderNotOpenedException If called before opening the reader
*/ */
public function hasNextRow() public function getSheetIterator()
{ {
if (!$this->isStreamOpened) { if (!$this->isStreamOpened) {
throw new ReaderNotOpenedException('Stream should be opened first.'); throw new ReaderNotOpenedException('Reader should be opened first.');
} }
if ($this->hasReachedEndOfFile) { return $this->getConcreteSheetIterator();
return false;
}
// if the buffer contains unprocessed row
if (!$this->isRowDataBufferEmpty()) {
return true;
}
// otherwise, try to read the next line line, and store it in the buffer
$this->rowDataBuffer = $this->read();
// if the buffer is still empty after reading a row, it means end of file was reached
$this->hasReachedEndOfFile = $this->isRowDataBufferEmpty();
return (!$this->hasReachedEndOfFile);
}
/**
* Returns next row if available. The row is either retrieved from the buffer if it is not empty or fetched by
* actually reading the file.
*
* @return array Array that contains the data for the read row
* @throws \Box\Spout\Common\Exception\IOException If the stream was not opened first
* @throws \Box\Spout\Reader\Exception\EndOfFileReachedException
*/
public function nextRow()
{
if (!$this->hasNextRow()) {
throw new EndOfFileReachedException('End of file was reached. Cannot read more rows.');
}
// Get data from buffer (if the buffer was empty, it was filled by the call to hasNextRow())
$rowData = $this->rowDataBuffer;
// empty buffer to mark the row as consumed
$this->emptyRowDataBuffer();
$this->currentRowIndex++;
return $rowData;
}
/**
* Returns whether the buffer where the row data is stored is empty
*
* @return bool
*/
protected function isRowDataBufferEmpty()
{
return ($this->rowDataBuffer === null);
}
/**
* Empty the buffer that stores row data
*
* @return void
*/
protected function emptyRowDataBuffer()
{
$this->rowDataBuffer = null;
} }
/** /**
@ -190,6 +113,12 @@ abstract class AbstractReader implements ReaderInterface
{ {
if ($this->isStreamOpened) { if ($this->isStreamOpened) {
$this->closeReader(); $this->closeReader();
$sheetIterator = $this->getConcreteSheetIterator();
if ($sheetIterator) {
$sheetIterator->end();
}
$this->isStreamOpened = false; $this->isStreamOpened = false;
} }
} }

View File

@ -1,130 +0,0 @@
<?php
namespace Box\Spout\Reader;
use Box\Spout\Common\Exception\IOException;
/**
* Class CSV
* This class provides support to read data from a CSV file.
*
* @package Box\Spout\Reader
*/
class CSV extends AbstractReader
{
const UTF8_BOM = "\xEF\xBB\xBF";
/** @var resource Pointer to the file to be written */
protected $filePointer;
/** @var string Defines the character used to delimit fields (one character only) */
protected $fieldDelimiter = ',';
/** @var string Defines the character used to enclose fields (one character only) */
protected $fieldEnclosure = '"';
/**
* Sets the field delimiter for the CSV
*
* @param string $fieldDelimiter Character that delimits fields
* @return CSV
*/
public function setFieldDelimiter($fieldDelimiter)
{
$this->fieldDelimiter = $fieldDelimiter;
return $this;
}
/**
* Sets the field enclosure for the CSV
*
* @param string $fieldEnclosure Character that enclose fields
* @return CSV
*/
public function setFieldEnclosure($fieldEnclosure)
{
$this->fieldEnclosure = $fieldEnclosure;
return $this;
}
/**
* Opens the file at the given path to make it ready to be read.
* The file must be UTF-8 encoded.
* @TODO add encoding detection/conversion
*
* @param string $filePath Path of the CSV file to be read
* @return void
* @throws \Box\Spout\Common\Exception\IOException
*/
protected function openReader($filePath)
{
$this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r');
if (!$this->filePointer) {
throw new IOException('Could not open file ' . $filePath . ' for reading.');
}
$this->skipUtf8Bom();
}
/**
* This skips the UTF-8 BOM if inserted at the beginning of the file
* by moving the file pointer after it, so that it is not read.
*
* @return void
*/
protected function skipUtf8Bom()
{
$this->globalFunctionsHelper->rewind($this->filePointer);
$hasUtf8Bom = ($this->globalFunctionsHelper->fgets($this->filePointer, 4) === self::UTF8_BOM);
if ($hasUtf8Bom) {
// we skip the 2 first bytes (so start from the 3rd byte)
$this->globalFunctionsHelper->fseek($this->filePointer, 3);
} else {
// if no BOM, reset the pointer to read from the beginning
$this->globalFunctionsHelper->fseek($this->filePointer, 0);
}
}
/**
* Reads and returns next row if available.
* Empty rows are skipped.
*
* @return array|null Array that contains the data for the read row or null at the end of the file
*/
protected function read()
{
$lineData = null;
if ($this->filePointer) {
do {
$lineData = $this->globalFunctionsHelper->fgetcsv($this->filePointer, 0, $this->fieldDelimiter, $this->fieldEnclosure);
} while ($lineData && $this->isEmptyLine($lineData));
}
// When reaching the end of the file, return null instead of false
return ($lineData !== false) ? $lineData : null;
}
/**
* @param array $lineData Array containing the cells value for the line
* @return bool Whether the given line is empty
*/
protected function isEmptyLine($lineData)
{
return (count($lineData) === 1 && $lineData[0] === null);
}
/**
* Closes the reader. To be used after reading the file.
*
* @return void
*/
protected function closeReader()
{
if ($this->filePointer) {
$this->globalFunctionsHelper->fclose($this->filePointer);
}
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace Box\Spout\Reader\CSV;
use Box\Spout\Reader\AbstractReader;
use Box\Spout\Common\Exception\IOException;
/**
* Class Reader
* This class provides support to read data from a CSV file.
*
* @package Box\Spout\Reader\CSV
*/
class Reader extends AbstractReader
{
/** @var resource Pointer to the file to be written */
protected $filePointer;
/** @var SheetIterator To iterator over the CSV unique "sheet" */
protected $sheetIterator;
/** @var string Defines the character used to delimit fields (one character only) */
protected $fieldDelimiter = ',';
/** @var string Defines the character used to enclose fields (one character only) */
protected $fieldEnclosure = '"';
/**
* Sets the field delimiter for the CSV.
* Needs to be called before opening the reader.
*
* @param string $fieldDelimiter Character that delimits fields
* @return Reader
*/
public function setFieldDelimiter($fieldDelimiter)
{
$this->fieldDelimiter = $fieldDelimiter;
return $this;
}
/**
* Sets the field enclosure for the CSV.
* Needs to be called before opening the reader.
*
* @param string $fieldEnclosure Character that enclose fields
* @return Reader
*/
public function setFieldEnclosure($fieldEnclosure)
{
$this->fieldEnclosure = $fieldEnclosure;
return $this;
}
/**
* Opens the file at the given path to make it ready to be read.
* The file must be UTF-8 encoded.
* @TODO add encoding detection/conversion
*
* @param string $filePath Path of the CSV file to be read
* @return void
* @throws \Box\Spout\Common\Exception\IOException
*/
protected function openReader($filePath)
{
$this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r');
if (!$this->filePointer) {
throw new IOException('Could not open file ' . $filePath . ' for reading.');
}
$this->sheetIterator = new SheetIterator($this->filePointer, $this->fieldDelimiter, $this->fieldEnclosure, $this->globalFunctionsHelper);
}
/**
* Returns an iterator to iterate over sheets.
*
* @return SheetIterator To iterate over sheets
*/
public function getConcreteSheetIterator()
{
return $this->sheetIterator;
}
/**
* Closes the reader. To be used after reading the file.
*
* @return void
*/
protected function closeReader()
{
if ($this->filePointer) {
$this->globalFunctionsHelper->fclose($this->filePointer);
}
}
}

View File

@ -0,0 +1,163 @@
<?php
namespace Box\Spout\Reader\CSV;
use Box\Spout\Reader\IteratorInterface;
/**
* Class RowIterator
* Iterate over CSV rows.
*
* @package Box\Spout\Reader\CSV
*/
class RowIterator implements IteratorInterface
{
const UTF8_BOM = "\xEF\xBB\xBF";
/** @var resource Pointer to the CSV file to read */
protected $filePointer;
/** @var int Number of read rows */
protected $numReadRows = 0;
/** @var array|null Buffer used to store the row data, while checking if there are more rows to read */
protected $rowDataBuffer = null;
/** @var bool Indicates whether all rows have been read */
protected $hasReachedEndOfFile = false;
/** @var string Defines the character used to delimit fields (one character only) */
protected $fieldDelimiter = ',';
/** @var string Defines the character used to enclose fields (one character only) */
protected $fieldEnclosure = '"';
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper;
/**
* @param resource $filePointer Pointer to the CSV file to read
* @param string $fieldDelimiter Character that delimits fields
* @param string $fieldEnclosure Character that enclose fields
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
*/
public function __construct($filePointer, $fieldDelimiter, $fieldEnclosure, $globalFunctionsHelper)
{
$this->filePointer = $filePointer;
$this->fieldDelimiter = $fieldDelimiter;
$this->fieldEnclosure = $fieldEnclosure;
$this->globalFunctionsHelper = $globalFunctionsHelper;
}
/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
*
* @return void
*/
public function rewind()
{
$this->rewindAndSkipUtf8Bom();
$this->numReadRows = 0;
$this->rowDataBuffer = null;
$this->next();
}
/**
* This rewinds and skips the UTF-8 BOM if inserted at the beginning of the file
* by moving the file pointer after it, so that it is not read.
*
* @return void
*/
protected function rewindAndSkipUtf8Bom()
{
$this->globalFunctionsHelper->rewind($this->filePointer);
$hasUtf8Bom = ($this->globalFunctionsHelper->fgets($this->filePointer, 4) === self::UTF8_BOM);
if ($hasUtf8Bom) {
// we skip the 2 first bytes (so start from the 3rd byte)
$this->globalFunctionsHelper->fseek($this->filePointer, 3);
} else {
// if no BOM, reset the pointer to read from the beginning
$this->globalFunctionsHelper->fseek($this->filePointer, 0);
}
}
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
*
* @return boolean
*/
public function valid()
{
return ($this->filePointer && !$this->hasReachedEndOfFile);
}
/**
* Move forward to next element. Empty rows are skipped.
* @link http://php.net/manual/en/iterator.next.php
*
* @return void
*/
public function next()
{
$lineData = null;
$this->hasReachedEndOfFile = feof($this->filePointer);
if (!$this->hasReachedEndOfFile) {
do {
$lineData = $this->globalFunctionsHelper->fgetcsv($this->filePointer, 0, $this->fieldDelimiter, $this->fieldEnclosure);
} while ($lineData === false || ($lineData !== null && $this->isEmptyLine($lineData)));
if ($lineData !== false && $lineData !== null) {
$this->rowDataBuffer = $lineData;
$this->numReadRows++;
}
}
}
/**
* @param array $lineData Array containing the cells value for the line
* @return bool Whether the given line is empty
*/
protected function isEmptyLine($lineData)
{
return (count($lineData) === 1 && $lineData[0] === null);
}
/**
* Return the current element from the buffer
* @link http://php.net/manual/en/iterator.current.php
*
* @return array|null
*/
public function current()
{
return $this->rowDataBuffer;
}
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
*
* @return int
*/
public function key()
{
return $this->numReadRows;
}
/**
* Cleans up what was created to iterate over the object.
*
* @return void
*/
public function end()
{
// do nothing
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace Box\Spout\Reader\CSV;
use Box\Spout\Reader\SheetInterface;
/**
* Class Sheet
*
* @package Box\Spout\Reader\CSV
*/
class Sheet implements SheetInterface
{
/** @var RowIterator To iterate over the CSV's rows */
protected $rowIterator;
/**
* @param resource $filePointer Pointer to the CSV file to read
* @param string $fieldDelimiter Character that delimits fields
* @param string $fieldEnclosure Character that enclose fields
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
*/
public function __construct($filePointer, $fieldDelimiter, $fieldEnclosure, $globalFunctionsHelper)
{
$this->rowIterator = new RowIterator($filePointer, $fieldDelimiter, $fieldEnclosure, $globalFunctionsHelper);
}
/**
* @return RowIterator
*/
public function getRowIterator()
{
return $this->rowIterator;
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace Box\Spout\Reader\CSV;
use Box\Spout\Reader\IteratorInterface;
/**
* Class SheetIterator
* Iterate over CSV unique "sheet".
*
* @package Box\Spout\Reader\CSV
*/
class SheetIterator implements IteratorInterface
{
/** @var Sheet The CSV unique "sheet" */
protected $sheet;
/** @var bool Whether the unique "sheet" has already been read */
protected $hasReadUniqueSheet = false;
/**
* @param resource $filePointer
* @param string $fieldDelimiter Character that delimits fields
* @param string $fieldEnclosure Character that enclose fields
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
*/
public function __construct($filePointer, $fieldDelimiter, $fieldEnclosure, $globalFunctionsHelper)
{
$this->sheet = new Sheet($filePointer, $fieldDelimiter, $fieldEnclosure, $globalFunctionsHelper);
}
/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
*
* @return void
*/
public function rewind()
{
$this->hasReadUniqueSheet = false;
}
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
*
* @return boolean
*/
public function valid()
{
return (!$this->hasReadUniqueSheet);
}
/**
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
*
* @return void
*/
public function next()
{
$this->hasReadUniqueSheet = true;
}
/**
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
*
* @return Sheet
*/
public function current()
{
return $this->sheet;
}
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
*
* @return int
*/
public function key()
{
return 1;
}
/**
* Cleans up what was created to iterate over the object.
*
* @return void
*/
public function end()
{
// do nothing
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace Box\Spout\Reader\Exception;
/**
* Class EndOfWorksheetsReachedException
*
* @package Box\Spout\Reader\Exception
*/
class EndOfWorksheetsReachedException extends ReaderException
{
}

View File

@ -3,10 +3,10 @@
namespace Box\Spout\Reader\Exception; namespace Box\Spout\Reader\Exception;
/** /**
* Class EndOfFileReachedException * Class NoSheetsFoundException
* *
* @package Box\Spout\Reader\Exception * @package Box\Spout\Reader\Exception
*/ */
class EndOfFileReachedException extends ReaderException class NoSheetsFoundException extends ReaderException
{ {
} }

View File

@ -1,12 +0,0 @@
<?php
namespace Box\Spout\Reader\Exception;
/**
* Class NoWorksheetsFoundException
*
* @package Box\Spout\Reader\Exception
*/
class NoWorksheetsFoundException extends ReaderException
{
}

View File

@ -1,58 +0,0 @@
<?php
namespace Box\Spout\Reader\Internal\XLSX;
/**
* Class Worksheet
* Represents a worksheet within a XLSX file
*
* @package Box\Spout\Reader\Internal\XLSX
*/
class Worksheet
{
/** @var \Box\Spout\Reader\Sheet The "external" sheet */
protected $externalSheet;
/** @var int Worksheet index, based on the order of appareance in [Content_Types].xml (zero-based) */
protected $worksheetIndex;
/** @var string Path of the XML file containing the worksheet data */
protected $dataXmlFilePath;
/**\
* @param \Box\Spout\Reader\Sheet $externalSheet The associated "external" sheet
* @param int $worksheetIndex Worksheet index, based on the order of appareance in [Content_Types].xml (zero-based)
* @param string $dataXmlFilePath Path of the XML file containing the worksheet data
*/
public function __construct($externalSheet, $worksheetIndex, $dataXmlFilePath)
{
$this->externalSheet = $externalSheet;
$this->worksheetIndex = $worksheetIndex;
$this->dataXmlFilePath = $dataXmlFilePath;
}
/**
* @return string Path of the XML file containing the worksheet data,
* without the leading slash.
*/
public function getDataXmlFilePath()
{
return ltrim($this->dataXmlFilePath, '/');
}
/**
* @return \Box\Spout\Reader\Sheet The "external" sheet
*/
public function getExternalSheet()
{
return $this->externalSheet;
}
/**
* @return int
*/
public function getWorksheetIndex()
{
return $this->worksheetIndex;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Box\Spout\Reader;
/**
* Interface IteratorInterface
*
* @package Box\Spout\Reader
*/
interface IteratorInterface extends \Iterator
{
/**
* Cleans up what was created to iterate over the object.
*
* @return void
*/
public function end();
}

View File

@ -19,7 +19,7 @@ class ReaderFactory
* This creates an instance of the appropriate reader, given the type of the file to be read * This creates an instance of the appropriate reader, given the type of the file to be read
* *
* @param string $readerType Type of the reader to instantiate * @param string $readerType Type of the reader to instantiate
* @return \Box\Spout\Reader\CSV|\Box\Spout\Reader\XLSX * @return ReaderInterface
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException * @throws \Box\Spout\Common\Exception\UnsupportedTypeException
*/ */
public static function create($readerType) public static function create($readerType)
@ -28,10 +28,10 @@ class ReaderFactory
switch ($readerType) { switch ($readerType) {
case Type::CSV: case Type::CSV:
$reader = new CSV(); $reader = new CSV\Reader();
break; break;
case Type::XLSX: case Type::XLSX:
$reader = new XLSX(); $reader = new XLSX\Reader();
break; break;
default: default:
throw new UnsupportedTypeException('No readers supporting the given type: ' . $readerType); throw new UnsupportedTypeException('No readers supporting the given type: ' . $readerType);

View File

@ -20,26 +20,12 @@ interface ReaderInterface
public function open($filePath); public function open($filePath);
/** /**
* Returns whether all rows have been read (i.e. if we are at the end of the file). * Returns an iterator to iterate over sheets.
* To know if the end of file has been reached, it uses a buffer. If the buffer is
* empty (meaning, nothing has been read or previous read line has been consumed), then
* it reads the next line, store it in the buffer for the next time or flip a variable if
* the end of file has been reached.
* *
* @return bool * @return \Iterator To iterate over sheets
* @throws \Box\Spout\Common\Exception\IOException If the stream was not opened first * @throws \Box\Spout\Reader\Exception\ReaderNotOpenedException If called before opening the reader
*/ */
public function hasNextRow(); public function getSheetIterator();
/**
* Returns next row if available. The row is either retrieved from the buffer if it is not empty or fetched by
* actually reading the file.
*
* @return array Array that contains the data for the read row
* @throws \Box\Spout\Common\Exception\IOException If the stream was not opened first
* @throws \Box\Spout\Reader\Exception\EndOfFileReachedException
*/
public function nextRow();
/** /**
* Closes the reader, preventing any additional reading * Closes the reader, preventing any additional reading

View File

@ -0,0 +1,18 @@
<?php
namespace Box\Spout\Reader;
/**
* Interface SheetInterface
*
* @package Box\Spout\Reader
*/
interface SheetInterface
{
/**
* Returns an iterator to iterate over the sheet's rows.
*
* @return \Iterator
*/
public function getRowIterator();
}

View File

@ -1,394 +0,0 @@
<?php
namespace Box\Spout\Reader;
use Box\Spout\Common\Exception\BadUsageException;
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Reader\Exception\EndOfWorksheetsReachedException;
use Box\Spout\Reader\Exception\NoWorksheetsFoundException;
use Box\Spout\Reader\Exception\ReaderNotOpenedException;
use Box\Spout\Reader\Helper\XLSX\CellHelper;
use Box\Spout\Reader\Helper\XLSX\SharedStringsHelper;
use Box\Spout\Reader\Helper\XLSX\WorksheetHelper;
/**
* Class XLSX
* This class provides support to read data from a XLSX file
*
* @package Box\Spout\Reader
*/
class XLSX extends AbstractReader
{
const CELL_TYPE_INLINE_STRING = 'inlineStr';
const CELL_TYPE_STR = 'str';
const CELL_TYPE_SHARED_STRING = 's';
const CELL_TYPE_BOOLEAN = 'b';
const CELL_TYPE_NUMERIC = 'n';
const CELL_TYPE_DATE = 'd';
const CELL_TYPE_ERROR = 'e';
/** @var string Real path of the file to read */
protected $filePath;
/** @var string Temporary folder where the temporary files will be created */
protected $tempFolder;
/** @var \ZipArchive */
protected $zip;
/** @var Helper\XLSX\SharedStringsHelper Helper to work with shared strings */
protected $sharedStringsHelper;
/** @var Helper\XLSX\WorksheetHelper Helper to work with worksheets */
protected $worksheetHelper;
/** @var Internal\XLSX\Worksheet[] The list of worksheets present in the file */
protected $worksheets;
/** @var Internal\XLSX\Worksheet The worksheet being read */
protected $currentWorksheet;
/** @var \XMLReader The XMLReader object that will help read sheets XML data */
protected $xmlReader;
/** @var int The number of columns the worksheet has (0 meaning undefined) */
protected $numberOfColumns = 0;
/**
* @param string $tempFolder Temporary folder where the temporary files will be created
* @return XLSX
*/
public function setTempFolder($tempFolder)
{
$this->tempFolder = $tempFolder;
return $this;
}
/**
* Opens the file at the given file path to make it ready to be read.
* It also parses the sharedStrings.xml file to get all the shared strings available in memory
* and fetches all the available worksheets.
*
* @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 or its content cannot be read
* @throws Exception\NoWorksheetsFoundException If there are no worksheets in the file
*/
protected function openReader($filePath)
{
$this->filePath = $filePath;
$this->zip = new \ZipArchive();
if ($this->zip->open($filePath) === true) {
$this->sharedStringsHelper = new SharedStringsHelper($filePath, $this->tempFolder);
if ($this->sharedStringsHelper->hasSharedStrings()) {
// Extracts all the strings from the worksheets for easy access in the future
$this->sharedStringsHelper->extractSharedStrings();
}
// Fetch all available worksheets
$this->worksheetHelper = new WorksheetHelper($filePath, $this->globalFunctionsHelper);
$this->worksheets = $this->worksheetHelper->getWorksheets($filePath);
if (count($this->worksheets) === 0) {
throw new NoWorksheetsFoundException('The file must contain at least one worksheet.');
}
} else {
throw new IOException('Could not open ' . $filePath . ' for reading.');
}
}
/**
* Returns whether another worksheet exists after the current worksheet.
*
* @return bool Whether another worksheet exists after the current worksheet.
* @throws Exception\ReaderNotOpenedException If the stream was not opened first
*/
public function hasNextSheet()
{
if (!$this->isStreamOpened) {
throw new ReaderNotOpenedException('Stream should be opened first.');
}
return $this->worksheetHelper->hasNextWorksheet($this->currentWorksheet, $this->worksheets);
}
/**
* Moves the pointer to the current worksheet.
* Moving to another worksheet will stop the reading in the current worksheet.
*
* @return \Box\Spout\Reader\Sheet The next sheet
* @throws Exception\ReaderNotOpenedException If the stream was not opened first
* @throws Exception\EndOfWorksheetsReachedException If there is no more worksheets to read
*/
public function nextSheet()
{
if (!$this->hasNextSheet()) {
throw new EndOfWorksheetsReachedException('End of worksheets was reached. Cannot read more worksheets.');
}
if ($this->currentWorksheet === null) {
$nextWorksheet = $this->worksheets[0];
} else {
$currentWorksheetIndex = $this->currentWorksheet->getWorksheetIndex();
$nextWorksheet = $this->worksheets[$currentWorksheetIndex + 1];
}
$this->initXmlReaderForWorksheetData($nextWorksheet);
$this->currentWorksheet = $nextWorksheet;
// make sure that we are ready to read more rows
$this->hasReachedEndOfFile = false;
$this->emptyRowDataBuffer();
return $this->currentWorksheet->getExternalSheet();
}
/**
* Initializes the XMLReader object that reads worksheet data for the given worksheet.
* If another worksheet was being read, it closes the reader before reopening it for the new worksheet.
* The XMLReader is configured to be safe from billion laughs attack.
*
* @param Internal\XLSX\Worksheet $worksheet The worksheet to initialize the XMLReader with
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the worksheet data XML cannot be read
*/
protected function initXmlReaderForWorksheetData($worksheet)
{
// if changing worksheet and the XMLReader was initialized for the current worksheet
if ($worksheet != $this->currentWorksheet && $this->xmlReader) {
$this->xmlReader->close();
} else if (!$this->xmlReader) {
$this->xmlReader = new \XMLReader();
}
$worksheetDataXMLFilePath = $worksheet->getDataXmlFilePath();
$worksheetDataFilePath = 'zip://' . $this->filePath . '#' . $worksheetDataXMLFilePath;
if ($this->xmlReader->open($worksheetDataFilePath, null, LIBXML_NONET) === false) {
throw new IOException('Could not open "' . $worksheetDataXMLFilePath . '".');
}
}
/**
* Reads and returns data of the line that comes after the last read line, on the current worksheet.
* Empty rows will be skipped.
*
* @return array|null Array that contains the data for the read line or null at the end of the file
* @throws \Box\Spout\Common\Exception\BadUsageException If the pointer to the current worksheet has not been set
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
*/
protected function read()
{
if (!$this->currentWorksheet) {
throw new BadUsageException('You must call nextSheet() before calling hasNextRow() or nextRow()');
}
$escaper = new \Box\Spout\Common\Escaper\XLSX();
$isInsideRowTag = false;
$rowData = [];
while ($this->xmlReader->read()) {
if ($this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'dimension') {
// Read dimensions of the worksheet
$dimensionRef = $this->xmlReader->getAttribute('ref'); // returns 'A1:M13' for instance (or 'A1' for empty sheet)
if (preg_match('/[A-Z\d]+:([A-Z\d]+)/', $dimensionRef, $matches)) {
$lastCellIndex = $matches[1];
$this->numberOfColumns = CellHelper::getColumnIndexFromCellIndex($lastCellIndex) + 1;
}
} else if ($this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'row') {
// Start of the row description
$isInsideRowTag = true;
// Read spans info if present
$numberOfColumnsForRow = $this->numberOfColumns;
$spans = $this->xmlReader->getAttribute('spans'); // returns '1:5' for instance
if ($spans) {
list(, $numberOfColumnsForRow) = explode(':', $spans);
$numberOfColumnsForRow = intval($numberOfColumnsForRow);
}
$rowData = ($numberOfColumnsForRow !== 0) ? array_fill(0, $numberOfColumnsForRow, '') : [];
} else if ($isInsideRowTag && $this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'c') {
// Start of a cell description
$currentCellIndex = $this->xmlReader->getAttribute('r');
$currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
$node = $this->xmlReader->expand();
$rowData[$currentColumnIndex] = $this->getCellValue($node, $escaper);
} else if ($this->xmlReader->nodeType == \XMLReader::END_ELEMENT && $this->xmlReader->name === 'row') {
// End of the row description
// If needed, we fill the empty cells
$rowData = ($this->numberOfColumns !== 0) ? $rowData : CellHelper::fillMissingArrayIndexes($rowData);
break;
}
}
// no data means "end of file"
return ($rowData !== []) ? $rowData : null;
}
/**
* Returns the cell's string value from a node's nested value node
*
* @param \DOMNode $node
* @return string The value associated with the cell
*/
protected function getVNodeValue($node)
{
// for cell types having a "v" tag containing the value.
// if not, the returned value should be empty string.
$vNode = $node->getElementsByTagName('v')->item(0);
if ($vNode !== null) {
return $vNode->nodeValue;
}
return "";
}
/**
* Returns the cell String value where string is inline.
*
* @param \DOMNode $node
* @param \Box\Spout\Common\Escaper\XLSX $escaper
* @return string The value associated with the cell (null when the cell has an error)
*/
protected function formatInlineStringCellValue($node, $escaper)
{
// inline strings are formatted this way:
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
$tNode = $node->getElementsByTagName('t')->item(0);
$escapedCellValue = trim($tNode->nodeValue);
$cellValue = $escaper->unescape($escapedCellValue);
return $cellValue;
}
/**
* Returns the cell String value from shared-strings file using nodeValue index.
*
* @param string $nodeValue
* @param \Box\Spout\Common\Escaper\XLSX $escaper
* @return string The value associated with the cell (null when the cell has an error)
*/
protected function formatSharedStringCellValue($nodeValue, $escaper)
{
// shared strings are formatted this way:
// <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
$sharedStringIndex = intval($nodeValue);
$escapedCellValue = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
$cellValue = $escaper->unescape($escapedCellValue);
return $cellValue;
}
/**
* Returns the cell String value, where string is stored in value node.
*
* @param string $nodeValue
* @param \Box\Spout\Common\Escaper\XLSX $escaper
* @return string The value associated with the cell (null when the cell has an error)
*/
protected function formatStrCellValue($nodeValue, $escaper)
{
$escapedCellValue = trim($nodeValue);
$cellValue = $escaper->unescape($escapedCellValue);
return $cellValue;
}
/**
* Returns the cell Numeric value from string of nodeValue.
*
* @param string $nodeValue
* @param \Box\Spout\Common\Escaper\XLSX $escaper
* @return int|float The value associated with the cell
*/
protected function formatNumericCellValue($nodeValue)
{
$cellValue = is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
return $cellValue;
}
/**
* Returns the cell Boolean value from a specific node's Value.
*
* @param string $nodeValue
* @return bool The value associated with the cell
*/
protected function formatBooleanCellValue($nodeValue)
{
// !! is similar to boolval()
$cellValue = !!$nodeValue;
return $cellValue;
}
/**
* Returns a cell's PHP Date value, associated to the given stored nodeValue.
*
* @param string $nodeValue
* @param \Box\Spout\Common\Escaper\XLSX $escaper
* @return DateTime|null The value associated with the cell (null when the cell has an error)
*/
protected function formatDateCellValue($nodeValue)
{
try { // Mitigate thrown Exception on invalid date-time format (http://php.net/manual/en/datetime.construct.php)
$cellValue = new \DateTime($nodeValue);
return $cellValue;
} catch (\Exception $e) {
return null;
}
}
/**
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
*
* @param \DOMNode $node
* @param \Box\Spout\Common\Escaper\XLSX $escaper
* @return string|int|float|bool|null The value associated with the cell (null when the cell has an error)
*/
protected function getCellValue($node, $escaper)
{
// Default cell type is "n"
$cellType = $node->getAttribute('t') ?: 'n';
$vNodeValue = $this->getVNodeValue($node);
if ( ($vNodeValue === "") && ($cellType !== self::CELL_TYPE_INLINE_STRING) ) {
return $vNodeValue;
}
switch ($cellType)
{
case self::CELL_TYPE_INLINE_STRING:
return $this->formatInlineStringCellValue($node, $escaper);
case self::CELL_TYPE_SHARED_STRING:
return $this->formatSharedStringCellValue($vNodeValue, $escaper);
case self::CELL_TYPE_STR:
return $this->formatStrCellValue($vNodeValue, $escaper);
case self::CELL_TYPE_BOOLEAN:
return $this->formatBooleanCellValue($vNodeValue);
case self::CELL_TYPE_NUMERIC:
return $this->formatNumericCellValue($vNodeValue);
case self::CELL_TYPE_DATE:
return $this->formatDateCellValue($vNodeValue);
default:
return null;
}
}
/**
* Closes the reader. To be used after reading the file.
*
* @return void
*/
protected function closeReader()
{
if ($this->xmlReader) {
$this->xmlReader->close();
}
if ($this->zip) {
$this->zip->close();
}
$this->sharedStringsHelper->cleanup();
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX; namespace Box\Spout\Reader\XLSX\Helper;
use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\InvalidArgumentException;
@ -8,7 +8,7 @@ use Box\Spout\Common\Exception\InvalidArgumentException;
* Class CellHelper * Class CellHelper
* This class provides helper functions when working with cells * This class provides helper functions when working with cells
* *
* @package Box\Spout\Reader\Helper\XLSX * @package Box\Spout\Reader\XLSX\Helper
*/ */
class CellHelper class CellHelper
{ {

View File

@ -1,11 +1,11 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX\SharedStringsCaching; namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
/** /**
* Class CachingStrategyFactory * Class CachingStrategyFactory
* *
* @package Box\Spout\Reader\Helper\XLSX\SharedStringsCaching * @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
*/ */
class CachingStrategyFactory class CachingStrategyFactory
{ {

View File

@ -1,11 +1,11 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX\SharedStringsCaching; namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
/** /**
* Interface CachingStrategyInterface * Interface CachingStrategyInterface
* *
* @package Box\Spout\Reader\Helper\XLSX\SharedStringsCaching * @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
*/ */
interface CachingStrategyInterface interface CachingStrategyInterface
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX\SharedStringsCaching; namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
use Box\Spout\Common\Helper\FileSystemHelper; use Box\Spout\Common\Helper\FileSystemHelper;
use Box\Spout\Common\Helper\GlobalFunctionsHelper; use Box\Spout\Common\Helper\GlobalFunctionsHelper;
@ -13,7 +13,7 @@ use Box\Spout\Reader\Exception\SharedStringNotFoundException;
* Shared strings are stored in small files (with a max number of strings per file). * Shared strings are stored in small files (with a max number of strings per file).
* This strategy is slower than an in-memory strategy but is used to avoid out of memory crashes. * This strategy is slower than an in-memory strategy but is used to avoid out of memory crashes.
* *
* @package Box\Spout\Reader\Helper\XLSX\SharedStringsCaching * @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
*/ */
class FileBasedStrategy implements CachingStrategyInterface class FileBasedStrategy implements CachingStrategyInterface
{ {
@ -26,6 +26,9 @@ class FileBasedStrategy implements CachingStrategyInterface
/** @var \Box\Spout\Common\Helper\FileSystemHelper Helper to perform file system operations */ /** @var \Box\Spout\Common\Helper\FileSystemHelper Helper to perform file system operations */
protected $fileSystemHelper; protected $fileSystemHelper;
/** @var string Temporary folder where the temporary files will be created */
protected $tempFolder;
/** /**
* @var int Maximum number of strings that can be stored in one temp file * @var int Maximum number of strings that can be stored in one temp file
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE * @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
@ -42,7 +45,7 @@ class FileBasedStrategy implements CachingStrategyInterface
protected $inMemoryTempFilePath; protected $inMemoryTempFilePath;
/** /**
* @var string Contents of the temporary file that was last read * @var array Contents of the temporary file that was last read
* @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE * @see CachingStrategyFactory::MAX_NUM_STRINGS_PER_TEMP_FILE
*/ */
protected $inMemoryTempFileContents; protected $inMemoryTempFileContents;

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX\SharedStringsCaching; namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
use Box\Spout\Reader\Exception\SharedStringNotFoundException; use Box\Spout\Reader\Exception\SharedStringNotFoundException;
/** /**
@ -9,7 +10,7 @@ use Box\Spout\Reader\Exception\SharedStringNotFoundException;
* This class implements the in-memory caching strategy for shared strings. * This class implements the in-memory caching strategy for shared strings.
* This strategy is used when the number of unique strings is low, compared to the memory available. * This strategy is used when the number of unique strings is low, compared to the memory available.
* *
* @package Box\Spout\Reader\Helper\XLSX\SharedStringsCaching * @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
*/ */
class InMemoryStrategy implements CachingStrategyInterface class InMemoryStrategy implements CachingStrategyInterface
{ {

View File

@ -1,16 +1,16 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX; namespace Box\Spout\Reader\XLSX\Helper;
use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\IOException;
use Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\CachingStrategyFactory; use Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\CachingStrategyFactory;
use Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\CachingStrategyInterface; use Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\CachingStrategyInterface;
/** /**
* Class SharedStringsHelper * Class SharedStringsHelper
* This class provides helper functions for reading sharedStrings XML file * This class provides helper functions for reading sharedStrings XML file
* *
* @package Box\Spout\Reader\Helper\XLSX * @package Box\Spout\Reader\XLSX\Helper
*/ */
class SharedStringsHelper class SharedStringsHelper
{ {
@ -58,13 +58,13 @@ class SharedStringsHelper
} }
/** /**
* Builds an in-memory array containing all the shared strings of the worksheet. * Builds an in-memory array containing all the shared strings of the sheet.
* All the strings are stored in a XML file, located at 'xl/sharedStrings.xml'. * All the strings are stored in a XML file, located at 'xl/sharedStrings.xml'.
* It is then accessed by the worksheet data, via the string index in the built table. * It is then accessed by the sheet data, via the string index in the built table.
* *
* More documentation available here: http://msdn.microsoft.com/en-us/library/office/gg278314.aspx * More documentation available here: http://msdn.microsoft.com/en-us/library/office/gg278314.aspx
* *
* The XML file can be really big with worksheets containing a lot of data. That is why * The XML file can be really big with sheets containing a lot of data. That is why
* we need to use a XML reader that provides streaming like the XMLReader library. * we need to use a XML reader that provides streaming like the XMLReader library.
* Please note that SimpleXML does not provide such a functionality but since it is faster * Please note that SimpleXML does not provide such a functionality but since it is faster
* and more handy to parse few XML nodes, it is used in combination with XMLReader for that purpose. * and more handy to parse few XML nodes, it is used in combination with XMLReader for that purpose.
@ -154,7 +154,8 @@ class SharedStringsHelper
$readError = libxml_get_last_error(); $readError = libxml_get_last_error();
if ($readError !== false) { if ($readError !== false) {
throw new IOException("The sharedStrings.xml file is invalid and cannot be read. [{$readError->message}]"); $readErrorMessage = trim($readError->message);
throw new IOException("The sharedStrings.xml file is invalid and cannot be read. [{$readErrorMessage}]");
} }
// reset the setting to display XML warnings/errors // reset the setting to display XML warnings/errors

View File

@ -1,17 +1,16 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX; namespace Box\Spout\Reader\XLSX\Helper;
use Box\Spout\Reader\Internal\XLSX\Worksheet; use Box\Spout\Reader\XLSX\Sheet;
use Box\Spout\Reader\Sheet;
/** /**
* Class WorksheetHelper * Class SheetHelper
* This class provides helper functions related to XLSX worksheets * This class provides helper functions related to XLSX sheets
* *
* @package Box\Spout\Reader\Helper\XLSX * @package Box\Spout\Reader\XLSX\Helper
*/ */
class WorksheetHelper class SheetHelper
{ {
/** Extension for XML files */ /** Extension for XML files */
const XML_EXTENSION = '.xml'; const XML_EXTENSION = '.xml';
@ -26,12 +25,15 @@ class WorksheetHelper
const MAIN_NAMESPACE_FOR_WORKBOOK_XML_RELS = 'http://schemas.openxmlformats.org/package/2006/relationships'; const MAIN_NAMESPACE_FOR_WORKBOOK_XML_RELS = 'http://schemas.openxmlformats.org/package/2006/relationships';
const MAIN_NAMESPACE_FOR_WORKBOOK_XML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'; const MAIN_NAMESPACE_FOR_WORKBOOK_XML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
/** Value of the Override attribute used in [Content_Types].xml to define worksheets */ /** Value of the Override attribute used in [Content_Types].xml to define sheets */
const OVERRIDE_CONTENT_TYPES_ATTRIBUTE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'; const OVERRIDE_CONTENT_TYPES_ATTRIBUTE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml';
/** @var string Path of the XLSX file being read */ /** @var string Path of the XLSX file being read */
protected $filePath; protected $filePath;
/** @var \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings */
protected $sharedStringsHelper;
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */ /** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper; protected $globalFunctionsHelper;
@ -43,41 +45,43 @@ class WorksheetHelper
/** /**
* @param string $filePath Path of the XLSX file being read * @param string $filePath Path of the XLSX file being read
* @param \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper * @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
*/ */
public function __construct($filePath, $globalFunctionsHelper) public function __construct($filePath, $sharedStringsHelper, $globalFunctionsHelper)
{ {
$this->filePath = $filePath; $this->filePath = $filePath;
$this->sharedStringsHelper = $sharedStringsHelper;
$this->globalFunctionsHelper = $globalFunctionsHelper; $this->globalFunctionsHelper = $globalFunctionsHelper;
} }
/** /**
* Returns the file paths of the worksheet data XML files within the XLSX file. * Returns the sheets metadata of the file located at the previously given file path.
* The paths are read from the [Content_Types].xml file. * The paths to the sheets' data are read from the [Content_Types].xml file.
* *
* @return Worksheet[] Worksheets within the XLSX file * @return Sheet[] Sheets within the XLSX file
*/ */
public function getWorksheets() public function getSheets()
{ {
$worksheets = []; $sheets = [];
$contentTypesAsXMLElement = $this->getFileAsXMLElementWithNamespace( $contentTypesAsXMLElement = $this->getFileAsXMLElementWithNamespace(
self::CONTENT_TYPES_XML_FILE_PATH, self::CONTENT_TYPES_XML_FILE_PATH,
self::MAIN_NAMESPACE_FOR_CONTENT_TYPES_XML self::MAIN_NAMESPACE_FOR_CONTENT_TYPES_XML
); );
// find all nodes defining a worksheet // find all nodes defining a sheet
$sheetNodes = $contentTypesAsXMLElement->xpath('//ns:Override[@ContentType="' . self::OVERRIDE_CONTENT_TYPES_ATTRIBUTE . '"]'); $sheetNodes = $contentTypesAsXMLElement->xpath('//ns:Override[@ContentType="' . self::OVERRIDE_CONTENT_TYPES_ATTRIBUTE . '"]');
$numSheetNodes = count($sheetNodes);
for ($i = 0; $i < count($sheetNodes); $i++) { for ($i = 0; $i < $numSheetNodes; $i++) {
$sheetNode = $sheetNodes[$i]; $sheetNode = $sheetNodes[$i];
$sheetDataXMLFilePath = (string) $sheetNode->attributes()->PartName; $sheetDataXMLFilePath = (string) $sheetNode->attributes()->PartName;
$sheet = $this->getSheet($sheetDataXMLFilePath, $i); $sheets[] = $this->getSheetFromXML($sheetDataXMLFilePath, $i);
$worksheets[] = new Worksheet($sheet, $i, $sheetDataXMLFilePath);
} }
return $worksheets; return $sheets;
} }
/** /**
@ -92,9 +96,9 @@ class WorksheetHelper
* *
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml * @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
* @param int $sheetIndexZeroBased Index of the sheet, based on order in [Content_Types].xml (zero-based) * @param int $sheetIndexZeroBased Index of the sheet, based on order in [Content_Types].xml (zero-based)
* @return \Box\Spout\Reader\Sheet Sheet instance * @return \Box\Spout\Reader\XLSX\Sheet Sheet instance
*/ */
protected function getSheet($sheetDataXMLFilePath, $sheetIndexZeroBased) protected function getSheetFromXML($sheetDataXMLFilePath, $sheetIndexZeroBased)
{ {
$sheetId = $sheetIndexZeroBased + 1; $sheetId = $sheetIndexZeroBased + 1;
$sheetName = $this->getDefaultSheetName($sheetDataXMLFilePath); $sheetName = $this->getDefaultSheetName($sheetDataXMLFilePath);
@ -126,14 +130,14 @@ class WorksheetHelper
} }
} }
return new Sheet($sheetId, $sheetIndexZeroBased, $sheetName); return new Sheet($this->filePath, $sheetDataXMLFilePath, $this->sharedStringsHelper, $sheetId, $sheetIndexZeroBased, $sheetName);
} }
/** /**
* Returns the default name of the sheet whose data is located * Returns the default name of the sheet whose data is located
* at the given path. * at the given path.
* *
* @param $sheetDataXMLFilePath * @param string $sheetDataXMLFilePath Path of the sheet data XML file
* @return string The default sheet name * @return string The default sheet name
*/ */
protected function getDefaultSheetName($sheetDataXMLFilePath) protected function getDefaultSheetName($sheetDataXMLFilePath)
@ -193,17 +197,4 @@ class WorksheetHelper
return $xmlElement; return $xmlElement;
} }
/**
* Returns whether another worksheet exists after the current worksheet.
* The order is determined by the order of appearance in the [Content_Types].xml file.
*
* @param Worksheet|null $currentWorksheet The worksheet being currently read or null if reading has not started yet
* @param Worksheet[] $allWorksheets A list of all worksheets in the XLSX file. Must contain at least one worksheet
* @return bool Whether another worksheet exists after the current sheet
*/
public function hasNextWorksheet($currentWorksheet, $allWorksheets)
{
return ($currentWorksheet === null || ($currentWorksheet->getWorksheetIndex() + 1 < count($allWorksheets)));
}
} }

View File

@ -0,0 +1,93 @@
<?php
namespace Box\Spout\Reader\XLSX;
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Reader\AbstractReader;
use Box\Spout\Reader\XLSX\Helper\SharedStringsHelper;
/**
* Class Reader
* This class provides support to read data from a XLSX file
*
* @package Box\Spout\Reader\XLSX
*/
class Reader extends AbstractReader
{
/** @var string Temporary folder where the temporary files will be created */
protected $tempFolder;
/** @var \ZipArchive */
protected $zip;
/** @var \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper Helper to work with shared strings */
protected $sharedStringsHelper;
/** @var SheetIterator To iterator over the XLSX sheets */
protected $sheetIterator;
/**
* @param string $tempFolder Temporary folder where the temporary files will be created
* @return Reader
*/
public function setTempFolder($tempFolder)
{
$this->tempFolder = $tempFolder;
return $this;
}
/**
* Opens the file at the given file path to make it ready to be read.
* It also parses the sharedStrings.xml file to get all the shared strings available in memory
* and fetches all the available sheets.
*
* @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 or its content cannot be read
* @throws \Box\Spout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
*/
protected function openReader($filePath)
{
$this->zip = new \ZipArchive();
if ($this->zip->open($filePath) === true) {
$this->sharedStringsHelper = new SharedStringsHelper($filePath, $this->tempFolder);
if ($this->sharedStringsHelper->hasSharedStrings()) {
// Extracts all the strings from the sheets for easy access in the future
$this->sharedStringsHelper->extractSharedStrings();
}
$this->sheetIterator = new SheetIterator($filePath, $this->sharedStringsHelper, $this->globalFunctionsHelper);
} else {
throw new IOException('Could not open ' . $filePath . ' for reading.');
}
}
/**
* Returns an iterator to iterate over sheets.
*
* @return SheetIterator To iterate over sheets
*/
public function getConcreteSheetIterator()
{
return $this->sheetIterator;
}
/**
* Closes the reader. To be used after reading the file.
*
* @return void
*/
protected function closeReader()
{
if ($this->zip) {
$this->zip->close();
}
if ($this->sharedStringsHelper) {
$this->sharedStringsHelper->cleanup();
}
}
}

View File

@ -0,0 +1,369 @@
<?php
namespace Box\Spout\Reader\XLSX;
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Reader\IteratorInterface;
use Box\Spout\Reader\XLSX\Helper\CellHelper;
/**
* Class RowIterator
*
* @package Box\Spout\Reader\XLSX
*/
class RowIterator implements IteratorInterface
{
/** Definition of all possible cell types */
const CELL_TYPE_INLINE_STRING = 'inlineStr';
const CELL_TYPE_STR = 'str';
const CELL_TYPE_SHARED_STRING = 's';
const CELL_TYPE_BOOLEAN = 'b';
const CELL_TYPE_NUMERIC = 'n';
const CELL_TYPE_DATE = 'd';
const CELL_TYPE_ERROR = 'e';
/** Definition of XML nodes names used to parse data */
const XML_NODE_DIMENSION = 'dimension';
const XML_NODE_WORKSHEET = 'worksheet';
const XML_NODE_ROW = 'row';
const XML_NODE_CELL = 'c';
const XML_NODE_VALUE = 'v';
const XML_NODE_INLINE_STRING_VALUE = 't';
/** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_REF = 'ref';
const XML_ATTRIBUTE_SPANS = 'spans';
const XML_ATTRIBUTE_CELL_INDEX = 'r';
const XML_ATTRIBUTE_TYPE = 't';
/** @var string Path of the XLSX file being read */
protected $filePath;
/** @var string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml */
protected $sheetDataXMLFilePath;
/** @var Helper\SharedStringsHelper Helper to work with shared strings */
protected $sharedStringsHelper;
/** @var \XMLReader The XMLReader object that will help read sheet's XML data */
protected $xmlReader;
/** @var \Box\Spout\Common\Escaper\XLSX Used to unescape XML data */
protected $escaper;
/** @var int Number of read rows */
protected $numReadRows = 0;
/** @var array|null Buffer used to store the row data, while checking if there are more rows to read */
protected $rowDataBuffer = null;
/** @var bool Indicates whether all rows have been read */
protected $hasReachedEndOfFile = false;
/** @var int The number of columns the sheet has (0 meaning undefined) */
protected $numColumns = 0;
/**
* @param string $filePath Path of the XLSX file being read
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
* @param Helper\SharedStringsHelper $sharedStringsHelper Helper to work with shared strings
*/
public function __construct($filePath, $sheetDataXMLFilePath, $sharedStringsHelper)
{
$this->filePath = $filePath;
$this->sheetDataXMLFilePath = $this->normalizeSheetDataXMLFilePath($sheetDataXMLFilePath);
$this->sharedStringsHelper = $sharedStringsHelper;
$this->xmlReader = new \XMLReader();
$this->escaper = new \Box\Spout\Common\Escaper\XLSX();
}
/**
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
* @return string Path of the XML file containing the sheet data,
* without the leading slash.
*/
protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath)
{
return ltrim($sheetDataXMLFilePath, '/');
}
/**
* Rewind the Iterator to the first element.
* Initializes the XMLReader object that reads the associated sheet data.
* The XMLReader is configured to be safe from billion laughs attack.
* @link http://php.net/manual/en/iterator.rewind.php
*
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the sheet data XML cannot be read
*/
public function rewind()
{
$this->xmlReader->close();
$sheetDataFilePath = 'zip://' . $this->filePath . '#' . $this->sheetDataXMLFilePath;
if ($this->xmlReader->open($sheetDataFilePath, null, LIBXML_NONET) === false) {
throw new IOException('Could not open "' . $this->sheetDataXMLFilePath . '".');
}
$this->numReadRows = 0;
$this->rowDataBuffer = null;
$this->hasReachedEndOfFile = false;
$this->numColumns = 0;
$this->next();
}
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
*
* @return boolean
*/
public function valid()
{
return (!$this->hasReachedEndOfFile);
}
/**
* Move forward to next element. Empty rows will be skipped.
* @link http://php.net/manual/en/iterator.next.php
*
* @return void
* @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If a shared string was not found
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
*/
public function next()
{
$isInsideRowTag = false;
$rowData = [];
// Use internal errors to avoid displaying lots of warning messages in case of invalid file
// For instance on HHVM, XMLReader->open() won't fail when trying to read a unexisting file within a zip...
// But the XMLReader->read() will fail!
libxml_clear_errors();
libxml_use_internal_errors(true);
while ($this->xmlReader->read()) {
if ($this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === self::XML_NODE_DIMENSION) {
// Read dimensions of the sheet
$dimensionRef = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet)
if (preg_match('/[A-Z\d]+:([A-Z\d]+)/', $dimensionRef, $matches)) {
$lastCellIndex = $matches[1];
$this->numColumns = CellHelper::getColumnIndexFromCellIndex($lastCellIndex) + 1;
}
} else if ($this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === self::XML_NODE_ROW) {
// Start of the row description
$isInsideRowTag = true;
// Read spans info if present
$numberOfColumnsForRow = $this->numColumns;
$spans = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance
if ($spans) {
list(, $numberOfColumnsForRow) = explode(':', $spans);
$numberOfColumnsForRow = intval($numberOfColumnsForRow);
}
$rowData = ($numberOfColumnsForRow !== 0) ? array_fill(0, $numberOfColumnsForRow, '') : [];
} else if ($isInsideRowTag && $this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === self::XML_NODE_CELL) {
// Start of a cell description
$currentCellIndex = $this->xmlReader->getAttribute(self::XML_ATTRIBUTE_CELL_INDEX);
$currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
$node = $this->xmlReader->expand();
$rowData[$currentColumnIndex] = $this->getCellValue($node);
} else if ($this->xmlReader->nodeType == \XMLReader::END_ELEMENT && $this->xmlReader->name === self::XML_NODE_ROW) {
// End of the row description
// If needed, we fill the empty cells
$rowData = ($this->numColumns !== 0) ? $rowData : CellHelper::fillMissingArrayIndexes($rowData);
$this->numReadRows++;
break;
} else if ($this->xmlReader->nodeType == \XMLReader::END_ELEMENT && $this->xmlReader->name === self::XML_NODE_WORKSHEET) {
// The closing "</worksheet>" marks the end of the file
$this->hasReachedEndOfFile = true;
}
}
$readError = libxml_get_last_error();
if ($readError !== false) {
$readErrorMessage = trim($readError->message);
throw new IOException("The {$this->sheetDataXMLFilePath} file cannot be read. [{$readErrorMessage}]");
}
$this->rowDataBuffer = $rowData;
}
/**
* Returns the cell's string value from a node's nested value node
*
* @param \DOMNode $node
* @return string The value associated with the cell
*/
protected function getVNodeValue($node)
{
// for cell types having a "v" tag containing the value.
// if not, the returned value should be empty string.
$vNode = $node->getElementsByTagName(self::XML_NODE_VALUE)->item(0);
if ($vNode !== null) {
return $vNode->nodeValue;
}
return "";
}
/**
* Returns the cell String value where string is inline.
*
* @param \DOMNode $node
* @return string The value associated with the cell (null when the cell has an error)
*/
protected function formatInlineStringCellValue($node)
{
// inline strings are formatted this way:
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
$tNode = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE)->item(0);
$escapedCellValue = trim($tNode->nodeValue);
$cellValue = $this->escaper->unescape($escapedCellValue);
return $cellValue;
}
/**
* Returns the cell String value from shared-strings file using nodeValue index.
*
* @param string $nodeValue
* @return string The value associated with the cell (null when the cell has an error)
*/
protected function formatSharedStringCellValue($nodeValue)
{
// shared strings are formatted this way:
// <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
$sharedStringIndex = intval($nodeValue);
$escapedCellValue = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
$cellValue = $this->escaper->unescape($escapedCellValue);
return $cellValue;
}
/**
* Returns the cell String value, where string is stored in value node.
*
* @param string $nodeValue
* @return string The value associated with the cell (null when the cell has an error)
*/
protected function formatStrCellValue($nodeValue)
{
$escapedCellValue = trim($nodeValue);
$cellValue = $this->escaper->unescape($escapedCellValue);
return $cellValue;
}
/**
* Returns the cell Numeric value from string of nodeValue.
*
* @param string $nodeValue
* @return int|float The value associated with the cell
*/
protected function formatNumericCellValue($nodeValue)
{
$cellValue = is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
return $cellValue;
}
/**
* Returns the cell Boolean value from a specific node's Value.
*
* @param string $nodeValue
* @return bool The value associated with the cell
*/
protected function formatBooleanCellValue($nodeValue)
{
// !! is similar to boolval()
$cellValue = !!$nodeValue;
return $cellValue;
}
/**
* Returns a cell's PHP Date value, associated to the given stored nodeValue.
*
* @param string $nodeValue
* @return \DateTime|null The value associated with the cell (null when the cell has an error)
*/
protected function formatDateCellValue($nodeValue)
{
// Mitigate thrown Exception on invalid date-time format (http://php.net/manual/en/datetime.construct.php)
try {
$cellValue = new \DateTime($nodeValue);
return $cellValue;
} catch (\Exception $e) {
return null;
}
}
/**
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
*
* @param \DOMNode $node
* @return string|int|float|bool|\DateTime|null The value associated with the cell (null when the cell has an error)
*/
protected function getCellValue($node)
{
// Default cell type is "n"
$cellType = $node->getAttribute(self::XML_ATTRIBUTE_TYPE) ?: self::CELL_TYPE_NUMERIC;
$vNodeValue = $this->getVNodeValue($node);
if (($vNodeValue === '') && ($cellType !== self::CELL_TYPE_INLINE_STRING)) {
return $vNodeValue;
}
switch ($cellType) {
case self::CELL_TYPE_INLINE_STRING:
return $this->formatInlineStringCellValue($node);
case self::CELL_TYPE_SHARED_STRING:
return $this->formatSharedStringCellValue($vNodeValue);
case self::CELL_TYPE_STR:
return $this->formatStrCellValue($vNodeValue);
case self::CELL_TYPE_BOOLEAN:
return $this->formatBooleanCellValue($vNodeValue);
case self::CELL_TYPE_NUMERIC:
return $this->formatNumericCellValue($vNodeValue);
case self::CELL_TYPE_DATE:
return $this->formatDateCellValue($vNodeValue);
default:
return null;
}
}
/**
* Return the current element, from the buffer.
* @link http://php.net/manual/en/iterator.current.php
*
* @return array|null
*/
public function current()
{
return $this->rowDataBuffer;
}
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
*
* @return int
*/
public function key()
{
return $this->numReadRows;
}
/**
* Cleans up what was created to iterate over the object.
*
* @return void
*/
public function end()
{
$this->xmlReader->close();
}
}

View File

@ -1,15 +1,20 @@
<?php <?php
namespace Box\Spout\Reader; namespace Box\Spout\Reader\XLSX;
use Box\Spout\Reader\SheetInterface;
/** /**
* Class Sheet * Class Sheet
* Represents a worksheet within a XLSX file * Represents a sheet within a XLSX file
* *
* @package Box\Spout\Reader * @package Box\Spout\Reader\XLSX
*/ */
class Sheet class Sheet implements SheetInterface
{ {
/** @var RowIterator To iterate over sheet's rows */
protected $rowIterator;
/** @var int ID of the sheet */ /** @var int ID of the sheet */
protected $id; protected $id;
@ -20,17 +25,29 @@ class Sheet
protected $name; protected $name;
/** /**
* @param string $filePath Path of the XLSX file being read
* @param string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml
* @param Helper\SharedStringsHelper Helper to work with shared strings
* @param int $sheetId ID of the sheet * @param int $sheetId ID of the sheet
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based) * @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
* @param string $sheetName Name of the sheet * @param string $sheetName Name of the sheet
*/ */
function __construct($sheetId, $sheetIndex, $sheetName) public function __construct($filePath, $sheetDataXMLFilePath, $sharedStringsHelper, $sheetId, $sheetIndex, $sheetName)
{ {
$this->rowIterator = new RowIterator($filePath, $sheetDataXMLFilePath, $sharedStringsHelper);
$this->id = $sheetId; $this->id = $sheetId;
$this->index = $sheetIndex; $this->index = $sheetIndex;
$this->name = $sheetName; $this->name = $sheetName;
} }
/**
* @return RowIterator
*/
public function getRowIterator()
{
return $this->rowIterator;
}
/** /**
* @return int ID of the sheet * @return int ID of the sheet
*/ */

View File

@ -0,0 +1,112 @@
<?php
namespace Box\Spout\Reader\XLSX;
use Box\Spout\Reader\IteratorInterface;
use Box\Spout\Reader\XLSX\Helper\SheetHelper;
use Box\Spout\Reader\Exception\NoSheetsFoundException;
/**
* Class SheetIterator
* Iterate over XLSX sheet.
*
* @package Box\Spout\Reader\XLSX
*/
class SheetIterator implements IteratorInterface
{
/** @var Sheet[] The list of sheet present in the file */
protected $sheets;
/** @var int The index of the sheet being read (zero-based) */
protected $currentSheetIndex;
/**
* @param string $filePath Path of the file to be read
* @param \Box\Spout\Reader\XLSX\Helper\SharedStringsHelper $sharedStringsHelper
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
* @throws \Box\Spout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
*/
public function __construct($filePath, $sharedStringsHelper, $globalFunctionsHelper)
{
// Fetch all available sheets
$sheetHelper = new SheetHelper($filePath, $sharedStringsHelper, $globalFunctionsHelper);
$this->sheets = $sheetHelper->getSheets();
if (count($this->sheets) === 0) {
throw new NoSheetsFoundException('The file must contain at least one sheet.');
}
}
/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
*
* @return void
*/
public function rewind()
{
$this->currentSheetIndex = 0;
}
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
*
* @return boolean
*/
public function valid()
{
return ($this->currentSheetIndex < count($this->sheets));
}
/**
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
*
* @return void
*/
public function next()
{
if (array_key_exists($this->currentSheetIndex, $this->sheets)) {
$currentSheet = $this->sheets[$this->currentSheetIndex];
$currentSheet->getRowIterator()->end();
$this->currentSheetIndex++;
}
}
/**
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
*
* @return Sheet
*/
public function current()
{
return $this->sheets[$this->currentSheetIndex];
}
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
*
* @return int
*/
public function key()
{
return $this->currentSheetIndex + 1;
}
/**
* Cleans up what was created to iterate over the object.
*
* @return void
*/
public function end()
{
// make sure we are not leaking memory in case the iteration stopped before the end
foreach ($this->sheets as $sheet) {
$sheet->getRowIterator()->end();
}
}
}

View File

@ -69,7 +69,7 @@ abstract class AbstractWriter implements WriterInterface
* By using this method, the data will be written to a file. * By using this method, the data will be written to a file.
* *
* @param string $outputFilePath Path of the output file that will contain the data * @param string $outputFilePath Path of the output file that will contain the data
* @return \Box\Spout\Writer\AbstractWriter * @return AbstractWriter
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable
*/ */
public function openToFile($outputFilePath) public function openToFile($outputFilePath)
@ -92,7 +92,7 @@ abstract class AbstractWriter implements WriterInterface
* @codeCoverageIgnore * @codeCoverageIgnore
* *
* @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 * @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 \Box\Spout\Writer\AbstractWriter * @return AbstractWriter
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
*/ */
public function openToBrowser($outputFileName) public function openToBrowser($outputFileName)
@ -144,7 +144,7 @@ abstract class AbstractWriter implements WriterInterface
* If empty, no data is added (i.e. not even as a blank row) * If empty, no data is added (i.e. not even as a blank row)
* Example: $dataRow = ['data1', 1234, null, '', 'data5', false]; * Example: $dataRow = ['data1', 1234, null, '', 'data5', false];
* *
* @return \Box\Spout\Writer\AbstractWriter * @return AbstractWriter
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer * @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 * @throws \Box\Spout\Common\Exception\IOException If unable to write data
*/ */
@ -173,7 +173,7 @@ abstract class AbstractWriter implements WriterInterface
* ['data21', 'data22', null, false], * ['data21', 'data22', null, false],
* ]; * ];
* *
* @return \Box\Spout\Writer\AbstractWriter * @return AbstractWriter
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid * @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 * @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 * @throws \Box\Spout\Common\Exception\IOException If unable to write data

View File

@ -1,16 +1,17 @@
<?php <?php
namespace Box\Spout\Writer; namespace Box\Spout\Writer\CSV;
use Box\Spout\Writer\AbstractWriter;
use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\IOException;
/** /**
* Class CSV * Class Writer
* This class provides support to write data to CSV files * This class provides support to write data to CSV files
* *
* @package Box\Spout\Writer * @package Box\Spout\Writer\CSV
*/ */
class CSV extends AbstractWriter class Writer extends AbstractWriter
{ {
/** Number of rows to write before flushing */ /** Number of rows to write before flushing */
const FLUSH_THRESHOLD = 500; const FLUSH_THRESHOLD = 500;
@ -32,7 +33,7 @@ class CSV extends AbstractWriter
* Sets the field delimiter for the CSV * Sets the field delimiter for the CSV
* *
* @param string $fieldDelimiter Character that delimits fields * @param string $fieldDelimiter Character that delimits fields
* @return CSV * @return Writer
*/ */
public function setFieldDelimiter($fieldDelimiter) public function setFieldDelimiter($fieldDelimiter)
{ {
@ -44,7 +45,7 @@ class CSV extends AbstractWriter
* Sets the field enclosure for the CSV * Sets the field enclosure for the CSV
* *
* @param string $fieldEnclosure Character that enclose fields * @param string $fieldEnclosure Character that enclose fields
* @return CSV * @return Writer
*/ */
public function setFieldEnclosure($fieldEnclosure) public function setFieldEnclosure($fieldEnclosure)
{ {

View File

@ -19,7 +19,7 @@ class WriterFactory
* This creates an instance of the appropriate writer, given the type of the file to be read * This creates an instance of the appropriate writer, given the type of the file to be read
* *
* @param string $writerType Type of the writer to instantiate * @param string $writerType Type of the writer to instantiate
* @return \Box\Spout\Writer\CSV|\Box\Spout\Writer\XLSX * @return WriterInterface
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException * @throws \Box\Spout\Common\Exception\UnsupportedTypeException
*/ */
public static function create($writerType) public static function create($writerType)
@ -28,10 +28,10 @@ class WriterFactory
switch ($writerType) { switch ($writerType) {
case Type::CSV: case Type::CSV:
$writer = new CSV(); $writer = new CSV\Writer();
break; break;
case Type::XLSX: case Type::XLSX:
$writer = new XLSX(); $writer = new XLSX\Writer();
break; break;
default: default:
throw new UnsupportedTypeException('No writers supporting the given type: ' . $writerType); throw new UnsupportedTypeException('No writers supporting the given type: ' . $writerType);

View File

@ -14,7 +14,7 @@ interface WriterInterface
* By using this method, the data will be written to a file. * By using this method, the data will be written to a file.
* *
* @param string $outputFilePath Path of the output file that will contain the data * @param string $outputFilePath Path of the output file that will contain the data
* @return \Box\Spout\Writer\WriterInterface * @return WriterInterface
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened or if the given path is not writable
*/ */
public function openToFile($outputFilePath); public function openToFile($outputFilePath);
@ -24,7 +24,7 @@ interface WriterInterface
* By using this method, the data will be outputted directly to the browser. * By using this method, the data will be outputted directly to the browser.
* *
* @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 * @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 \Box\Spout\Writer\WriterInterface * @return WriterInterface
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened * @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
*/ */
public function openToBrowser($outputFileName); public function openToBrowser($outputFileName);
@ -34,7 +34,7 @@ interface WriterInterface
* *
* @param array $dataRow Array containing data to be streamed. * @param array $dataRow Array containing data to be streamed.
* Example $dataRow = ['data1', 1234, null, '', 'data5']; * Example $dataRow = ['data1', 1234, null, '', 'data5'];
* @return \Box\Spout\Writer\WriterInterface * @return WriterInterface
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yetthe writer * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yetthe writer
* @throws \Box\Spout\Common\Exception\IOException If unable to write data * @throws \Box\Spout\Common\Exception\IOException If unable to write data
*/ */
@ -48,7 +48,7 @@ interface WriterInterface
* ['data11', 12, , '', 'data13'], * ['data11', 12, , '', 'data13'],
* ['data21', 'data22', null], * ['data21', 'data22', null],
* ]; * ];
* @return \Box\Spout\Writer\WriterInterface * @return WriterInterface
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid * @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet * @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If the writer has not been opened yet
* @throws \Box\Spout\Common\Exception\IOException If unable to write data * @throws \Box\Spout\Common\Exception\IOException If unable to write data

View File

@ -1,12 +1,12 @@
<?php <?php
namespace Box\Spout\Writer\Helper\XLSX; namespace Box\Spout\Writer\XLSX\Helper;
/** /**
* Class CellHelper * Class CellHelper
* This class provides helper functions when working with cells * This class provides helper functions when working with cells
* *
* @package Box\Spout\Writer\Helper\XLSX * @package Box\Spout\Writer\XLSX\Helper
*/ */
class CellHelper class CellHelper
{ {

View File

@ -1,15 +1,15 @@
<?php <?php
namespace Box\Spout\Writer\Helper\XLSX; namespace Box\Spout\Writer\XLSX\Helper;
use Box\Spout\Writer\Internal\XLSX\Worksheet; use Box\Spout\Writer\XLSX\Internal\Worksheet;
/** /**
* Class FileSystemHelper * Class FileSystemHelper
* This class provides helper functions to help with the file system operations * This class provides helper functions to help with the file system operations
* like files/folders creation & deletion for XLSX files * like files/folders creation & deletion for XLSX files
* *
* @package Box\Spout\Writer\Helper\XLSX * @package Box\Spout\Writer\XLSX\Helper
*/ */
class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
namespace Box\Spout\Writer\Helper\XLSX; namespace Box\Spout\Writer\XLSX\Helper;
use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\IOException;
@ -8,7 +8,7 @@ use Box\Spout\Common\Exception\IOException;
* Class SharedStringsHelper * Class SharedStringsHelper
* This class provides helper functions to write shared strings * This class provides helper functions to write shared strings
* *
* @package Box\Spout\Writer\Helper\XLSX * @package Box\Spout\Writer\XLSX\Helper
*/ */
class SharedStringsHelper class SharedStringsHelper
{ {
@ -25,6 +25,9 @@ EOD;
*/ */
const DEFAULT_STRINGS_COUNT_PART = 'count="9999999999999" uniqueCount="9999999999999"'; const DEFAULT_STRINGS_COUNT_PART = 'count="9999999999999" uniqueCount="9999999999999"';
/** @var resource Pointer to the sharedStrings.xml file */
protected $sharedStringsFilePointer;
/** @var int Number of shared strings already written */ /** @var int Number of shared strings already written */
protected $numSharedStrings = 0; protected $numSharedStrings = 0;

View File

@ -1,12 +1,12 @@
<?php <?php
namespace Box\Spout\Writer\Helper\XLSX; namespace Box\Spout\Writer\XLSX\Helper;
/** /**
* Class ZipHelper * Class ZipHelper
* This class provides helper functions to create zip files * This class provides helper functions to create zip files
* *
* @package Box\Spout\Writer\Helper\XLSX * @package Box\Spout\Writer\XLSX\Helper
*/ */
class ZipHelper class ZipHelper
{ {

View File

@ -1,18 +1,18 @@
<?php <?php
namespace Box\Spout\Writer\Internal\XLSX; namespace Box\Spout\Writer\XLSX\Internal;
use Box\Spout\Writer\Exception\SheetNotFoundException; use Box\Spout\Writer\Exception\SheetNotFoundException;
use Box\Spout\Writer\Helper\XLSX\FileSystemHelper; use Box\Spout\Writer\XLSX\Helper\FileSystemHelper;
use Box\Spout\Writer\Helper\XLSX\SharedStringsHelper; use Box\Spout\Writer\XLSX\Helper\SharedStringsHelper;
use Box\Spout\Writer\Sheet; use Box\Spout\Writer\XLSX\Sheet;
/** /**
* Class Book * Class Book
* Represents a workbook within a XLSX file. * Represents a workbook within a XLSX file.
* It provides the functions to work with worksheets. * It provides the functions to work with worksheets.
* *
* @package Box\Spout\Writer\Internal\XLSX * @package Box\Spout\Writer\XLSX\Internal
*/ */
class Workbook class Workbook
{ {
@ -28,10 +28,10 @@ class Workbook
/** @var bool Whether new sheets should be automatically created when the max rows limit per sheet is reached */ /** @var bool Whether new sheets should be automatically created when the max rows limit per sheet is reached */
protected $shouldCreateNewSheetsAutomatically; protected $shouldCreateNewSheetsAutomatically;
/** @var \Box\Spout\Writer\Helper\XLSX\FileSystemHelper Helper to perform file system operations */ /** @var \Box\Spout\Writer\XLSX\Helper\FileSystemHelper Helper to perform file system operations */
protected $fileSystemHelper; protected $fileSystemHelper;
/** @var \Box\Spout\Writer\Helper\XLSX\SharedStringsHelper Helper to write shared strings */ /** @var \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper Helper to write shared strings */
protected $sharedStringsHelper; protected $sharedStringsHelper;
/** @var Worksheet[] Array containing the workbook's sheets */ /** @var Worksheet[] Array containing the workbook's sheets */
@ -114,7 +114,7 @@ class Workbook
* Sets the given sheet as the current one. New data will be written to this sheet. * 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). * The writing will resume where it stopped (i.e. data won't be truncated).
* *
* @param \Box\Spout\Writer\Sheet $sheet The "external" sheet to set as current * @param \Box\Spout\Writer\XLSX\Sheet $sheet The "external" sheet to set as current
* @return void * @return void
* @throws \Box\Spout\Writer\Exception\SheetNotFoundException If the given sheet does not exist in the workbook * @throws \Box\Spout\Writer\Exception\SheetNotFoundException If the given sheet does not exist in the workbook
*/ */
@ -140,7 +140,7 @@ class Workbook
/** /**
* Returns the worksheet associated to the given external sheet. * Returns the worksheet associated to the given external sheet.
* *
* @param \Box\Spout\Writer\Sheet $sheet * @param \Box\Spout\Writer\XLSX\Sheet $sheet
* @return Worksheet|null The worksheet associated to the given external sheet or null if not found. * @return Worksheet|null The worksheet associated to the given external sheet or null if not found.
*/ */
protected function getWorksheetFromExternalSheet($sheet) protected function getWorksheetFromExternalSheet($sheet)

View File

@ -1,17 +1,17 @@
<?php <?php
namespace Box\Spout\Writer\Internal\XLSX; namespace Box\Spout\Writer\XLSX\Internal;
use Box\Spout\Common\Exception\InvalidArgumentException; use Box\Spout\Common\Exception\InvalidArgumentException;
use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\IOException;
use Box\Spout\Writer\Helper\XLSX\CellHelper; use Box\Spout\Writer\XLSX\Helper\CellHelper;
/** /**
* Class Worksheet * Class Worksheet
* Represents a worksheet within a XLSX file. The difference with the Sheet object is * Represents a worksheet within a XLSX file. The difference with the Sheet object is
* that this class provides an interface to write data * that this class provides an interface to write data
* *
* @package Box\Spout\Writer\Internal\XLSX * @package Box\Spout\Writer\XLSX\Internal
*/ */
class Worksheet class Worksheet
{ {
@ -20,13 +20,13 @@ class Worksheet
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"> <worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
EOD; EOD;
/** @var \Box\Spout\Writer\Sheet The "external" sheet */ /** @var \Box\Spout\Writer\XLSX\Sheet The "external" sheet */
protected $externalSheet; protected $externalSheet;
/** @var string Path to the XML file that will contain the sheet data */ /** @var string Path to the XML file that will contain the sheet data */
protected $worksheetFilePath; protected $worksheetFilePath;
/** @var \Box\Spout\Writer\Helper\XLSX\SharedStringsHelper Helper to write shared strings */ /** @var \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper Helper to write shared strings */
protected $sharedStringsHelper; protected $sharedStringsHelper;
/** @var bool Whether inline or shared strings should be used */ /** @var bool Whether inline or shared strings should be used */
@ -42,9 +42,9 @@ EOD;
protected $lastWrittenRowIndex = 0; protected $lastWrittenRowIndex = 0;
/** /**
* @param \Box\Spout\Writer\Sheet $externalSheet The associated "external" sheet * @param \Box\Spout\Writer\XLSX\Sheet $externalSheet The associated "external" sheet
* @param string $worksheetFilesFolder Temporary folder where the files to create the XLSX will be stored * @param string $worksheetFilesFolder Temporary folder where the files to create the XLSX will be stored
* @param \Box\Spout\Writer\Helper\XLSX\SharedStringsHelper $sharedStringsHelper Helper for shared strings * @param \Box\Spout\Writer\XLSX\Helper\SharedStringsHelper $sharedStringsHelper Helper for shared strings
* @param bool $shouldUseInlineStrings Whether inline or shared strings should be used * @param bool $shouldUseInlineStrings Whether inline or shared strings should be used
* @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing * @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
*/ */
@ -76,7 +76,7 @@ EOD;
} }
/** /**
* @return \Box\Spout\Writer\Sheet The "external" sheet * @return \Box\Spout\Writer\XLSX\Sheet The "external" sheet
*/ */
public function getExternalSheet() public function getExternalSheet()
{ {

View File

@ -1,12 +1,12 @@
<?php <?php
namespace Box\Spout\Writer; namespace Box\Spout\Writer\XLSX;
/** /**
* Class Sheet * Class Sheet
* Represents a worksheet within a XLSX file * Represents a worksheet within a XLSX file
* *
* @package Box\Spout\Writer * @package Box\Spout\Writer\XLSX
*/ */
class Sheet class Sheet
{ {
@ -21,7 +21,7 @@ class Sheet
/** /**
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based) * @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
*/ */
function __construct($sheetIndex) public function __construct($sheetIndex)
{ {
$this->index = $sheetIndex; $this->index = $sheetIndex;
$this->name = self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1); $this->name = self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1);
@ -45,7 +45,7 @@ class Sheet
/** /**
* @param string $name Name of the sheet * @param string $name Name of the sheet
* @return \Box\Spout\Writer\Sheet * @return Sheet
*/ */
public function setName($name) public function setName($name)
{ {

View File

@ -1,17 +1,18 @@
<?php <?php
namespace Box\Spout\Writer; namespace Box\Spout\Writer\XLSX;
use Box\Spout\Writer\AbstractWriter;
use Box\Spout\Writer\Exception\WriterNotOpenedException; use Box\Spout\Writer\Exception\WriterNotOpenedException;
use Box\Spout\Writer\Internal\XLSX\Workbook; use Box\Spout\Writer\XLSX\Internal\Workbook;
/** /**
* Class XLSX * Class Writer
* This class provides base support to write data to XLSX files * This class provides base support to write data to XLSX files
* *
* @package Box\Spout\Writer * @package Box\Spout\Writer\XLSX
*/ */
class XLSX extends AbstractWriter class Writer extends AbstractWriter
{ {
/** @var string Content-Type value for the header */ /** @var string Content-Type value for the header */
protected static $headerContentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; protected static $headerContentType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
@ -25,7 +26,7 @@ class XLSX extends AbstractWriter
/** @var bool Whether new sheets should be automatically created when the max rows limit per sheet is reached */ /** @var bool Whether new sheets should be automatically created when the max rows limit per sheet is reached */
protected $shouldCreateNewSheetsAutomatically = true; protected $shouldCreateNewSheetsAutomatically = true;
/** @var Internal\XLSX\Workbook The workbook for the XLSX file */ /** @var Internal\Workbook The workbook for the XLSX file */
protected $book; protected $book;
/** @var int */ /** @var int */
@ -33,7 +34,7 @@ class XLSX extends AbstractWriter
/** /**
* @param string $tempFolder Temporary folder where the files to create the XLSX will be stored * @param string $tempFolder Temporary folder where the files to create the XLSX will be stored
* @return XLSX * @return Writer
*/ */
public function setTempFolder($tempFolder) public function setTempFolder($tempFolder)
{ {
@ -45,7 +46,7 @@ class XLSX extends AbstractWriter
* Use inline string to be more memory efficient. If set to false, it will use shared strings. * Use inline string to be more memory efficient. If set to false, it will use shared strings.
* *
* @param bool $shouldUseInlineStrings Whether inline or shared strings should be used * @param bool $shouldUseInlineStrings Whether inline or shared strings should be used
* @return XLSX * @return Writer
*/ */
public function setShouldUseInlineStrings($shouldUseInlineStrings) public function setShouldUseInlineStrings($shouldUseInlineStrings)
{ {
@ -55,7 +56,7 @@ class XLSX extends AbstractWriter
/** /**
* @param bool $shouldCreateNewSheetsAutomatically Whether new sheets should be automatically created when the max rows limit per sheet is reached * @param bool $shouldCreateNewSheetsAutomatically Whether new sheets should be automatically created when the max rows limit per sheet is reached
* @return XLSX * @return Writer
*/ */
public function setShouldCreateNewSheetsAutomatically($shouldCreateNewSheetsAutomatically) public function setShouldCreateNewSheetsAutomatically($shouldCreateNewSheetsAutomatically)
{ {
@ -91,7 +92,7 @@ class XLSX extends AbstractWriter
$externalSheets = []; $externalSheets = [];
$worksheets = $this->book->getWorksheets(); $worksheets = $this->book->getWorksheets();
/** @var Internal\XLSX\Worksheet $worksheet */ /** @var Internal\Worksheet $worksheet */
foreach ($worksheets as $worksheet) { foreach ($worksheets as $worksheet) {
$externalSheets[] = $worksheet->getExternalSheet(); $externalSheets[] = $worksheet->getExternalSheet();
} }

View File

@ -1,16 +1,17 @@
<?php <?php
namespace Box\Spout\Reader; namespace Box\Spout\Reader\CSV;
use Box\Spout\Common\Type; use Box\Spout\Common\Type;
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\TestUsingResource; use Box\Spout\TestUsingResource;
/** /**
* Class CSVTest * Class ReaderTest
* *
* @package Box\Spout\Reader * @package Box\Spout\Reader\CSV
*/ */
class CSVTest extends \PHPUnit_Framework_TestCase class ReaderTest extends \PHPUnit_Framework_TestCase
{ {
use TestUsingResource; use TestUsingResource;
@ -24,6 +25,16 @@ class CSVTest extends \PHPUnit_Framework_TestCase
ReaderFactory::create(Type::CSV)->open('/path/to/fake/file.csv'); ReaderFactory::create(Type::CSV)->open('/path/to/fake/file.csv');
} }
/**
* @expectedException \Box\Spout\Reader\Exception\ReaderNotOpenedException
*
* @return void
*/
public function testOpenShouldThrowExceptionIfTryingToReadBeforeOpeningReader()
{
ReaderFactory::create(Type::CSV)->getSheetIterator();
}
/** /**
* @expectedException \Box\Spout\Common\Exception\IOException * @expectedException \Box\Spout\Common\Exception\IOException
* *
@ -44,33 +55,22 @@ class CSVTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @expectedException \Box\Spout\Reader\Exception\ReaderNotOpenedException * @expectedException \Box\Spout\Common\Exception\IOException
* *
* @return void * @return void
*/ */
public function testReadShouldThrowExceptionIfReadBeforeReaderOpened() public function testOpenShouldThrowExceptionIfCannotOpenFile()
{ {
$reader = ReaderFactory::create(Type::CSV); $helperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\GlobalFunctionsHelper')
$reader->hasNextRow(); ->setMethods(['fopen'])
} ->getMock();
$helperStub->method('fopen')->willReturn(false);
/**
* @expectedException \Box\Spout\Reader\Exception\EndOfFileReachedException
*
* @return void
*/
public function testReadShouldThrowExceptionIfNextRowCalledAfterReadingDone()
{
$resourcePath = $this->getResourcePath('csv_standard.csv'); $resourcePath = $this->getResourcePath('csv_standard.csv');
$reader = ReaderFactory::create(Type::CSV); $reader = ReaderFactory::create(Type::CSV);
$reader->setGlobalFunctionsHelper($helperStub);
$reader->open($resourcePath); $reader->open($resourcePath);
while ($reader->hasNextRow()) {
$reader->nextRow();
}
$reader->nextRow();
} }
@ -180,6 +180,50 @@ class CSVTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expectedRows, $allRows); $this->assertEquals($expectedRows, $allRows);
} }
/**
* @return void
*/
public function testReadMultipleTimesShouldRewindReader()
{
$allRows = [];
$resourcePath = $this->getResourcePath('csv_standard.csv');
$reader = ReaderFactory::create(Type::CSV);
$reader->open($resourcePath);
foreach ($reader->getSheetIterator() as $sheet) {
// do nothing
}
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
$allRows[] = $row;
break;
}
foreach ($sheet->getRowIterator() as $row) {
$allRows[] = $row;
break;
}
}
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
$allRows[] = $row;
break;
}
}
$reader->close();
$expectedRows = [
['csv--11', 'csv--12', 'csv--13'],
['csv--11', 'csv--12', 'csv--13'],
['csv--11', 'csv--12', 'csv--13'],
];
$this->assertEquals($expectedRows, $allRows);
}
/** /**
* @param string $fileName * @param string $fileName
* @param string|void $fieldDelimiter * @param string|void $fieldDelimiter
@ -197,8 +241,10 @@ class CSVTest extends \PHPUnit_Framework_TestCase
$reader->open($resourcePath); $reader->open($resourcePath);
while ($reader->hasNextRow()) { foreach ($reader->getSheetIterator() as $sheetIndex => $sheet) {
$allRows[] = $reader->nextRow(); foreach ($sheet->getRowIterator() as $rowIndex => $row) {
$allRows[] = $row;
}
} }
$reader->close(); $reader->close();

View File

@ -1,11 +1,11 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX; namespace Box\Spout\Reader\XLSX\Helper;
/** /**
* Class CellHelperTest * Class CellHelperTest
* *
* @package Box\Spout\Reader\Helper\XLSX * @package Box\Spout\Reader\XLSX\Helper
*/ */
class CellHelperTest extends \PHPUnit_Framework_TestCase class CellHelperTest extends \PHPUnit_Framework_TestCase
{ {

View File

@ -1,11 +1,11 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX\SharedStringsCaching; namespace Box\Spout\Reader\XLSX\Helper\SharedStringsCaching;
/** /**
* Class CachingStrategyFactoryTest * Class CachingStrategyFactoryTest
* *
* @package Box\Spout\Reader\Helper\XLSX\SharedStringsCaching * @package Box\Spout\Reader\XLSX\Helper\SharedStringsCaching
*/ */
class CachingStrategyFactoryTest extends \PHPUnit_Framework_TestCase class CachingStrategyFactoryTest extends \PHPUnit_Framework_TestCase
{ {
@ -36,18 +36,18 @@ class CachingStrategyFactoryTest extends \PHPUnit_Framework_TestCase
{ {
/** @var CachingStrategyFactory|\PHPUnit_Framework_MockObject_MockObject $factoryStub */ /** @var CachingStrategyFactory|\PHPUnit_Framework_MockObject_MockObject $factoryStub */
$factoryStub = $this $factoryStub = $this
->getMockBuilder('\Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\CachingStrategyFactory') ->getMockBuilder('\Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\CachingStrategyFactory')
->disableOriginalConstructor() ->disableOriginalConstructor()
->setMethods(['getMemoryLimitInKB']) ->setMethods(['getMemoryLimitInKB'])
->getMock(); ->getMock();
$factoryStub->method('getMemoryLimitInKB')->willReturn($memoryLimitInKB); $factoryStub->method('getMemoryLimitInKB')->willReturn($memoryLimitInKB);
\ReflectionHelper::setStaticValue('\Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\CachingStrategyFactory', 'instance', $factoryStub); \ReflectionHelper::setStaticValue('\Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\CachingStrategyFactory', 'instance', $factoryStub);
$strategy = $factoryStub->getBestCachingStrategy($sharedStringsUniqueCount, null); $strategy = $factoryStub->getBestCachingStrategy($sharedStringsUniqueCount, null);
$fullExpectedStrategyClassName = 'Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\\' . $expectedStrategyClassName; $fullExpectedStrategyClassName = 'Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\\' . $expectedStrategyClassName;
$this->assertEquals($fullExpectedStrategyClassName, get_class($strategy)); $this->assertEquals($fullExpectedStrategyClassName, get_class($strategy));
$strategy->clearCache(); $strategy->clearCache();
@ -85,7 +85,7 @@ class CachingStrategyFactoryTest extends \PHPUnit_Framework_TestCase
{ {
/** @var CachingStrategyFactory|\PHPUnit_Framework_MockObject_MockObject $factoryStub */ /** @var CachingStrategyFactory|\PHPUnit_Framework_MockObject_MockObject $factoryStub */
$factoryStub = $this $factoryStub = $this
->getMockBuilder('\Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\CachingStrategyFactory') ->getMockBuilder('\Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\CachingStrategyFactory')
->disableOriginalConstructor() ->disableOriginalConstructor()
->setMethods(['getMemoryLimitFromIni']) ->setMethods(['getMemoryLimitFromIni'])
->getMock(); ->getMock();

View File

@ -1,16 +1,16 @@
<?php <?php
namespace Box\Spout\Reader\Helper\XLSX; namespace Box\Spout\Reader\XLSX\Helper;
use Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\CachingStrategyFactory; use Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\CachingStrategyFactory;
use Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\FileBasedStrategy; use Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\FileBasedStrategy;
use Box\Spout\Reader\Helper\XLSX\SharedStringsCaching\InMemoryStrategy; use Box\Spout\Reader\XLSX\Helper\SharedStringsCaching\InMemoryStrategy;
use Box\Spout\TestUsingResource; use Box\Spout\TestUsingResource;
/** /**
* Class SharedStringsHelperTest * Class SharedStringsHelperTest
* *
* @package Box\Spout\Reader\Helper\XLSX * @package Box\Spout\Reader\XLSX\Helper
*/ */
class SharedStringsHelperTest extends \PHPUnit_Framework_TestCase class SharedStringsHelperTest extends \PHPUnit_Framework_TestCase
{ {

View File

@ -1,17 +1,19 @@
<?php <?php
namespace Box\Spout\Reader; namespace Box\Spout\Reader\XLSX;
use Box\Spout\Common\Exception\IOException; use Box\Spout\Common\Exception\IOException;
use Box\Spout\Common\Type; use Box\Spout\Common\Type;
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\TestUsingResource; use Box\Spout\TestUsingResource;
use Symfony\Component\Config\Definition\Exception\Exception;
/** /**
* Class XLSXTest * Class ReaderTest
* *
* @package Box\Spout\Reader * @package Box\Spout\Reader\XLSX
*/ */
class XLSXTest extends \PHPUnit_Framework_TestCase class ReaderTest extends \PHPUnit_Framework_TestCase
{ {
use TestUsingResource; use TestUsingResource;
@ -23,6 +25,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
return [ return [
['/path/to/fake/file.xlsx'], ['/path/to/fake/file.xlsx'],
['file_with_no_sheets_in_content_types.xlsx'], ['file_with_no_sheets_in_content_types.xlsx'],
['file_with_sheet_xml_not_matching_content_types.xlsx'],
['file_corrupted.xlsx'], ['file_corrupted.xlsx'],
]; ];
} }
@ -36,38 +39,8 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
*/ */
public function testReadShouldThrowException($filePath) public function testReadShouldThrowException($filePath)
{ {
$this->getAllRowsForFile($filePath); // using @ to prevent warnings/errors from being displayed
} @$this->getAllRowsForFile($filePath);
/**
* @expectedException \Box\Spout\Reader\Exception\ReaderNotOpenedException
*
* @return void
*/
public function testHasNextSheetShouldThrowExceptionIfReaderNotOpened()
{
$reader = ReaderFactory::create(Type::XLSX);
$reader->hasNextSheet();
}
/**
* @expectedException \Box\Spout\Reader\Exception\EndOfWorksheetsReachedException
*
* @return void
*/
public function testNextSheetShouldThrowExceptionIfNoMoreSheetsToRead()
{
$fileName = 'one_sheet_with_shared_strings.xlsx';
$resourcePath = $this->getResourcePath($fileName);
$reader = ReaderFactory::create(Type::XLSX);
$reader->open($resourcePath);
while ($reader->hasNextSheet()) {
$reader->nextSheet();
}
$reader->nextSheet();
} }
/** /**
@ -120,6 +93,9 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
*/ */
public function testReadShouldSupportAllCellTypes() public function testReadShouldSupportAllCellTypes()
{ {
// make sure dates are always created with the same timezone
date_default_timezone_set('UTC');
$allRows = $this->getAllRowsForFile('sheet_with_all_cell_types.xlsx'); $allRows = $this->getAllRowsForFile('sheet_with_all_cell_types.xlsx');
$expectedRows = [ $expectedRows = [
@ -270,7 +246,8 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
$startTime = microtime(true); $startTime = microtime(true);
try { try {
$this->getAllRowsForFile($fileName); // using @ to prevent warnings/errors from being displayed
@$this->getAllRowsForFile($fileName);
$this->fail('An exception should have been thrown'); $this->fail('An exception should have been thrown');
} catch (IOException $exception) { } catch (IOException $exception) {
$duration = microtime(true) - $startTime; $duration = microtime(true) - $startTime;
@ -305,6 +282,60 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
$this->assertEquals($expectedRows, $allRows); $this->assertEquals($expectedRows, $allRows);
} }
/**
* @return void
*/
public function testReadMultipleTimesShouldRewindReader()
{
$allRows = [];
$resourcePath = $this->getResourcePath('two_sheets_with_inline_strings.xlsx');
$reader = ReaderFactory::create(Type::XLSX);
$reader->open($resourcePath);
foreach ($reader->getSheetIterator() as $sheet) {
// do nothing
}
foreach ($reader->getSheetIterator() as $sheet) {
// this loop should only add the first row of the first sheet
foreach ($sheet->getRowIterator() as $row) {
$allRows[] = $row;
break;
}
// this loop should rewind the iterator and restart reading from the 1st row again
// therefore, it should only add the first row of the first sheet
foreach ($sheet->getRowIterator() as $row) {
$allRows[] = $row;
break;
}
// not reading any more sheets
break;
}
foreach ($reader->getSheetIterator() as $sheet) {
// this loop should only add the first row of the current sheet
foreach ($sheet->getRowIterator() as $row) {
$allRows[] = $row;
break;
}
// not breaking, so we keep reading the next sheets
}
$reader->close();
$expectedRows = [
['s1 - A1', 's1 - B1', 's1 - C1', 's1 - D1', 's1 - E1'],
['s1 - A1', 's1 - B1', 's1 - C1', 's1 - D1', 's1 - E1'],
['s1 - A1', 's1 - B1', 's1 - C1', 's1 - D1', 's1 - E1'],
['s2 - A1', 's2 - B1', 's2 - C1', 's2 - D1', 's2 - E1'],
];
$this->assertEquals($expectedRows, $allRows);
}
/** /**
* @param string $fileName * @param string $fileName
* @return array All the read rows the given file * @return array All the read rows the given file
@ -317,11 +348,9 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
$reader = ReaderFactory::create(Type::XLSX); $reader = ReaderFactory::create(Type::XLSX);
$reader->open($resourcePath); $reader->open($resourcePath);
while ($reader->hasNextSheet()) { foreach ($reader->getSheetIterator() as $sheetIndex => $sheet) {
$reader->nextSheet(); foreach ($sheet->getRowIterator() as $rowIndex => $row) {
$allRows[] = $row;
while ($reader->hasNextRow()) {
$allRows[] = $reader->nextRow();
} }
} }

View File

@ -1,14 +1,15 @@
<?php <?php
namespace Box\Spout\Reader; namespace Box\Spout\Reader\XLSX;
use Box\Spout\Common\Type; use Box\Spout\Common\Type;
use Box\Spout\Reader\ReaderFactory;
use Box\Spout\TestUsingResource; use Box\Spout\TestUsingResource;
/** /**
* Class SheetTest * Class SheetTest
* *
* @package Box\Spout\Reader * @package Box\Spout\Reader\XLSX
*/ */
class SheetTest extends \PHPUnit_Framework_TestCase class SheetTest extends \PHPUnit_Framework_TestCase
{ {
@ -41,8 +42,8 @@ class SheetTest extends \PHPUnit_Framework_TestCase
$reader->open($resourcePath); $reader->open($resourcePath);
$sheets = []; $sheets = [];
while ($reader->hasNextSheet()) { foreach ($reader->getSheetIterator() as $sheet) {
$sheets[] = $reader->nextSheet(); $sheets[] = $sheet;
} }
$reader->close(); $reader->close();

View File

@ -1,16 +1,17 @@
<?php <?php
namespace Box\Spout\Writer; namespace Box\Spout\Writer\CSV;
use Box\Spout\Common\Type; use Box\Spout\Common\Type;
use Box\Spout\TestUsingResource; use Box\Spout\TestUsingResource;
use Box\Spout\Writer\WriterFactory;
/** /**
* Class CSVTest * Class WriterTest
* *
* @package Box\Spout\Writer * @package Box\Spout\Writer\CSV
*/ */
class CSVTest extends \PHPUnit_Framework_TestCase class WriterTest extends \PHPUnit_Framework_TestCase
{ {
use TestUsingResource; use TestUsingResource;
@ -69,7 +70,7 @@ class CSVTest extends \PHPUnit_Framework_TestCase
]; ];
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_utf8_bom.csv'); $writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_utf8_bom.csv');
$this->assertContains(CSV::UTF8_BOM, $writtenContent, 'The CSV file should contain a UTF-8 BOM'); $this->assertContains(Writer::UTF8_BOM, $writtenContent, 'The CSV file should contain a UTF-8 BOM');
} }
/** /**
@ -161,6 +162,6 @@ class CSVTest extends \PHPUnit_Framework_TestCase
private function trimWrittenContent($writtenContent) private function trimWrittenContent($writtenContent)
{ {
// remove line feeds and UTF-8 BOM // remove line feeds and UTF-8 BOM
return trim($writtenContent, PHP_EOL . CSV::UTF8_BOM); return trim($writtenContent, PHP_EOL . Writer::UTF8_BOM);
} }
} }

View File

@ -1,11 +1,11 @@
<?php <?php
namespace Box\Spout\Writer\Helper\XLSX; namespace Box\Spout\Writer\XLSX\Helper;
/** /**
* Class CellHelperTest * Class CellHelperTest
* *
* @package Box\Spout\Writer\Helper\XLSX * @package Box\Spout\Writer\XLSX\Helper
*/ */
class CellHelperTest extends \PHPUnit_Framework_TestCase class CellHelperTest extends \PHPUnit_Framework_TestCase
{ {

View File

@ -1,14 +1,15 @@
<?php <?php
namespace Box\Spout\Writer; namespace Box\Spout\Writer\XLSX;
use Box\Spout\Common\Type; use Box\Spout\Common\Type;
use Box\Spout\TestUsingResource; use Box\Spout\TestUsingResource;
use Box\Spout\Writer\WriterFactory;
/** /**
* Class SheetTest * Class SheetTest
* *
* @package Box\Spout\Writer * @package Box\Spout\Writer\XLSX
*/ */
class SheetTest extends \PHPUnit_Framework_TestCase class SheetTest extends \PHPUnit_Framework_TestCase
{ {
@ -69,7 +70,7 @@ class SheetTest extends \PHPUnit_Framework_TestCase
$this->createGeneratedFolderIfNeeded($fileName); $this->createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName); $resourcePath = $this->getGeneratedResourcePath($fileName);
/** @var \Box\Spout\Writer\XLSX $writer */ /** @var \Box\Spout\Writer\XLSX\Writer $writer */
$writer = WriterFactory::create(Type::XLSX); $writer = WriterFactory::create(Type::XLSX);
$writer->openToFile($resourcePath); $writer->openToFile($resourcePath);

View File

@ -1,16 +1,17 @@
<?php <?php
namespace Box\Spout\Writer; namespace Box\Spout\Writer\XLSX;
use Box\Spout\Common\Type; use Box\Spout\Common\Type;
use Box\Spout\TestUsingResource; use Box\Spout\TestUsingResource;
use Box\Spout\Writer\WriterFactory;
/** /**
* Class XLSXTest * Class XLSXTest
* *
* @package Box\Spout\Writer * @package Box\Spout\Writer
*/ */
class XLSXTest extends \PHPUnit_Framework_TestCase class WriterTest extends \PHPUnit_Framework_TestCase
{ {
use TestUsingResource; use TestUsingResource;
@ -230,7 +231,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
$this->createGeneratedFolderIfNeeded($fileName); $this->createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName); $resourcePath = $this->getGeneratedResourcePath($fileName);
/** @var \Box\Spout\Writer\XLSX $writer */ /** @var \Box\Spout\Writer\XLSX\Writer $writer */
$writer = WriterFactory::create(Type::XLSX); $writer = WriterFactory::create(Type::XLSX);
$writer->setShouldUseInlineStrings(true); $writer->setShouldUseInlineStrings(true);
@ -278,7 +279,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
]; ];
// set the maxRowsPerSheet limit to 2 // set the maxRowsPerSheet limit to 2
\ReflectionHelper::setStaticValue('\Box\Spout\Writer\Internal\XLSX\Workbook', 'maxRowsPerWorksheet', 2); \ReflectionHelper::setStaticValue('\Box\Spout\Writer\XLSX\Internal\Workbook', 'maxRowsPerWorksheet', 2);
$writer = $this->writeToXLSXFile($dataRows, $fileName, true, $shouldCreateSheetsAutomatically = true); $writer = $this->writeToXLSXFile($dataRows, $fileName, true, $shouldCreateSheetsAutomatically = true);
$this->assertEquals(2, count($writer->getSheets()), '2 sheets should have been created.'); $this->assertEquals(2, count($writer->getSheets()), '2 sheets should have been created.');
@ -302,7 +303,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
]; ];
// set the maxRowsPerSheet limit to 2 // set the maxRowsPerSheet limit to 2
\ReflectionHelper::setStaticValue('\Box\Spout\Writer\Internal\XLSX\Workbook', 'maxRowsPerWorksheet', 2); \ReflectionHelper::setStaticValue('\Box\Spout\Writer\XLSX\Internal\Workbook', 'maxRowsPerWorksheet', 2);
$writer = $this->writeToXLSXFile($dataRows, $fileName, true, $shouldCreateSheetsAutomatically = false); $writer = $this->writeToXLSXFile($dataRows, $fileName, true, $shouldCreateSheetsAutomatically = false);
$this->assertEquals(1, count($writer->getSheets()), 'Only 1 sheet should have been created.'); $this->assertEquals(1, count($writer->getSheets()), 'Only 1 sheet should have been created.');
@ -348,14 +349,14 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
* @param string $fileName * @param string $fileName
* @param bool $shouldUseInlineStrings * @param bool $shouldUseInlineStrings
* @param bool $shouldCreateSheetsAutomatically * @param bool $shouldCreateSheetsAutomatically
* @return XLSX * @return Writer
*/ */
private function writeToXLSXFile($allRows, $fileName, $shouldUseInlineStrings = true, $shouldCreateSheetsAutomatically = true) private function writeToXLSXFile($allRows, $fileName, $shouldUseInlineStrings = true, $shouldCreateSheetsAutomatically = true)
{ {
$this->createGeneratedFolderIfNeeded($fileName); $this->createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName); $resourcePath = $this->getGeneratedResourcePath($fileName);
/** @var \Box\Spout\Writer\XLSX $writer */ /** @var \Box\Spout\Writer\XLSX\Writer $writer */
$writer = WriterFactory::create(Type::XLSX); $writer = WriterFactory::create(Type::XLSX);
$writer->setShouldUseInlineStrings($shouldUseInlineStrings); $writer->setShouldUseInlineStrings($shouldUseInlineStrings);
$writer->setShouldCreateNewSheetsAutomatically($shouldCreateSheetsAutomatically); $writer->setShouldCreateNewSheetsAutomatically($shouldCreateSheetsAutomatically);
@ -373,14 +374,14 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
* @param string $fileName * @param string $fileName
* @param bool $shouldUseInlineStrings * @param bool $shouldUseInlineStrings
* @param bool $shouldCreateSheetsAutomatically * @param bool $shouldCreateSheetsAutomatically
* @return XLSX * @return Writer
*/ */
private function writeToMultipleSheetsInXLSXFile($allRows, $numSheets, $fileName, $shouldUseInlineStrings = true, $shouldCreateSheetsAutomatically = true) private function writeToMultipleSheetsInXLSXFile($allRows, $numSheets, $fileName, $shouldUseInlineStrings = true, $shouldCreateSheetsAutomatically = true)
{ {
$this->createGeneratedFolderIfNeeded($fileName); $this->createGeneratedFolderIfNeeded($fileName);
$resourcePath = $this->getGeneratedResourcePath($fileName); $resourcePath = $this->getGeneratedResourcePath($fileName);
/** @var \Box\Spout\Writer\XLSX $writer */ /** @var \Box\Spout\Writer\XLSX\Writer $writer */
$writer = WriterFactory::create(Type::XLSX); $writer = WriterFactory::create(Type::XLSX);
$writer->setShouldUseInlineStrings($shouldUseInlineStrings); $writer->setShouldUseInlineStrings($shouldUseInlineStrings);
$writer->setShouldCreateNewSheetsAutomatically($shouldCreateSheetsAutomatically); $writer->setShouldCreateNewSheetsAutomatically($shouldCreateSheetsAutomatically);