Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3edb056286 | ||
|
|
edc0c22009 | ||
|
|
0319e578cb | ||
|
|
1e2452934c | ||
|
|
3e1793d852 | ||
|
|
c1b1bd0b76 | ||
|
|
cf239905d7 | ||
|
|
7c8f9293cc | ||
|
|
503ba97e9d | ||
|
|
b3df57d2e5 | ||
|
|
c6ebf115fc | ||
|
|
7d922e6776 | ||
|
|
d4cf853270 | ||
|
|
8bac924d48 | ||
|
|
591d86cf07 | ||
|
|
ba4a55292d |
No files matched your search
+7
-3
@@ -8,7 +8,11 @@ php:
|
|||||||
- hhvm
|
- hhvm
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- composer self-update
|
- composer install --no-interaction
|
||||||
- composer install --prefer-source
|
|
||||||
|
|
||||||
script: phpunit
|
script:
|
||||||
|
- mkdir -p build/logs
|
||||||
|
- php vendor/bin/phpunit --coverage-clover build/logs/clover.xml
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- php vendor/bin/coveralls -v
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
[](https://packagist.org/packages/box/spout)
|
[](https://packagist.org/packages/box/spout)
|
||||||
[](http://opensource.box.com/badges)
|
[](http://opensource.box.com/badges)
|
||||||
[](http://travis-ci.org/box/spout)
|
[](http://travis-ci.org/box/spout)
|
||||||
[](https://packagist.org/packages/box/spout) [](https://packagist.org/packages/box/spout)
|
[](https://coveralls.io/r/box/spout?branch=master)
|
||||||
|
[](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).
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,6 @@
|
|||||||
"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.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",
|
||||||
@@ -19,7 +18,8 @@
|
|||||||
"ext-simplexml": "*"
|
"ext-simplexml": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": ">=3.7"
|
"phpunit/phpunit": ">=3.7",
|
||||||
|
"satooshi/php-coveralls": "^0.6.1"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
|||||||
@@ -14,10 +14,6 @@
|
|||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
|
|
||||||
<logging>
|
|
||||||
<log type="coverage-html" target="tests/coverage/"/>
|
|
||||||
</logging>
|
|
||||||
|
|
||||||
<filter>
|
<filter>
|
||||||
<whitelist>
|
<whitelist>
|
||||||
<directory suffix=".php">src/</directory>
|
<directory suffix=".php">src/</directory>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class FileSystemHelper
|
|||||||
{
|
{
|
||||||
$this->throwIfOperationNotInBaseFolder($parentFolderPath);
|
$this->throwIfOperationNotInBaseFolder($parentFolderPath);
|
||||||
|
|
||||||
$folderPath = $parentFolderPath . DIRECTORY_SEPARATOR . $folderName;
|
$folderPath = $parentFolderPath . '/' . $folderName;
|
||||||
|
|
||||||
$wasCreationSuccessful = mkdir($folderPath, 0777, true);
|
$wasCreationSuccessful = mkdir($folderPath, 0777, true);
|
||||||
if (!$wasCreationSuccessful) {
|
if (!$wasCreationSuccessful) {
|
||||||
@@ -60,7 +60,7 @@ class FileSystemHelper
|
|||||||
{
|
{
|
||||||
$this->throwIfOperationNotInBaseFolder($parentFolderPath);
|
$this->throwIfOperationNotInBaseFolder($parentFolderPath);
|
||||||
|
|
||||||
$filePath = $parentFolderPath . DIRECTORY_SEPARATOR . $fileName;
|
$filePath = $parentFolderPath . '/' . $fileName;
|
||||||
|
|
||||||
$wasCreationSuccessful = file_put_contents($filePath, $fileContents);
|
$wasCreationSuccessful = file_put_contents($filePath, $fileContents);
|
||||||
if (!$wasCreationSuccessful) {
|
if (!$wasCreationSuccessful) {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class SharedStringsHelper
|
|||||||
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
||||||
|
|
||||||
$sharedStringsFilePath = $this->getSharedStringsFilePath();
|
$sharedStringsFilePath = $this->getSharedStringsFilePath();
|
||||||
if ($xmlReader->open($sharedStringsFilePath, null, LIBXML_NONET) === false) {
|
if ($xmlReader->open($sharedStringsFilePath, null, LIBXML_NOENT|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 . '".');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,12 +162,11 @@ class SharedStringsHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string The path to the shared strings XML file, working cross-platforms
|
* @return string The path to the shared strings XML file
|
||||||
*/
|
*/
|
||||||
protected function getSharedStringsFilePath()
|
protected function getSharedStringsFilePath()
|
||||||
{
|
{
|
||||||
$sharedStringsXmlFilePath = str_replace('/', DIRECTORY_SEPARATOR, self::SHARED_STRINGS_XML_FILE_PATH);
|
return 'zip://' . $this->filePath . '#' . self::SHARED_STRINGS_XML_FILE_PATH;
|
||||||
return 'zip://' . $this->filePath . '#' . $sharedStringsXmlFilePath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -250,7 +249,7 @@ class SharedStringsHelper
|
|||||||
protected function getSharedStringTempFilePath($sharedStringIndex)
|
protected function getSharedStringTempFilePath($sharedStringIndex)
|
||||||
{
|
{
|
||||||
$numTempFile = intval($sharedStringIndex / self::MAX_NUM_STRINGS_PER_TEMP_FILE);
|
$numTempFile = intval($sharedStringIndex / self::MAX_NUM_STRINGS_PER_TEMP_FILE);
|
||||||
return $this->tempFolder . DIRECTORY_SEPARATOR . 'sharedstrings' . $numTempFile;
|
return $this->tempFolder . '/sharedstrings' . $numTempFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -186,8 +186,7 @@ class WorksheetHelper
|
|||||||
*/
|
*/
|
||||||
protected function getFileAsXMLElementWithNamespace($xmlFilePath, $mainNamespace)
|
protected function getFileAsXMLElementWithNamespace($xmlFilePath, $mainNamespace)
|
||||||
{
|
{
|
||||||
$normalizedXmlFilePath = str_replace('/', DIRECTORY_SEPARATOR, $xmlFilePath);
|
$xmlContents = $this->globalFunctionsHelper->file_get_contents('zip://' . $this->filePath . '#' . $xmlFilePath);
|
||||||
$xmlContents = $this->globalFunctionsHelper->file_get_contents('zip://' . $this->filePath . '#' . $normalizedXmlFilePath);
|
|
||||||
|
|
||||||
$xmlElement = new \SimpleXMLElement($xmlContents);
|
$xmlElement = new \SimpleXMLElement($xmlContents);
|
||||||
$xmlElement->registerXPathNamespace('ns', $mainNamespace);
|
$xmlElement->registerXPathNamespace('ns', $mainNamespace);
|
||||||
|
|||||||
@@ -33,12 +33,11 @@ class Worksheet
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string Path of the XML file containing the worksheet data,
|
* @return string Path of the XML file containing the worksheet data,
|
||||||
* without the leading slash and working cross-platforms.
|
* without the leading slash.
|
||||||
*/
|
*/
|
||||||
public function getDataXmlFilePath()
|
public function getDataXmlFilePath()
|
||||||
{
|
{
|
||||||
$dataXmlFilePath = ltrim($this->dataXmlFilePath, '/');
|
return ltrim($this->dataXmlFilePath, '/');
|
||||||
return str_replace('/', DIRECTORY_SEPARATOR, $dataXmlFilePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+157
-21
@@ -19,6 +19,14 @@ use Box\Spout\Reader\Helper\XLSX\WorksheetHelper;
|
|||||||
*/
|
*/
|
||||||
class XLSX extends AbstractReader
|
class XLSX extends AbstractReader
|
||||||
{
|
{
|
||||||
|
const CELL_TYPE_INLINE_STRING = 'inlineStr';
|
||||||
|
const CELL_TYPE_STR = 'str';
|
||||||
|
const CELL_TYPE_SHARED_STRING = 's';
|
||||||
|
const CELL_TYPE_BOOLEAN = 'b';
|
||||||
|
const CELL_TYPE_NUMERIC = 'n';
|
||||||
|
const CELL_TYPE_DATE = 'd';
|
||||||
|
const CELL_TYPE_ERROR = 'e';
|
||||||
|
|
||||||
/** @var string Real path of the file to read */
|
/** @var string Real path of the file to read */
|
||||||
protected $filePath;
|
protected $filePath;
|
||||||
|
|
||||||
@@ -158,7 +166,7 @@ class XLSX extends AbstractReader
|
|||||||
$worksheetDataXMLFilePath = $worksheet->getDataXmlFilePath();
|
$worksheetDataXMLFilePath = $worksheet->getDataXmlFilePath();
|
||||||
|
|
||||||
$worksheetDataFilePath = 'zip://' . $this->filePath . '#' . $worksheetDataXMLFilePath;
|
$worksheetDataFilePath = 'zip://' . $this->filePath . '#' . $worksheetDataXMLFilePath;
|
||||||
if ($this->xmlReader->open($worksheetDataFilePath, null, LIBXML_NONET) === false) {
|
if ($this->xmlReader->open($worksheetDataFilePath, null, LIBXML_NOENT|LIBXML_NONET) === false) {
|
||||||
throw new IOException('Could not open "' . $worksheetDataXMLFilePath . '".');
|
throw new IOException('Could not open "' . $worksheetDataXMLFilePath . '".');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,6 +185,7 @@ class XLSX extends AbstractReader
|
|||||||
throw new BadUsageException('You must call nextSheet() before calling hasNextRow() or nextRow()');
|
throw new BadUsageException('You must call nextSheet() before calling hasNextRow() or nextRow()');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$escaper = new \Box\Spout\Common\Escaper\XLSX();
|
||||||
$isInsideRowTag = false;
|
$isInsideRowTag = false;
|
||||||
$rowData = [];
|
$rowData = [];
|
||||||
|
|
||||||
@@ -188,6 +197,7 @@ class XLSX extends AbstractReader
|
|||||||
$lastCellIndex = $matches[1];
|
$lastCellIndex = $matches[1];
|
||||||
$this->numberOfColumns = CellHelper::getColumnIndexFromCellIndex($lastCellIndex) + 1;
|
$this->numberOfColumns = CellHelper::getColumnIndexFromCellIndex($lastCellIndex) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if ($this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'row') {
|
} else if ($this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'row') {
|
||||||
// Start of the row description
|
// Start of the row description
|
||||||
$isInsideRowTag = true;
|
$isInsideRowTag = true;
|
||||||
@@ -200,32 +210,15 @@ class XLSX extends AbstractReader
|
|||||||
$numberOfColumnsForRow = intval($numberOfColumnsForRow);
|
$numberOfColumnsForRow = intval($numberOfColumnsForRow);
|
||||||
}
|
}
|
||||||
$rowData = ($numberOfColumnsForRow !== 0) ? array_fill(0, $numberOfColumnsForRow, '') : [];
|
$rowData = ($numberOfColumnsForRow !== 0) ? array_fill(0, $numberOfColumnsForRow, '') : [];
|
||||||
|
|
||||||
} else if ($isInsideRowTag && $this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'c') {
|
} else if ($isInsideRowTag && $this->xmlReader->nodeType == \XMLReader::ELEMENT && $this->xmlReader->name === 'c') {
|
||||||
// Start of a cell description
|
// Start of a cell description
|
||||||
$currentCellIndex = $this->xmlReader->getAttribute('r');
|
$currentCellIndex = $this->xmlReader->getAttribute('r');
|
||||||
$currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
|
$currentColumnIndex = CellHelper::getColumnIndexFromCellIndex($currentCellIndex);
|
||||||
|
|
||||||
$node = $this->xmlReader->expand();
|
$node = $this->xmlReader->expand();
|
||||||
|
$rowData[$currentColumnIndex] = $this->getCellValue($node, $escaper);
|
||||||
|
|
||||||
$hasInlineString = ($this->xmlReader->getAttribute('t') === 'inlineStr');
|
|
||||||
$hasSharedString = ($this->xmlReader->getAttribute('t') === 's');
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// 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') {
|
} else if ($this->xmlReader->nodeType == \XMLReader::END_ELEMENT && $this->xmlReader->name === 'row') {
|
||||||
// End of the row description
|
// End of the row description
|
||||||
// If needed, we fill the empty cells
|
// If needed, we fill the empty cells
|
||||||
@@ -238,6 +231,149 @@ class XLSX extends AbstractReader
|
|||||||
return ($rowData !== []) ? $rowData : null;
|
return ($rowData !== []) ? $rowData : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cell's string value from a node's nested value node
|
||||||
|
*
|
||||||
|
* @param \DOMNode $node
|
||||||
|
* @return string The value associated with the cell
|
||||||
|
*/
|
||||||
|
protected function getVNodeValue($node)
|
||||||
|
{
|
||||||
|
// for cell types having a "v" tag containing the value.
|
||||||
|
// if not, the returned value should be empty string.
|
||||||
|
$vNode = $node->getElementsByTagName('v')->item(0);
|
||||||
|
if ($vNode !== null) {
|
||||||
|
return $vNode->nodeValue;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cell String value where string is inline.
|
||||||
|
*
|
||||||
|
* @param \DOMNode $node
|
||||||
|
* @param \Box\Spout\Common\Escaper\XLSX $escaper
|
||||||
|
* @return string The value associated with the cell (null when the cell has an error)
|
||||||
|
*/
|
||||||
|
protected function formatInlineStringCellValue($node, $escaper)
|
||||||
|
{
|
||||||
|
// inline strings are formatted this way:
|
||||||
|
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
|
||||||
|
$tNode = $node->getElementsByTagName('t')->item(0);
|
||||||
|
$escapedCellValue = trim($tNode->nodeValue);
|
||||||
|
$cellValue = $escaper->unescape($escapedCellValue);
|
||||||
|
return $cellValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cell String value from shared-strings file using nodeValue index.
|
||||||
|
*
|
||||||
|
* @param string $nodeValue
|
||||||
|
* @param \Box\Spout\Common\Escaper\XLSX $escaper
|
||||||
|
* @return string The value associated with the cell (null when the cell has an error)
|
||||||
|
*/
|
||||||
|
protected function formatSharedStringCellValue($nodeValue, $escaper)
|
||||||
|
{
|
||||||
|
// shared strings are formatted this way:
|
||||||
|
// <c r="A1" t="s"><v>[SHARED_STRING_INDEX]</v></c>
|
||||||
|
$sharedStringIndex = intval($nodeValue);
|
||||||
|
$escapedCellValue = $this->sharedStringsHelper->getStringAtIndex($sharedStringIndex);
|
||||||
|
$cellValue = $escaper->unescape($escapedCellValue);
|
||||||
|
return $cellValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cell String value, where string is stored in value node.
|
||||||
|
*
|
||||||
|
* @param string $nodeValue
|
||||||
|
* @param \Box\Spout\Common\Escaper\XLSX $escaper
|
||||||
|
* @return string The value associated with the cell (null when the cell has an error)
|
||||||
|
*/
|
||||||
|
protected function formatStrCellValue($nodeValue, $escaper)
|
||||||
|
{
|
||||||
|
$escapedCellValue = trim($nodeValue);
|
||||||
|
$cellValue = $escaper->unescape($escapedCellValue);
|
||||||
|
return $cellValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cell Numeric value from string of nodeValue.
|
||||||
|
*
|
||||||
|
* @param string $nodeValue
|
||||||
|
* @param \Box\Spout\Common\Escaper\XLSX $escaper
|
||||||
|
* @return int|float The value associated with the cell
|
||||||
|
*/
|
||||||
|
protected function formatNumericCellValue($nodeValue)
|
||||||
|
{
|
||||||
|
$cellValue = is_int($nodeValue) ? intval($nodeValue) : floatval($nodeValue);
|
||||||
|
return $cellValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cell Boolean value from a specific node's Value.
|
||||||
|
*
|
||||||
|
* @param string $nodeValue
|
||||||
|
* @return bool The value associated with the cell
|
||||||
|
*/
|
||||||
|
protected function formatBooleanCellValue($nodeValue)
|
||||||
|
{
|
||||||
|
// !! is similar to boolval()
|
||||||
|
$cellValue = !!$nodeValue;
|
||||||
|
return $cellValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a cell's PHP Date value, associated to the given stored nodeValue.
|
||||||
|
*
|
||||||
|
* @param string $nodeValue
|
||||||
|
* @param \Box\Spout\Common\Escaper\XLSX $escaper
|
||||||
|
* @return DateTime|null The value associated with the cell (null when the cell has an error)
|
||||||
|
*/
|
||||||
|
protected function formatDateCellValue($nodeValue)
|
||||||
|
{
|
||||||
|
try { // Mitigate thrown Exception on invalid date-time format (http://php.net/manual/en/datetime.construct.php)
|
||||||
|
$cellValue = new \DateTime($nodeValue);
|
||||||
|
return $cellValue;
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
||||||
|
*
|
||||||
|
* @param \DOMNode $node
|
||||||
|
* @param \Box\Spout\Common\Escaper\XLSX $escaper
|
||||||
|
* @return string|int|float|bool|null The value associated with the cell (null when the cell has an error)
|
||||||
|
*/
|
||||||
|
protected function getCellValue($node, $escaper)
|
||||||
|
{
|
||||||
|
// Default cell type is "n"
|
||||||
|
$cellType = $node->getAttribute('t') ?: 'n';
|
||||||
|
$vNodeValue = $this->getVNodeValue($node);
|
||||||
|
if ( ($vNodeValue === "") && ($cellType !== self::CELL_TYPE_INLINE_STRING) ) {
|
||||||
|
return $vNodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($cellType)
|
||||||
|
{
|
||||||
|
case self::CELL_TYPE_INLINE_STRING:
|
||||||
|
return $this->formatInlineStringCellValue($node, $escaper);
|
||||||
|
case self::CELL_TYPE_SHARED_STRING:
|
||||||
|
return $this->formatSharedStringCellValue($vNodeValue, $escaper);
|
||||||
|
case self::CELL_TYPE_STR:
|
||||||
|
return $this->formatStrCellValue($vNodeValue, $escaper);
|
||||||
|
case self::CELL_TYPE_BOOLEAN:
|
||||||
|
return $this->formatBooleanCellValue($vNodeValue);
|
||||||
|
case self::CELL_TYPE_NUMERIC:
|
||||||
|
return $this->formatNumericCellValue($vNodeValue);
|
||||||
|
case self::CELL_TYPE_DATE:
|
||||||
|
return $this->formatDateCellValue($vNodeValue);
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the reader. To be used after reading the file.
|
* Closes the reader. To be used after reading the file.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ EOD;
|
|||||||
*/
|
*/
|
||||||
public function __construct($xlFolder)
|
public function __construct($xlFolder)
|
||||||
{
|
{
|
||||||
$sharedStringsFilePath = $xlFolder . DIRECTORY_SEPARATOR . self::SHARED_STRINGS_FILE_NAME;
|
$sharedStringsFilePath = $xlFolder . '/' . self::SHARED_STRINGS_FILE_NAME;
|
||||||
$this->sharedStringsFilePointer = fopen($sharedStringsFilePath, 'w');
|
$this->sharedStringsFilePointer = fopen($sharedStringsFilePath, 'w');
|
||||||
|
|
||||||
$this->throwIfSharedStringsFilePointerIsNotAvailable();
|
$this->throwIfSharedStringsFilePointerIsNotAvailable();
|
||||||
|
|||||||
@@ -33,18 +33,30 @@ class ZipHelper
|
|||||||
*/
|
*/
|
||||||
protected function addFolderToZip($zip, $folderPath)
|
protected function addFolderToZip($zip, $folderPath)
|
||||||
{
|
{
|
||||||
$folderRealPath = realpath($folderPath) . DIRECTORY_SEPARATOR;
|
$folderRealPath = $this->getNormalizedRealPath($folderPath) . '/';
|
||||||
$itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
|
$itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
|
||||||
|
|
||||||
foreach ($itemIterator as $itemInfo) {
|
foreach ($itemIterator as $itemInfo) {
|
||||||
$itemRealPath = realpath($itemInfo->getPathname());
|
$itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
|
||||||
$itemLocalPath = str_replace($folderRealPath, '', $itemRealPath);
|
$itemLocalPath = str_replace($folderRealPath, '', $itemRealPath);
|
||||||
|
|
||||||
if ($itemInfo->isFile()) {
|
if ($itemInfo->isFile()) {
|
||||||
$zip->addFile($itemInfo->getPathname(), $itemLocalPath);
|
$zip->addFile($itemRealPath, $itemLocalPath);
|
||||||
} else if ($itemInfo->isDir()) {
|
} else if ($itemInfo->isDir()) {
|
||||||
$zip->addEmptyDir($itemLocalPath);
|
$zip->addEmptyDir($itemLocalPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns canonicalized absolute pathname, containing only forward slashes.
|
||||||
|
*
|
||||||
|
* @param string $path Path to normalize
|
||||||
|
* @return string Normalized and canonicalized path
|
||||||
|
*/
|
||||||
|
protected function getNormalizedRealPath($path)
|
||||||
|
{
|
||||||
|
$realPath = realpath($path);
|
||||||
|
return str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ EOD;
|
|||||||
|
|
||||||
$this->stringsEscaper = new \Box\Spout\Common\Escaper\XLSX();
|
$this->stringsEscaper = new \Box\Spout\Common\Escaper\XLSX();
|
||||||
|
|
||||||
$this->worksheetFilePath = $worksheetFilesFolder . DIRECTORY_SEPARATOR . strtolower($this->externalSheet->getName()) . '.xml';
|
$this->worksheetFilePath = $worksheetFilesFolder . '/' . strtolower($this->externalSheet->getName()) . '.xml';
|
||||||
$this->startSheet();
|
$this->startSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,6 +114,29 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals($expectedRows, $allRows);
|
$this->assertEquals($expectedRows, $allRows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function testReadShouldSupportAllCellTypes()
|
||||||
|
{
|
||||||
|
$allRows = $this->getAllRowsForFile('sheet_with_all_cell_types.xlsx');
|
||||||
|
|
||||||
|
$expectedRows = [
|
||||||
|
[
|
||||||
|
's1--A1', 's1--A2',
|
||||||
|
false, true,
|
||||||
|
\DateTime::createFromFormat('Y-m-d H:i:s', '2015-06-03 13:21:58'),
|
||||||
|
\DateTime::createFromFormat('Y-m-d H:i:s', '2015-06-01 00:00:00'),
|
||||||
|
10, 10.43,
|
||||||
|
null,
|
||||||
|
'weird string', // valid 'str' string
|
||||||
|
null, // invalid date
|
||||||
|
],
|
||||||
|
['', '', '', '', '', '', '', '', ''],
|
||||||
|
];
|
||||||
|
$this->assertEquals($expectedRows, $allRows);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
@@ -229,7 +252,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
{
|
{
|
||||||
$allRows = $this->getAllRowsForFile('billion_laughs_test_file.xlsx');
|
$allRows = $this->getAllRowsForFile('billion_laughs_test_file.xlsx');
|
||||||
|
|
||||||
$expectedMaxMemoryUsage = 20 * 1024 * 1024; // 20MB
|
$expectedMaxMemoryUsage = 30 * 1024 * 1024; // 30MB
|
||||||
$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'];
|
||||||
|
|||||||
@@ -90,18 +90,9 @@ class SheetTest extends \PHPUnit_Framework_TestCase
|
|||||||
*/
|
*/
|
||||||
private function assertSheetNameEquals($expectedName, $resourcePath, $message = '')
|
private function assertSheetNameEquals($expectedName, $resourcePath, $message = '')
|
||||||
{
|
{
|
||||||
$pathToWorkbookFile = $resourcePath . $this->normalizePath('#xl/workbook.xml');
|
$pathToWorkbookFile = $resourcePath . '#xl/workbook.xml';
|
||||||
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
|
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
|
||||||
|
|
||||||
$this->assertContains('<sheet name="' . $expectedName . '"', $xmlContents, $message);
|
$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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -408,7 +408,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
private function assertInlineDataWasWrittenToSheet($fileName, $sheetIndex, $inlineData, $message = '')
|
private function assertInlineDataWasWrittenToSheet($fileName, $sheetIndex, $inlineData, $message = '')
|
||||||
{
|
{
|
||||||
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||||
$pathToSheetFile = $resourcePath . $this->normalizePath('#xl/worksheets/sheet' . $sheetIndex . '.xml');
|
$pathToSheetFile = $resourcePath . '#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);
|
||||||
@@ -424,7 +424,7 @@ class XLSXTest extends \PHPUnit_Framework_TestCase
|
|||||||
private function assertInlineDataWasNotWrittenToSheet($fileName, $sheetIndex, $inlineData, $message = '')
|
private function assertInlineDataWasNotWrittenToSheet($fileName, $sheetIndex, $inlineData, $message = '')
|
||||||
{
|
{
|
||||||
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
||||||
$pathToSheetFile = $resourcePath . $this->normalizePath('#xl/worksheets/sheet' . $sheetIndex . '.xml');
|
$pathToSheetFile = $resourcePath . '#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);
|
||||||
@@ -439,18 +439,9 @@ 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 . $this->normalizePath('#xl/sharedStrings.xml');
|
$pathToSharedStringsFile = $resourcePath . '#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.
Reference in new issue
Block a user