Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d617a50b85 | ||
|
|
92981fa80b | ||
|
|
766c733466 | ||
|
|
46cae76546 | ||
|
|
03be58b84d | ||
|
|
b21bb86682 | ||
|
|
8a45cec220 | ||
|
|
04d41d7c9f | ||
|
|
f8f194bc8f | ||
|
|
044ad11596 | ||
|
|
935ba1fb5e | ||
|
|
fb0175d633 | ||
|
|
69190cd855 | ||
|
|
13a53fb43f | ||
|
|
a848be52de | ||
|
|
52faef0168 | ||
|
|
3b4dfba38e | ||
|
|
cfd3e0ffa3 | ||
|
|
c6e943041e | ||
|
|
502dd54c81 | ||
|
|
c6cfc4c80f | ||
|
|
d02013c82e | ||
|
|
71d9c18a81 | ||
|
|
e9ec4e745c | ||
|
|
e58284d27b | ||
|
|
27a47f509f | ||
|
|
8aeb883ebc | ||
|
|
05c504e72c | ||
|
|
abbc158877 | ||
|
|
3f3461b002 |
No files matched your search
@@ -1,4 +1,5 @@
|
|||||||
/tests/resources/generated
|
/tests/resources/generated
|
||||||
|
/tests/coverage
|
||||||
/vendor
|
/vendor
|
||||||
composer.lock
|
composer.lock
|
||||||
/.idea
|
/.idea
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ php:
|
|||||||
- 5.4
|
- 5.4
|
||||||
- 5.5
|
- 5.5
|
||||||
- 5.6
|
- 5.6
|
||||||
|
- nightly
|
||||||
|
- hhvm
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- composer self-update
|
- composer self-update
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
# Spout
|
# Spout
|
||||||
|
|
||||||
|
[](https://packagist.org/packages/box/spout)
|
||||||
|
[](http://opensource.box.com/badges)
|
||||||
|
[](http://travis-ci.org/box/spout)
|
||||||
|
[](https://packagist.org/packages/box/spout) [](https://packagist.org/packages/box/spout)
|
||||||
|
|
||||||
Spout is a PHP library to read and write CSV and XLSX files, in a fast and scalable way.
|
Spout is a PHP library to read and write CSV and XLSX files, in a fast and scalable way.
|
||||||
Contrary to other file readers or writers, it is capable of processing very large files while keeping the memory usage really low (less than 10MB).
|
Contrary to other file readers or writers, it is capable of processing very large files while keeping the memory usage really low (less than 10MB).
|
||||||
|
|
||||||
[](http://travis-ci.org/box/spout)
|
|
||||||
[](http://opensource.box.com/badges)
|
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -180,6 +182,23 @@ It is also possible to retrieve all the sheets currently created:
|
|||||||
$sheets = $writer->getSheets();
|
$sheets = $writer->getSheets();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you rely on the sheet's name in your application, you can access it and customize it this way:
|
||||||
|
```php
|
||||||
|
// Accessing the sheet name when reading
|
||||||
|
while ($reader->hasNextSheet()) {
|
||||||
|
$sheet = $reader->nextSheet();
|
||||||
|
$sheetName = $sheet->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessing the sheet name when writing
|
||||||
|
$sheet = $writer->getCurrentSheet();
|
||||||
|
$sheetName = $sheet->getName();
|
||||||
|
|
||||||
|
// Customizing the sheet name when writing
|
||||||
|
$sheet = $writer->getCurrentSheet();
|
||||||
|
$sheetName = $sheet->setName('My custom name');
|
||||||
|
```
|
||||||
|
|
||||||
### Fluent interface
|
### Fluent interface
|
||||||
|
|
||||||
Because fluent interfaces are great, you can use them with Spout:
|
Because fluent interfaces are great, you can use them with Spout:
|
||||||
@@ -231,6 +250,11 @@ Here are a few numbers regarding the performance of Spout:
|
|||||||
| Read XLSX (using shared strings) | 1 second | 1-2 minutes | 35-40 minutes |
|
| Read XLSX (using shared strings) | 1 second | 1-2 minutes | 35-40 minutes |
|
||||||
| Write XLSX | 1 second | 20-25 seconds | 8-10 minutes |
|
| Write XLSX | 1 second | 20-25 seconds | 8-10 minutes |
|
||||||
|
|
||||||
|
#### Does Spout support charts or formulas?
|
||||||
|
|
||||||
|
No. This is a compromise to keep memory usage low. Charts and formulas requires data to be kept in memory in order to be used.
|
||||||
|
So the larger the file would be, the more memory would be consumed, preventing your code to scale well.
|
||||||
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "box/spout",
|
"name": "box/spout",
|
||||||
"description": "PHP Library to read and write CSV and XLSX files, in a fast and scalable way",
|
"description": "PHP Library to read and write CSV and XLSX files, in a fast and scalable way",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"version": "1.0.4",
|
"version": "1.0.9",
|
||||||
"keywords": ["php","read","write","csv","xlsx","excel","spreadsheet","scale","memory","stream","ooxml"],
|
"keywords": ["php","read","write","csv","xlsx","excel","spreadsheet","scale","memory","stream","ooxml"],
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"homepage": "https://www.github.com/box/spout",
|
"homepage": "https://www.github.com/box/spout",
|
||||||
|
|||||||
+10
@@ -14,4 +14,14 @@
|
|||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
|
|
||||||
|
<logging>
|
||||||
|
<log type="coverage-html" target="tests/coverage/"/>
|
||||||
|
</logging>
|
||||||
|
|
||||||
|
<filter>
|
||||||
|
<whitelist>
|
||||||
|
<directory suffix=".php">src/</directory>
|
||||||
|
</whitelist>
|
||||||
|
</filter>
|
||||||
|
|
||||||
</phpunit>
|
</phpunit>
|
||||||
@@ -13,6 +13,8 @@ class CSV implements EscaperInterface
|
|||||||
/**
|
/**
|
||||||
* Escapes the given string to make it compatible with CSV
|
* Escapes the given string to make it compatible with CSV
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param string $string The string to escape
|
* @param string $string The string to escape
|
||||||
* @return string The escaped string
|
* @return string The escaped string
|
||||||
*/
|
*/
|
||||||
@@ -24,6 +26,8 @@ class CSV implements EscaperInterface
|
|||||||
/**
|
/**
|
||||||
* Unescapes the given string to make it compatible with CSV
|
* Unescapes the given string to make it compatible with CSV
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @param string $string The string to unescape
|
* @param string $string The string to unescape
|
||||||
* @return string The unescaped string
|
* @return string The unescaped string
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ namespace Box\Spout\Common\Helper;
|
|||||||
* Class GlobalFunctionsHelper
|
* Class GlobalFunctionsHelper
|
||||||
* This class wraps global functions to facilitate testing
|
* This class wraps global functions to facilitate testing
|
||||||
*
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
* @package Box\Spout\Common\Helper
|
* @package Box\Spout\Common\Helper
|
||||||
*/
|
*/
|
||||||
class GlobalFunctionsHelper
|
class GlobalFunctionsHelper
|
||||||
@@ -89,6 +91,21 @@ class GlobalFunctionsHelper
|
|||||||
return fgetcsv($handle, $length, $delimiter, $enclosure);
|
return fgetcsv($handle, $length, $delimiter, $enclosure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around global function fputcsv()
|
||||||
|
* @see fputcsv()
|
||||||
|
*
|
||||||
|
* @param resource $handle
|
||||||
|
* @param array $fields
|
||||||
|
* @param string|void $delimiter
|
||||||
|
* @param string|void $enclosure
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function fputcsv($handle, array $fields, $delimiter = null, $enclosure = null)
|
||||||
|
{
|
||||||
|
return fputcsv($handle, $fields, $delimiter, $enclosure);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around global function fclose()
|
* Wrapper around global function fclose()
|
||||||
* @see fclose()
|
* @see fclose()
|
||||||
@@ -117,7 +134,7 @@ class GlobalFunctionsHelper
|
|||||||
* Wrapper around global function file_exists()
|
* Wrapper around global function file_exists()
|
||||||
* @see file_exists()
|
* @see file_exists()
|
||||||
*
|
*
|
||||||
* @param string $filename
|
* @param string $fileName
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function file_exists($fileName)
|
public function file_exists($fileName)
|
||||||
@@ -125,11 +142,23 @@ class GlobalFunctionsHelper
|
|||||||
return file_exists($fileName);
|
return file_exists($fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around global function file_get_contents()
|
||||||
|
* @see file_get_contents()
|
||||||
|
*
|
||||||
|
* @param string $filePath
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function file_get_contents($filePath)
|
||||||
|
{
|
||||||
|
return file_get_contents($filePath);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapper around global function is_readable()
|
* Wrapper around global function is_readable()
|
||||||
* @see is_readable()
|
* @see is_readable()
|
||||||
*
|
*
|
||||||
* @param string $filename
|
* @param string $fileName
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function is_readable($fileName)
|
public function is_readable($fileName)
|
||||||
@@ -142,11 +171,12 @@ class GlobalFunctionsHelper
|
|||||||
* @see basename()
|
* @see basename()
|
||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
|
* @param string|void $suffix
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function basename($path)
|
public function basename($path, $suffix = null)
|
||||||
{
|
{
|
||||||
return basename($path);
|
return basename($path, $suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ 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 number */
|
/** @var int Used to keep track of the row index */
|
||||||
protected $currentRowIndex = 0;
|
protected $currentRowIndex = 0;
|
||||||
|
|
||||||
/** @var bool Indicates whether the stream is currently open */
|
/** @var bool Indicates whether the stream is currently open */
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class CellHelper
|
|||||||
{
|
{
|
||||||
$existingIndexes = array_keys($dataArray);
|
$existingIndexes = array_keys($dataArray);
|
||||||
|
|
||||||
$newIndexes = array_fill_keys(range(min($existingIndexes), max($existingIndexes)), $fillValue);
|
$newIndexes = array_fill_keys(range(0, max($existingIndexes)), $fillValue);
|
||||||
$dataArray += $newIndexes;
|
$dataArray += $newIndexes;
|
||||||
|
|
||||||
ksort($dataArray);
|
ksort($dataArray);
|
||||||
|
|||||||
@@ -71,6 +71,24 @@ class SharedStringsHelper
|
|||||||
$this->tempFolder = $this->fileSystemHelper->createFolder($rootTempFolder, uniqid('sharedstrings'));
|
$this->tempFolder = $this->fileSystemHelper->createFolder($rootTempFolder, uniqid('sharedstrings'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the XLSX file contains a shared strings XML file
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasSharedStrings()
|
||||||
|
{
|
||||||
|
$hasSharedStrings = false;
|
||||||
|
$zip = new \ZipArchive();
|
||||||
|
|
||||||
|
if ($zip->open($this->filePath) === true) {
|
||||||
|
$hasSharedStrings = ($zip->locateName(self::SHARED_STRINGS_XML_FILE_PATH) !== false);
|
||||||
|
$zip->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $hasSharedStrings;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 worksheet.
|
||||||
* 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'.
|
||||||
@@ -93,7 +111,7 @@ class SharedStringsHelper
|
|||||||
$this->tempFilePointer = null;
|
$this->tempFilePointer = null;
|
||||||
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
||||||
|
|
||||||
$sharedStringsFilePath = 'zip://' . $this->filePath . '#' . self::SHARED_STRINGS_XML_FILE_PATH;
|
$sharedStringsFilePath = $this->getSharedStringsFilePath();
|
||||||
if ($xmlReader->open($sharedStringsFilePath, null, LIBXML_NONET) === false) {
|
if ($xmlReader->open($sharedStringsFilePath, null, LIBXML_NONET) === false) {
|
||||||
throw new IOException('Could not open "' . self::SHARED_STRINGS_XML_FILE_PATH . '".');
|
throw new IOException('Could not open "' . self::SHARED_STRINGS_XML_FILE_PATH . '".');
|
||||||
}
|
}
|
||||||
@@ -143,6 +161,15 @@ class SharedStringsHelper
|
|||||||
$xmlReader->close();
|
$xmlReader->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string The path to the shared strings XML file, working cross-platforms
|
||||||
|
*/
|
||||||
|
protected function getSharedStringsFilePath()
|
||||||
|
{
|
||||||
|
$sharedStringsXmlFilePath = str_replace('/', DIRECTORY_SEPARATOR, self::SHARED_STRINGS_XML_FILE_PATH);
|
||||||
|
return 'zip://' . $this->filePath . '#' . $sharedStringsXmlFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes nodes that should not be read, like the pronunciation of the Kanji characters.
|
* Removes nodes that should not be read, like the pronunciation of the Kanji characters.
|
||||||
* By keeping them, their text content would be added to the read string.
|
* By keeping them, their text content would be added to the read string.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace Box\Spout\Reader\Helper\XLSX;
|
namespace Box\Spout\Reader\Helper\XLSX;
|
||||||
|
|
||||||
use Box\Spout\Reader\Internal\XLSX\Worksheet;
|
use Box\Spout\Reader\Internal\XLSX\Worksheet;
|
||||||
|
use Box\Spout\Reader\Sheet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class WorksheetHelper
|
* Class WorksheetHelper
|
||||||
@@ -12,11 +13,18 @@ use Box\Spout\Reader\Internal\XLSX\Worksheet;
|
|||||||
*/
|
*/
|
||||||
class WorksheetHelper
|
class WorksheetHelper
|
||||||
{
|
{
|
||||||
/** Path of Content_Types XML file inside the XLSX file */
|
/** Extension for XML files */
|
||||||
const CONTENT_TYPES_XML_FILE_PATH = '[Content_Types].xml';
|
const XML_EXTENSION = '.xml';
|
||||||
|
|
||||||
/** Main namespace for the [Content_Types].xml file */
|
/** Paths of XML files relative to the XLSX file root */
|
||||||
|
const CONTENT_TYPES_XML_FILE_PATH = '[Content_Types].xml';
|
||||||
|
const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
|
||||||
|
const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
|
||||||
|
|
||||||
|
/** Namespaces for the XML files */
|
||||||
const MAIN_NAMESPACE_FOR_CONTENT_TYPES_XML = 'http://schemas.openxmlformats.org/package/2006/content-types';
|
const MAIN_NAMESPACE_FOR_CONTENT_TYPES_XML = 'http://schemas.openxmlformats.org/package/2006/content-types';
|
||||||
|
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';
|
||||||
|
|
||||||
/** 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 worksheets */
|
||||||
const OVERRIDE_CONTENT_TYPES_ATTRIBUTE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml';
|
const OVERRIDE_CONTENT_TYPES_ATTRIBUTE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml';
|
||||||
@@ -24,12 +32,23 @@ class WorksheetHelper
|
|||||||
/** @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\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
||||||
|
protected $globalFunctionsHelper;
|
||||||
|
|
||||||
|
/** @var \SimpleXMLElement XML element representing the workbook.xml.rels file */
|
||||||
|
protected $workbookXMLRelsAsXMLElement;
|
||||||
|
|
||||||
|
/** @var \SimpleXMLElement XML element representing the workbook.xml file */
|
||||||
|
protected $workbookXMLAsXMLElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $filePath Path of the XLSX file being read
|
* @param string $filePath Path of the XLSX file being read
|
||||||
|
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
||||||
*/
|
*/
|
||||||
public function __construct($filePath)
|
public function __construct($filePath, $globalFunctionsHelper)
|
||||||
{
|
{
|
||||||
$this->filePath = $filePath;
|
$this->filePath = $filePath;
|
||||||
|
$this->globalFunctionsHelper = $globalFunctionsHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,23 +61,140 @@ class WorksheetHelper
|
|||||||
{
|
{
|
||||||
$worksheets = [];
|
$worksheets = [];
|
||||||
|
|
||||||
$xmlContents = file_get_contents('zip://' . $this->filePath . '#' . self::CONTENT_TYPES_XML_FILE_PATH);
|
$contentTypesAsXMLElement = $this->getFileAsXMLElementWithNamespace(
|
||||||
|
self::CONTENT_TYPES_XML_FILE_PATH,
|
||||||
$contentTypes = new \SimpleXMLElement($xmlContents);
|
self::MAIN_NAMESPACE_FOR_CONTENT_TYPES_XML
|
||||||
$contentTypes->registerXPathNamespace('ns', self::MAIN_NAMESPACE_FOR_CONTENT_TYPES_XML);
|
);
|
||||||
|
|
||||||
// find all nodes defining a worksheet
|
// find all nodes defining a worksheet
|
||||||
$sheetNodes = $contentTypes->xpath('//ns:Override[@ContentType="' . self::OVERRIDE_CONTENT_TYPES_ATTRIBUTE . '"]');
|
$sheetNodes = $contentTypesAsXMLElement->xpath('//ns:Override[@ContentType="' . self::OVERRIDE_CONTENT_TYPES_ATTRIBUTE . '"]');
|
||||||
|
|
||||||
for ($i = 0; $i < count($sheetNodes); $i++) {
|
for ($i = 0; $i < count($sheetNodes); $i++) {
|
||||||
$sheetNode = $sheetNodes[$i];
|
$sheetNode = $sheetNodes[$i];
|
||||||
$sheetDataXMLFilePath = (string) $sheetNode->attributes()->PartName;
|
$sheetDataXMLFilePath = (string) $sheetNode->attributes()->PartName;
|
||||||
$worksheets[] = new Worksheet($i, $sheetDataXMLFilePath);
|
|
||||||
|
$sheet = $this->getSheet($sheetDataXMLFilePath, $i);
|
||||||
|
$worksheets[] = new Worksheet($sheet, $i, $sheetDataXMLFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $worksheets;
|
return $worksheets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an instance of a sheet, given the path of its data XML file.
|
||||||
|
* We first look at "xl/_rels/workbook.xml.rels" to find the relationship ID of the sheet.
|
||||||
|
* Then we look at "xl/worbook.xml" to find the sheet entry associated to the found ID.
|
||||||
|
* The entry contains the ID and name of the sheet.
|
||||||
|
*
|
||||||
|
* If this piece of data can't be found by parsing the different XML files, the ID will default
|
||||||
|
* to the sheet index, based on order in [Content_Types].xml. Similarly, the sheet's name will
|
||||||
|
* default to the data sheet XML file name ("xl/worksheets/sheet2.xml" => "sheet2").
|
||||||
|
*
|
||||||
|
* @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)
|
||||||
|
* @return \Box\Spout\Reader\Sheet Sheet instance
|
||||||
|
*/
|
||||||
|
protected function getSheet($sheetDataXMLFilePath, $sheetIndexZeroBased)
|
||||||
|
{
|
||||||
|
$sheetId = $sheetIndexZeroBased + 1;
|
||||||
|
$sheetName = $this->getDefaultSheetName($sheetDataXMLFilePath);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* In [Content_Types].xml, the path is "/xl/worksheets/sheet1.xml"
|
||||||
|
* In workbook.xml.rels, it is only "worksheets/sheet1.xml"
|
||||||
|
*/
|
||||||
|
$sheetDataXMLFilePathInWorkbookXMLRels = ltrim($sheetDataXMLFilePath, '/xl/');
|
||||||
|
|
||||||
|
// find the node associated to the given file path
|
||||||
|
$workbookXMLResElement = $this->getWorkbookXMLRelsAsXMLElement();
|
||||||
|
$relationshipNodes = $workbookXMLResElement->xpath('//ns:Relationship[@Target="' . $sheetDataXMLFilePathInWorkbookXMLRels . '"]');
|
||||||
|
|
||||||
|
if (count($relationshipNodes) === 1) {
|
||||||
|
$relationshipNode = $relationshipNodes[0];
|
||||||
|
$sheetId = (string) $relationshipNode->attributes()->Id;
|
||||||
|
|
||||||
|
$workbookXMLElement = $this->getWorkbookXMLAsXMLElement();
|
||||||
|
$sheetNodes = $workbookXMLElement->xpath('//ns:sheet[@r:id="' . $sheetId . '"]');
|
||||||
|
|
||||||
|
if (count($sheetNodes) === 1) {
|
||||||
|
$sheetNode = $sheetNodes[0];
|
||||||
|
$sheetId = (int) $sheetNode->attributes()->sheetId;
|
||||||
|
$escapedSheetName = (string) $sheetNode->attributes()->name;
|
||||||
|
|
||||||
|
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
||||||
|
$sheetName = $escaper->unescape($escapedSheetName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Sheet($sheetId, $sheetIndexZeroBased, $sheetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default name of the sheet whose data is located
|
||||||
|
* at the given path.
|
||||||
|
*
|
||||||
|
* @param $sheetDataXMLFilePath
|
||||||
|
* @return string The default sheet name
|
||||||
|
*/
|
||||||
|
protected function getDefaultSheetName($sheetDataXMLFilePath)
|
||||||
|
{
|
||||||
|
return $this->globalFunctionsHelper->basename($sheetDataXMLFilePath, self::XML_EXTENSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a representation of the workbook.xml.rels file, ready to be parsed.
|
||||||
|
* The returned value is cached.
|
||||||
|
*
|
||||||
|
* @return \SimpleXMLElement XML element representating the workbook.xml.rels file
|
||||||
|
*/
|
||||||
|
protected function getWorkbookXMLRelsAsXMLElement()
|
||||||
|
{
|
||||||
|
if (!$this->workbookXMLRelsAsXMLElement) {
|
||||||
|
$this->workbookXMLRelsAsXMLElement = $this->getFileAsXMLElementWithNamespace(
|
||||||
|
self::WORKBOOK_XML_RELS_FILE_PATH,
|
||||||
|
self::MAIN_NAMESPACE_FOR_WORKBOOK_XML_RELS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->workbookXMLRelsAsXMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a representation of the workbook.xml file, ready to be parsed.
|
||||||
|
* The returned value is cached.
|
||||||
|
*
|
||||||
|
* @return \SimpleXMLElement XML element representating the workbook.xml.rels file
|
||||||
|
*/
|
||||||
|
protected function getWorkbookXMLAsXMLElement()
|
||||||
|
{
|
||||||
|
if (!$this->workbookXMLAsXMLElement) {
|
||||||
|
$this->workbookXMLAsXMLElement = $this->getFileAsXMLElementWithNamespace(
|
||||||
|
self::WORKBOOK_XML_FILE_PATH,
|
||||||
|
self::MAIN_NAMESPACE_FOR_WORKBOOK_XML
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->workbookXMLAsXMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the contents of the given file in an XML parser and register the given XPath namespace.
|
||||||
|
*
|
||||||
|
* @param string $xmlFilePath The path of the XML file inside the XLSX file
|
||||||
|
* @param string $mainNamespace The main XPath namespace to register
|
||||||
|
* @return \SimpleXMLElement The XML element representing the file
|
||||||
|
*/
|
||||||
|
protected function getFileAsXMLElementWithNamespace($xmlFilePath, $mainNamespace)
|
||||||
|
{
|
||||||
|
$normalizedXmlFilePath = str_replace('/', DIRECTORY_SEPARATOR, $xmlFilePath);
|
||||||
|
$xmlContents = $this->globalFunctionsHelper->file_get_contents('zip://' . $this->filePath . '#' . $normalizedXmlFilePath);
|
||||||
|
|
||||||
|
$xmlElement = new \SimpleXMLElement($xmlContents);
|
||||||
|
$xmlElement->registerXPathNamespace('ns', $mainNamespace);
|
||||||
|
|
||||||
|
return $xmlElement;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether another worksheet exists after the current worksheet.
|
* Returns whether another worksheet exists after the current worksheet.
|
||||||
* The order is determined by the order of appearance in the [Content_Types].xml file.
|
* The order is determined by the order of appearance in the [Content_Types].xml file.
|
||||||
@@ -69,6 +205,6 @@ class WorksheetHelper
|
|||||||
*/
|
*/
|
||||||
public function hasNextWorksheet($currentWorksheet, $allWorksheets)
|
public function hasNextWorksheet($currentWorksheet, $allWorksheets)
|
||||||
{
|
{
|
||||||
return ($currentWorksheet === null || ($currentWorksheet->getWorksheetNumber() + 1 < count($allWorksheets)));
|
return ($currentWorksheet === null || ($currentWorksheet->getWorksheetIndex() + 1 < count($allWorksheets)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,35 +10,50 @@ namespace Box\Spout\Reader\Internal\XLSX;
|
|||||||
*/
|
*/
|
||||||
class Worksheet
|
class Worksheet
|
||||||
{
|
{
|
||||||
/** @var int Worksheet number, based on the order of appareance in [Content_Types].xml (zero-based) */
|
/** @var \Box\Spout\Reader\Sheet The "external" sheet */
|
||||||
protected $worksheetNumber;
|
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 */
|
/** @var string Path of the XML file containing the worksheet data */
|
||||||
protected $dataXmlFilePath;
|
protected $dataXmlFilePath;
|
||||||
|
|
||||||
/**
|
/**\
|
||||||
* @param int $worksheetNumber Worksheet number, based on the order of appareance in [Content_Types].xml (zero-based)
|
* @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
|
* @param string $dataXmlFilePath Path of the XML file containing the worksheet data
|
||||||
*/
|
*/
|
||||||
public function __construct($worksheetNumber, $dataXmlFilePath)
|
public function __construct($externalSheet, $worksheetIndex, $dataXmlFilePath)
|
||||||
{
|
{
|
||||||
$this->worksheetNumber = $worksheetNumber;
|
$this->externalSheet = $externalSheet;
|
||||||
|
$this->worksheetIndex = $worksheetIndex;
|
||||||
$this->dataXmlFilePath = $dataXmlFilePath;
|
$this->dataXmlFilePath = $dataXmlFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string Path of the XML file containing the worksheet data, without the leading slash
|
* @return string Path of the XML file containing the worksheet data,
|
||||||
|
* without the leading slash and working cross-platforms.
|
||||||
*/
|
*/
|
||||||
public function getDataXmlFilePath()
|
public function getDataXmlFilePath()
|
||||||
{
|
{
|
||||||
return ltrim($this->dataXmlFilePath, '/');
|
$dataXmlFilePath = ltrim($this->dataXmlFilePath, '/');
|
||||||
|
return str_replace('/', DIRECTORY_SEPARATOR, $dataXmlFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Box\Spout\Reader\Sheet The "external" sheet
|
||||||
|
*/
|
||||||
|
public function getExternalSheet()
|
||||||
|
{
|
||||||
|
return $this->externalSheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function getWorksheetNumber()
|
public function getWorksheetIndex()
|
||||||
{
|
{
|
||||||
return $this->worksheetNumber;
|
return $this->worksheetIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Box\Spout\Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Sheet
|
||||||
|
* Represents a worksheet within a XLSX file
|
||||||
|
*
|
||||||
|
* @package Box\Spout\Reader
|
||||||
|
*/
|
||||||
|
class Sheet
|
||||||
|
{
|
||||||
|
/** @var int ID of the sheet */
|
||||||
|
protected $id;
|
||||||
|
|
||||||
|
/** @var int Index of the sheet, based on order of creation (zero-based) */
|
||||||
|
protected $index;
|
||||||
|
|
||||||
|
/** @var string Name of the sheet */
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $sheetId ID of the sheet
|
||||||
|
* @param int $sheetIndex Index of the sheet, based on order of creation (zero-based)
|
||||||
|
* @param string $sheetName Name of the sheet
|
||||||
|
*/
|
||||||
|
function __construct($sheetId, $sheetIndex, $sheetName)
|
||||||
|
{
|
||||||
|
$this->id = $sheetId;
|
||||||
|
$this->index = $sheetIndex;
|
||||||
|
$this->name = $sheetName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int ID of the sheet
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int Index of the sheet, based on order of creation (zero-based)
|
||||||
|
*/
|
||||||
|
public function getIndex()
|
||||||
|
{
|
||||||
|
return $this->index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string Name of the sheet
|
||||||
|
*/
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
+41
-36
@@ -72,11 +72,15 @@ class XLSX extends AbstractReader
|
|||||||
$this->zip = new \ZipArchive();
|
$this->zip = new \ZipArchive();
|
||||||
|
|
||||||
if ($this->zip->open($filePath) === true) {
|
if ($this->zip->open($filePath) === true) {
|
||||||
// Extracts all the strings from the worksheets for easy access in the future
|
$this->sharedStringsHelper = new SharedStringsHelper($filePath, $this->tempFolder);
|
||||||
$this->extractSharedStrings($filePath);
|
|
||||||
|
if ($this->sharedStringsHelper->hasSharedStrings()) {
|
||||||
|
// Extracts all the strings from the worksheets for easy access in the future
|
||||||
|
$this->sharedStringsHelper->extractSharedStrings();
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch all available worksheets
|
// Fetch all available worksheets
|
||||||
$this->worksheetHelper = new WorksheetHelper($filePath);
|
$this->worksheetHelper = new WorksheetHelper($filePath, $this->globalFunctionsHelper);
|
||||||
$this->worksheets = $this->worksheetHelper->getWorksheets($filePath);
|
$this->worksheets = $this->worksheetHelper->getWorksheets($filePath);
|
||||||
|
|
||||||
if (count($this->worksheets) === 0) {
|
if (count($this->worksheets) === 0) {
|
||||||
@@ -87,19 +91,6 @@ class XLSX extends AbstractReader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds an in-memory array containing all the shared strings of the worksheets.
|
|
||||||
*
|
|
||||||
* @param string $filePath Path of the XLSX file to be read
|
|
||||||
* @return void
|
|
||||||
* @throws \Box\Spout\Common\Exception\IOException If sharedStrings XML file can't be read
|
|
||||||
*/
|
|
||||||
protected function extractSharedStrings($filePath)
|
|
||||||
{
|
|
||||||
$this->sharedStringsHelper = new SharedStringsHelper($filePath, $this->tempFolder);
|
|
||||||
$this->sharedStringsHelper->extractSharedStrings();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether another worksheet exists after the current worksheet.
|
* Returns whether another worksheet exists after the current worksheet.
|
||||||
*
|
*
|
||||||
@@ -119,29 +110,31 @@ class XLSX extends AbstractReader
|
|||||||
* Moves the pointer to the current worksheet.
|
* Moves the pointer to the current worksheet.
|
||||||
* Moving to another worksheet will stop the reading in the current worksheet.
|
* Moving to another worksheet will stop the reading in the current worksheet.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return \Box\Spout\Reader\Sheet The next sheet
|
||||||
* @throws Exception\ReaderNotOpenedException If the stream was not opened first
|
* @throws Exception\ReaderNotOpenedException If the stream was not opened first
|
||||||
* @throws Exception\EndOfWorksheetsReachedException If there is no more worksheets to read
|
* @throws Exception\EndOfWorksheetsReachedException If there is no more worksheets to read
|
||||||
*/
|
*/
|
||||||
public function nextSheet()
|
public function nextSheet()
|
||||||
{
|
{
|
||||||
if ($this->hasNextSheet()) {
|
if (!$this->hasNextSheet()) {
|
||||||
if ($this->currentWorksheet === null) {
|
|
||||||
$nextWorksheet = $this->worksheets[0];
|
|
||||||
} else {
|
|
||||||
$currentWorksheetNumber = $this->currentWorksheet->getWorksheetNumber();
|
|
||||||
$nextWorksheet = $this->worksheets[$currentWorksheetNumber + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->initXmlReaderForWorksheetData($nextWorksheet);
|
|
||||||
$this->currentWorksheet = $nextWorksheet;
|
|
||||||
|
|
||||||
// make sure that we are ready to read more rows
|
|
||||||
$this->hasReachedEndOfFile = false;
|
|
||||||
$this->emptyRowDataBuffer();
|
|
||||||
} else {
|
|
||||||
throw new EndOfWorksheetsReachedException('End of worksheets was reached. Cannot read more worksheets.');
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -213,13 +206,25 @@ class XLSX extends AbstractReader
|
|||||||
$currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
|
$currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
|
||||||
$node = $this->xmlReader->expand();
|
$node = $this->xmlReader->expand();
|
||||||
|
|
||||||
|
$hasInlineString = ($this->xmlReader->getAttribute('t') === 'inlineStr');
|
||||||
$hasSharedString = ($this->xmlReader->getAttribute('t') === 's');
|
$hasSharedString = ($this->xmlReader->getAttribute('t') === 's');
|
||||||
if ($hasSharedString) {
|
|
||||||
$sharedStringIndex = intval($node->nodeValue);
|
if ($hasInlineString) {
|
||||||
|
// inline strings are formatted this way:
|
||||||
|
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
|
||||||
|
$tNode = $node->getElementsByTagName('t')->item(0);
|
||||||
|
$rowData[$currentColumnIndex] = trim($tNode->nodeValue);
|
||||||
|
} else if ($hasSharedString) {
|
||||||
|
// shared strings are formatted this way:
|
||||||
|
// <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
|
||||||
|
$vNode = $node->getElementsByTagName('v')->item(0);
|
||||||
|
$sharedStringIndex = intval($vNode->nodeValue);
|
||||||
$rowData[$currentColumnIndex] = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
|
$rowData[$currentColumnIndex] = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
|
||||||
} else {
|
} else {
|
||||||
// for inline strings or numbers, just get the value
|
// other values are formatted this way:
|
||||||
$rowData[$currentColumnIndex] = trim($node->nodeValue);
|
// <c r="A1"><v>[VALUE]</v></c>
|
||||||
|
$vNode = $node->getElementsByTagName('v')->item(0);
|
||||||
|
$rowData[$currentColumnIndex] = intval($vNode->nodeValue);
|
||||||
}
|
}
|
||||||
} else if ($this->xmlReader->nodeType == \XMLReader::END_ELEMENT && $this->xmlReader->name === 'row') {
|
} else if ($this->xmlReader->nodeType == \XMLReader::END_ELEMENT && $this->xmlReader->name === 'row') {
|
||||||
// End of the row description
|
// End of the row description
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ abstract class AbstractWriter implements WriterInterface
|
|||||||
* Inits the writer and opens it to accept data.
|
* Inits the writer and opens it to accept data.
|
||||||
* 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.
|
||||||
*
|
*
|
||||||
|
* @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 \Box\Spout\Writer\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
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class CSV extends AbstractWriter
|
|||||||
*/
|
*/
|
||||||
protected function addRowToWriter(array $dataRow)
|
protected function addRowToWriter(array $dataRow)
|
||||||
{
|
{
|
||||||
$wasWriteSuccessful = fputcsv($this->filePointer, $dataRow, $this->fieldDelimiter, $this->fieldEnclosure);
|
$wasWriteSuccessful = $this->globalFunctionsHelper->fputcsv($this->filePointer, $dataRow, $this->fieldDelimiter, $this->fieldEnclosure);
|
||||||
if ($wasWriteSuccessful === false) {
|
if ($wasWriteSuccessful === false) {
|
||||||
throw new IOException('Unable to write data');
|
throw new IOException('Unable to write data');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,8 +67,8 @@ class Workbook
|
|||||||
*/
|
*/
|
||||||
public function addNewSheet()
|
public function addNewSheet()
|
||||||
{
|
{
|
||||||
$newSheetNumber = count($this->worksheets);
|
$newSheetIndex = count($this->worksheets);
|
||||||
$sheet = new Sheet($newSheetNumber);
|
$sheet = new Sheet($newSheetIndex);
|
||||||
|
|
||||||
$worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder();
|
$worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder();
|
||||||
$worksheet = new Worksheet($sheet, $worksheetFilesFolder, $this->sharedStringsHelper, $this->shouldUseInlineStrings);
|
$worksheet = new Worksheet($sheet, $worksheetFilesFolder, $this->sharedStringsHelper, $this->shouldUseInlineStrings);
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ EOD;
|
|||||||
*/
|
*/
|
||||||
public function getId()
|
public function getId()
|
||||||
{
|
{
|
||||||
// sheet number is zero-based, while ID is 1-based
|
// sheet index is zero-based, while ID is 1-based
|
||||||
return $this->externalSheet->getSheetNumber() + 1;
|
return $this->externalSheet->getIndex() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -12,27 +12,27 @@ class Sheet
|
|||||||
{
|
{
|
||||||
const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
|
const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
|
||||||
|
|
||||||
/** @var int Number of the sheet, based on order of creation (zero-based) */
|
/** @var int Index of the sheet, based on order of creation (zero-based) */
|
||||||
protected $sheetNumber;
|
protected $index;
|
||||||
|
|
||||||
/** @var string Name of the sheet */
|
/** @var string Name of the sheet */
|
||||||
protected $name;
|
protected $name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $sheetNumber Number 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($sheetNumber)
|
function __construct($sheetIndex)
|
||||||
{
|
{
|
||||||
$this->sheetNumber = $sheetNumber;
|
$this->index = $sheetIndex;
|
||||||
$this->name = self::DEFAULT_SHEET_NAME_PREFIX . ($sheetNumber + 1);
|
$this->name = self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int Number of the sheet, based on order of creation (zero-based)
|
* @return int Index of the sheet, based on order of creation (zero-based)
|
||||||
*/
|
*/
|
||||||
public function getSheetNumber()
|
public function getIndex()
|
||||||
{
|
{
|
||||||
return $this->sheetNumber;
|
return $this->index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,4 +42,14 @@ class Sheet
|
|||||||
{
|
{
|
||||||
return $this->name;
|
return $this->name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name Name of the sheet
|
||||||
|
* @return \Box\Spout\Writer\Sheet
|
||||||
|
*/
|
||||||
|
public function setName($name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Box\Spout\Reader\Helper\XLSX;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class CellHelperTest
|
||||||
|
*
|
||||||
|
* @package Box\Spout\Reader\Helper\XLSX
|
||||||
|
*/
|
||||||
|
class CellHelperTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testFillMissingArrayIndexes()
|
||||||
|
{
|
||||||
|
$arrayToFill = [1 => 1, 3 => 3];
|
||||||
|
$filledArray = CellHelper::fillMissingArrayIndexes($arrayToFill, 'FILL');
|
||||||
|
|
||||||
|
$expectedFilledArray = ['FILL', 1, 'FILL', 3];
|
||||||
|
$this->assertEquals($expectedFilledArray, $filledArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function dataProviderForTestGetColumnIndexFromCellIndex()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['A1', 0],
|
||||||
|
['Z3', 25],
|
||||||
|
['AA5', 26],
|
||||||
|
['AB24', 27],
|
||||||
|
['BC5', 54],
|
||||||
|
['BCZ99', 1455],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider dataProviderForTestGetColumnIndexFromCellIndex
|
||||||
|
*
|
||||||
|
* @param string $cellIndex
|
||||||
|
* @param int $expectedColumnIndex
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testGetColumnIndexFromCellIndex($cellIndex, $expectedColumnIndex)
|
||||||
|
{
|
||||||
|
$this->assertEquals($expectedColumnIndex, CellHelper::getColumnIndexFromCellIndex($cellIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Box\Spout\Common\Exception\InvalidArgumentException
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testGetColumnIndexFromCellIndexShouldThrowIfInvalidCellIndex()
|
||||||
|
{
|
||||||
|
CellHelper::getColumnIndexFromCellIndex('InvalidCellIndex');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Box\Spout\Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ReaderFactoryTest
|
||||||
|
*
|
||||||
|
* @package Box\Spout\Writer
|
||||||
|
*/
|
||||||
|
class ReaderFactoryTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException \Box\Spout\Common\Exception\UnsupportedTypeException
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testCreateReaderShouldThrowWithUnsupportedType()
|
||||||
|
{
|
||||||
|
ReaderFactory::create('unsupportedType');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Box\Spout\Reader;
|
||||||
|
|
||||||
|
use Box\Spout\Common\Type;
|
||||||
|
use Box\Spout\TestUsingResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SheetTest
|
||||||
|
*
|
||||||
|
* @package Box\Spout\Reader
|
||||||
|
*/
|
||||||
|
class SheetTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
use TestUsingResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testNextSheetShouldReturnCorrectSheetInfos()
|
||||||
|
{
|
||||||
|
$sheets = $this->openFileAndReturnSheets('two_sheets_with_custom_names.xlsx');
|
||||||
|
|
||||||
|
$this->assertEquals('CustomName1', $sheets[0]->getName());
|
||||||
|
$this->assertEquals(0, $sheets[0]->getIndex());
|
||||||
|
$this->assertEquals(1, $sheets[0]->getId());
|
||||||
|
|
||||||
|
$this->assertEquals('CustomName2', $sheets[1]->getName());
|
||||||
|
$this->assertEquals(1, $sheets[1]->getIndex());
|
||||||
|
$this->assertEquals(2, $sheets[1]->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fileName
|
||||||
|
* @return Sheet[]
|
||||||
|
*/
|
||||||
|
private function openFileAndReturnSheets($fileName)
|
||||||
|
{
|
||||||
|
$resourcePath = $this->getResourcePath($fileName);
|
||||||
|
$reader = ReaderFactory::create(Type::XLSX);
|
||||||
|
$reader->open($resourcePath);
|
||||||
|
|
||||||
|
$sheets = [];
|
||||||
|
while ($reader->hasNextSheet()) {
|
||||||
|
$sheets[] = $reader->nextSheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
$reader->close();
|
||||||
|
|
||||||
|
return $sheets;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,37 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->getAllRowsForFile($filePath);
|
$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();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@@ -69,6 +100,20 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testReadShouldSupportFilesWithoutSharedStringsFile()
|
||||||
|
{
|
||||||
|
$allRows = $this->getAllRowsForFile('sheet_with_no_shared_strings_file.xlsx');
|
||||||
|
|
||||||
|
$expectedRows = [
|
||||||
|
[10, 11],
|
||||||
|
[20, 21],
|
||||||
|
];
|
||||||
|
$this->assertEquals($expectedRows, $allRows);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -153,6 +198,19 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($expectedRows, $allRows);
|
$this->assertEquals($expectedRows, $allRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testReadShouldPreserveSpaceIfSpecified()
|
||||||
|
{
|
||||||
|
$allRows = $this->getAllRowsForFile('sheet_with_preserve_space_shared_strings.xlsx');
|
||||||
|
|
||||||
|
$expectedRows = [
|
||||||
|
[' s1--A1', 's1--B1 ', ' s1--C1 '],
|
||||||
|
];
|
||||||
|
$this->assertEquals($expectedRows, $allRows);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -171,7 +229,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$allRows = $this->getAllRowsForFile('billion_laughs_test_file.xlsx');
|
$allRows = $this->getAllRowsForFile('billion_laughs_test_file.xlsx');
|
||||||
|
|
||||||
$expectedMaxMemoryUsage = 10 * 1024 * 1024; // 10MB
|
$expectedMaxMemoryUsage = 20 * 1024 * 1024; // 20MB
|
||||||
$this->assertLessThan($expectedMaxMemoryUsage, memory_get_peak_usage(true), 'Entities should not be expanded and therefore consume all the memory.');
|
$this->assertLessThan($expectedMaxMemoryUsage, memory_get_peak_usage(true), 'Entities should not be expanded and therefore consume all the memory.');
|
||||||
|
|
||||||
$expectedFirstRow = ['s1--A1', 's1--B1', 's1--C1', 's1--D1', 's1--E1'];
|
$expectedFirstRow = ['s1--A1', 's1--B1', 's1--C1', 's1--D1', 's1--E1'];
|
||||||
@@ -187,6 +245,21 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals([], $allRows, 'Sheet with no cells should be correctly processed.');
|
$this->assertEquals([], $allRows, 'Sheet with no cells should be correctly processed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testReadShouldSkipFormulas()
|
||||||
|
{
|
||||||
|
$allRows = $this->getAllRowsForFile('sheet_with_formulas.xlsx');
|
||||||
|
|
||||||
|
$expectedRows = [
|
||||||
|
['val1', 'val2', 'total1', 'total2'],
|
||||||
|
[10, 20, 30, 21],
|
||||||
|
[11, 21, 32, 41],
|
||||||
|
];
|
||||||
|
$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
|
||||||
|
|||||||
@@ -49,6 +49,16 @@ class CSVTest extends \PHPUnit_Framework_TestCase
|
|||||||
$writer->close();
|
$writer->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Box\Spout\Common\Exception\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testAddRowsShouldThrowExceptionIfRowsAreNotArrayOfArrays()
|
||||||
|
{
|
||||||
|
$writer = WriterFactory::create(Type::CSV);
|
||||||
|
$writer->addRows(['csv--11', 'csv--12']);
|
||||||
|
$writer->close();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ class SheetTest extends \PHPUnit_Framework_TestCase
|
|||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function testGetSheetNumber()
|
public function testGetSheetIndex()
|
||||||
{
|
{
|
||||||
$sheets = $this->writeDataAndReturnSheets('test_get_sheet_number.xlsx');
|
$sheets = $this->writeDataAndReturnSheets('test_get_sheet_index.xlsx');
|
||||||
|
|
||||||
$this->assertEquals(2, count($sheets), '2 sheets should have been created');
|
$this->assertEquals(2, count($sheets), '2 sheets should have been created');
|
||||||
$this->assertEquals(0, $sheets[0]->getSheetNumber(), 'The first sheet should be number 0');
|
$this->assertEquals(0, $sheets[0]->getIndex(), 'The first sheet should be index 0');
|
||||||
$this->assertEquals(1, $sheets[1]->getSheetNumber(), 'The second sheet should be number 1');
|
$this->assertEquals(1, $sheets[1]->getIndex(), 'The second sheet should be index 1');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +38,28 @@ class SheetTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals('Sheet2', $sheets[1]->getName(), 'Invalid name for the second sheet');
|
$this->assertEquals('Sheet2', $sheets[1]->getName(), 'Invalid name for the second sheet');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testSetSheetNameShouldCreateSheetWithCustomName()
|
||||||
|
{
|
||||||
|
$fileName = 'test_set_name_should_create_sheet_with_custom_name.xlsx';
|
||||||
|
$this->createGeneratedFolderIfNeeded($fileName);
|
||||||
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||||
|
|
||||||
|
$writer = WriterFactory::create(Type::XLSX);
|
||||||
|
$writer->openToFile($resourcePath);
|
||||||
|
|
||||||
|
$customSheetName = 'CustomName';
|
||||||
|
$sheet = $writer->getCurrentSheet();
|
||||||
|
$sheet->setName($customSheetName);
|
||||||
|
|
||||||
|
$writer->addRow(['xlsx--11', 'xlsx--12']);
|
||||||
|
$writer->close();
|
||||||
|
|
||||||
|
$this->assertSheetNameEquals($customSheetName, $resourcePath, "The sheet name should have been changed to '$customSheetName'");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $fileName
|
* @param string $fileName
|
||||||
* @return Sheet[]
|
* @return Sheet[]
|
||||||
@@ -59,4 +81,27 @@ class SheetTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
return $writer->getSheets();
|
return $writer->getSheets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $expectedName
|
||||||
|
* @param string $resourcePath
|
||||||
|
* @param string $message
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function assertSheetNameEquals($expectedName, $resourcePath, $message = '')
|
||||||
|
{
|
||||||
|
$pathToWorkbookFile = $resourcePath . $this->normalizePath('#xl/workbook.xml');
|
||||||
|
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
|
||||||
|
|
||||||
|
$this->assertContains('<sheet name="' . $expectedName . '"', $xmlContents, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @return string The path with the correct directory separators, as defined for the current platform
|
||||||
|
*/
|
||||||
|
private function normalizePath($path)
|
||||||
|
{
|
||||||
|
return str_replace('/', DIRECTORY_SEPARATOR, $path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Box\Spout\Writer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class WriterFactoryTest
|
||||||
|
*
|
||||||
|
* @package Box\Spout\Writer
|
||||||
|
*/
|
||||||
|
class WriterFactoryTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @expectedException \Box\Spout\Common\Exception\UnsupportedTypeException
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testCreateWriterShouldThrowWithUnsupportedType()
|
||||||
|
{
|
||||||
|
WriterFactory::create('unsupportedType');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -343,7 +343,6 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"');
|
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array $allRows
|
* @param array $allRows
|
||||||
* @param string $fileName
|
* @param string $fileName
|
||||||
@@ -401,15 +400,15 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $fileName
|
* @param string $fileName
|
||||||
* @param int $sheetNumber
|
* @param int $sheetIndex
|
||||||
* @param mixed $inlineData
|
* @param mixed $inlineData
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function assertInlineDataWasWrittenToSheet($fileName, $sheetNumber, $inlineData, $message = '')
|
private function assertInlineDataWasWrittenToSheet($fileName, $sheetIndex, $inlineData, $message = '')
|
||||||
{
|
{
|
||||||
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||||
$pathToSheetFile = $resourcePath . '#xl/worksheets/sheet' . $sheetNumber . '.xml';
|
$pathToSheetFile = $resourcePath . $this->normalizePath('#xl/worksheets/sheet' . $sheetIndex . '.xml');
|
||||||
$xmlContents = file_get_contents('zip://' . $pathToSheetFile);
|
$xmlContents = file_get_contents('zip://' . $pathToSheetFile);
|
||||||
|
|
||||||
$this->assertContains((string)$inlineData, $xmlContents, $message);
|
$this->assertContains((string)$inlineData, $xmlContents, $message);
|
||||||
@@ -417,15 +416,15 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $fileName
|
* @param string $fileName
|
||||||
* @param int $sheetNumber
|
* @param int $sheetIndex
|
||||||
* @param mixed $inlineData
|
* @param mixed $inlineData
|
||||||
* @param string $message
|
* @param string $message
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private function assertInlineDataWasNotWrittenToSheet($fileName, $sheetNumber, $inlineData, $message = '')
|
private function assertInlineDataWasNotWrittenToSheet($fileName, $sheetIndex, $inlineData, $message = '')
|
||||||
{
|
{
|
||||||
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||||
$pathToSheetFile = $resourcePath . '#xl/worksheets/sheet' . $sheetNumber . '.xml';
|
$pathToSheetFile = $resourcePath . $this->normalizePath('#xl/worksheets/sheet' . $sheetIndex . '.xml');
|
||||||
$xmlContents = file_get_contents('zip://' . $pathToSheetFile);
|
$xmlContents = file_get_contents('zip://' . $pathToSheetFile);
|
||||||
|
|
||||||
$this->assertNotContains((string)$inlineData, $xmlContents, $message);
|
$this->assertNotContains((string)$inlineData, $xmlContents, $message);
|
||||||
@@ -440,9 +439,18 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
private function assertSharedStringWasWritten($fileName, $sharedString, $message = '')
|
private function assertSharedStringWasWritten($fileName, $sharedString, $message = '')
|
||||||
{
|
{
|
||||||
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||||
$pathToSharedStringsFile = $resourcePath . '#xl/sharedStrings.xml';
|
$pathToSharedStringsFile = $resourcePath . $this->normalizePath('#xl/sharedStrings.xml');
|
||||||
$xmlContents = file_get_contents('zip://' . $pathToSharedStringsFile);
|
$xmlContents = file_get_contents('zip://' . $pathToSharedStringsFile);
|
||||||
|
|
||||||
$this->assertContains($sharedString, $xmlContents, $message);
|
$this->assertContains($sharedString, $xmlContents, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $path
|
||||||
|
* @return string The path with the correct directory separators, as defined for the current platform
|
||||||
|
*/
|
||||||
|
private function normalizePath($path)
|
||||||
|
{
|
||||||
|
return str_replace('/', DIRECTORY_SEPARATOR, $path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user