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 | ||
|
|
2074781852 | ||
|
|
fd5cfc5440 | ||
|
|
a538813827 | ||
|
|
3e5ef284a5 | ||
|
|
f770f38967 | ||
|
|
0edd9c434f | ||
|
|
3c9bf12a5f | ||
|
|
69c1436625 | ||
|
|
af940069bc | ||
|
|
d6155a4243 | ||
|
|
538f6109ad | ||
|
|
93cdd398dd | ||
|
|
66e5792a7e | ||
|
|
d4dbc32a53 | ||
|
|
09eac62c22 | ||
|
|
7bb5e02d2c | ||
|
|
aebdd1acd7 | ||
|
|
41a449f245 | ||
|
|
39c72a91b4 | ||
|
|
4ca1fc5851 | ||
|
|
419544a85f | ||
|
|
6e11a043c1 | ||
|
|
c24cdbb9be | ||
|
|
18f6e64910 |
No files matched your search
@@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[{.travis.yml}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -1,4 +1,5 @@
|
||||
/tests/resources/generated
|
||||
/tests/coverage
|
||||
/vendor
|
||||
composer.lock
|
||||
/.idea
|
||||
|
||||
@@ -4,6 +4,8 @@ php:
|
||||
- 5.4
|
||||
- 5.5
|
||||
- 5.6
|
||||
- nightly
|
||||
- hhvm
|
||||
|
||||
install:
|
||||
- composer self-update
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# 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.
|
||||
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
|
||||
|
||||
@@ -149,7 +151,7 @@ $writer->setShouldCreateNewSheetsAutomatically(true); // default value
|
||||
$writer->setShouldCreateNewSheetsAutomatically(false); // will stop writing new data when limit is reached
|
||||
```
|
||||
|
||||
### Using custom temporary folder
|
||||
#### Using custom temporary folder
|
||||
|
||||
Processing XLSX files require temporary files to be created. By default, Spout will use the system default temporary folder (as returned by sys_get_temp_dir()). It is possible to override this by explicitly setting it on the reader or writer:
|
||||
```php
|
||||
@@ -180,6 +182,23 @@ It is also possible to retrieve all the sheets currently created:
|
||||
$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
|
||||
|
||||
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 |
|
||||
| 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
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"name": "box/spout",
|
||||
"description": "PHP Library to read and write CSV and XLSX files, in a fast and scalable way",
|
||||
"type": "library",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.9",
|
||||
"keywords": ["php","read","write","csv","xlsx","excel","spreadsheet","scale","memory","stream","ooxml"],
|
||||
"license": "Apache-2.0",
|
||||
"homepage": "https://www.github.com/box/spout",
|
||||
|
||||
+10
@@ -14,4 +14,14 @@
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<logging>
|
||||
<log type="coverage-html" target="tests/coverage/"/>
|
||||
</logging>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">src/</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
</phpunit>
|
||||
@@ -13,6 +13,8 @@ class CSV implements EscaperInterface
|
||||
/**
|
||||
* Escapes the given string to make it compatible with CSV
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $string The string to escape
|
||||
* @return string The escaped string
|
||||
*/
|
||||
@@ -24,6 +26,8 @@ class CSV implements EscaperInterface
|
||||
/**
|
||||
* Unescapes the given string to make it compatible with CSV
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @param string $string The string to unescape
|
||||
* @return string The unescaped string
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,7 @@ class XLSX implements EscaperInterface
|
||||
public function escape($string)
|
||||
{
|
||||
$escapedString = $this->escapeControlCharacters($string);
|
||||
$escapedString = htmlspecialchars($escapedString, ENT_QUOTES, 'utf-8');
|
||||
$escapedString = htmlspecialchars($escapedString, ENT_QUOTES);
|
||||
|
||||
return $escapedString;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Box\Spout\Common\Helper;
|
||||
* Class GlobalFunctionsHelper
|
||||
* This class wraps global functions to facilitate testing
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*
|
||||
* @package Box\Spout\Common\Helper
|
||||
*/
|
||||
class GlobalFunctionsHelper
|
||||
@@ -89,6 +91,21 @@ class GlobalFunctionsHelper
|
||||
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()
|
||||
* @see fclose()
|
||||
@@ -117,7 +134,7 @@ class GlobalFunctionsHelper
|
||||
* Wrapper around global function file_exists()
|
||||
* @see file_exists()
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $fileName
|
||||
* @return bool
|
||||
*/
|
||||
public function file_exists($fileName)
|
||||
@@ -125,11 +142,23 @@ class GlobalFunctionsHelper
|
||||
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()
|
||||
* @see is_readable()
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $fileName
|
||||
* @return bool
|
||||
*/
|
||||
public function is_readable($fileName)
|
||||
@@ -142,11 +171,12 @@ class GlobalFunctionsHelper
|
||||
* @see basename()
|
||||
*
|
||||
* @param string $path
|
||||
* @param string|void $suffix
|
||||
* @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
|
||||
{
|
||||
/** @var int Used to keep track of the row number */
|
||||
/** @var int Used to keep track of the row index */
|
||||
protected $currentRowIndex = 0;
|
||||
|
||||
/** @var bool Indicates whether the stream is currently open */
|
||||
|
||||
@@ -52,7 +52,7 @@ class CSV extends AbstractReader
|
||||
* The file must be UTF-8 encoded.
|
||||
* @TODO add encoding detection/conversion
|
||||
*
|
||||
* @param string $filePath Path of the XLSX file to be read
|
||||
* @param string $filePath Path of the CSV file to be read
|
||||
* @return void
|
||||
* @throws \Box\Spout\Common\Exception\IOException
|
||||
*/
|
||||
|
||||
@@ -25,7 +25,7 @@ class CellHelper
|
||||
{
|
||||
$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;
|
||||
|
||||
ksort($dataArray);
|
||||
|
||||
@@ -31,6 +31,9 @@ class SharedStringsHelper
|
||||
*/
|
||||
const MAX_NUM_STRINGS_PER_TEMP_FILE = 10000;
|
||||
|
||||
/** Value to use to escape the line feed character ("\n") */
|
||||
const ESCAPED_LINE_FEED_CHARACTER = '_x000A_';
|
||||
|
||||
/** @var string Path of the XLSX file being read */
|
||||
protected $filePath;
|
||||
|
||||
@@ -68,6 +71,24 @@ class SharedStringsHelper
|
||||
$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.
|
||||
* All the strings are stored in a XML file, located at 'xl/sharedStrings.xml'.
|
||||
@@ -80,7 +101,6 @@ class SharedStringsHelper
|
||||
* 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.
|
||||
*
|
||||
* @param string $filePath
|
||||
* @return void
|
||||
* @throws \Box\Spout\Common\Exception\IOException If sharedStrings.xml can't be read
|
||||
*/
|
||||
@@ -91,7 +111,7 @@ class SharedStringsHelper
|
||||
$this->tempFilePointer = null;
|
||||
$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) {
|
||||
throw new IOException('Could not open "' . self::SHARED_STRINGS_XML_FILE_PATH . '".');
|
||||
}
|
||||
@@ -120,7 +140,12 @@ class SharedStringsHelper
|
||||
}
|
||||
|
||||
$unescapedTextValue = $escaper->unescape($textValue);
|
||||
$this->writeSharedStringToTempFile($unescapedTextValue, $sharedStringIndex);
|
||||
|
||||
// The shared string retrieval logic expects each cell data to be on one line only
|
||||
// Encoding the line feed character allows to preserve this assumption
|
||||
$lineFeedEncodedTextValue = $this->escapeLineFeed($unescapedTextValue);
|
||||
|
||||
$this->writeSharedStringToTempFile($lineFeedEncodedTextValue, $sharedStringIndex);
|
||||
|
||||
$sharedStringIndex++;
|
||||
|
||||
@@ -136,6 +161,15 @@ class SharedStringsHelper
|
||||
$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.
|
||||
* By keeping them, their text content would be added to the read string.
|
||||
@@ -246,16 +280,39 @@ class SharedStringsHelper
|
||||
|
||||
$sharedString = null;
|
||||
if (array_key_exists($indexInFile, $this->inMemoryTempFileContents)) {
|
||||
$sharedString = $this->inMemoryTempFileContents[$indexInFile];
|
||||
$escapedSharedString = $this->inMemoryTempFileContents[$indexInFile];
|
||||
$sharedString = $this->unescapeLineFeed($escapedSharedString);
|
||||
}
|
||||
|
||||
if (!$sharedString) {
|
||||
if ($sharedString === null) {
|
||||
throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
|
||||
}
|
||||
|
||||
return rtrim($sharedString, PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes the line feed character (\n)
|
||||
*
|
||||
* @param string $unescapedString
|
||||
* @return string
|
||||
*/
|
||||
private function escapeLineFeed($unescapedString)
|
||||
{
|
||||
return str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescapes the line feed character (\n)
|
||||
*
|
||||
* @param string $escapedString
|
||||
* @return string
|
||||
*/
|
||||
private function unescapeLineFeed($escapedString)
|
||||
{
|
||||
return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the created temporary folder and all its contents
|
||||
*
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Box\Spout\Reader\Helper\XLSX;
|
||||
|
||||
use Box\Spout\Reader\Internal\XLSX\Worksheet;
|
||||
use Box\Spout\Reader\Sheet;
|
||||
|
||||
/**
|
||||
* Class WorksheetHelper
|
||||
@@ -12,11 +13,18 @@ use Box\Spout\Reader\Internal\XLSX\Worksheet;
|
||||
*/
|
||||
class WorksheetHelper
|
||||
{
|
||||
/** Path of Content_Types XML file inside the XLSX file */
|
||||
const CONTENT_TYPES_XML_FILE_PATH = '[Content_Types].xml';
|
||||
/** Extension for XML files */
|
||||
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_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 */
|
||||
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 */
|
||||
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 \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
|
||||
*/
|
||||
public function __construct($filePath)
|
||||
public function __construct($filePath, $globalFunctionsHelper)
|
||||
{
|
||||
$this->filePath = $filePath;
|
||||
$this->globalFunctionsHelper = $globalFunctionsHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,23 +61,140 @@ class WorksheetHelper
|
||||
{
|
||||
$worksheets = [];
|
||||
|
||||
$xmlContents = file_get_contents('zip://' . $this->filePath . '#' . self::CONTENT_TYPES_XML_FILE_PATH);
|
||||
|
||||
$contentTypes = new \SimpleXMLElement($xmlContents);
|
||||
$contentTypes->registerXPathNamespace('ns', self::MAIN_NAMESPACE_FOR_CONTENT_TYPES_XML);
|
||||
$contentTypesAsXMLElement = $this->getFileAsXMLElementWithNamespace(
|
||||
self::CONTENT_TYPES_XML_FILE_PATH,
|
||||
self::MAIN_NAMESPACE_FOR_CONTENT_TYPES_XML
|
||||
);
|
||||
|
||||
// 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++) {
|
||||
$sheetNode = $sheetNodes[$i];
|
||||
$sheetDataXMLFilePath = (string) $sheetNode->attributes()->PartName;
|
||||
$worksheets[] = new Worksheet($i, $sheetDataXMLFilePath);
|
||||
|
||||
$sheet = $this->getSheet($sheetDataXMLFilePath, $i);
|
||||
$worksheets[] = new Worksheet($sheet, $i, $sheetDataXMLFilePath);
|
||||
}
|
||||
|
||||
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.
|
||||
* 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)
|
||||
{
|
||||
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
|
||||
{
|
||||
/** @var int Worksheet number, based on the order of appareance in [Content_Types].xml (zero-based) */
|
||||
protected $worksheetNumber;
|
||||
/** @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 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
|
||||
*/
|
||||
public function __construct($worksheetNumber, $dataXmlFilePath)
|
||||
public function __construct($externalSheet, $worksheetIndex, $dataXmlFilePath)
|
||||
{
|
||||
$this->worksheetNumber = $worksheetNumber;
|
||||
$this->externalSheet = $externalSheet;
|
||||
$this->worksheetIndex = $worksheetIndex;
|
||||
$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()
|
||||
{
|
||||
return ltrim($this->dataXmlFilePath, DIRECTORY_SEPARATOR);
|
||||
$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
|
||||
*/
|
||||
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();
|
||||
|
||||
if ($this->zip->open($filePath) === true) {
|
||||
// Extracts all the strings from the worksheets for easy access in the future
|
||||
$this->extractSharedStrings($filePath);
|
||||
$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->worksheetHelper = new WorksheetHelper($filePath, $this->globalFunctionsHelper);
|
||||
$this->worksheets = $this->worksheetHelper->getWorksheets($filePath);
|
||||
|
||||
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.
|
||||
*
|
||||
@@ -119,29 +110,31 @@ class XLSX extends AbstractReader
|
||||
* Moves the pointer to 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\EndOfWorksheetsReachedException If there is no more worksheets to read
|
||||
*/
|
||||
public function nextSheet()
|
||||
{
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,13 +206,25 @@ class XLSX extends AbstractReader
|
||||
$currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
|
||||
$node = $this->xmlReader->expand();
|
||||
|
||||
$hasInlineString = ($this->xmlReader->getAttribute('t') === 'inlineStr');
|
||||
$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);
|
||||
} else {
|
||||
// for inline strings or numbers, just get the value
|
||||
$rowData[$currentColumnIndex] = trim($node->nodeValue);
|
||||
// other values are formatted this way:
|
||||
// <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') {
|
||||
// End of the row description
|
||||
|
||||
@@ -39,6 +39,7 @@ abstract class AbstractWriter implements WriterInterface
|
||||
|
||||
/**
|
||||
* Adds data to the currently openned writer.
|
||||
* The data must be UTF-8 encoded.
|
||||
*
|
||||
* @param array $dataRow Array containing data to be streamed.
|
||||
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
||||
@@ -88,6 +89,8 @@ abstract class AbstractWriter implements WriterInterface
|
||||
* Inits the writer and opens it to accept data.
|
||||
* 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
|
||||
* @return \Box\Spout\Writer\AbstractWriter
|
||||
* @throws \Box\Spout\Common\Exception\IOException If the writer cannot be opened
|
||||
@@ -135,9 +138,11 @@ abstract class AbstractWriter implements WriterInterface
|
||||
|
||||
/**
|
||||
* Write given data to the output. New data will be appended to end of stream.
|
||||
* The data must be UTF-8 encoded.
|
||||
*
|
||||
* @param array $dataRow Array containing data to be streamed.
|
||||
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
||||
* If empty, no data is added (i.e. not even as a blank row)
|
||||
* Example: $dataRow = ['data1', 1234, null, '', 'data5', false];
|
||||
*
|
||||
* @return \Box\Spout\Writer\AbstractWriter
|
||||
* @throws \Box\Spout\Writer\Exception\WriterNotOpenedException If this function is called before opening the writer
|
||||
@@ -146,7 +151,10 @@ abstract class AbstractWriter implements WriterInterface
|
||||
public function addRow(array $dataRow)
|
||||
{
|
||||
if ($this->isWriterOpened) {
|
||||
$this->addRowToWriter($dataRow);
|
||||
// empty $dataRow should not add an empty line
|
||||
if (!empty($dataRow)) {
|
||||
$this->addRowToWriter($dataRow);
|
||||
}
|
||||
} else {
|
||||
throw new WriterNotOpenedException('The writer needs to be opened before adding row.');
|
||||
}
|
||||
@@ -156,12 +164,14 @@ abstract class AbstractWriter implements WriterInterface
|
||||
|
||||
/**
|
||||
* Write given data to the output. New data will be appended to end of stream.
|
||||
* The data must be UTF-8 encoded.
|
||||
*
|
||||
* @param array $dataRows Array of array containing data to be streamed.
|
||||
* Example $dataRow = [
|
||||
* ['data11', 12, , '', 'data13'],
|
||||
* ['data21', 'data22', null],
|
||||
* ];
|
||||
* If a row is empty, it won't be added (i.e. not even as a blank row)
|
||||
* Example: $dataRows = [
|
||||
* ['data11', 12, , '', 'data13'],
|
||||
* ['data21', 'data22', null, false],
|
||||
* ];
|
||||
*
|
||||
* @return \Box\Spout\Writer\AbstractWriter
|
||||
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If the input param is not valid
|
||||
|
||||
@@ -73,7 +73,7 @@ class CSV extends AbstractWriter
|
||||
*/
|
||||
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) {
|
||||
throw new IOException('Unable to write data');
|
||||
}
|
||||
|
||||
@@ -34,4 +34,38 @@ class CellHelper
|
||||
|
||||
return $cellIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return bool Whether the given value is a non empty string
|
||||
*/
|
||||
public static function isNonEmptyString($value)
|
||||
{
|
||||
return (gettype($value) === 'string' && $value !== '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given value is numeric.
|
||||
* A numeric value is from type "integer" or "double" ("float" is not returned by gettype).
|
||||
*
|
||||
* @param $value
|
||||
* @return bool Whether the given value is numeric
|
||||
*/
|
||||
public static function isNumeric($value)
|
||||
{
|
||||
$valueType = gettype($value);
|
||||
return ($valueType === 'integer' || $valueType === 'double');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given value is boolean.
|
||||
* "true"/"false" and 0/1 are not booleans.
|
||||
*
|
||||
* @param $value
|
||||
* @return bool Whether the given value is boolean
|
||||
*/
|
||||
public static function isBoolean($value)
|
||||
{
|
||||
return gettype($value) === 'boolean';
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ namespace Box\Spout\Writer\Internal\XLSX;
|
||||
use Box\Spout\Writer\Exception\SheetNotFoundException;
|
||||
use Box\Spout\Writer\Helper\XLSX\FileSystemHelper;
|
||||
use Box\Spout\Writer\Helper\XLSX\SharedStringsHelper;
|
||||
use Box\Spout\Writer\Helper\XLSX\ZipHelper;
|
||||
use Box\Spout\Writer\Sheet;
|
||||
|
||||
/**
|
||||
@@ -68,8 +67,8 @@ class Workbook
|
||||
*/
|
||||
public function addNewSheet()
|
||||
{
|
||||
$newSheetNumber = count($this->worksheets);
|
||||
$sheet = new Sheet($newSheetNumber);
|
||||
$newSheetIndex = count($this->worksheets);
|
||||
$sheet = new Sheet($newSheetIndex);
|
||||
|
||||
$worksheetFilesFolder = $this->fileSystemHelper->getXlWorksheetsFolder();
|
||||
$worksheet = new Worksheet($sheet, $worksheetFilesFolder, $this->sharedStringsHelper, $this->shouldUseInlineStrings);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Box\Spout\Writer\Internal\XLSX;
|
||||
|
||||
use Box\Spout\Common\Exception\InvalidArgumentException;
|
||||
use Box\Spout\Common\Exception\IOException;
|
||||
use Box\Spout\Writer\Helper\XLSX\CellHelper;
|
||||
|
||||
@@ -42,7 +43,8 @@ EOD;
|
||||
|
||||
/**
|
||||
* @param \Box\Spout\Writer\Sheet $externalSheet The associated "external" sheet
|
||||
* @param string $tempFolder 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 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
|
||||
*/
|
||||
@@ -94,8 +96,8 @@ EOD;
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
// sheet number is zero-based, while ID is 1-based
|
||||
return $this->externalSheet->getSheetNumber() + 1;
|
||||
// sheet index is zero-based, while ID is 1-based
|
||||
return $this->externalSheet->getIndex() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,6 +120,7 @@ EOD;
|
||||
* Example $dataRow = ['data1', 1234, null, '', 'data5'];
|
||||
* @return void
|
||||
* @throws \Box\Spout\Common\Exception\IOException If the data cannot be written
|
||||
* @throws \Box\Spout\Common\Exception\InvalidArgumentException If a cell value's type is not supported
|
||||
*/
|
||||
public function addRow($dataRow)
|
||||
{
|
||||
@@ -131,19 +134,19 @@ EOD;
|
||||
$columnIndex = CellHelper::getCellIndexFromColumnIndex($cellNumber);
|
||||
$data .= ' <c r="' . $columnIndex . $rowIndex . '"';
|
||||
|
||||
if (empty($cellValue)) {
|
||||
if (CellHelper::isNonEmptyString($cellValue)) {
|
||||
if ($this->shouldUseInlineStrings) {
|
||||
$data .= ' t="inlineStr"><is><t>' . $this->stringsEscaper->escape($cellValue) . '</t></is></c>' . PHP_EOL;
|
||||
} else {
|
||||
$sharedStringId = $this->sharedStringsHelper->writeString($cellValue);
|
||||
$data .= ' t="s"><v>' . $sharedStringId . '</v></c>' . PHP_EOL;
|
||||
}
|
||||
} else if (CellHelper::isNumeric($cellValue) || CellHelper::isBoolean($cellValue)) {
|
||||
$data .= '><v>' . $cellValue . '</v></c>' . PHP_EOL;
|
||||
} else if (empty($cellValue)) {
|
||||
$data .= '/>' . PHP_EOL;
|
||||
} else {
|
||||
if (is_numeric($cellValue)) {
|
||||
$data .= '><v>' . $cellValue . '</v></c>' . PHP_EOL;
|
||||
} else {
|
||||
if ($this->shouldUseInlineStrings) {
|
||||
$data .= ' t="inlineStr"><is><t>' . $this->stringsEscaper->escape($cellValue) . '</t></is></c>' . PHP_EOL;
|
||||
} else {
|
||||
$sharedStringId = $this->sharedStringsHelper->writeString($cellValue);
|
||||
$data .= ' t="s"><v>' . $sharedStringId . '</v></c>' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
throw new InvalidArgumentException('Trying to add a value with an unsupported type: ' . gettype($cellValue));
|
||||
}
|
||||
|
||||
$cellNumber++;
|
||||
|
||||
@@ -12,27 +12,27 @@ class Sheet
|
||||
{
|
||||
const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
|
||||
|
||||
/** @var int Number of the sheet, based on order of creation (zero-based) */
|
||||
protected $sheetNumber;
|
||||
/** @var int Index of the sheet, based on order of creation (zero-based) */
|
||||
protected $index;
|
||||
|
||||
/** @var string Name of the sheet */
|
||||
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->name = self::DEFAULT_SHEET_NAME_PREFIX . ($sheetNumber + 1);
|
||||
$this->index = $sheetIndex;
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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');
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class SharedStringsHelperTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(1, count($filesInTempFolder), 'One temp file should have been created in the temp folder.');
|
||||
|
||||
$tempFileContents = file_get_contents($filesInTempFolder[0]);
|
||||
$tempFileContentsPerLine = explode("\n", $tempFileContents);
|
||||
$tempFileContentsPerLine = explode(PHP_EOL, $tempFileContents);
|
||||
|
||||
$this->assertEquals('s1--A1', $tempFileContentsPerLine[0]);
|
||||
$this->assertEquals('s1--E5', $tempFileContentsPerLine[24]);
|
||||
@@ -96,4 +96,23 @@ class SharedStringsHelperTest extends \PHPUnit_Framework_TestCase
|
||||
$sharedString = $this->sharedStringsHelper->getStringAtIndex(24);
|
||||
$this->assertEquals('s1--E5', $sharedString);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testGetStringAtIndexShouldWorkWithMultilineStrings()
|
||||
{
|
||||
$resourcePath = $this->getResourcePath('one_sheet_with_shared_multiline_strings.xlsx');
|
||||
$sharedStringsHelper = new SharedStringsHelper($resourcePath);
|
||||
|
||||
$sharedStringsHelper->extractSharedStrings();
|
||||
|
||||
$sharedString = $sharedStringsHelper->getStringAtIndex(0);
|
||||
$this->assertEquals("s1\nA1", $sharedString);
|
||||
|
||||
$sharedString = $sharedStringsHelper->getStringAtIndex(24);
|
||||
$this->assertEquals("s1\nE5", $sharedString);
|
||||
|
||||
$sharedStringsHelper->cleanup();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
@@ -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
|
||||
*/
|
||||
@@ -140,6 +185,32 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals($expectedRows, $allRows);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testReadShouldSupportEmptySharedString()
|
||||
{
|
||||
$allRows = $this->getAllRowsForFile('sheet_with_empty_shared_string.xlsx');
|
||||
|
||||
$expectedRows = [
|
||||
['s1--A1', '', 's1--C1'],
|
||||
];
|
||||
$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
|
||||
*/
|
||||
@@ -158,7 +229,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$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.');
|
||||
|
||||
$expectedFirstRow = ['s1--A1', 's1--B1', 's1--C1', 's1--D1', 's1--E1'];
|
||||
@@ -174,6 +245,21 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
$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
|
||||
* @return array All the read rows the given file
|
||||
|
||||
@@ -49,6 +49,16 @@ class CSVTest extends \PHPUnit_Framework_TestCase
|
||||
$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
|
||||
*/
|
||||
@@ -76,6 +86,22 @@ class CSVTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertEquals('csv--11,,csv--13', $writtenContent, 'The null values should be replaced by empty values');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testWriteShouldSkipEmptyRows()
|
||||
{
|
||||
$allRows = [
|
||||
['csv--11', 'csv--12'],
|
||||
[],
|
||||
['csv--31', 'csv--32'],
|
||||
];
|
||||
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_empty_rows.csv');
|
||||
$writtenContent = $this->trimWrittenContent($writtenContent);
|
||||
|
||||
$this->assertEquals("csv--11,csv--12\ncsv--31,csv--32", $writtenContent, 'Empty rows should be skipped');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
@@ -135,6 +161,6 @@ class CSVTest extends \PHPUnit_Framework_TestCase
|
||||
private function trimWrittenContent($writtenContent)
|
||||
{
|
||||
// remove line feeds and UTF-8 BOM
|
||||
return trim($writtenContent, "\n" . CSV::UTF8_BOM);
|
||||
return trim($writtenContent, PHP_EOL . CSV::UTF8_BOM);
|
||||
}
|
||||
}
|
||||
@@ -34,4 +34,62 @@ class CellHelperTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
$this->assertEquals($expectedCellIndex, CellHelper::getCellIndexFromColumnIndex($columnIndex));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function testIsNonEmptyString()
|
||||
{
|
||||
$this->assertTrue(CellHelper::isNonEmptyString("string"));
|
||||
|
||||
$this->assertFalse(CellHelper::isNonEmptyString(""));
|
||||
$this->assertFalse(CellHelper::isNonEmptyString(0));
|
||||
$this->assertFalse(CellHelper::isNonEmptyString(1));
|
||||
$this->assertFalse(CellHelper::isNonEmptyString(true));
|
||||
$this->assertFalse(CellHelper::isNonEmptyString(false));
|
||||
$this->assertFalse(CellHelper::isNonEmptyString(["string"]));
|
||||
$this->assertFalse(CellHelper::isNonEmptyString(new \stdClass()));
|
||||
$this->assertFalse(CellHelper::isNonEmptyString(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function testIsNumeric()
|
||||
{
|
||||
$this->assertTrue(CellHelper::isNumeric(0));
|
||||
$this->assertTrue(CellHelper::isNumeric(10));
|
||||
$this->assertTrue(CellHelper::isNumeric(10.1));
|
||||
$this->assertTrue(CellHelper::isNumeric(10.10000000000000000000001));
|
||||
$this->assertTrue(CellHelper::isNumeric(0x539));
|
||||
$this->assertTrue(CellHelper::isNumeric(02471));
|
||||
$this->assertTrue(CellHelper::isNumeric(0b10100111001));
|
||||
$this->assertTrue(CellHelper::isNumeric(1337e0));
|
||||
|
||||
$this->assertFalse(CellHelper::isNumeric("0"));
|
||||
$this->assertFalse(CellHelper::isNumeric("42"));
|
||||
$this->assertFalse(CellHelper::isNumeric(true));
|
||||
$this->assertFalse(CellHelper::isNumeric([2]));
|
||||
$this->assertFalse(CellHelper::isNumeric(new \stdClass()));
|
||||
$this->assertFalse(CellHelper::isNumeric(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function testIsBoolean()
|
||||
{
|
||||
$this->assertTrue(CellHelper::isBoolean(true));
|
||||
$this->assertTrue(CellHelper::isBoolean(false));
|
||||
|
||||
$this->assertFalse(CellHelper::isBoolean(0));
|
||||
$this->assertFalse(CellHelper::isBoolean(1));
|
||||
$this->assertFalse(CellHelper::isBoolean("0"));
|
||||
$this->assertFalse(CellHelper::isBoolean("1"));
|
||||
$this->assertFalse(CellHelper::isBoolean("true"));
|
||||
$this->assertFalse(CellHelper::isBoolean("false"));
|
||||
$this->assertFalse(CellHelper::isBoolean([true]));
|
||||
$this->assertFalse(CellHelper::isBoolean(new \stdClass()));
|
||||
$this->assertFalse(CellHelper::isBoolean(null));
|
||||
}
|
||||
}
|
||||
@@ -17,13 +17,13 @@ class SheetTest extends \PHPUnit_Framework_TestCase
|
||||
/**
|
||||
* @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(0, $sheets[0]->getSheetNumber(), 'The first sheet should be number 0');
|
||||
$this->assertEquals(1, $sheets[1]->getSheetNumber(), 'The second sheet should be number 1');
|
||||
$this->assertEquals(0, $sheets[0]->getIndex(), 'The first sheet should be index 0');
|
||||
$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');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @return Sheet[]
|
||||
@@ -59,4 +81,27 @@ class SheetTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,19 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
$writer->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Box\Spout\Common\Exception\InvalidArgumentException
|
||||
*/
|
||||
public function testAddRowShouldThrowExceptionIfUnsupportedDataTypePassedIn()
|
||||
{
|
||||
$fileName = 'test_add_row_should_throw_exception_if_unsupported_data_type_passed_in.xlsx';
|
||||
$dataRows = [
|
||||
[new \stdClass()],
|
||||
];
|
||||
|
||||
$this->writeToXLSXFile($dataRows, $fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
@@ -96,7 +109,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testAddRowShouldWriteGivenDataToSheetUsingInlineStrings()
|
||||
{
|
||||
$fileName = 'test_add_row_should_write_given_data_to_sheet.xlsx';
|
||||
$fileName = 'test_add_row_should_write_given_data_to_sheet_using_inline_strings.xlsx';
|
||||
$dataRows = [
|
||||
['xlsx--11', 'xlsx--12'],
|
||||
['xlsx--21', 'xlsx--22', 'xlsx--23'],
|
||||
@@ -106,7 +119,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
foreach ($dataRows as $dataRow) {
|
||||
foreach ($dataRow as $cellValue) {
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, 1, $cellValue);
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, $cellValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,7 +129,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testAddRowShouldWriteGivenDataToTwoSheetUsingInlineStrings()
|
||||
{
|
||||
$fileName = 'test_add_row_should_write_given_data_to_sheet.xlsx';
|
||||
$fileName = 'test_add_row_should_write_given_data_to_two_sheets_using_inline_strings.xlsx';
|
||||
$dataRows = [
|
||||
['xlsx--11', 'xlsx--12'],
|
||||
['xlsx--21', 'xlsx--22', 'xlsx--23'],
|
||||
@@ -128,7 +141,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
for ($i = 1; $i <= $numSheets; $i++) {
|
||||
foreach ($dataRows as $dataRow) {
|
||||
foreach ($dataRow as $cellValue) {
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, $numSheets, $cellValue);
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, $numSheets, $cellValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +152,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testAddRowShouldWriteGivenDataToSheetUsingSharedStrings()
|
||||
{
|
||||
$fileName = 'test_add_row_should_write_given_data_to_sheet.xlsx';
|
||||
$fileName = 'test_add_row_should_write_given_data_to_sheet_using_shared_strings.xlsx';
|
||||
$dataRows = [
|
||||
['xlsx--11', 'xlsx--12'],
|
||||
['xlsx--21', 'xlsx--22', 'xlsx--23'],
|
||||
@@ -159,7 +172,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
*/
|
||||
public function testAddRowShouldWriteGivenDataToTwoSheetUsingSharedStrings()
|
||||
{
|
||||
$fileName = 'test_add_row_should_write_given_data_to_two_sheet_using_shared_strings.xlsx';
|
||||
$fileName = 'test_add_row_should_write_given_data_to_two_sheets_using_shared_strings.xlsx';
|
||||
$dataRows = [
|
||||
['xlsx--11', 'xlsx--12'],
|
||||
['xlsx--21', 'xlsx--22', 'xlsx--23'],
|
||||
@@ -177,6 +190,24 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testAddRowShouldSupportMultipleTypesOfData()
|
||||
{
|
||||
$fileName = 'test_add_row_should_support_multiple_types_of_data.xlsx';
|
||||
$dataRows = [
|
||||
['xlsx--11', true, '', 0, 10.2, null],
|
||||
];
|
||||
|
||||
$this->writeToXLSXFile($dataRows, $fileName, $shouldUseInlineStrings = false);
|
||||
|
||||
$this->assertSharedStringWasWritten($fileName, 'xlsx--11');
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 1); // true is converted to 1
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 0);
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 10.2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
@@ -219,17 +250,17 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
foreach ($dataRowsSheet1 as $dataRow) {
|
||||
foreach ($dataRow as $cellValue) {
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, 1, $cellValue, 'Data should have been written in Sheet 1');
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, $cellValue, 'Data should have been written in Sheet 1');
|
||||
}
|
||||
}
|
||||
foreach ($dataRowsSheet2 as $dataRow) {
|
||||
foreach ($dataRow as $cellValue) {
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, 2, $cellValue, 'Data should have been written in Sheet 2');
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 2, $cellValue, 'Data should have been written in Sheet 2');
|
||||
}
|
||||
}
|
||||
foreach ($dataRowsSheet1Again as $dataRow) {
|
||||
foreach ($dataRow as $cellValue) {
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, 1, $cellValue, 'Data should have been written in Sheet 1');
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, $cellValue, 'Data should have been written in Sheet 1');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,8 +283,8 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
$writer = $this->writeToXLSXFile($dataRows, $fileName, true, $shouldCreateSheetsAutomatically = true);
|
||||
$this->assertEquals(2, count($writer->getSheets()), '2 sheets should have been created.');
|
||||
|
||||
$this->assertInlineStringWasNotWrittenToSheet($fileName, 1, 'xlsx--sheet2--11');
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, 2, 'xlsx--sheet2--11');
|
||||
$this->assertInlineDataWasNotWrittenToSheet($fileName, 1, 'xlsx--sheet2--11');
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 2, 'xlsx--sheet2--11');
|
||||
|
||||
\ReflectionHelper::reset();
|
||||
}
|
||||
@@ -276,7 +307,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
$writer = $this->writeToXLSXFile($dataRows, $fileName, true, $shouldCreateSheetsAutomatically = false);
|
||||
$this->assertEquals(1, count($writer->getSheets()), 'Only 1 sheet should have been created.');
|
||||
|
||||
$this->assertInlineStringWasNotWrittenToSheet($fileName, 1, 'xlsx--sheet1--31');
|
||||
$this->assertInlineDataWasNotWrittenToSheet($fileName, 1, 'xlsx--sheet1--31');
|
||||
|
||||
\ReflectionHelper::reset();
|
||||
}
|
||||
@@ -293,8 +324,8 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->writeToXLSXFile($dataRows, $fileName);
|
||||
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, 1, 'I'm in "great" mood', 'Quotes should be escaped');
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, 1, 'This <must> be escaped & tested', '<, > and & should be escaped');
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 'I'm in "great" mood', 'Quotes should be escaped');
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 'This <must> be escaped & tested', '<, > and & should be escaped');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,10 +340,9 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
$this->writeToXLSXFile($dataRows, $fileName);
|
||||
|
||||
$this->assertInlineStringWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"');
|
||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 'control's _x0015_ "character"');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $allRows
|
||||
* @param string $fileName
|
||||
@@ -370,34 +400,34 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
|
||||
/**
|
||||
* @param string $fileName
|
||||
* @param int $sheetNumber
|
||||
* @param string $inlineString
|
||||
* @param int $sheetIndex
|
||||
* @param mixed $inlineData
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
private function assertInlineStringWasWrittenToSheet($fileName, $sheetNumber, $inlineString, $message = '')
|
||||
private function assertInlineDataWasWrittenToSheet($fileName, $sheetIndex, $inlineData, $message = '')
|
||||
{
|
||||
$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);
|
||||
|
||||
$this->assertContains($inlineString, $xmlContents, $message);
|
||||
$this->assertContains((string)$inlineData, $xmlContents, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileName
|
||||
* @param int $sheetNumber
|
||||
* @param string $inlineString
|
||||
* @param int $sheetIndex
|
||||
* @param mixed $inlineData
|
||||
* @param string $message
|
||||
* @return void
|
||||
*/
|
||||
private function assertInlineStringWasNotWrittenToSheet($fileName, $sheetNumber, $inlineString, $message = '')
|
||||
private function assertInlineDataWasNotWrittenToSheet($fileName, $sheetIndex, $inlineData, $message = '')
|
||||
{
|
||||
$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);
|
||||
|
||||
$this->assertNotContains($inlineString, $xmlContents, $message);
|
||||
$this->assertNotContains((string)$inlineData, $xmlContents, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -409,9 +439,18 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
||||
private function assertSharedStringWasWritten($fileName, $sharedString, $message = '')
|
||||
{
|
||||
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||
$pathToSharedStringsFile = $resourcePath . '#xl/sharedStrings.xml';
|
||||
$pathToSharedStringsFile = $resourcePath . $this->normalizePath('#xl/sharedStrings.xml');
|
||||
$xmlContents = file_get_contents('zip://' . $pathToSharedStringsFile);
|
||||
|
||||
$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.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user