Compare commits

..
28 Commits
Author SHA1 Message Date
Adrien Loison 9bdb027d31 Update documentation with number format 2021-05-14 23:18:09 +02:00
Adrien Loison 76017f0949 Skipped cells are in wrong order
This only happens when no sheet's dimension is specified.
When filling empty cells with empty strings, we push these new cells with the correct cell index but they are added at the end of the cells array (normal PHP behavior). This means that we were going from `{[0] => 'A', [2] => 'C'}` to `{[0] => 'A', [2] => 'C', [1] => ''}`. We therefore need to sort the array to get the values in the correct order ( `{[0] => 'A', [1] => '', [2] => 'C'}`).
2021-05-14 23:01:36 +02:00
Adrien Loison fde8a495ca Inline strings can contain multiple value nodes
We were working under the assumption that XLSX's inline strings only had a single value node (`<t>`). This is incorrect.
To get the actual value of an inline string node, we need to concatenate the value of all its child nodes.
2021-05-14 22:36:34 +02:00
Adrien Loison 69eeeff478 Remove var_dump 2021-05-14 15:17:30 +02:00
Adrien Loison 2ff515c306 Support for strict OOXML
There are 2 types of OOXML format: transitional and strict. Transitional is what's mostly used but some softwares still allow XLSX to be generated using the strict OOXML format.
In this format, namespaces of the XML files are different: `http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings` is replaced by `http://purl.oclc.org/ooxml/officeDocument/relationships/sharedStrings` for instance. To support both formats, Spout needs to be able to look for both.
2021-05-13 12:16:20 +02:00
Toby Allen 0837d49c2b Remove unneeded comma 2021-05-10 10:04:14 +02:00
Toby Allen 110876e32c Some small grammar changes 2021-05-10 10:04:14 +02:00
Adrien Loison 8c1f0cc447 Floats must not be stored as locale dependent
Floats are currently stored formatted per the locale setting. This leads to different values being written whether the locale uses "." or "," for the decimal point for instance. This poses a problem as floats must be stored using "." as the decimal point to be valid.
This commit ensures that the floats are stored correctly by forcing the formatting of the value.
2021-05-05 20:43:02 +02:00
Antoine Lamirault eb84ec9364 Rename ManagedStyle to PossiblyUpdatedStyle and add documentation 2021-03-30 19:42:21 +02:00
Antoine Lamirault 8a17d6c71f Remove rowStyle reference and replace it by new RegisteredStyle class 2021-03-30 19:42:21 +02:00
Antoine Lamirault c6f596c776 New code review fixs 2021-03-30 19:42:21 +02:00
Antoine Lamirault 11d91e1740 Code review changes 2021-03-30 19:42:21 +02:00
Antoine Lamirault 197fb9987a Register style can be skipped when already registered 2021-03-30 19:42:21 +02:00
Antoine Lamirault a58b340835 Empty style on cell 2021-03-30 19:42:21 +02:00
Antoine Lamirault 57b6e87a65 Begin optimize xlsx write 2021-03-30 19:42:21 +02:00
yiranzai 91f756be0b remove custom headers 2021-03-18 20:05:10 +01:00
yiranzai 03e1ce438a Fixed Code Style 2021-03-18 20:05:10 +01:00
yiranzai df9d96366f Fixed WriterAbstract::openToBrowser meet RFC6266 2021-03-18 20:05:10 +01:00
Andrii Dembitskyi 0f20c99a7f Fix constant usage in example 2021-02-10 10:53:55 +01:00
jmsche 9ab0b10a0f Contributing: added info about code style 2021-02-09 17:39:42 +01:00
jmsche ed9322e309 Shorter (relevant) diff by php-cs-fixer for Travis CI 2021-02-09 13:58:05 +01:00
Oded Arbel 73347517f0 added comment with spec link, as requested 2021-02-09 11:21:25 +01:00
Oded Arbel ad913f0100 write boolean value according to http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#datatype-boolean instead of just "1" for true or "" for false; 2021-02-09 11:21:25 +01:00
jmsche c29d1877b8 Fixed code style (probably due to recent php-cs-fixer version) 2021-02-08 22:03:03 +01:00
Petr Skoda 816596183f Add full support for PHP 8.0
Unfortunately due to PHPUnit 8.5 dependency
this also drops support for PHP 7.1
2021-02-08 14:31:55 +01:00
madflow ab973cab34 use existing base folder 2019-12-19 23:24:24 +01:00
madflow b8eb2bb814 error_reporting set to -1 2019-12-16 13:42:24 +01:00
madflow f54f7a400c add PHP 7.4 2019-12-16 13:42:24 +01:00
59 changed files with 563 additions and 168 deletions

No files matched your search

+1
View File
@@ -6,3 +6,4 @@
/vendor
/composer.lock
/.php_cs.cache
/.phpunit.result.cache
+9 -6
View File
@@ -1,18 +1,21 @@
dist: trusty
dist: bionic
sudo: false
language: php
matrix:
include:
- php: 7.1
env: WITH_CS=true
- php: 7.1
env: WITH_PHPUNIT=true WITH_COVERAGE=true
- php: 7.2
env: WITH_PHPUNIT=true
- php: 7.3
env: WITH_PHPUNIT=true
- php: 7.3
env: WITH_PHPUNIT=true WITH_COVERAGE=true
- php: 7.4
env: WITH_PHPUNIT=true
- php: 8.0
env: WITH_PHPUNIT=true
cache:
directories:
@@ -39,7 +42,7 @@ before_script:
script:
- |
if [[ "$WITH_CS" == "true" ]]; then
vendor/bin/php-cs-fixer fix --config=.php_cs.dist --verbose --diff --dry-run
vendor/bin/php-cs-fixer fix --config=.php_cs.dist --verbose --diff --dry-run --diff-format=udiff
fi
- |
if [[ "$WITH_PHPUNIT" == "true" ]]; then
+15 -1
View File
@@ -68,7 +68,21 @@ This will add your changes on top of what's already in upstream, minimizing merg
Make sure that all tests are passing before submitting a pull request.
### Step 8: Send the pull request
### Step 8: Fix code style
Run the following command to check the code style of your changes:
```
vendor/bin/php-cs-fixer fix --config=.php_cs.dist --verbose --diff --dry-run --diff-format=udiff
```
This will print a diff of proposed code style changes. To apply these suggestions, run the following command:
```
vendor/bin/php-cs-fixer fix --config=.php_cs.dist
```
### Step 9: Send the pull request
Send the pull request from your feature branch to us. Be sure to include a description that lets us know what work you did.
+4 -4
View File
@@ -8,9 +8,9 @@
[![Total Downloads](https://poser.pugx.org/box/spout/downloads)](https://packagist.org/packages/box/spout)
Spout is a PHP library to read and write spreadsheet files (CSV, XLSX and ODS), 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 3MB).
Unlike other file readers or writers, it is capable of processing very large files, while keeping the memory usage really low (less than 3MB).
Join the community and come discuss about Spout: [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Join the community and come discuss Spout: [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
## Documentation
@@ -20,7 +20,7 @@ Full documentation can be found at [https://opensource.box.com/spout/](https://o
## Requirements
* PHP version 7.1 or higher
* PHP version 7.2 or higher
* PHP extension `php_zip` enabled
* PHP extension `php_xmlreader` enabled
@@ -43,7 +43,7 @@ For information, the performance tests take about 10 minutes to run (processing
## Support
You can ask questions, submit new features ideas or discuss about Spout in the chat room:<br>
You can ask questions, submit new features ideas or discuss Spout in the chat room:<br>
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
## Copyright and License
+1 -1
View File
@@ -11,7 +11,7 @@ With the 3.0 version, this is now possible: each cell can have its own style.
Spout 3.0 tries to enforce better typing. For instance, instead of using/returning generic arrays, Spout now makes use of specific `Row` and `Cell` objects that can encapsulate more data such as type, style, value.
Finally, **_Spout 3.0 only supports PHP 7.1 and above_**, as other PHP versions are no longer supported by the community.
Finally, **_Spout 3.2 only supports PHP 7.2 and above_**, as other PHP versions are no longer supported by the community.
Reader changes
--------------
+5 -4
View File
@@ -12,12 +12,13 @@
}
],
"require": {
"php": ">=7.1.0",
"php": ">=7.2.0",
"ext-zip": "*",
"ext-xmlreader" : "*"
"ext-xmlreader": "*",
"ext-dom": "*"
},
"require-dev": {
"phpunit/phpunit": "^7",
"phpunit/phpunit": "^8",
"friendsofphp/php-cs-fixer": "^2"
},
"suggest": {
@@ -36,7 +37,7 @@
},
"config": {
"platform": {
"php": "7.1"
"php": "7.2"
}
}
}
+13 -12
View File
@@ -116,18 +116,19 @@ $reader->setShouldPreserveEmptyRows(true);
For fonts and alignments, {{ site.spout_html }} does not support all the possible formatting options yet. But you can find the most important ones:
| Category | Property | API
|:----------|:---------------|:--------------------------------------
| Font | Bold | `StyleBuilder::setFontBold()`
| | Italic | `StyleBuilder::setFontItalic()`
| | Underline | `StyleBuilder::setFontUnderline()`
| | Strikethrough | `StyleBuilder::setFontStrikethrough()`
| | Font name | `StyleBuilder::setFontName('Arial')`
| | Font size | `StyleBuilder::setFontSize(14)`
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
| Alignment | Cell alignment | `StyleBuilder::setCellAlignment(CellAlignment::CENTER)`
| | Wrap text | `StyleBuilder::setShouldWrapText(true)`
| Category | Property | API
|:---------------------|:---------------|:--------------------------------------
| Font | Bold | `StyleBuilder::setFontBold()`
| | Italic | `StyleBuilder::setFontItalic()`
| | Underline | `StyleBuilder::setFontUnderline()`
| | Strikethrough | `StyleBuilder::setFontStrikethrough()`
| | Font name | `StyleBuilder::setFontName('Arial')`
| | Font size | `StyleBuilder::setFontSize(14)`
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
| Alignment | Cell alignment | `StyleBuilder::setCellAlignment(CellAlignment::CENTER)`
| | Wrap text | `StyleBuilder::setShouldWrapText(true)`
| Format _(XLSX only)_ | Number format | `StyleBuilder::setFormat('0.000')`
| | Date format | `StyleBuilder::setFormat('m/d/yy h:mm')`
### Styling rows
+1 -1
View File
@@ -10,7 +10,7 @@ This guide will help you install {{ site.spout_html }} and teach you how to use
## Requirements
* PHP version 7.1 or higher
* PHP version 7.2 or higher
* PHP extension `ext-zip` enabled
* PHP extension `ext-xmlreader` enabled
@@ -94,7 +94,7 @@ class MyStreamController extends Controller
$i++;
// Flushing the buffer every N rows to stream echo'ed content.
if ($i % FLUSH_THRESHOLD === 0) {
if ($i % self::FLUSH_THRESHOLD === 0) {
flush();
}
}
+5 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.5/phpunit.xsd"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.5/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="false"
@@ -8,6 +8,10 @@
defaultTestSuite="unit-tests"
verbose="false">
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="unit-tests">
+1 -1
View File
@@ -65,7 +65,7 @@ class Cell
protected $style;
/**
* @param $value mixed
* @param mixed|null $value
* @param Style|null $style
*/
public function __construct($value, Style $style = null)
+43
View File
@@ -84,6 +84,12 @@ class Style
/** @var bool */
private $hasSetFormat = false;
/** @var bool */
private $isRegistered = false;
/** @var bool */
private $isEmpty = true;
/**
* @return int|null
*/
@@ -119,6 +125,7 @@ class Style
{
$this->shouldApplyBorder = true;
$this->border = $border;
$this->isEmpty = false;
return $this;
}
@@ -147,6 +154,7 @@ class Style
$this->fontBold = true;
$this->hasSetFontBold = true;
$this->shouldApplyFont = true;
$this->isEmpty = false;
return $this;
}
@@ -175,6 +183,7 @@ class Style
$this->fontItalic = true;
$this->hasSetFontItalic = true;
$this->shouldApplyFont = true;
$this->isEmpty = false;
return $this;
}
@@ -203,6 +212,7 @@ class Style
$this->fontUnderline = true;
$this->hasSetFontUnderline = true;
$this->shouldApplyFont = true;
$this->isEmpty = false;
return $this;
}
@@ -231,6 +241,7 @@ class Style
$this->fontStrikethrough = true;
$this->hasSetFontStrikethrough = true;
$this->shouldApplyFont = true;
$this->isEmpty = false;
return $this;
}
@@ -260,6 +271,7 @@ class Style
$this->fontSize = $fontSize;
$this->hasSetFontSize = true;
$this->shouldApplyFont = true;
$this->isEmpty = false;
return $this;
}
@@ -291,6 +303,7 @@ class Style
$this->fontColor = $fontColor;
$this->hasSetFontColor = true;
$this->shouldApplyFont = true;
$this->isEmpty = false;
return $this;
}
@@ -320,6 +333,7 @@ class Style
$this->fontName = $fontName;
$this->hasSetFontName = true;
$this->shouldApplyFont = true;
$this->isEmpty = false;
return $this;
}
@@ -350,6 +364,7 @@ class Style
$this->cellAlignment = $cellAlignment;
$this->hasSetCellAlignment = true;
$this->shouldApplyCellAlignment = true;
$this->isEmpty = false;
return $this;
}
@@ -386,6 +401,7 @@ class Style
{
$this->shouldWrapText = $shouldWrap;
$this->hasSetWrapText = true;
$this->isEmpty = false;
return $this;
}
@@ -415,6 +431,7 @@ class Style
{
$this->hasSetBackgroundColor = true;
$this->backgroundColor = $color;
$this->isEmpty = false;
return $this;
}
@@ -444,6 +461,7 @@ class Style
{
$this->hasSetFormat = true;
$this->format = $format;
$this->isEmpty = false;
return $this;
}
@@ -463,4 +481,29 @@ class Style
{
return $this->hasSetFormat;
}
/**
* @return bool
*/
public function isRegistered() : bool
{
return $this->isRegistered;
}
public function markAsRegistered(?int $id) : void
{
$this->setId($id);
$this->isRegistered = true;
}
public function unmarkAsRegistered() : void
{
$this->setId(0);
$this->isRegistered = false;
}
public function isEmpty() : bool
{
return $this->isEmpty;
}
}
+7 -3
View File
@@ -17,7 +17,7 @@ class FileSystemHelper implements FileSystemHelperInterface
/**
* @param string $baseFolderPath The path of the base folder where all the I/O can occur
*/
public function __construct($baseFolderPath)
public function __construct(string $baseFolderPath)
{
$this->baseFolderRealPath = \realpath($baseFolderPath);
}
@@ -117,12 +117,16 @@ class FileSystemHelper implements FileSystemHelperInterface
* should occur is not inside the base folder.
*
* @param string $operationFolderPath The path of the folder where the I/O operation should occur
* @throws \Box\Spout\Common\Exception\IOException If the folder where the I/O operation should occur is not inside the base folder
* @throws \Box\Spout\Common\Exception\IOException If the folder where the I/O operation should occur
* is not inside the base folder or the base folder does not exist
* @return void
*/
protected function throwIfOperationNotInBaseFolder($operationFolderPath)
protected function throwIfOperationNotInBaseFolder(string $operationFolderPath)
{
$operationFolderRealPath = \realpath($operationFolderPath);
if (!$this->baseFolderRealPath) {
throw new IOException("The base folder path is invalid: {$this->baseFolderRealPath}");
}
$isInBaseFolder = (\strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0);
if (!$isInBaseFolder) {
throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}");
+34
View File
@@ -13,12 +13,20 @@ class StringHelper
/** @var bool Whether the mbstring extension is loaded */
protected $hasMbstringSupport;
/** @var bool Whether the code is running with PHP7 or older versions */
private $isRunningPhp7OrOlder;
/** @var array Locale info, used for number formatting */
private $localeInfo;
/**
*
*/
public function __construct()
{
$this->hasMbstringSupport = \extension_loaded('mbstring');
$this->isRunningPhp7OrOlder = \version_compare(PHP_VERSION, '8.0.0') < 0;
$this->localeInfo = \localeconv();
}
/**
@@ -68,4 +76,30 @@ class StringHelper
return ($position !== false) ? $position : -1;
}
/**
* Formats a numeric value (int or float) in a way that's compatible with the expected spreadsheet format.
*
* Formatting of float values is locale dependent in PHP < 8.
* Thousands separators and decimal points vary from locale to locale (en_US: 12.34 vs pl_PL: 12,34).
* However, float values must be formatted with no thousands separator and a "." as decimal point
* to work properly. This method can be used to convert the value to the correct format before storing it.
*
* @see https://wiki.php.net/rfc/locale_independent_float_to_string for the changed behavior in PHP8.
*
* @param int|float $numericValue
* @return string
*/
public function formatNumericValue($numericValue)
{
if ($this->isRunningPhp7OrOlder && is_float($numericValue)) {
return str_replace(
[$this->localeInfo['thousands_sep'], $this->localeInfo['decimal_point']],
['', '.'],
$numericValue
);
}
return $numericValue;
}
}
@@ -56,12 +56,25 @@ class RowManager
$rowCells = $row->getCells();
$maxCellIndex = $numCells;
// If the row has empty cells, calling "setCellAtIndex" will add the cell
// but in the wrong place (the new cell is added at the end of the array).
// Therefore, we need to sort the array using keys to have proper order.
// @see https://github.com/box/spout/issues/740
$needsSorting = false;
for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) {
if (!isset($rowCells[$cellIndex])) {
$row->setCellAtIndex($this->entityFactory->createCell(''), $cellIndex);
$needsSorting = true;
}
}
if ($needsSorting) {
$rowCells = $row->getCells();
ksort($rowCells);
$row->setCells($rowCells);
}
return $row;
}
}
+2 -2
View File
@@ -28,13 +28,13 @@ class SheetIterator implements IteratorInterface
const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name';
const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display';
/** @var string $filePath Path of the file to be read */
/** @var string Path of the file to be read */
protected $filePath;
/** @var \Box\Spout\Common\Manager\OptionsManagerInterface Reader's options manager */
protected $optionsManager;
/** @var InternalEntityFactory $entityFactory Factory to create entities */
/** @var InternalEntityFactory Factory to create entities */
protected $entityFactory;
/** @var XMLReader The XMLReader object that will help read sheet's XML data */
@@ -31,14 +31,6 @@ class CellValueFormatter
/** Constants used for date formatting */
const NUM_SECONDS_IN_ONE_DAY = 86400;
const NUM_SECONDS_IN_ONE_HOUR = 3600;
const NUM_SECONDS_IN_ONE_MINUTE = 60;
/**
* February 29th, 1900 is NOT a leap year but Excel thinks it is...
* @see https://en.wikipedia.org/wiki/Year_1900_problem#Microsoft_Excel
*/
const ERRONEOUS_EXCEL_LEAP_YEAR_DAY = 60;
/** @var SharedStringsManager Manages shared strings */
protected $sharedStringsManager;
@@ -130,10 +122,15 @@ class CellValueFormatter
*/
protected function formatInlineStringCellValue($node)
{
// inline strings are formatted this way:
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
$tNode = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE)->item(0);
$cellValue = $this->escaper->unescape($tNode->nodeValue);
// inline strings are formatted this way (they can contain any number of <t> nodes):
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t><t>[INLINE_STRING_2]</t></is></c>
$tNodes = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE);
$cellValue = '';
for ($i = 0; $i < $tNodes->count(); $i++) {
$tNode = $tNodes->item($i);
$cellValue .= $this->escaper->unescape($tNode->nodeValue);
}
return $cellValue;
}
@@ -16,9 +16,6 @@ use Box\Spout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyInterface;
*/
class SharedStringsManager
{
/** Main namespace for the sharedStrings.xml file */
const MAIN_NAMESPACE_FOR_SHARED_STRINGS_XML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
/** Definition of XML nodes names used to parse data */
const XML_NODE_SST = 'sst';
const XML_NODE_SI = 'si';
@@ -43,7 +40,7 @@ class SharedStringsManager
/** @var InternalEntityFactory Factory to create entities */
protected $entityFactory;
/** @var HelperFactory $helperFactory Factory to create helpers */
/** @var HelperFactory Factory to create helpers */
protected $helperFactory;
/** @var CachingStrategyFactory Factory to create shared strings caching strategies */
@@ -17,10 +17,11 @@ class WorkbookRelationshipsManager
/** Path of workbook relationships XML file inside the XLSX file */
const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
/** Relationships types */
/** Relationships types - For Transitional and Strict OOXML */
const RELATIONSHIP_TYPE_SHARED_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
const RELATIONSHIP_TYPE_STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
const RELATIONSHIP_TYPE_WORKSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
const RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/sharedStrings';
const RELATIONSHIP_TYPE_STYLES_STRICT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/styles';
/** Nodes and attributes used to find relevant information in the workbook relationships XML file */
const XML_NODE_RELATIONSHIP = 'Relationship';
@@ -52,7 +53,8 @@ class WorkbookRelationshipsManager
public function getSharedStringsXMLFilePath()
{
$workbookRelationships = $this->getWorkbookRelationships();
$sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS];
$sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]
?? $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT];
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
$doesContainBasePath = (\strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
@@ -71,7 +73,8 @@ class WorkbookRelationshipsManager
{
$workbookRelationships = $this->getWorkbookRelationships();
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]);
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS])
|| isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT]);
}
/**
@@ -81,7 +84,8 @@ class WorkbookRelationshipsManager
{
$workbookRelationships = $this->getWorkbookRelationships();
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]);
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES])
|| isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES_STRICT]);
}
/**
@@ -90,7 +94,8 @@ class WorkbookRelationshipsManager
public function getStylesXMLFilePath()
{
$workbookRelationships = $this->getWorkbookRelationships();
$stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES];
$stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]
?? $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES_STRICT];
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
$doesContainBasePath = (\strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
+1 -1
View File
@@ -35,7 +35,7 @@ class RowIterator implements IteratorInterface
/** @var string Path of the XLSX file being read */
protected $filePath;
/** @var string $sheetDataXMLFilePath Path of the sheet data XML file as in [Content_Types].xml */
/** @var string Path of the sheet data XML file as in [Content_Types].xml */
protected $sheetDataXMLFilePath;
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
@@ -0,0 +1,38 @@
<?php
namespace Box\Spout\Writer\Common\Manager;
use Box\Spout\Common\Entity\Style\Style;
/**
* Class RegisteredStyle
* Allow to know if this style must replace actual row style.
*/
class RegisteredStyle
{
/**
* @var Style
*/
private $style;
/**
* @var bool
*/
private $isMatchingRowStyle;
public function __construct(Style $style, bool $isMatchingRowStyle)
{
$this->style = $style;
$this->isMatchingRowStyle = $isMatchingRowStyle;
}
public function getStyle() : Style
{
return $this->style;
}
public function isMatchingRowStyle() : bool
{
return $this->isMatchingRowStyle;
}
}
@@ -0,0 +1,32 @@
<?php
namespace Box\Spout\Writer\Common\Manager\Style;
use Box\Spout\Common\Entity\Style\Style;
/**
* Class PossiblyUpdatedStyle
* Indicates if style is updated.
* It allow to know if style registration must be done.
*/
class PossiblyUpdatedStyle
{
private $style;
private $isUpdated;
public function __construct(Style $style, bool $isUpdated)
{
$this->style = $style;
$this->isUpdated = $isUpdated;
}
public function getStyle() : Style
{
return $this->style;
}
public function isUpdated() : bool
{
return $this->isUpdated;
}
}
@@ -50,13 +50,11 @@ class StyleManager implements StyleManagerInterface
* Typically, set "wrap text" if a cell contains a new line.
*
* @param Cell $cell
* @return Style
* @return PossiblyUpdatedStyle The eventually updated style
*/
public function applyExtraStylesIfNeeded(Cell $cell)
public function applyExtraStylesIfNeeded(Cell $cell) : PossiblyUpdatedStyle
{
$updatedStyle = $this->applyWrapTextIfCellContainsNewLine($cell);
return $updatedStyle;
return $this->applyWrapTextIfCellContainsNewLine($cell);
}
/**
@@ -69,21 +67,19 @@ class StyleManager implements StyleManagerInterface
* on the Windows version of Excel...
*
* @param Cell $cell The cell the style should be applied to
* @return \Box\Spout\Common\Entity\Style\Style The eventually updated style
* @return PossiblyUpdatedStyle The eventually updated style
*/
protected function applyWrapTextIfCellContainsNewLine(Cell $cell)
protected function applyWrapTextIfCellContainsNewLine(Cell $cell) : PossiblyUpdatedStyle
{
$cellStyle = $cell->getStyle();
// if the "wrap text" option is already set, no-op
if ($cellStyle->hasSetWrapText()) {
return $cellStyle;
}
if ($cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
if (!$cellStyle->hasSetWrapText() && $cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
$cellStyle->setShouldWrapText();
return new PossiblyUpdatedStyle($cellStyle, true);
}
return $cellStyle;
return new PossiblyUpdatedStyle($cellStyle, false);
}
}
@@ -24,7 +24,7 @@ interface StyleManagerInterface
* Typically, set "wrap text" if a cell contains a new line.
*
* @param Cell $cell
* @return Style The updated style
* @return PossiblyUpdatedStyle The eventually updated style
*/
public function applyExtraStylesIfNeeded(Cell $cell);
public function applyExtraStylesIfNeeded(Cell $cell) : PossiblyUpdatedStyle;
}
@@ -36,9 +36,9 @@ class StyleRegistry
{
$serializedStyle = $this->serialize($style);
if (!$this->hasStyleAlreadyBeenRegistered($style)) {
if (!$this->hasSerializedStyleAlreadyBeenRegistered($serializedStyle)) {
$nextStyleId = \count($this->serializedStyleToStyleIdMappingTable);
$style->setId($nextStyleId);
$style->markAsRegistered($nextStyleId);
$this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
$this->styleIdToStyleMappingTable[$nextStyleId] = $style;
@@ -48,15 +48,13 @@ class StyleRegistry
}
/**
* Returns whether the given style has already been registered.
* Returns whether the serialized style has already been registered.
*
* @param Style $style
* @param string $serializedStyle The serialized style
* @return bool
*/
protected function hasStyleAlreadyBeenRegistered(Style $style)
protected function hasSerializedStyleAlreadyBeenRegistered(string $serializedStyle)
{
$serializedStyle = $this->serialize($style);
// Using isset here because it is way faster than array_key_exists...
return isset($this->serializedStyleToStyleIdMappingTable[$serializedStyle]);
}
@@ -101,13 +99,13 @@ class StyleRegistry
*/
public function serialize(Style $style)
{
// In order to be able to properly compare style, set static ID value
// In order to be able to properly compare style, set static ID value and reset registration
$currentId = $style->getId();
$style->setId(0);
$style->unmarkAsRegistered();
$serializedStyle = \serialize($style);
$style->setId($currentId);
$style->markAsRegistered($currentId);
return $serializedStyle;
}
@@ -44,7 +44,7 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
/** @var InternalEntityFactory Factory to create entities */
protected $entityFactory;
/** @var ManagerFactoryInterface $managerFactory Factory to create managers */
/** @var ManagerFactoryInterface Factory to create managers */
protected $managerFactory;
/** @var Worksheet The worksheet where data will be written to */
@@ -22,7 +22,7 @@ class ManagerFactory implements ManagerFactoryInterface
/** @var InternalEntityFactory */
protected $entityFactory;
/** @var HelperFactory $helperFactory */
/** @var HelperFactory */
protected $helperFactory;
/**
@@ -22,6 +22,10 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
*/
public function registerStyle(Style $style)
{
if ($style->isRegistered()) {
return $style;
}
$registeredStyle = parent::registerStyle($style);
$this->usedFontsSet[$style->getFontName()] = true;
@@ -10,6 +10,7 @@ use Box\Spout\Common\Exception\IOException;
use Box\Spout\Common\Helper\Escaper\ODS as ODSEscaper;
use Box\Spout\Common\Helper\StringHelper;
use Box\Spout\Writer\Common\Entity\Worksheet;
use Box\Spout\Writer\Common\Manager\RegisteredStyle;
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
use Box\Spout\Writer\ODS\Manager\Style\StyleManager;
@@ -93,7 +94,7 @@ class WorksheetManager implements WorksheetManagerInterface
$escapedSheetName = $this->stringsEscaper->escape($externalSheet->getName());
$tableStyleName = 'ta' . ($externalSheet->getIndex() + 1);
$tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
$tableElement = '<table:table table:style-name="' . $tableStyleName . '" table:name="' . $escapedSheetName . '">';
$tableElement .= '<table:table-column table:default-cell-style-name="ce1" table:style-name="co1" table:number-columns-repeated="' . $worksheet->getMaxNumColumns() . '"/>';
return $tableElement;
@@ -104,8 +105,8 @@ class WorksheetManager implements WorksheetManagerInterface
*
* @param Worksheet $worksheet The worksheet to add the row to
* @param Row $row The row to be added
* @throws IOException If the data cannot be written
* @throws InvalidArgumentException If a cell value's type is not supported
* @throws IOException If the data cannot be written
* @return void
*/
public function addRow(Worksheet $worksheet, Row $row)
@@ -125,7 +126,13 @@ class WorksheetManager implements WorksheetManagerInterface
$nextCell = isset($cells[$nextCellIndex]) ? $cells[$nextCellIndex] : null;
if ($nextCell === null || $cell->getValue() !== $nextCell->getValue()) {
$data .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $currentCellIndex, $nextCellIndex);
$registeredStyle = $this->applyStyleAndRegister($cell, $rowStyle);
$cellStyle = $registeredStyle->getStyle();
if ($registeredStyle->isMatchingRowStyle()) {
$rowStyle = $cellStyle; // Replace actual rowStyle (possibly with null id) by registered style (with id)
}
$data .= $this->getCellXMLWithStyle($cell, $cellStyle, $currentCellIndex, $nextCellIndex);
$currentCellIndex = $nextCellIndex;
}
@@ -146,24 +153,46 @@ class WorksheetManager implements WorksheetManagerInterface
/**
* Applies styles to the given style, merging the cell's style with its row's style
* Then builds and returns xml for the cell.
*
* @param Cell $cell
* @param Style $rowStyle
* @param int $currentCellIndex
* @param int $nextCellIndex
* @throws InvalidArgumentException If a cell value's type is not supported
* @return string
* @return RegisteredStyle
*/
private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $currentCellIndex, $nextCellIndex)
private function applyStyleAndRegister(Cell $cell, Style $rowStyle) : RegisteredStyle
{
// Apply row and extra styles
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
$cell->setStyle($mergedCellAndRowStyle);
$newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
$isMatchingRowStyle = false;
if ($cell->getStyle()->isEmpty()) {
$cell->setStyle($rowStyle);
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
$styleIndex = $registeredStyle->getId() + 1; // 1-based
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
if ($possiblyUpdatedStyle->isUpdated()) {
$registeredStyle = $this->styleManager->registerStyle($possiblyUpdatedStyle->getStyle());
} else {
$registeredStyle = $this->styleManager->registerStyle($rowStyle);
$isMatchingRowStyle = true;
}
} else {
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
$cell->setStyle($mergedCellAndRowStyle);
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
if ($possiblyUpdatedStyle->isUpdated()) {
$newCellStyle = $possiblyUpdatedStyle->getStyle();
} else {
$newCellStyle = $mergedCellAndRowStyle;
}
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
}
return new RegisteredStyle($registeredStyle, $isMatchingRowStyle);
}
private function getCellXMLWithStyle(Cell $cell, Style $style, int $currentCellIndex, int $nextCellIndex) : string
{
$styleIndex = $style->getId() + 1; // 1-based
$numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
@@ -197,12 +226,14 @@ class WorksheetManager implements WorksheetManagerInterface
$data .= '</table:table-cell>';
} elseif ($cell->isBoolean()) {
$data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $cell->getValue() . '">';
$value = $cell->getValue() ? 'true' : 'false'; // boolean-value spec: http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#datatype-boolean
$data .= ' office:value-type="boolean" calcext:value-type="boolean" office:boolean-value="' . $value . '">';
$data .= '<text:p>' . $cell->getValue() . '</text:p>';
$data .= '</table:table-cell>';
} elseif ($cell->isNumeric()) {
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
$data .= '<text:p>' . $cell->getValue() . '</text:p>';
$cellValue = $this->stringHelper->formatNumericValue($cell->getValue());
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">';
$data .= '<text:p>' . $cellValue . '</text:p>';
$data .= '</table:table-cell>';
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
// only writes the error value if it's a string
+20 -3
View File
@@ -33,7 +33,7 @@ abstract class WriterAbstract implements WriterInterface
/** @var GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper;
/** @var HelperFactory $helperFactory */
/** @var HelperFactory */
protected $helperFactory;
/** @var OptionsManagerInterface Writer options manager */
@@ -123,9 +123,26 @@ abstract class WriterAbstract implements WriterInterface
// @see https://github.com/box/spout/issues/241
$this->globalFunctionsHelper->ob_end_clean();
// Set headers
/*
* Set headers
*
* For newer browsers such as Firefox, Chrome, Opera, Safari, etc., they all support and use `filename*`
* specified by the new standard, even if they do not automatically decode filename; it does not matter;
* and for older versions of Internet Explorer, they are not recognized `filename*`, will automatically
* ignore it and use the old `filename` (the only minor flaw is that there must be an English suffix name).
* In this way, the multi-browser multi-language compatibility problem is perfectly solved, which does not
* require UA judgment and is more in line with the standard.
*
* @see https://github.com/box/spout/issues/745
* @see https://tools.ietf.org/html/rfc6266
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
*/
$this->globalFunctionsHelper->header('Content-Type: ' . static::$headerContentType);
$this->globalFunctionsHelper->header('Content-Disposition: attachment; filename="' . $this->outputFilePath . '"');
$this->globalFunctionsHelper->header(
'Content-Disposition: attachment; ' .
'filename="' . rawurldecode($this->outputFilePath) . '"; ' .
'filename*=UTF-8\'\'' . rawurldecode($this->outputFilePath)
);
/*
* When forcing the download of a file over SSL,IE8 and lower browsers fail
@@ -24,7 +24,7 @@ class ManagerFactory implements ManagerFactoryInterface
/** @var InternalEntityFactory */
protected $entityFactory;
/** @var HelperFactory $helperFactory */
/** @var HelperFactory */
protected $helperFactory;
/**
@@ -119,6 +119,10 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
*/
public function registerStyle(Style $style)
{
if ($style->isRegistered()) {
return $style;
}
$registeredStyle = parent::registerStyle($style);
$this->registerFill($registeredStyle);
$this->registerFormat($registeredStyle);
@@ -14,6 +14,7 @@ use Box\Spout\Writer\Common\Creator\InternalEntityFactory;
use Box\Spout\Writer\Common\Entity\Options;
use Box\Spout\Writer\Common\Entity\Worksheet;
use Box\Spout\Writer\Common\Helper\CellHelper;
use Box\Spout\Writer\Common\Manager\RegisteredStyle;
use Box\Spout\Writer\Common\Manager\RowManager;
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
@@ -160,7 +161,12 @@ EOD;
$rowXML = '<row r="' . $rowIndexOneBased . '" spans="1:' . $numCells . '">';
foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
$rowXML .= $this->applyStyleAndGetCellXML($cell, $rowStyle, $rowIndexOneBased, $columnIndexZeroBased);
$registeredStyle = $this->applyStyleAndRegister($cell, $rowStyle);
$cellStyle = $registeredStyle->getStyle();
if ($registeredStyle->isMatchingRowStyle()) {
$rowStyle = $cellStyle; // Replace actual rowStyle (possibly with null id) by registered style (with id)
}
$rowXML .= $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $cellStyle->getId());
}
$rowXML .= '</row>';
@@ -173,26 +179,43 @@ EOD;
/**
* Applies styles to the given style, merging the cell's style with its row's style
* Then builds and returns xml for the cell.
*
* @param Cell $cell
* @param Style $rowStyle
* @param int $rowIndexOneBased
* @param int $columnIndexZeroBased
*
* @throws InvalidArgumentException If the given value cannot be processed
* @return string
* @return RegisteredStyle
*/
private function applyStyleAndGetCellXML(Cell $cell, Style $rowStyle, $rowIndexOneBased, $columnIndexZeroBased)
private function applyStyleAndRegister(Cell $cell, Style $rowStyle) : RegisteredStyle
{
// Apply row and extra styles
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
$cell->setStyle($mergedCellAndRowStyle);
$newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
$isMatchingRowStyle = false;
if ($cell->getStyle()->isEmpty()) {
$cell->setStyle($rowStyle);
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
return $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $registeredStyle->getId());
if ($possiblyUpdatedStyle->isUpdated()) {
$registeredStyle = $this->styleManager->registerStyle($possiblyUpdatedStyle->getStyle());
} else {
$registeredStyle = $this->styleManager->registerStyle($rowStyle);
$isMatchingRowStyle = true;
}
} else {
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
$cell->setStyle($mergedCellAndRowStyle);
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
if ($possiblyUpdatedStyle->isUpdated()) {
$newCellStyle = $possiblyUpdatedStyle->getStyle();
} else {
$newCellStyle = $mergedCellAndRowStyle;
}
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
}
return new RegisteredStyle($registeredStyle, $isMatchingRowStyle);
}
/**
@@ -217,7 +240,7 @@ EOD;
} elseif ($cell->isBoolean()) {
$cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
} elseif ($cell->isNumeric()) {
$cellXML .= '><v>' . $cell->getValue() . '</v></c>';
$cellXML .= '><v>' . $this->stringHelper->formatNumericValue($cell->getValue()) . '</v></c>';
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
// only writes the error value if it's a string
$cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
+1 -1
View File
@@ -122,6 +122,6 @@ class RowTest extends \PHPUnit\Framework\TestCase
->setStyle($this->getStyleMock())
->setCells([]);
$this->assertInternalType('object', $row);
$this->assertInstanceOf(Row::class, $row);
}
}
@@ -16,9 +16,9 @@ class FileSystemHelperTest extends TestCase
/**
* @return void
*/
public function setUp()
public function setUp() : void
{
$baseFolder = '/tmp/base_folder';
$baseFolder = \sys_get_temp_dir();
$this->fileSystemHelper = new FileSystemHelper($baseFolder);
}
@@ -28,7 +28,7 @@ class FileSystemHelperTest extends TestCase
public function testCreateFolderShouldThrowExceptionIfOutsideOfBaseFolder()
{
$this->expectException(IOException::class);
$this->expectExceptionMessage('Cannot perform I/O operation outside of the base folder');
$this->fileSystemHelper->createFolder('/tmp/folder_outside_base_folder', 'folder_name');
}
@@ -38,7 +38,7 @@ class FileSystemHelperTest extends TestCase
public function testCreateFileWithContentsShouldThrowExceptionIfOutsideOfBaseFolder()
{
$this->expectException(IOException::class);
$this->expectExceptionMessage('Cannot perform I/O operation outside of the base folder');
$this->fileSystemHelper->createFileWithContents('/tmp/folder_outside_base_folder', 'file_name', 'contents');
}
@@ -48,7 +48,7 @@ class FileSystemHelperTest extends TestCase
public function testDeleteFileShouldThrowExceptionIfOutsideOfBaseFolder()
{
$this->expectException(IOException::class);
$this->expectExceptionMessage('Cannot perform I/O operation outside of the base folder');
$this->fileSystemHelper->deleteFile('/tmp/folder_outside_base_folder/file_name');
}
@@ -58,6 +58,7 @@ class FileSystemHelperTest extends TestCase
public function testDeleteFolderRecursivelyShouldThrowExceptionIfOutsideOfBaseFolder()
{
$this->expectException(IOException::class);
$this->expectExceptionMessage('Cannot perform I/O operation outside of the base folder');
$this->fileSystemHelper->deleteFolderRecursively('/tmp/folder_outside_base_folder');
}
@@ -14,7 +14,7 @@ class OptionsManagerTest extends TestCase
*/
protected $optionsManager;
protected function setUp()
protected function setUp() : void
{
$this->optionsManager = new class() extends OptionsManagerAbstract {
protected function getSupportedOptions()
+2 -2
View File
@@ -14,10 +14,10 @@ class SpoutTestStream
const PATH_TO_CSV_RESOURCES = 'tests/resources/csv/';
const CSV_EXTENSION = '.csv';
/** @var int $position */
/** @var int */
private $position;
/** @var resource $fileHandle */
/** @var resource */
private $fileHandle;
/**
+8
View File
@@ -295,6 +295,10 @@ class ReaderTest extends TestCase
*/
public function testReadShouldBeProtectedAgainstBillionLaughsAttack()
{
if (function_exists('xdebug_code_coverage_started') && xdebug_code_coverage_started()) {
$this->markTestSkipped('test not compatible with code coverage');
}
$startTime = microtime(true);
$fileName = 'attack_billion_laughs.ods';
@@ -318,6 +322,10 @@ class ReaderTest extends TestCase
*/
public function testReadShouldBeProtectedAgainstQuadraticBlowupAttack()
{
if (function_exists('xdebug_code_coverage_started') && xdebug_code_coverage_started()) {
$this->markTestSkipped('test not compatible with code coverage');
}
$startTime = microtime(true);
$fileName = 'attack_quadratic_blowup.ods';
@@ -186,18 +186,22 @@ class CellValueFormatterTest extends TestCase
public function testFormatInlineStringCellValue($value, $expectedFormattedValue)
{
$nodeListMock = $this->createMock(\DOMNodeList::class);
$nodeListMock
->expects($this->atLeastOnce())
->method('count')
->willReturn(1);
$nodeListMock
->expects($this->atLeastOnce())
->method('item')
->with(0)
->will($this->returnValue((object) ['nodeValue' => $value]));
->willReturn((object) ['nodeValue' => $value]);
$nodeMock = $this->createMock(\DOMElement::class);
$nodeMock
->expects($this->atLeastOnce())
->method('getElementsByTagName')
->with(CellValueFormatter::XML_NODE_INLINE_STRING_VALUE)
->will($this->returnValue($nodeListMock));
->willReturn($nodeListMock);
$formatter = new CellValueFormatter(null, null, false, false, new Escaper\XLSX());
$formattedValue = \ReflectionHelper::callMethodOnObject($formatter, 'formatInlineStringCellValue', $nodeMock);
@@ -25,7 +25,7 @@ class SharedStringsManagerTest extends TestCase
/**
* @return void
*/
public function setUp()
public function setUp() : void
{
$this->sharedStringsManager = null;
}
@@ -33,7 +33,7 @@ class SharedStringsManagerTest extends TestCase
/**
* @return void
*/
public function tearDown()
public function tearDown() : void
{
if ($this->sharedStringsManager !== null) {
$this->sharedStringsManager->cleanup();
+46 -1
View File
@@ -72,6 +72,19 @@ class ReaderTest extends TestCase
}
}
/**
* @return void
*/
public function testReadShouldSupportInlineStringsWithMultipleValueNodes()
{
$allRows = $this->getAllRowsForFile('sheet_with_multiple_value_nodes_in_inline_strings.xlsx');
$expectedRows = [
['VALUE 1 VALUE 2 VALUE 3 VALUE 4', 's1 - B1'],
];
$this->assertEquals($expectedRows, $allRows);
}
/**
* @return void
*/
@@ -539,6 +552,10 @@ class ReaderTest extends TestCase
*/
public function testReadShouldBeProtectedAgainstQuadraticBlowupAttack()
{
if (function_exists('xdebug_code_coverage_started') && xdebug_code_coverage_started()) {
$this->markTestSkipped('test not compatible with code coverage');
}
$startTime = microtime(true);
$this->getAllRowsForFile('attack_quadratic_blowup.xlsx');
@@ -675,13 +692,29 @@ class ReaderTest extends TestCase
$allRows = $this->getAllRowsForFile('sheet_with_empty_cells.xlsx');
$expectedRows = [
['A', 'B', 'C'],
['A', '', 'C'],
['0', '', ''],
['1', '1', ''],
];
$this->assertEquals($expectedRows, $allRows, 'There should be 3 rows, with equal length');
}
/**
* https://github.com/box/spout/issues/184
* @return void
*/
public function testReadShouldCreateOutputEmptyCellPreservedWhenNoDimensionsSpecified()
{
$allRows = $this->getAllRowsForFile('sheet_with_empty_cells_without_dimensions.xlsx');
$expectedRows = [
['A', '', 'C'],
['0'],
['1', '1'],
];
$this->assertEquals($expectedRows, $allRows);
}
/**
* https://github.com/box/spout/issues/195
* @return void
@@ -699,6 +732,18 @@ class ReaderTest extends TestCase
$this->assertEquals($expectedRows, $allRows, 'Cell values should not be trimmed');
}
/**
* https://github.com/box/spout/issues/726
* @return void
*/
public function testReaderShouldSupportStrictOOXML()
{
$allRows = $this->getAllRowsForFile('sheet_with_strict_ooxml.xlsx');
$this->assertEquals('UNIQUE_ACCOUNT_IDENTIFIER', $allRows[0][0]);
$this->assertEquals('A2Z34NJA7N2ESJ', $allRows[1][0]);
}
/**
* @param string $fileName
* @param bool $shouldFormatDates
+1 -1
View File
@@ -61,7 +61,7 @@ class WriterPerfTest extends TestCase
*/
private function getNumWrittenRows($resourcePath)
{
$lineCountResult = `wc -l $resourcePath`;
$lineCountResult = shell_exec("wc -l $resourcePath");
return (int) $lineCountResult;
}
+2 -2
View File
@@ -101,7 +101,7 @@ class WriterTest extends TestCase
]);
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_utf8_bom.csv');
$this->assertContains(EncodingHelper::BOM_UTF8, $writtenContent, 'The CSV file should contain a UTF-8 BOM');
$this->assertStringStartsWith(EncodingHelper::BOM_UTF8, $writtenContent, 'The CSV file should contain a UTF-8 BOM');
}
/**
@@ -114,7 +114,7 @@ class WriterTest extends TestCase
]);
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_no_bom.csv', ',', '"', false);
$this->assertNotContains(EncodingHelper::BOM_UTF8, $writtenContent, 'The CSV file should not contain a UTF-8 BOM');
$this->assertStringNotContainsString(EncodingHelper::BOM_UTF8, $writtenContent, 'The CSV file should not contain a UTF-8 BOM');
}
/**
@@ -18,7 +18,7 @@ class SheetTest extends TestCase
/**
* @return void
*/
public function setUp()
public function setUp() : void
{
$this->sheetManager = new SheetManager(new StringHelper());
}
@@ -14,7 +14,7 @@ class StyleManagerTest extends TestCase
/**
* @return StyleManager
*/
private function getStyleManager()
private function getStyleManager() : StyleManager
{
$style = (new StyleBuilder())->build();
$styleRegistry = new StyleRegistry($style);
@@ -22,31 +22,37 @@ class StyleManagerTest extends TestCase
return new StyleManager($styleRegistry);
}
/**
* @return void
*/
public function testApplyExtraStylesIfNeededShouldApplyWrapTextIfCellContainsNewLine()
public function testApplyExtraStylesIfNeededShouldApplyWrapTextIfCellContainsNewLine() : void
{
$style = (new StyleBuilder())->build();
$this->assertFalse($style->shouldWrapText());
$styleManager = $this->getStyleManager();
$updatedStyle = $styleManager->applyExtraStylesIfNeeded(new Cell("multi\nlines", $style));
$possiblyUpdatedStyle = $styleManager->applyExtraStylesIfNeeded(new Cell("multi\nlines", $style));
$this->assertTrue($updatedStyle->shouldWrapText());
$this->assertTrue($possiblyUpdatedStyle->isUpdated());
$this->assertTrue($possiblyUpdatedStyle->getStyle()->shouldWrapText());
}
/**
* @return void
*/
public function testApplyExtraStylesIfNeededShouldDoNothingIfWrapTextAlreadyApplied()
public function testApplyExtraStylesIfNeededShouldReturnNullIfWrapTextNotNeeded() : void
{
$style = (new StyleBuilder())->build();
$this->assertFalse($style->shouldWrapText());
$styleManager = $this->getStyleManager();
$possiblyUpdatedStyle = $styleManager->applyExtraStylesIfNeeded(new Cell('oneline', $style));
$this->assertFalse($possiblyUpdatedStyle->isUpdated());
}
public function testApplyExtraStylesIfNeededShouldReturnNullIfWrapTextAlreadyApplied() : void
{
$style = (new StyleBuilder())->setShouldWrapText()->build();
$this->assertTrue($style->shouldWrapText());
$styleManager = $this->getStyleManager();
$updatedStyle = $styleManager->applyExtraStylesIfNeeded(new Cell("multi\nlines", $style));
$possiblyUpdatedStyle = $styleManager->applyExtraStylesIfNeeded(new Cell("multi\nlines", $style));
$this->assertTrue($updatedStyle->shouldWrapText());
$this->assertFalse($possiblyUpdatedStyle->isUpdated());
}
}
@@ -18,7 +18,7 @@ class StyleMergerTest extends TestCase
/**
* @return void
*/
public function setUp()
public function setUp() : void
{
$this->styleMerger = new StyleMerger();
}
@@ -20,7 +20,7 @@ class StyleRegistryTest extends TestCase
/**
* @return void
*/
public function setUp()
public function setUp() : void
{
$this->defaultStyle = (new StyleBuilder())->build();
$this->styleRegistry = new StyleRegistry($this->defaultStyle);
+2 -2
View File
@@ -89,7 +89,7 @@ class SheetTest extends TestCase
$pathToContentFile = $resourcePath . '#content.xml';
$xmlContents = file_get_contents('zip://' . $pathToContentFile);
$this->assertContains(' table:display="false"', $xmlContents, 'The sheet visibility should have been changed to "hidden"');
$this->assertStringContainsString(' table:display="false"', $xmlContents, 'The sheet visibility should have been changed to "hidden"');
}
/**
@@ -164,6 +164,6 @@ class SheetTest extends TestCase
$pathToWorkbookFile = $resourcePath . '#content.xml';
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
$this->assertContains("table:name=\"$expectedName\"", $xmlContents, $message);
$this->assertStringContainsString("table:name=\"$expectedName\"", $xmlContents, $message);
}
}
+1 -1
View File
@@ -88,7 +88,7 @@ class WriterPerfTest extends TestCase
copy($pathToContentXmlFile, $tmpFile);
// Get the last 200 characters
$lastCharacters = `tail -c 200 $tmpFile`;
$lastCharacters = shell_exec("tail -c 200 $tmpFile");
// remove the temporary file
unlink($tmpFile);
+38 -3
View File
@@ -279,6 +279,41 @@ class WriterTest extends TestCase
$this->assertValueWasWrittenToSheet($fileName, 1, 10.2);
}
/**
* @return void
*/
public function testAddRowShouldSupportFloatValuesInDifferentLocale()
{
$previousLocale = \setlocale(LC_ALL, 0);
try {
// Pick a supported locale whose decimal point is a comma.
// Installed locales differ from one system to another, so we can't pick
// a given locale.
$supportedLocales = explode("\n", shell_exec('locale -a'));
foreach ($supportedLocales as $supportedLocale) {
\setlocale(LC_ALL, $supportedLocale);
if (\localeconv()['decimal_point'] === ',') {
break;
}
}
$this->assertEquals(',', \localeconv()['decimal_point']);
$fileName = 'test_add_row_should_support_float_values_in_different_locale.xlsx';
$dataRows = $this->createRowsFromValues([
[1234.5],
]);
$this->writeToODSFile($dataRows, $fileName);
$this->assertValueWasNotWrittenToSheet($fileName, 1, "1234,5");
$this->assertValueWasWrittenToSheet($fileName, 1, "1234.5");
} finally {
// reset locale
\setlocale(LC_ALL, $previousLocale);
}
}
/**
* @return array
*/
@@ -547,7 +582,7 @@ class WriterTest extends TestCase
$pathToContentFile = $resourcePath . '#content.xml';
$xmlContents = file_get_contents('zip://' . $pathToContentFile);
$this->assertContains($value, $xmlContents, $message);
$this->assertStringContainsString($value, $xmlContents, $message);
}
/**
@@ -562,7 +597,7 @@ class WriterTest extends TestCase
$sheetXmlAsString = $this->getSheetXmlNodeAsString($fileName, $sheetIndex);
$valueAsXmlString = "<text:p>$value</text:p>";
$this->assertContains($valueAsXmlString, $sheetXmlAsString, $message);
$this->assertStringContainsString($valueAsXmlString, $sheetXmlAsString, $message);
}
/**
@@ -577,7 +612,7 @@ class WriterTest extends TestCase
$sheetXmlAsString = $this->getSheetXmlNodeAsString($fileName, $sheetIndex);
$valueAsXmlString = "<text:p>$value</text:p>";
$this->assertNotContains($valueAsXmlString, $sheetXmlAsString, $message);
$this->assertStringNotContainsString($valueAsXmlString, $sheetXmlAsString, $message);
}
/**
@@ -30,7 +30,7 @@ class WriterWithStyleTest extends TestCase
/**
* @return void
*/
public function setUp()
public function setUp() : void
{
$this->defaultStyle = (new StyleBuilder())->build();
}
+2 -2
View File
@@ -89,7 +89,7 @@ class SheetTest extends TestCase
$pathToWorkbookFile = $resourcePath . '#xl/workbook.xml';
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
$this->assertContains(' state="hidden"', $xmlContents, 'The sheet visibility should have been changed to "hidden"');
$this->assertStringContainsString(' state="hidden"', $xmlContents, 'The sheet visibility should have been changed to "hidden"');
}
/**
@@ -166,6 +166,6 @@ class SheetTest extends TestCase
$pathToWorkbookFile = $resourcePath . '#xl/workbook.xml';
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
$this->assertContains("<sheet name=\"$expectedName\"", $xmlContents, $message);
$this->assertStringContainsString("<sheet name=\"$expectedName\"", $xmlContents, $message);
}
}
+1 -1
View File
@@ -135,7 +135,7 @@ class WriterPerfTest extends TestCase
copy($filePath, $tmpFile);
// Get the last 200 characters
$lastCharacters = `tail -c $numCharacters $tmpFile`;
$lastCharacters = shell_exec("tail -c $numCharacters $tmpFile");
// remove the temporary file
unlink($tmpFile);
+39 -3
View File
@@ -393,6 +393,42 @@ class WriterTest extends TestCase
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 't="e"><v>#DIV/0</v>');
}
/**
* @return void
*/
public function testAddRowShouldSupportFloatValuesInDifferentLocale()
{
$previousLocale = \setlocale(LC_ALL, 0);
$valueToWrite = 1234.5; // needs to be defined before changing the locale as PHP8 would expect 1234,5
try {
// Pick a supported locale whose decimal point is a comma.
// Installed locales differ from one system to another, so we can't pick
// a given locale.
$supportedLocales = explode("\n", shell_exec('locale -a'));
foreach ($supportedLocales as $supportedLocale) {
\setlocale(LC_ALL, $supportedLocale);
if (\localeconv()['decimal_point'] === ',') {
break;
}
}
$this->assertEquals(',', \localeconv()['decimal_point']);
$fileName = 'test_add_row_should_support_float_values_in_different_locale.xlsx';
$dataRows = $this->createRowsFromValues([
[$valueToWrite],
]);
$this->writeToXLSXFile($dataRows, $fileName, $shouldUseInlineStrings = false);
$this->assertInlineDataWasNotWrittenToSheet($fileName, 1, "1234,5");
$this->assertInlineDataWasWrittenToSheet($fileName, 1, "1234.5");
} finally {
// reset locale
\setlocale(LC_ALL, $previousLocale);
}
}
/**
* @return void
*/
@@ -608,7 +644,7 @@ class WriterTest extends TestCase
$pathToSheetFile = $resourcePath . '#xl/worksheets/sheet' . $sheetIndex . '.xml';
$xmlContents = file_get_contents('zip://' . $pathToSheetFile);
$this->assertContains((string) $inlineData, $xmlContents, $message);
$this->assertStringContainsString((string) $inlineData, $xmlContents, $message);
}
/**
@@ -624,7 +660,7 @@ class WriterTest extends TestCase
$pathToSheetFile = $resourcePath . '#xl/worksheets/sheet' . $sheetIndex . '.xml';
$xmlContents = file_get_contents('zip://' . $pathToSheetFile);
$this->assertNotContains((string) $inlineData, $xmlContents, $message);
$this->assertStringNotContainsString((string) $inlineData, $xmlContents, $message);
}
/**
@@ -639,6 +675,6 @@ class WriterTest extends TestCase
$pathToSharedStringsFile = $resourcePath . '#xl/sharedStrings.xml';
$xmlContents = file_get_contents('zip://' . $pathToSharedStringsFile);
$this->assertContains($sharedString, $xmlContents, $message);
$this->assertStringContainsString($sharedString, $xmlContents, $message);
}
}
@@ -32,7 +32,7 @@ class WriterWithStyleTest extends TestCase
/**
* @return void
*/
public function setUp()
public function setUp() : void
{
$this->defaultStyle = (new StyleBuilder())->build();
}
Binary file not shown.
Binary file not shown.