Merge branch 'master' of github.com:box/spout into add-phpstan
Conflicts: .travis.yml CONTRIBUTING.md composer.json src/Spout/Common/Entity/Cell.php tests/Spout/Writer/ODS/WriterTest.php
This commit is contained in:
commit
540667870a
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
github: adrilo
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@
|
|||||||
/vendor
|
/vendor
|
||||||
/composer.lock
|
/composer.lock
|
||||||
/.php_cs.cache
|
/.php_cs.cache
|
||||||
|
/.phpunit.result.cache
|
||||||
|
12
.travis.yml
12
.travis.yml
@ -5,16 +5,14 @@ language: php
|
|||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- php: 7.1
|
|
||||||
env: WITH_CS=true
|
|
||||||
- php: 7.1
|
|
||||||
env: WITH_PHPSTAN=true
|
|
||||||
- php: 7.1
|
|
||||||
env: WITH_PHPUNIT=true WITH_COVERAGE=true
|
|
||||||
- php: 7.2
|
- php: 7.2
|
||||||
env: WITH_PHPUNIT=true
|
env: WITH_PHPUNIT=true
|
||||||
|
- php: 7.2
|
||||||
|
env: WITH_PHPSTAN=true
|
||||||
- php: 7.3
|
- php: 7.3
|
||||||
env: WITH_PHPUNIT=true
|
env: WITH_PHPUNIT=true
|
||||||
|
- php: 7.3
|
||||||
|
env: WITH_PHPUNIT=true WITH_COVERAGE=true
|
||||||
- php: 7.4
|
- php: 7.4
|
||||||
env: WITH_PHPUNIT=true
|
env: WITH_PHPUNIT=true
|
||||||
|
|
||||||
@ -47,7 +45,7 @@ before_script:
|
|||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [[ "$WITH_CS" == "true" ]]; then
|
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
|
fi
|
||||||
- |
|
- |
|
||||||
if [[ "$WITH_PHPSTAN" == "true" ]]; then
|
if [[ "$WITH_PHPSTAN" == "true" ]]; then
|
||||||
|
@ -68,11 +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.
|
Make sure that all tests are passing before submitting a pull request.
|
||||||
|
|
||||||
- Phpunit: `composer phpunit`
|
### Step 8: Fix code style
|
||||||
- Phpstan: `composer phpstan`
|
|
||||||
- Php-cs-fixer: `composer phpcs`
|
|
||||||
|
|
||||||
### Step 8: Send the pull request
|
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.
|
Send the pull request from your feature branch to us. Be sure to include a description that lets us know what work you did.
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
[](https://packagist.org/packages/box/spout)
|
[](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.
|
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: [](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
Join the community and come discuss Spout: [](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
@ -20,7 +20,7 @@ Full documentation can be found at [https://opensource.box.com/spout/](https://o
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* PHP version 7.1 or higher
|
* PHP version 7.2 or higher
|
||||||
* PHP extension `php_zip` enabled
|
* PHP extension `php_zip` enabled
|
||||||
* PHP extension `php_xmlreader` enabled
|
* PHP extension `php_xmlreader` enabled
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ For information, the performance tests take about 10 minutes to run (processing
|
|||||||
|
|
||||||
## Support
|
## 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>
|
||||||
[](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
## Copyright and License
|
## Copyright and License
|
||||||
|
@ -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.
|
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
|
Reader changes
|
||||||
--------------
|
--------------
|
||||||
|
@ -12,13 +12,13 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1.0",
|
"php": ">=7.2.0",
|
||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"ext-xmlreader" : "*"
|
"ext-xmlreader": "*",
|
||||||
|
"ext-dom": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"ext-dom": "*",
|
"phpunit/phpunit": "^8",
|
||||||
"phpunit/phpunit": "^7",
|
|
||||||
"friendsofphp/php-cs-fixer": "^2",
|
"friendsofphp/php-cs-fixer": "^2",
|
||||||
"phpstan/phpstan": "^0.12.98"
|
"phpstan/phpstan": "^0.12.98"
|
||||||
},
|
},
|
||||||
@ -43,7 +43,7 @@
|
|||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.1"
|
"php": "7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ $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:
|
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
|
| Category | Property | API
|
||||||
|:----------|:---------------|:--------------------------------------
|
|:---------------------|:---------------|:--------------------------------------
|
||||||
| Font | Bold | `StyleBuilder::setFontBold()`
|
| Font | Bold | `StyleBuilder::setFontBold()`
|
||||||
| | Italic | `StyleBuilder::setFontItalic()`
|
| | Italic | `StyleBuilder::setFontItalic()`
|
||||||
| | Underline | `StyleBuilder::setFontUnderline()`
|
| | Underline | `StyleBuilder::setFontUnderline()`
|
||||||
@ -127,7 +127,8 @@ For fonts and alignments, {{ site.spout_html }} does not support all the possibl
|
|||||||
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
|
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
|
||||||
| Alignment | Cell alignment | `StyleBuilder::setCellAlignment(CellAlignment::CENTER)`
|
| Alignment | Cell alignment | `StyleBuilder::setCellAlignment(CellAlignment::CENTER)`
|
||||||
| | Wrap text | `StyleBuilder::setShouldWrapText(true)`
|
| | 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
|
### Styling rows
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ This guide will help you install {{ site.spout_html }} and teach you how to use
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* PHP version 7.1 or higher
|
* PHP version 7.2 or higher
|
||||||
* PHP extension `ext-zip` enabled
|
* PHP extension `ext-zip` enabled
|
||||||
* PHP extension `ext-xmlreader` enabled
|
* PHP extension `ext-xmlreader` enabled
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ class MyStreamController extends Controller
|
|||||||
|
|
||||||
$i++;
|
$i++;
|
||||||
// Flushing the buffer every N rows to stream echo'ed content.
|
// Flushing the buffer every N rows to stream echo'ed content.
|
||||||
if ($i % FLUSH_THRESHOLD === 0) {
|
if ($i % self::FLUSH_THRESHOLD === 0) {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<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"
|
bootstrap="tests/bootstrap.php"
|
||||||
colors="true"
|
colors="true"
|
||||||
convertErrorsToExceptions="false"
|
convertErrorsToExceptions="false"
|
||||||
|
@ -65,7 +65,7 @@ class Cell
|
|||||||
protected $style;
|
protected $style;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mixed $value
|
* @param mixed|null $value
|
||||||
* @param Style|null $style
|
* @param Style|null $style
|
||||||
*/
|
*/
|
||||||
public function __construct($value, Style $style = null)
|
public function __construct($value, Style $style = null)
|
||||||
|
@ -84,6 +84,12 @@ class Style
|
|||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $hasSetFormat = false;
|
private $hasSetFormat = false;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $isRegistered = false;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $isEmpty = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int|null
|
* @return int|null
|
||||||
*/
|
*/
|
||||||
@ -119,6 +125,7 @@ class Style
|
|||||||
{
|
{
|
||||||
$this->shouldApplyBorder = true;
|
$this->shouldApplyBorder = true;
|
||||||
$this->border = $border;
|
$this->border = $border;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -147,6 +154,7 @@ class Style
|
|||||||
$this->fontBold = true;
|
$this->fontBold = true;
|
||||||
$this->hasSetFontBold = true;
|
$this->hasSetFontBold = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -175,6 +183,7 @@ class Style
|
|||||||
$this->fontItalic = true;
|
$this->fontItalic = true;
|
||||||
$this->hasSetFontItalic = true;
|
$this->hasSetFontItalic = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -203,6 +212,7 @@ class Style
|
|||||||
$this->fontUnderline = true;
|
$this->fontUnderline = true;
|
||||||
$this->hasSetFontUnderline = true;
|
$this->hasSetFontUnderline = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -231,6 +241,7 @@ class Style
|
|||||||
$this->fontStrikethrough = true;
|
$this->fontStrikethrough = true;
|
||||||
$this->hasSetFontStrikethrough = true;
|
$this->hasSetFontStrikethrough = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -260,6 +271,7 @@ class Style
|
|||||||
$this->fontSize = $fontSize;
|
$this->fontSize = $fontSize;
|
||||||
$this->hasSetFontSize = true;
|
$this->hasSetFontSize = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -291,6 +303,7 @@ class Style
|
|||||||
$this->fontColor = $fontColor;
|
$this->fontColor = $fontColor;
|
||||||
$this->hasSetFontColor = true;
|
$this->hasSetFontColor = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -320,6 +333,7 @@ class Style
|
|||||||
$this->fontName = $fontName;
|
$this->fontName = $fontName;
|
||||||
$this->hasSetFontName = true;
|
$this->hasSetFontName = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -350,6 +364,7 @@ class Style
|
|||||||
$this->cellAlignment = $cellAlignment;
|
$this->cellAlignment = $cellAlignment;
|
||||||
$this->hasSetCellAlignment = true;
|
$this->hasSetCellAlignment = true;
|
||||||
$this->shouldApplyCellAlignment = true;
|
$this->shouldApplyCellAlignment = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -386,6 +401,7 @@ class Style
|
|||||||
{
|
{
|
||||||
$this->shouldWrapText = $shouldWrap;
|
$this->shouldWrapText = $shouldWrap;
|
||||||
$this->hasSetWrapText = true;
|
$this->hasSetWrapText = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -415,6 +431,7 @@ class Style
|
|||||||
{
|
{
|
||||||
$this->hasSetBackgroundColor = true;
|
$this->hasSetBackgroundColor = true;
|
||||||
$this->backgroundColor = $color;
|
$this->backgroundColor = $color;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -444,6 +461,7 @@ class Style
|
|||||||
{
|
{
|
||||||
$this->hasSetFormat = true;
|
$this->hasSetFormat = true;
|
||||||
$this->format = $format;
|
$this->format = $format;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -463,4 +481,29 @@ class Style
|
|||||||
{
|
{
|
||||||
return $this->hasSetFormat;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,20 @@ class StringHelper
|
|||||||
/** @var bool Whether the mbstring extension is loaded */
|
/** @var bool Whether the mbstring extension is loaded */
|
||||||
protected $hasMbstringSupport;
|
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()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->hasMbstringSupport = \extension_loaded('mbstring');
|
$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;
|
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();
|
$rowCells = $row->getCells();
|
||||||
$maxCellIndex = $numCells;
|
$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++) {
|
for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) {
|
||||||
if (!isset($rowCells[$cellIndex])) {
|
if (!isset($rowCells[$cellIndex])) {
|
||||||
$row->setCellAtIndex($this->entityFactory->createCell(''), $cellIndex);
|
$row->setCellAtIndex($this->entityFactory->createCell(''), $cellIndex);
|
||||||
|
$needsSorting = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($needsSorting) {
|
||||||
|
$rowCells = $row->getCells();
|
||||||
|
ksort($rowCells);
|
||||||
|
$row->setCells($rowCells);
|
||||||
|
}
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,14 +31,6 @@ class CellValueFormatter
|
|||||||
|
|
||||||
/** Constants used for date formatting */
|
/** Constants used for date formatting */
|
||||||
const NUM_SECONDS_IN_ONE_DAY = 86400;
|
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 */
|
/** @var SharedStringsManager Manages shared strings */
|
||||||
protected $sharedStringsManager;
|
protected $sharedStringsManager;
|
||||||
@ -130,10 +122,15 @@ class CellValueFormatter
|
|||||||
*/
|
*/
|
||||||
protected function formatInlineStringCellValue($node)
|
protected function formatInlineStringCellValue($node)
|
||||||
{
|
{
|
||||||
// inline strings are formatted this way:
|
// inline strings are formatted this way (they can contain any number of <t> nodes):
|
||||||
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
|
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t><t>[INLINE_STRING_2]</t></is></c>
|
||||||
$tNode = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE)->item(0);
|
$tNodes = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE);
|
||||||
$cellValue = $this->escaper->unescape($tNode->nodeValue);
|
|
||||||
|
$cellValue = '';
|
||||||
|
for ($i = 0; $i < $tNodes->count(); $i++) {
|
||||||
|
$tNode = $tNodes->item($i);
|
||||||
|
$cellValue .= $this->escaper->unescape($tNode->nodeValue);
|
||||||
|
}
|
||||||
|
|
||||||
return $cellValue;
|
return $cellValue;
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,6 @@ use Box\Spout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyInterface;
|
|||||||
*/
|
*/
|
||||||
class SharedStringsManager
|
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 */
|
/** Definition of XML nodes names used to parse data */
|
||||||
const XML_NODE_SST = 'sst';
|
const XML_NODE_SST = 'sst';
|
||||||
const XML_NODE_SI = 'si';
|
const XML_NODE_SI = 'si';
|
||||||
|
@ -17,10 +17,11 @@ class WorkbookRelationshipsManager
|
|||||||
/** Path of workbook relationships XML file inside the XLSX file */
|
/** Path of workbook relationships XML file inside the XLSX file */
|
||||||
const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
|
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_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_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 */
|
/** Nodes and attributes used to find relevant information in the workbook relationships XML file */
|
||||||
const XML_NODE_RELATIONSHIP = 'Relationship';
|
const XML_NODE_RELATIONSHIP = 'Relationship';
|
||||||
@ -52,7 +53,8 @@ class WorkbookRelationshipsManager
|
|||||||
public function getSharedStringsXMLFilePath()
|
public function getSharedStringsXMLFilePath()
|
||||||
{
|
{
|
||||||
$workbookRelationships = $this->getWorkbookRelationships();
|
$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")
|
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
|
||||||
$doesContainBasePath = (\strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
|
$doesContainBasePath = (\strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
|
||||||
@ -71,7 +73,8 @@ class WorkbookRelationshipsManager
|
|||||||
{
|
{
|
||||||
$workbookRelationships = $this->getWorkbookRelationships();
|
$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();
|
$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()
|
public function getStylesXMLFilePath()
|
||||||
{
|
{
|
||||||
$workbookRelationships = $this->getWorkbookRelationships();
|
$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")
|
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
|
||||||
$doesContainBasePath = (\strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
|
$doesContainBasePath = (\strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
|
||||||
|
38
src/Spout/Writer/Common/Manager/RegisteredStyle.php
Normal file
38
src/Spout/Writer/Common/Manager/RegisteredStyle.php
Normal file
@ -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.
|
* Typically, set "wrap text" if a cell contains a new line.
|
||||||
*
|
*
|
||||||
* @param Cell $cell
|
* @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 $this->applyWrapTextIfCellContainsNewLine($cell);
|
||||||
|
|
||||||
return $updatedStyle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,21 +67,19 @@ class StyleManager implements StyleManagerInterface
|
|||||||
* on the Windows version of Excel...
|
* on the Windows version of Excel...
|
||||||
*
|
*
|
||||||
* @param Cell $cell The cell the style should be applied to
|
* @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();
|
$cellStyle = $cell->getStyle();
|
||||||
|
|
||||||
// if the "wrap text" option is already set, no-op
|
// if the "wrap text" option is already set, no-op
|
||||||
if ($cellStyle->hasSetWrapText()) {
|
if (!$cellStyle->hasSetWrapText() && $cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
|
||||||
return $cellStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
|
|
||||||
$cellStyle->setShouldWrapText();
|
$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.
|
* Typically, set "wrap text" if a cell contains a new line.
|
||||||
*
|
*
|
||||||
* @param Cell $cell
|
* @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);
|
$serializedStyle = $this->serialize($style);
|
||||||
|
|
||||||
if (!$this->hasStyleAlreadyBeenRegistered($style)) {
|
if (!$this->hasSerializedStyleAlreadyBeenRegistered($serializedStyle)) {
|
||||||
$nextStyleId = \count($this->serializedStyleToStyleIdMappingTable);
|
$nextStyleId = \count($this->serializedStyleToStyleIdMappingTable);
|
||||||
$style->setId($nextStyleId);
|
$style->markAsRegistered($nextStyleId);
|
||||||
|
|
||||||
$this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
|
$this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
|
||||||
$this->styleIdToStyleMappingTable[$nextStyleId] = $style;
|
$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
|
* @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...
|
// Using isset here because it is way faster than array_key_exists...
|
||||||
return isset($this->serializedStyleToStyleIdMappingTable[$serializedStyle]);
|
return isset($this->serializedStyleToStyleIdMappingTable[$serializedStyle]);
|
||||||
}
|
}
|
||||||
@ -101,13 +99,13 @@ class StyleRegistry
|
|||||||
*/
|
*/
|
||||||
public function serialize(Style $style)
|
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();
|
$currentId = $style->getId();
|
||||||
$style->setId(0);
|
$style->unmarkAsRegistered();
|
||||||
|
|
||||||
$serializedStyle = \serialize($style);
|
$serializedStyle = \serialize($style);
|
||||||
|
|
||||||
$style->setId($currentId);
|
$style->markAsRegistered($currentId);
|
||||||
|
|
||||||
return $serializedStyle;
|
return $serializedStyle;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,10 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
|
|||||||
*/
|
*/
|
||||||
public function registerStyle(Style $style)
|
public function registerStyle(Style $style)
|
||||||
{
|
{
|
||||||
|
if ($style->isRegistered()) {
|
||||||
|
return $style;
|
||||||
|
}
|
||||||
|
|
||||||
$registeredStyle = parent::registerStyle($style);
|
$registeredStyle = parent::registerStyle($style);
|
||||||
$this->usedFontsSet[$style->getFontName()] = true;
|
$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\Escaper\ODS as ODSEscaper;
|
||||||
use Box\Spout\Common\Helper\StringHelper;
|
use Box\Spout\Common\Helper\StringHelper;
|
||||||
use Box\Spout\Writer\Common\Entity\Worksheet;
|
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\Style\StyleMerger;
|
||||||
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
|
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
|
||||||
use Box\Spout\Writer\ODS\Manager\Style\StyleManager;
|
use Box\Spout\Writer\ODS\Manager\Style\StyleManager;
|
||||||
@ -105,8 +106,8 @@ class WorksheetManager implements WorksheetManagerInterface
|
|||||||
*
|
*
|
||||||
* @param Worksheet $worksheet The worksheet to add the row to
|
* @param Worksheet $worksheet The worksheet to add the row to
|
||||||
* @param Row $row The row to be added
|
* @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 InvalidArgumentException If a cell value's type is not supported
|
||||||
|
* @throws IOException If the data cannot be written
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function addRow(Worksheet $worksheet, Row $row)
|
public function addRow(Worksheet $worksheet, Row $row)
|
||||||
@ -126,7 +127,13 @@ class WorksheetManager implements WorksheetManagerInterface
|
|||||||
$nextCell = isset($cells[$nextCellIndex]) ? $cells[$nextCellIndex] : null;
|
$nextCell = isset($cells[$nextCellIndex]) ? $cells[$nextCellIndex] : null;
|
||||||
|
|
||||||
if ($nextCell === null || $cell->getValue() !== $nextCell->getValue()) {
|
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;
|
$currentCellIndex = $nextCellIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,24 +154,46 @@ class WorksheetManager implements WorksheetManagerInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies styles to the given style, merging the cell's style with its row's style
|
* 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 Cell $cell
|
||||||
* @param Style $rowStyle
|
* @param Style $rowStyle
|
||||||
* @param int $currentCellIndex
|
|
||||||
* @param int $nextCellIndex
|
|
||||||
* @throws InvalidArgumentException If a cell value's type is not supported
|
* @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
|
$isMatchingRowStyle = false;
|
||||||
|
if ($cell->getStyle()->isEmpty()) {
|
||||||
|
$cell->setStyle($rowStyle);
|
||||||
|
|
||||||
|
$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);
|
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
|
||||||
$cell->setStyle($mergedCellAndRowStyle);
|
$cell->setStyle($mergedCellAndRowStyle);
|
||||||
$newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
|
||||||
|
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
||||||
|
if ($possiblyUpdatedStyle->isUpdated()) {
|
||||||
|
$newCellStyle = $possiblyUpdatedStyle->getStyle();
|
||||||
|
} else {
|
||||||
|
$newCellStyle = $mergedCellAndRowStyle;
|
||||||
|
}
|
||||||
|
|
||||||
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
|
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
|
||||||
$styleIndex = $registeredStyle->getId() + 1; // 1-based
|
}
|
||||||
|
|
||||||
|
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);
|
$numTimesValueRepeated = ($nextCellIndex - $currentCellIndex);
|
||||||
|
|
||||||
@ -198,12 +227,14 @@ class WorksheetManager implements WorksheetManagerInterface
|
|||||||
|
|
||||||
$data .= '</table:table-cell>';
|
$data .= '</table:table-cell>';
|
||||||
} elseif ($cell->isBoolean()) {
|
} 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 .= '<text:p>' . $cell->getValue() . '</text:p>';
|
||||||
$data .= '</table:table-cell>';
|
$data .= '</table:table-cell>';
|
||||||
} elseif ($cell->isNumeric()) {
|
} elseif ($cell->isNumeric()) {
|
||||||
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cell->getValue() . '">';
|
$cellValue = $this->stringHelper->formatNumericValue($cell->getValue());
|
||||||
$data .= '<text:p>' . $cell->getValue() . '</text:p>';
|
$data .= ' office:value-type="float" calcext:value-type="float" office:value="' . $cellValue . '">';
|
||||||
|
$data .= '<text:p>' . $cellValue . '</text:p>';
|
||||||
$data .= '</table:table-cell>';
|
$data .= '</table:table-cell>';
|
||||||
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
|
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
|
||||||
// only writes the error value if it's a string
|
// only writes the error value if it's a string
|
||||||
|
@ -128,9 +128,26 @@ abstract class WriterAbstract implements WriterInterface
|
|||||||
// @see https://github.com/box/spout/issues/241
|
// @see https://github.com/box/spout/issues/241
|
||||||
$this->globalFunctionsHelper->ob_end_clean();
|
$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-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
|
* When forcing the download of a file over SSL,IE8 and lower browsers fail
|
||||||
|
@ -119,6 +119,10 @@ class StyleRegistry extends \Box\Spout\Writer\Common\Manager\Style\StyleRegistry
|
|||||||
*/
|
*/
|
||||||
public function registerStyle(Style $style)
|
public function registerStyle(Style $style)
|
||||||
{
|
{
|
||||||
|
if ($style->isRegistered()) {
|
||||||
|
return $style;
|
||||||
|
}
|
||||||
|
|
||||||
$registeredStyle = parent::registerStyle($style);
|
$registeredStyle = parent::registerStyle($style);
|
||||||
$this->registerFill($registeredStyle);
|
$this->registerFill($registeredStyle);
|
||||||
$this->registerFormat($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\Options;
|
||||||
use Box\Spout\Writer\Common\Entity\Worksheet;
|
use Box\Spout\Writer\Common\Entity\Worksheet;
|
||||||
use Box\Spout\Writer\Common\Helper\CellHelper;
|
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\RowManager;
|
||||||
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
|
use Box\Spout\Writer\Common\Manager\Style\StyleMerger;
|
||||||
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
|
use Box\Spout\Writer\Common\Manager\WorksheetManagerInterface;
|
||||||
@ -161,7 +162,12 @@ EOD;
|
|||||||
$rowXML = '<row r="' . $rowIndexOneBased . '" spans="1:' . $numCells . '">';
|
$rowXML = '<row r="' . $rowIndexOneBased . '" spans="1:' . $numCells . '">';
|
||||||
|
|
||||||
foreach ($row->getCells() as $columnIndexZeroBased => $cell) {
|
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>';
|
$rowXML .= '</row>';
|
||||||
@ -174,26 +180,43 @@ EOD;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies styles to the given style, merging the cell's style with its row's style
|
* 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 Cell $cell
|
||||||
* @param Style $rowStyle
|
* @param Style $rowStyle
|
||||||
* @param int $rowIndexOneBased
|
|
||||||
* @param int $columnIndexZeroBased
|
|
||||||
*
|
*
|
||||||
* @throws InvalidArgumentException If the given value cannot be processed
|
* @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
|
$isMatchingRowStyle = false;
|
||||||
|
if ($cell->getStyle()->isEmpty()) {
|
||||||
|
$cell->setStyle($rowStyle);
|
||||||
|
|
||||||
|
$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);
|
$mergedCellAndRowStyle = $this->styleMerger->merge($cell->getStyle(), $rowStyle);
|
||||||
$cell->setStyle($mergedCellAndRowStyle);
|
$cell->setStyle($mergedCellAndRowStyle);
|
||||||
$newCellStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
|
||||||
|
$possiblyUpdatedStyle = $this->styleManager->applyExtraStylesIfNeeded($cell);
|
||||||
|
|
||||||
|
if ($possiblyUpdatedStyle->isUpdated()) {
|
||||||
|
$newCellStyle = $possiblyUpdatedStyle->getStyle();
|
||||||
|
} else {
|
||||||
|
$newCellStyle = $mergedCellAndRowStyle;
|
||||||
|
}
|
||||||
|
|
||||||
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
|
$registeredStyle = $this->styleManager->registerStyle($newCellStyle);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->getCellXML($rowIndexOneBased, $columnIndexZeroBased, $cell, $registeredStyle->getId());
|
return new RegisteredStyle($registeredStyle, $isMatchingRowStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -218,7 +241,7 @@ EOD;
|
|||||||
} elseif ($cell->isBoolean()) {
|
} elseif ($cell->isBoolean()) {
|
||||||
$cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
|
$cellXML .= ' t="b"><v>' . (int) ($cell->getValue()) . '</v></c>';
|
||||||
} elseif ($cell->isNumeric()) {
|
} 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())) {
|
} elseif ($cell->isError() && is_string($cell->getValueEvenIfError())) {
|
||||||
// only writes the error value if it's a string
|
// only writes the error value if it's a string
|
||||||
$cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
|
$cellXML .= ' t="e"><v>' . $cell->getValueEvenIfError() . '</v></c>';
|
||||||
|
@ -129,6 +129,6 @@ class RowTest extends \PHPUnit\Framework\TestCase
|
|||||||
->setStyle($this->getStyleMock())
|
->setStyle($this->getStyleMock())
|
||||||
->setCells([]);
|
->setCells([]);
|
||||||
|
|
||||||
$this->assertInternalType('object', $row);
|
$this->assertInstanceOf(Row::class, $row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ class FileSystemHelperTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setUp()
|
public function setUp() : void
|
||||||
{
|
{
|
||||||
$baseFolder = \sys_get_temp_dir();
|
$baseFolder = \sys_get_temp_dir();
|
||||||
$this->fileSystemHelper = new FileSystemHelper($baseFolder);
|
$this->fileSystemHelper = new FileSystemHelper($baseFolder);
|
||||||
|
@ -295,6 +295,10 @@ class ReaderTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testReadShouldBeProtectedAgainstBillionLaughsAttack()
|
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);
|
$startTime = microtime(true);
|
||||||
$fileName = 'attack_billion_laughs.ods';
|
$fileName = 'attack_billion_laughs.ods';
|
||||||
|
|
||||||
@ -318,6 +322,10 @@ class ReaderTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testReadShouldBeProtectedAgainstQuadraticBlowupAttack()
|
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);
|
$startTime = microtime(true);
|
||||||
|
|
||||||
$fileName = 'attack_quadratic_blowup.ods';
|
$fileName = 'attack_quadratic_blowup.ods';
|
||||||
|
@ -197,18 +197,22 @@ class CellValueFormatterTest extends TestCase
|
|||||||
public function testFormatInlineStringCellValue($value, $expectedFormattedValue)
|
public function testFormatInlineStringCellValue($value, $expectedFormattedValue)
|
||||||
{
|
{
|
||||||
$nodeListMock = $this->createMock(\DOMNodeList::class);
|
$nodeListMock = $this->createMock(\DOMNodeList::class);
|
||||||
|
$nodeListMock
|
||||||
|
->expects($this->atLeastOnce())
|
||||||
|
->method('count')
|
||||||
|
->willReturn(1);
|
||||||
$nodeListMock
|
$nodeListMock
|
||||||
->expects($this->atLeastOnce())
|
->expects($this->atLeastOnce())
|
||||||
->method('item')
|
->method('item')
|
||||||
->with(0)
|
->with(0)
|
||||||
->will($this->returnValue((object) ['nodeValue' => $value]));
|
->willReturn((object) ['nodeValue' => $value]);
|
||||||
|
|
||||||
$nodeMock = $this->createMock(\DOMElement::class);
|
$nodeMock = $this->createMock(\DOMElement::class);
|
||||||
$nodeMock
|
$nodeMock
|
||||||
->expects($this->atLeastOnce())
|
->expects($this->atLeastOnce())
|
||||||
->method('getElementsByTagName')
|
->method('getElementsByTagName')
|
||||||
->with(CellValueFormatter::XML_NODE_INLINE_STRING_VALUE)
|
->with(CellValueFormatter::XML_NODE_INLINE_STRING_VALUE)
|
||||||
->will($this->returnValue($nodeListMock));
|
->willReturn($nodeListMock);
|
||||||
|
|
||||||
/** @var SharedStringsManager $sharedStringManager */
|
/** @var SharedStringsManager $sharedStringManager */
|
||||||
$sharedStringManager = $this->createMock(SharedStringsManager::class);
|
$sharedStringManager = $this->createMock(SharedStringsManager::class);
|
||||||
|
@ -25,7 +25,7 @@ class SharedStringsManagerTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setUp()
|
public function setUp() : void
|
||||||
{
|
{
|
||||||
$this->sharedStringsManager = null;
|
$this->sharedStringsManager = null;
|
||||||
}
|
}
|
||||||
@ -33,7 +33,7 @@ class SharedStringsManagerTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function tearDown()
|
public function tearDown() : void
|
||||||
{
|
{
|
||||||
if ($this->sharedStringsManager !== null) {
|
if ($this->sharedStringsManager !== null) {
|
||||||
$this->sharedStringsManager->cleanup();
|
$this->sharedStringsManager->cleanup();
|
||||||
|
@ -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
|
* @return void
|
||||||
*/
|
*/
|
||||||
@ -539,6 +552,10 @@ class ReaderTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testReadShouldBeProtectedAgainstQuadraticBlowupAttack()
|
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);
|
$startTime = microtime(true);
|
||||||
|
|
||||||
$this->getAllRowsForFile('attack_quadratic_blowup.xlsx');
|
$this->getAllRowsForFile('attack_quadratic_blowup.xlsx');
|
||||||
@ -675,13 +692,29 @@ class ReaderTest extends TestCase
|
|||||||
$allRows = $this->getAllRowsForFile('sheet_with_empty_cells.xlsx');
|
$allRows = $this->getAllRowsForFile('sheet_with_empty_cells.xlsx');
|
||||||
|
|
||||||
$expectedRows = [
|
$expectedRows = [
|
||||||
['A', 'B', 'C'],
|
['A', '', 'C'],
|
||||||
['0', '', ''],
|
['0', '', ''],
|
||||||
['1', '1', ''],
|
['1', '1', ''],
|
||||||
];
|
];
|
||||||
$this->assertEquals($expectedRows, $allRows, 'There should be 3 rows, with equal length');
|
$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
|
* https://github.com/box/spout/issues/195
|
||||||
* @return void
|
* @return void
|
||||||
@ -699,6 +732,18 @@ class ReaderTest extends TestCase
|
|||||||
$this->assertEquals($expectedRows, $allRows, 'Cell values should not be trimmed');
|
$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 string $fileName
|
||||||
* @param bool $shouldFormatDates
|
* @param bool $shouldFormatDates
|
||||||
|
@ -102,7 +102,7 @@ class WriterTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_utf8_bom.csv');
|
$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');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,7 +115,7 @@ class WriterTest extends TestCase
|
|||||||
]);
|
]);
|
||||||
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_no_bom.csv', ',', '"', false);
|
$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
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setUp()
|
public function setUp() : void
|
||||||
{
|
{
|
||||||
$this->sheetManager = new SheetManager(new StringHelper());
|
$this->sheetManager = new SheetManager(new StringHelper());
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ class StyleManagerTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @return StyleManager
|
* @return StyleManager
|
||||||
*/
|
*/
|
||||||
private function getStyleManager()
|
private function getStyleManager() : StyleManager
|
||||||
{
|
{
|
||||||
$style = (new StyleBuilder())->build();
|
$style = (new StyleBuilder())->build();
|
||||||
$styleRegistry = new StyleRegistry($style);
|
$styleRegistry = new StyleRegistry($style);
|
||||||
@ -22,31 +22,37 @@ class StyleManagerTest extends TestCase
|
|||||||
return new StyleManager($styleRegistry);
|
return new StyleManager($styleRegistry);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testApplyExtraStylesIfNeededShouldApplyWrapTextIfCellContainsNewLine() : void
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function testApplyExtraStylesIfNeededShouldApplyWrapTextIfCellContainsNewLine()
|
|
||||||
{
|
{
|
||||||
$style = (new StyleBuilder())->build();
|
$style = (new StyleBuilder())->build();
|
||||||
$this->assertFalse($style->shouldWrapText());
|
$this->assertFalse($style->shouldWrapText());
|
||||||
|
|
||||||
$styleManager = $this->getStyleManager();
|
$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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testApplyExtraStylesIfNeededShouldReturnNullIfWrapTextNotNeeded() : void
|
||||||
* @return void
|
{
|
||||||
*/
|
$style = (new StyleBuilder())->build();
|
||||||
public function testApplyExtraStylesIfNeededShouldDoNothingIfWrapTextAlreadyApplied()
|
$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();
|
$style = (new StyleBuilder())->setShouldWrapText()->build();
|
||||||
$this->assertTrue($style->shouldWrapText());
|
$this->assertTrue($style->shouldWrapText());
|
||||||
|
|
||||||
$styleManager = $this->getStyleManager();
|
$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
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setUp()
|
public function setUp() : void
|
||||||
{
|
{
|
||||||
$this->styleMerger = new StyleMerger();
|
$this->styleMerger = new StyleMerger();
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class StyleRegistryTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setUp()
|
public function setUp() : void
|
||||||
{
|
{
|
||||||
$this->defaultStyle = (new StyleBuilder())->build();
|
$this->defaultStyle = (new StyleBuilder())->build();
|
||||||
$this->styleRegistry = new StyleRegistry($this->defaultStyle);
|
$this->styleRegistry = new StyleRegistry($this->defaultStyle);
|
||||||
|
@ -89,7 +89,7 @@ class SheetTest extends TestCase
|
|||||||
$pathToContentFile = $resourcePath . '#content.xml';
|
$pathToContentFile = $resourcePath . '#content.xml';
|
||||||
$xmlContents = file_get_contents('zip://' . $pathToContentFile);
|
$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';
|
$pathToWorkbookFile = $resourcePath . '#content.xml';
|
||||||
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
|
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
|
||||||
|
|
||||||
$this->assertContains("table:name=\"$expectedName\"", $xmlContents, $message);
|
$this->assertStringContainsString("table:name=\"$expectedName\"", $xmlContents, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -279,6 +279,41 @@ class WriterTest extends TestCase
|
|||||||
$this->assertValueWasWrittenToSheet($fileName, 1, 10.2);
|
$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<array>
|
* @return array<array>
|
||||||
*/
|
*/
|
||||||
@ -548,7 +583,7 @@ class WriterTest extends TestCase
|
|||||||
$pathToContentFile = $resourcePath . '#content.xml';
|
$pathToContentFile = $resourcePath . '#content.xml';
|
||||||
$xmlContents = file_get_contents('zip://' . $pathToContentFile);
|
$xmlContents = file_get_contents('zip://' . $pathToContentFile);
|
||||||
|
|
||||||
$this->assertContains($value, $xmlContents, $message);
|
$this->assertStringContainsString($value, $xmlContents, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -563,7 +598,7 @@ class WriterTest extends TestCase
|
|||||||
$sheetXmlAsString = $this->getSheetXmlNodeAsString($fileName, $sheetIndex);
|
$sheetXmlAsString = $this->getSheetXmlNodeAsString($fileName, $sheetIndex);
|
||||||
$valueAsXmlString = "<text:p>$value</text:p>";
|
$valueAsXmlString = "<text:p>$value</text:p>";
|
||||||
|
|
||||||
$this->assertContains($valueAsXmlString, $sheetXmlAsString, $message);
|
$this->assertStringContainsString($valueAsXmlString, $sheetXmlAsString, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -578,7 +613,7 @@ class WriterTest extends TestCase
|
|||||||
$sheetXmlAsString = $this->getSheetXmlNodeAsString($fileName, $sheetIndex);
|
$sheetXmlAsString = $this->getSheetXmlNodeAsString($fileName, $sheetIndex);
|
||||||
$valueAsXmlString = "<text:p>$value</text:p>";
|
$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
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setUp()
|
public function setUp() : void
|
||||||
{
|
{
|
||||||
$this->defaultStyle = (new StyleBuilder())->build();
|
$this->defaultStyle = (new StyleBuilder())->build();
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ class SheetTest extends TestCase
|
|||||||
$pathToWorkbookFile = $resourcePath . '#xl/workbook.xml';
|
$pathToWorkbookFile = $resourcePath . '#xl/workbook.xml';
|
||||||
$xmlContents = file_get_contents('zip://' . $pathToWorkbookFile);
|
$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';
|
$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->assertStringContainsString("<sheet name=\"$expectedName\"", $xmlContents, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,6 +394,42 @@ class WriterTest extends TestCase
|
|||||||
$this->assertInlineDataWasWrittenToSheet($fileName, 1, 't="e"><v>#DIV/0</v>');
|
$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
|
* @return void
|
||||||
*/
|
*/
|
||||||
@ -609,7 +645,7 @@ class WriterTest extends TestCase
|
|||||||
$pathToSheetFile = $resourcePath . '#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->assertStringContainsString((string) $inlineData, $xmlContents, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -625,7 +661,7 @@ class WriterTest extends TestCase
|
|||||||
$pathToSheetFile = $resourcePath . '#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->assertStringNotContainsString((string) $inlineData, $xmlContents, $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -640,6 +676,6 @@ class WriterTest extends TestCase
|
|||||||
$pathToSharedStringsFile = $resourcePath . '#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->assertStringContainsString($sharedString, $xmlContents, $message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ class WriterWithStyleTest extends TestCase
|
|||||||
/**
|
/**
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function setUp()
|
public function setUp() : void
|
||||||
{
|
{
|
||||||
$this->defaultStyle = (new StyleBuilder())->build();
|
$this->defaultStyle = (new StyleBuilder())->build();
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tests/resources/xlsx/sheet_with_strict_ooxml.xlsx
Normal file
BIN
tests/resources/xlsx/sheet_with_strict_ooxml.xlsx
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user