Compare commits

..
49 Commits
Author SHA1 Message Date
Adrien Loison 8459666841 Add "archived project" statement 2022-05-26 15:51:47 +02:00
Adrien Loison ec8d53b13c Make builds reproducible
By committing composer.lock, we make sure that dependencies are locked to a given version.
This ensures that builds are reproducible and deterministic.

Also, fixes some phpstan issues, that appeared with the latest version of PHPStan.
2022-05-26 15:49:00 +02:00
larsbonczek 0739e044da Fix encoding of Content-Disposition header
URL-encode file name in Content-Disposition header for file download.

fixes box/spout#878
2022-03-26 16:37:15 +01:00
Rodrigo Azevedo cc42c1d29f Use DateTimeInterface 2022-02-06 22:38:43 +01:00
Jörg Mönke 5926207012 Update GlobalFunctionsHelper.php
fix for php 8.1
2022-02-04 22:11:33 +01:00
Adrien Loison 550a6831f3 Update ci.yml
Launch CI on pull requests
2022-01-25 14:29:02 +01:00
Adrien Loison 6f1b67b39d PHPStan Level 4 2022-01-23 15:21:49 +01:00
Adrien Loison ea0a67d283 Remove var_dump 2022-01-21 14:31:32 +01:00
Adrien Loison e95e0eeefd PHPStan Level 3 2022-01-16 22:44:00 +01:00
Adrien Loison 64a09a748d More deprecations fixes 2022-01-13 23:04:26 +01:00
Adrien Loison 7517e5c4de Upgrade PHPUnit config 2022-01-13 23:04:26 +01:00
Adrien Loison e75f6f7301 Fix PHP 8.1 deprecations 2022-01-13 23:04:26 +01:00
Adrien Loison 27c6845b4b Upgrade to PHP 8.1 2022-01-13 23:04:26 +01:00
Adrien Loison 2499dc46b7 Integrate Coveralls 2022-01-13 14:46:46 +01:00
Adrien Loison 6b7366bb6f Upgrade PHP and dev dependencies
/!\ Removed PHP 7.2 support /!\

- PHPUnit 8 => 9 (+ fix the tests)
- PHP-CS-Fixer 2 => 3 (+ fix the code)
- Introduced PHP stan
2022-01-12 23:38:25 +01:00
Adrien Loison 6a10ec3586 Reduce number of jobs 2022-01-11 09:05:23 +01:00
Adrien Loison 9882bf0946 Fix errors on Windows 2022-01-11 09:05:23 +01:00
Adrien Loison 75c06807af Fix PHP Stan errors 2022-01-11 09:05:23 +01:00
Adrien Loison 0345b369c7 Fix tests with no locales + Apply CSFixer fixes 2022-01-11 09:05:23 +01:00
Adrien Loison f8595e9d63 Setup Github actions 2022-01-11 09:05:23 +01:00
Adrien Loison 9533accd73 Create FUNDING.yml 2021-06-07 12:33:43 +02:00
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
120 changed files with 5555 additions and 761 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
github: adrilo
+270
View File
@@ -0,0 +1,270 @@
name: Spout CI
on: [push, pull_request]
jobs:
tests-on-php-latest:
strategy:
matrix:
operating-system: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.operating-system }}
name: Tests - PHP 8.1 on ${{ matrix.operating-system }}
env:
extensions: zip, xmlreader, dom
cache-key: cache-matrix-v1 # can be any string, change to clear the extension cache.
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup cache environment
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: '8.1'
extensions: ${{ env.extensions }}
key: ${{ env.cache-key }}
- name: Cache extensions
uses: actions/cache@v2
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Setup PHP
uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
with:
php-version: '8.1'
extensions: ${{ env.extensions }}
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Test with phpunit
run: vendor/bin/phpunit --no-coverage
tests-on-older-php:
strategy:
matrix:
php-versions: ['7.3', '7.4', '8.0']
runs-on: ubuntu-latest
name: Tests - PHP ${{ matrix.php-versions }}
env:
extensions: zip, xmlreader, dom
cache-key: cache-matrix-v1 # can be any string, change to clear the extension cache.
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup cache environment
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: ${{ matrix.php-versions }}
extensions: ${{ env.extensions }}
key: ${{ env.cache-key }}
- name: Cache extensions
uses: actions/cache@v2
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Setup PHP
uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php
with:
php-version: ${{ matrix.php-versions }}
extensions: ${{ env.extensions }}
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Test with phpunit
run: vendor/bin/phpunit --no-coverage
code-coverage:
name: Code coverage
runs-on: ubuntu-latest
env:
extensions: zip, xmlreader, dom
cache-key: cache-single-v1 # can be any string, change to clear the extension cache.
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup cache environment
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: '8.1'
extensions: ${{ env.extensions }}
key: ${{ env.cache-key }}
- name: Cache extensions
uses: actions/cache@v2
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: ${{ env.extensions }}
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run Tests with Code Coverage
run: |
mkdir -p build/logs
vendor/bin/phpunit --coverage-clover=build/logs/clover.xml
- name: Upload coverage results to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
composer global require php-coveralls/php-coveralls
php-coveralls --coverage_clover=build/logs/clover.xml -v --exclude-no-stmt
coding-style:
name: Coding Style
runs-on: ubuntu-latest
env:
extensions: zip, xmlreader, dom
cache-key: cache-single-v1 # can be any string, change to clear the extension cache.
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup cache environment
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: '8.1'
extensions: ${{ env.extensions }}
key: ${{ env.cache-key }}
- name: Cache extensions
uses: actions/cache@v2
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: ${{ env.extensions }}
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Run PHP-CS-Fixer
run: vendor/bin/php-cs-fixer fix --verbose --diff --dry-run
static-analysis:
name: Static Analysis
runs-on: ubuntu-latest
env:
extensions: zip, xmlreader, dom
cache-key: cache-single-v1 # can be any string, change to clear the extension cache.
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup cache environment
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: '8.1'
extensions: ${{ env.extensions }}
key: ${{ env.cache-key }}
- name: Cache extensions
uses: actions/cache@v2
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: ${{ env.extensions }}
tools: phpstan
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
# Use composer.json for key, if composer.lock is not committed.
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: Static Analysis using PHPStan
run: ./vendor/bin/phpstan analyse -c phpstan.neon --no-progress
+2 -2
View File
@@ -4,5 +4,5 @@
/tests/resources/generated
/tests/coverage
/vendor
/composer.lock
/.php_cs.cache
/.php-cs-fixer.cache
/.phpunit.result.cache
+13 -18
View File
@@ -1,6 +1,12 @@
<?php
$config = PhpCsFixer\Config::create()
$finder = PhpCsFixer\Finder::create()
->in(__DIR__)
->name('*.php')
->exclude('vendor');
$config = new PhpCsFixer\Config();
return $config
->setRiskyAllowed(true)
->setRules([
'@Symfony' => true,
@@ -13,13 +19,13 @@ $config = PhpCsFixer\Config::create()
'declare_equal_normalize' => ['space' => 'single'],
'heredoc_to_nowdoc' => true,
'increment_style' => ['style' => 'post'],
'is_null' => ['use_yoda_style' => false],
'method_argument_space' => ['ensure_fully_multiline' => true],
'is_null' => true,
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
'modernize_types_casting' => true,
'no_break_comment' => ['comment_text' => 'do nothing'],
'no_empty_phpdoc' => false,
'no_null_property_initialization' => true,
'no_short_echo_tag' => true,
'echo_tag_syntax' => false,
'no_superfluous_elseif' => true,
'no_superfluous_phpdoc_tags' => false,
'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'switch_case', 'yield']],
@@ -37,23 +43,12 @@ $config = PhpCsFixer\Config::create()
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'phpdoc_separation' => false,
'protected_to_private' => true,
'psr4' => true,
'psr_autoloading' => true,
'return_type_declaration' => ['space_before' => 'one'],
'semicolon_after_instruction' => true,
'simplified_null_return' => false,
'single_line_comment_style' => ['comment_types' => ['hash']],
'strict_comparison' => true,
'yoda_style' => ['equal' => false, 'identical' => false],
]);
$config->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
->name('*.php')
);
$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__;
$config->setCacheFile($cacheDir . '/.php_cs.cache');
return $config;
])
->setFinder($finder);
-57
View File
@@ -1,57 +0,0 @@
filter:
excluded_paths: [vendor/*, tests/*]
tools:
external_code_coverage:
timeout: 600 # Wait 10 minutes for results
runs: 1 # Merge results for the 7.1 job
php_mess_detector: true
php_code_sniffer:
config:
standard: PSR4
filter:
paths: ['src']
sensiolabs_security_checker: true
php_pdepend: true
php_loc:
enabled: true
filter:
paths: ['src']
php_cpd: false # Must be disabled to use php_sim instead
php_sim:
enabled: true
filter:
paths: ['src']
build_failure_conditions:
- 'project.metric("scrutinizer.quality", < 9)' # Code Quality Rating drops below 9
- 'project.metric_change("scrutinizer.test_coverage", < -0.005)' # Code Coverage decreased by more than 0.5%
- 'project.metric("scrutinizer.test_coverage", < 0.97)' # Code Coverage drops below 97%
checks:
php:
remove_extra_empty_lines: true
remove_php_closing_tag: true
remove_trailing_whitespace: true
fix_use_statements:
remove_unused: true
preserve_multiple: false
preserve_blanklines: true
fix_php_opening_tag: true
fix_linefeed: true
fix_line_ending: true
fix_identation_4spaces: true
fix_doc_comments: true
uppercase_constants: true
use_self_instead_of_fqcn: true
simplify_boolean_return: true
return_doc_comments: true
return_doc_comment_if_not_inferrable: true
phpunit_assertions: true
parameters_in_camelcaps: true
parameter_doc_comments: true
param_doc_comment_if_not_inferrable: true
optional_parameters_at_the_end: true
newline_at_end_of_file: true
encourage_single_quotes: true
-58
View File
@@ -1,58 +0,0 @@
dist: trusty
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
cache:
directories:
- $HOME/.composer/cache
- $HOME/.php-cs-fixer
before_install:
- |
if [[ "$WITH_COVERAGE" != "true" ]]; then
phpenv config-rm xdebug.ini
fi
if [[ "$WITH_CS" != "true" ]]; then
composer remove friendsofphp/php-cs-fixer --dev --no-update
fi
- composer validate
install:
- composer install --no-interaction --prefer-source
before_script:
- mkdir -p "$HOME/.php-cs-fixer"
- mkdir -p build/logs
script:
- |
if [[ "$WITH_CS" == "true" ]]; then
vendor/bin/php-cs-fixer fix --config=.php_cs.dist --verbose --diff --dry-run
fi
- |
if [[ "$WITH_PHPUNIT" == "true" ]]; then
if [[ "$WITH_COVERAGE" == "true" ]]; then
vendor/bin/phpunit --coverage-clover=build/logs/coverage.clover
else
vendor/bin/phpunit --no-coverage
fi
fi
after_script:
- |
if [[ "$WITH_COVERAGE" == "true" ]]; then
wget https://scrutinizer-ci.com/ocular.phar
php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.clover
fi
+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.
+23 -13
View File
@@ -1,16 +1,29 @@
# Spout
[![Latest Stable Version](https://poser.pugx.org/box/spout/v/stable)](https://packagist.org/packages/box/spout)
[![Project Status](https://opensource.box.com/badges/active.svg)](https://opensource.box.com/badges)
[![Build Status](https://travis-ci.org/box/spout.svg?branch=master)](https://travis-ci.org/box/spout)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/box/spout/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/box/spout/?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/box/spout/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/box/spout/?branch=master)
[![Project Status](https://opensource.box.com/badges/inactive.svg)](https://opensource.box.com/badges)
[![example workflow](https://github.com/box/spout/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/box/spout/actions/workflows/ci.yml?query=branch%3Amaster)
[![Coverage Status](https://coveralls.io/repos/github/box/spout/badge.svg?branch=master)](https://coveralls.io/github/box/spout?branch=master)
[![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).
## 🪦 Archived project 🪦
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)
This project has been archived and is no longer maintained. No bug fix and no additional features will be added.<br>
You won't be able to submit new issues or pull requests, and no additional features will be added
You can still use Spout as is in your projects though :)
> Thanks to everyone who contributed to this project, from a typo fix to the new cool feature.<br>
> It was great to see the involvement of this community!
<br>
## About
Spout is a PHP library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way.
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 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 +33,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
@@ -38,17 +51,14 @@ If you just want to check that everything is working as expected, executing the
For information, the performance tests take about 10 minutes to run (processing 1 million rows files is not a quick thing).
> Performance tests status: [![Build Status](https://travis-ci.org/box/spout.svg?branch=perf-tests)](https://travis-ci.org/box/spout)
## Support
You can ask questions, submit new features ideas or discuss about Spout in the chat room:<br>
Spout is no longer actively supported. You can still ask questions, or discuss about it 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
Copyright 2017 Box, Inc. All rights reserved.
Copyright 2022 Box, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the 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
--------------
-35
View File
@@ -1,35 +0,0 @@
build: false
clone_depth: 1
environment:
PHP_CHOCO_VERSION: 7.2.0
PHP_CACHE_DIR: C:\tools\php
cache:
- '%PHP_CACHE_DIR% -> appveyor.yml'
init:
- SET PATH=%PHP_CACHE_DIR%;%PATH%
- SET COMPOSER_CACHE_DIR=%PHP_CACHE_DIR%
- SET COMPOSER_NO_INTERACTION=1
- SET PHP=0
- SET ANSICON=121x90 (121x90)
install:
- IF EXIST %PHP_CACHE_DIR% (SET PHP=1)
- IF %PHP%==0 cinst php -y --version %PHP_CHOCO_VERSION% --params "/InstallDir:%PHP_CACHE_DIR%"
- IF %PHP%==0 cinst composer -y --ia "/DEV=%PHP_CACHE_DIR%"
- cd %PHP_CACHE_DIR%
- IF %PHP%==0 copy php.ini-production php.ini
- IF %PHP%==0 echo extension_dir=ext >> php.ini
- IF %PHP%==0 echo extension=php_fileinfo.dll >> php.ini
- IF %PHP%==0 echo extension=php_mbstring.dll >> php.ini
- IF %PHP%==0 echo extension=php_openssl.dll >> php.ini
- php -v
- IF %PHP%==0 (composer --version) ELSE (composer self-update)
- cd %APPVEYOR_BUILD_FOLDER%
- composer install --prefer-dist --no-progress
test_script:
- cd %APPVEYOR_BUILD_FOLDER%
- vendor\bin\phpunit --colors=always
+7 -5
View File
@@ -12,13 +12,15 @@
}
],
"require": {
"php": ">=7.1.0",
"php": ">=7.3.0",
"ext-zip": "*",
"ext-xmlreader" : "*"
"ext-xmlreader": "*",
"ext-dom": "*"
},
"require-dev": {
"phpunit/phpunit": "^7",
"friendsofphp/php-cs-fixer": "^2"
"phpunit/phpunit": "^9",
"friendsofphp/php-cs-fixer": "^3",
"phpstan/phpstan": "^1"
},
"suggest": {
"ext-iconv": "To handle non UTF-8 CSV files (if \"php-intl\" is not already installed or is too limited)",
@@ -36,7 +38,7 @@
},
"config": {
"platform": {
"php": "7.1"
"php": "7.3"
}
}
}
Generated
+4196
View File
File diff suppressed because it is too large. Load diff
+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();
}
}
+9
View File
@@ -0,0 +1,9 @@
parameters:
level: 4
paths:
- src
- tests
excludePaths:
# Exclude these files that are OK
- src/Spout/Reader/Common/Creator/ReaderEntityFactory.php
- src/Spout/Writer/Common/Creator/WriterEntityFactory.php
+28 -26
View File
@@ -1,34 +1,36 @@
<?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"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="false"
convertWarningsToExceptions="false"
defaultTestSuite="unit-tests"
verbose="false">
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="false"
convertWarningsToExceptions="false"
defaultTestSuite="unit-tests"
verbose="false">
<testsuites>
<php>
<ini name="error_reporting" value="-1"/>
</php>
<testsuite name="unit-tests">
<directory>tests/</directory>
</testsuite>
<testsuites>
<testsuite name="unit-tests">
<directory>tests/</directory>
</testsuite>
</testsuites>
</testsuites>
<groups>
<exclude>
<group>perf-tests</group>
</exclude>
</groups>
<groups>
<exclude>
<group>perf-tests</group>
</exclude>
</groups>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
<exclude>
<directory>src/Spout/Autoloader</directory>
</exclude>
</whitelist>
</filter>
<coverage>
<include>
<directory suffix=".php">src/</directory>
</include>
<exclude>
<directory>src/Spout/Autoloader</directory>
</exclude>
</coverage>
</phpunit>
+8 -8
View File
@@ -13,38 +13,38 @@ class Cell
/**
* Numeric cell type (whole numbers, fractional numbers, dates)
*/
const TYPE_NUMERIC = 0;
public const TYPE_NUMERIC = 0;
/**
* String (text) cell type
*/
const TYPE_STRING = 1;
public const TYPE_STRING = 1;
/**
* Formula cell type
* Not used at the moment
*/
const TYPE_FORMULA = 2;
public const TYPE_FORMULA = 2;
/**
* Empty cell type
*/
const TYPE_EMPTY = 3;
public const TYPE_EMPTY = 3;
/**
* Boolean cell type
*/
const TYPE_BOOLEAN = 4;
public const TYPE_BOOLEAN = 4;
/**
* Date cell type
*/
const TYPE_DATE = 5;
public const TYPE_DATE = 5;
/**
* Error cell type
*/
const TYPE_ERROR = 6;
public const TYPE_ERROR = 6;
/**
* The value of this cell
@@ -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)
+12 -12
View File
@@ -7,20 +7,20 @@ namespace Box\Spout\Common\Entity\Style;
*/
class Border
{
const LEFT = 'left';
const RIGHT = 'right';
const TOP = 'top';
const BOTTOM = 'bottom';
public const LEFT = 'left';
public const RIGHT = 'right';
public const TOP = 'top';
public const BOTTOM = 'bottom';
const STYLE_NONE = 'none';
const STYLE_SOLID = 'solid';
const STYLE_DASHED = 'dashed';
const STYLE_DOTTED = 'dotted';
const STYLE_DOUBLE = 'double';
public const STYLE_NONE = 'none';
public const STYLE_SOLID = 'solid';
public const STYLE_DASHED = 'dashed';
public const STYLE_DOTTED = 'dotted';
public const STYLE_DOUBLE = 'double';
const WIDTH_THIN = 'thin';
const WIDTH_MEDIUM = 'medium';
const WIDTH_THICK = 'thick';
public const WIDTH_THIN = 'thin';
public const WIDTH_MEDIUM = 'medium';
public const WIDTH_THICK = 'thick';
/** @var array A list of BorderPart objects for this border. */
private $parts = [];
@@ -8,10 +8,10 @@ namespace Box\Spout\Common\Entity\Style;
*/
abstract class CellAlignment
{
const LEFT = 'left';
const RIGHT = 'right';
const CENTER = 'center';
const JUSTIFY = 'justify';
public const LEFT = 'left';
public const RIGHT = 'right';
public const CENTER = 'center';
public const JUSTIFY = 'justify';
private static $VALID_ALIGNMENTS = [
self::LEFT => 1,
+12 -12
View File
@@ -11,18 +11,18 @@ use Box\Spout\Common\Exception\InvalidColorException;
abstract class Color
{
/** Standard colors - based on Office Online */
const BLACK = '000000';
const WHITE = 'FFFFFF';
const RED = 'FF0000';
const DARK_RED = 'C00000';
const ORANGE = 'FFC000';
const YELLOW = 'FFFF00';
const LIGHT_GREEN = '92D040';
const GREEN = '00B050';
const LIGHT_BLUE = '00B0E0';
const BLUE = '0070C0';
const DARK_BLUE = '002060';
const PURPLE = '7030A0';
public const BLACK = '000000';
public const WHITE = 'FFFFFF';
public const RED = 'FF0000';
public const DARK_RED = 'C00000';
public const ORANGE = 'FFC000';
public const YELLOW = 'FFFF00';
public const LIGHT_GREEN = '92D040';
public const GREEN = '00B050';
public const LIGHT_BLUE = '00B0E0';
public const BLUE = '0070C0';
public const DARK_BLUE = '002060';
public const PURPLE = '7030A0';
/**
* Returns an RGB color from R, G and B values
+50 -7
View File
@@ -9,9 +9,9 @@ namespace Box\Spout\Common\Entity\Style;
class Style
{
/** Default values */
const DEFAULT_FONT_SIZE = 11;
const DEFAULT_FONT_COLOR = Color::BLACK;
const DEFAULT_FONT_NAME = 'Arial';
public const DEFAULT_FONT_SIZE = 11;
public const DEFAULT_FONT_COLOR = Color::BLACK;
public const DEFAULT_FONT_NAME = 'Arial';
/** @var int|null Style ID */
private $id;
@@ -66,7 +66,7 @@ class Style
/** @var bool Whether the wrap text property was set */
private $hasSetWrapText = false;
/** @var Border */
/** @var Border|null */
private $border;
/** @var bool Whether border properties should be applied */
@@ -78,12 +78,18 @@ class Style
/** @var bool */
private $hasSetBackgroundColor = false;
/** @var string Format */
/** @var string|null Format */
private $format;
/** @var bool */
private $hasSetFormat = false;
/** @var bool */
private $isRegistered = false;
/** @var bool */
private $isEmpty = true;
/**
* @return int|null
*/
@@ -104,7 +110,7 @@ class Style
}
/**
* @return Border
* @return Border|null
*/
public function getBorder()
{
@@ -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,12 +461,13 @@ class Style
{
$this->hasSetFormat = true;
$this->format = $format;
$this->isEmpty = false;
return $this;
}
/**
* @return string
* @return string|null
*/
public function getFormat()
{
@@ -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;
}
}
+6 -6
View File
@@ -9,7 +9,7 @@ namespace Box\Spout\Common\Helper;
class CellTypeHelper
{
/**
* @param $value
* @param mixed|null $value
* @return bool Whether the given value is considered "empty"
*/
public static function isEmpty($value)
@@ -18,7 +18,7 @@ class CellTypeHelper
}
/**
* @param $value
* @param mixed $value
* @return bool Whether the given value is a non empty string
*/
public static function isNonEmptyString($value)
@@ -30,7 +30,7 @@ class CellTypeHelper
* Returns whether the given value is numeric.
* A numeric value is from type "integer" or "double" ("float" is not returned by gettype).
*
* @param $value
* @param mixed $value
* @return bool Whether the given value is numeric
*/
public static function isNumeric($value)
@@ -44,7 +44,7 @@ class CellTypeHelper
* Returns whether the given value is boolean.
* "true"/"false" and 0/1 are not booleans.
*
* @param $value
* @param mixed $value
* @return bool Whether the given value is boolean
*/
public static function isBoolean($value)
@@ -55,13 +55,13 @@ class CellTypeHelper
/**
* Returns whether the given value is a DateTime or DateInterval object.
*
* @param $value
* @param mixed $value
* @return bool Whether the given value is a DateTime or DateInterval object
*/
public static function isDateTimeOrDateInterval($value)
{
return (
$value instanceof \DateTime ||
$value instanceof \DateTimeInterface ||
$value instanceof \DateInterval
);
}
+10 -10
View File
@@ -11,18 +11,18 @@ use Box\Spout\Common\Exception\EncodingConversionException;
class EncodingHelper
{
/** Definition of the encodings that can have a BOM */
const ENCODING_UTF8 = 'UTF-8';
const ENCODING_UTF16_LE = 'UTF-16LE';
const ENCODING_UTF16_BE = 'UTF-16BE';
const ENCODING_UTF32_LE = 'UTF-32LE';
const ENCODING_UTF32_BE = 'UTF-32BE';
public const ENCODING_UTF8 = 'UTF-8';
public const ENCODING_UTF16_LE = 'UTF-16LE';
public const ENCODING_UTF16_BE = 'UTF-16BE';
public const ENCODING_UTF32_LE = 'UTF-32LE';
public const ENCODING_UTF32_BE = 'UTF-32BE';
/** Definition of the BOMs for the different encodings */
const BOM_UTF8 = "\xEF\xBB\xBF";
const BOM_UTF16_LE = "\xFF\xFE";
const BOM_UTF16_BE = "\xFE\xFF";
const BOM_UTF32_LE = "\xFF\xFE\x00\x00";
const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
public const BOM_UTF8 = "\xEF\xBB\xBF";
public const BOM_UTF16_LE = "\xFF\xFE";
public const BOM_UTF16_BE = "\xFE\xFF";
public const BOM_UTF32_LE = "\xFF\xFE\x00\x00";
public const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper;
+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}");
@@ -82,7 +82,7 @@ class GlobalFunctionsHelper
* @param int|null $length
* @param string|null $delimiter
* @param string|null $enclosure
* @return array
* @return array|false
*/
public function fgetcsv($handle, $length = null, $delimiter = null, $enclosure = null)
{
@@ -103,7 +103,7 @@ class GlobalFunctionsHelper
* @param array $fields
* @param string|null $delimiter
* @param string|null $enclosure
* @return int
* @return int|false
*/
public function fputcsv($handle, array $fields, $delimiter = null, $enclosure = null)
{
@@ -243,10 +243,10 @@ class GlobalFunctionsHelper
* @see basename()
*
* @param string $path
* @param string|null $suffix
* @param string $suffix
* @return string
*/
public function basename($path, $suffix = null)
public function basename($path, $suffix = '')
{
return \basename($path, $suffix);
}
+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 int|float|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;
}
}
@@ -7,7 +7,7 @@ namespace Box\Spout\Common\Manager;
*/
abstract class OptionsManagerAbstract implements OptionsManagerInterface
{
const PREFIX_OPTION = 'OPTION_';
public const PREFIX_OPTION = 'OPTION_';
/** @var string[] List of all supported option names */
private $supportedOptions = [];
+3 -3
View File
@@ -8,7 +8,7 @@ namespace Box\Spout\Common;
*/
abstract class Type
{
const CSV = 'csv';
const XLSX = 'xlsx';
const ODS = 'ods';
public const CSV = 'csv';
public const XLSX = 'xlsx';
public const ODS = 'ods';
}
+30 -4
View File
@@ -3,6 +3,9 @@
namespace Box\Spout\Reader\CSV;
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
use Box\Spout\Common\Manager\OptionsManagerInterface;
use Box\Spout\Reader\Common\Creator\InternalEntityFactoryInterface;
use Box\Spout\Reader\Common\Entity\Options;
use Box\Spout\Reader\CSV\Creator\InternalEntityFactory;
use Box\Spout\Reader\ReaderAbstract;
@@ -22,6 +25,23 @@ class Reader extends ReaderAbstract
/** @var string Original value for the "auto_detect_line_endings" INI value */
protected $originalAutoDetectLineEndings;
/** @var bool Whether the code is running with PHP >= 8.1 */
private $isRunningAtLeastPhp81;
/**
* @param OptionsManagerInterface $optionsManager
* @param GlobalFunctionsHelper $globalFunctionsHelper
* @param InternalEntityFactoryInterface $entityFactory
*/
public function __construct(
OptionsManagerInterface $optionsManager,
GlobalFunctionsHelper $globalFunctionsHelper,
InternalEntityFactoryInterface $entityFactory
) {
parent::__construct($optionsManager, $globalFunctionsHelper, $entityFactory);
$this->isRunningAtLeastPhp81 = \version_compare(PHP_VERSION, '8.1.0') >= 0;
}
/**
* Sets the field delimiter for the CSV.
* Needs to be called before opening the reader.
@@ -84,8 +104,11 @@ class Reader extends ReaderAbstract
*/
protected function openReader($filePath)
{
$this->originalAutoDetectLineEndings = \ini_get('auto_detect_line_endings');
\ini_set('auto_detect_line_endings', '1');
// "auto_detect_line_endings" is deprecated in PHP 8.1
if (!$this->isRunningAtLeastPhp81) {
$this->originalAutoDetectLineEndings = \ini_get('auto_detect_line_endings');
\ini_set('auto_detect_line_endings', '1');
}
$this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r');
if (!$this->filePointer) {
@@ -119,10 +142,13 @@ class Reader extends ReaderAbstract
*/
protected function closeReader()
{
if ($this->filePointer) {
if (is_resource($this->filePointer)) {
$this->globalFunctionsHelper->fclose($this->filePointer);
}
\ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings);
// "auto_detect_line_endings" is deprecated in PHP 8.1
if (!$this->isRunningAtLeastPhp81) {
\ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings);
}
}
}
+10 -10
View File
@@ -19,9 +19,9 @@ class RowIterator implements IteratorInterface
/**
* Value passed to fgetcsv. 0 means "unlimited" (slightly slower but accomodates for very long lines).
*/
const MAX_READ_BYTES_PER_LINE = 0;
public const MAX_READ_BYTES_PER_LINE = 0;
/** @var resource Pointer to the CSV file to read */
/** @var resource|null Pointer to the CSV file to read */
protected $filePointer;
/** @var int Number of read rows */
@@ -84,7 +84,7 @@ class RowIterator implements IteratorInterface
*
* @return void
*/
public function rewind()
public function rewind() : void
{
$this->rewindAndSkipBom();
@@ -114,7 +114,7 @@ class RowIterator implements IteratorInterface
*
* @return bool
*/
public function valid()
public function valid() : bool
{
return ($this->filePointer && !$this->hasReachedEndOfFile);
}
@@ -126,7 +126,7 @@ class RowIterator implements IteratorInterface
* @throws \Box\Spout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
* @return void
*/
public function next()
public function next() : void
{
$this->hasReachedEndOfFile = $this->globalFunctionsHelper->feof($this->filePointer);
@@ -146,8 +146,8 @@ class RowIterator implements IteratorInterface
} while ($this->shouldReadNextRow($rowData));
if ($rowData !== false) {
// str_replace will replace NULL values by empty strings
$rowDataBufferAsArray = \str_replace(null, null, $rowData);
// array_map will replace NULL values by empty strings
$rowDataBufferAsArray = array_map(function ($value) { return (string) $value; }, $rowData);
$this->rowBuffer = $this->entityFactory->createRowFromArray($rowDataBufferAsArray);
$this->numReadRows++;
} else {
@@ -224,7 +224,7 @@ class RowIterator implements IteratorInterface
*
* @return Row|null
*/
public function current()
public function current() : ?Row
{
return $this->rowBuffer;
}
@@ -235,7 +235,7 @@ class RowIterator implements IteratorInterface
*
* @return int
*/
public function key()
public function key() : int
{
return $this->numReadRows;
}
@@ -245,7 +245,7 @@ class RowIterator implements IteratorInterface
*
* @return void
*/
public function end()
public function end() : void
{
// do nothing
}
+8 -8
View File
@@ -10,7 +10,7 @@ use Box\Spout\Reader\IteratorInterface;
*/
class SheetIterator implements IteratorInterface
{
/** @var \Box\Spout\Reader\CSV\Sheet The CSV unique "sheet" */
/** @var Sheet The CSV unique "sheet" */
protected $sheet;
/** @var bool Whether the unique "sheet" has already been read */
@@ -30,7 +30,7 @@ class SheetIterator implements IteratorInterface
*
* @return void
*/
public function rewind()
public function rewind() : void
{
$this->hasReadUniqueSheet = false;
}
@@ -41,7 +41,7 @@ class SheetIterator implements IteratorInterface
*
* @return bool
*/
public function valid()
public function valid() : bool
{
return (!$this->hasReadUniqueSheet);
}
@@ -52,7 +52,7 @@ class SheetIterator implements IteratorInterface
*
* @return void
*/
public function next()
public function next() : void
{
$this->hasReadUniqueSheet = true;
}
@@ -61,9 +61,9 @@ class SheetIterator implements IteratorInterface
* Return the current element
* @see http://php.net/manual/en/iterator.current.php
*
* @return \Box\Spout\Reader\CSV\Sheet
* @return Sheet
*/
public function current()
public function current() : Sheet
{
return $this->sheet;
}
@@ -74,7 +74,7 @@ class SheetIterator implements IteratorInterface
*
* @return int
*/
public function key()
public function key() : int
{
return 1;
}
@@ -84,7 +84,7 @@ class SheetIterator implements IteratorInterface
*
* @return void
*/
public function end()
public function end() : void
{
// do nothing
}
@@ -35,6 +35,7 @@ class ReaderEntityFactory
return ReaderFactory::createFromType(Type::CSV);
} catch (UnsupportedTypeException $e) {
// should never happen
return null;
}
}
@@ -49,6 +50,7 @@ class ReaderEntityFactory
return ReaderFactory::createFromType(Type::XLSX);
} catch (UnsupportedTypeException $e) {
// should never happen
return null;
}
}
@@ -63,6 +65,7 @@ class ReaderEntityFactory
return ReaderFactory::createFromType(Type::ODS);
} catch (UnsupportedTypeException $e) {
// should never happen
return null;
}
}
}
@@ -45,7 +45,7 @@ class ReaderFactory
/**
* This creates an instance of the appropriate reader, given the type of the file to be read
*
* @param string $readerType Type of the reader to instantiate
* @param string $readerType Type of the reader to instantiate
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException
* @return ReaderInterface
*/
+7 -7
View File
@@ -9,15 +9,15 @@ namespace Box\Spout\Reader\Common\Entity;
abstract class Options
{
// Common options
const SHOULD_FORMAT_DATES = 'shouldFormatDates';
const SHOULD_PRESERVE_EMPTY_ROWS = 'shouldPreserveEmptyRows';
public const SHOULD_FORMAT_DATES = 'shouldFormatDates';
public const SHOULD_PRESERVE_EMPTY_ROWS = 'shouldPreserveEmptyRows';
// CSV specific options
const FIELD_DELIMITER = 'fieldDelimiter';
const FIELD_ENCLOSURE = 'fieldEnclosure';
const ENCODING = 'encoding';
public const FIELD_DELIMITER = 'fieldDelimiter';
public const FIELD_ENCLOSURE = 'fieldEnclosure';
public const ENCODING = 'encoding';
// XLSX specific options
const TEMP_FOLDER = 'tempFolder';
const SHOULD_USE_1904_DATES = 'shouldUse1904Dates';
public const TEMP_FOLDER = 'tempFolder';
public const SHOULD_USE_1904_DATES = 'shouldUse1904Dates';
}
@@ -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;
}
}
+6 -6
View File
@@ -11,16 +11,16 @@ use Box\Spout\Reader\Wrapper\XMLReader;
class XMLProcessor
{
/* Node types */
const NODE_TYPE_START = XMLReader::ELEMENT;
const NODE_TYPE_END = XMLReader::END_ELEMENT;
public const NODE_TYPE_START = XMLReader::ELEMENT;
public const NODE_TYPE_END = XMLReader::END_ELEMENT;
/* Keys associated to reflection attributes to invoke a callback */
const CALLBACK_REFLECTION_METHOD = 'reflectionMethod';
const CALLBACK_REFLECTION_OBJECT = 'reflectionObject';
public const CALLBACK_REFLECTION_METHOD = 'reflectionMethod';
public const CALLBACK_REFLECTION_OBJECT = 'reflectionObject';
/* Values returned by the callbacks to indicate what the processor should do next */
const PROCESSING_CONTINUE = 1;
const PROCESSING_STOP = 2;
public const PROCESSING_CONTINUE = 1;
public const PROCESSING_STOP = 2;
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
protected $xmlReader;
@@ -105,7 +105,7 @@ class InternalEntityFactory implements InternalEntityFactoryInterface
}
/**
* @param $xmlReader
* @param XMLReader $xmlReader
* @return XMLProcessor
*/
private function createXMLProcessor($xmlReader)
@@ -11,31 +11,31 @@ use Box\Spout\Reader\Exception\InvalidValueException;
class CellValueFormatter
{
/** Definition of all possible cell types */
const CELL_TYPE_STRING = 'string';
const CELL_TYPE_FLOAT = 'float';
const CELL_TYPE_BOOLEAN = 'boolean';
const CELL_TYPE_DATE = 'date';
const CELL_TYPE_TIME = 'time';
const CELL_TYPE_CURRENCY = 'currency';
const CELL_TYPE_PERCENTAGE = 'percentage';
const CELL_TYPE_VOID = 'void';
public const CELL_TYPE_STRING = 'string';
public const CELL_TYPE_FLOAT = 'float';
public const CELL_TYPE_BOOLEAN = 'boolean';
public const CELL_TYPE_DATE = 'date';
public const CELL_TYPE_TIME = 'time';
public const CELL_TYPE_CURRENCY = 'currency';
public const CELL_TYPE_PERCENTAGE = 'percentage';
public const CELL_TYPE_VOID = 'void';
/** Definition of XML nodes names used to parse data */
const XML_NODE_P = 'p';
const XML_NODE_TEXT_A = 'text:a';
const XML_NODE_TEXT_SPAN = 'text:span';
const XML_NODE_TEXT_S = 'text:s';
const XML_NODE_TEXT_TAB = 'text:tab';
const XML_NODE_TEXT_LINE_BREAK = 'text:line-break';
public const XML_NODE_P = 'p';
public const XML_NODE_TEXT_A = 'text:a';
public const XML_NODE_TEXT_SPAN = 'text:span';
public const XML_NODE_TEXT_S = 'text:s';
public const XML_NODE_TEXT_TAB = 'text:tab';
public const XML_NODE_TEXT_LINE_BREAK = 'text:line-break';
/** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_TYPE = 'office:value-type';
const XML_ATTRIBUTE_VALUE = 'office:value';
const XML_ATTRIBUTE_BOOLEAN_VALUE = 'office:boolean-value';
const XML_ATTRIBUTE_DATE_VALUE = 'office:date-value';
const XML_ATTRIBUTE_TIME_VALUE = 'office:time-value';
const XML_ATTRIBUTE_CURRENCY = 'office:currency';
const XML_ATTRIBUTE_C = 'text:c';
public const XML_ATTRIBUTE_TYPE = 'office:value-type';
public const XML_ATTRIBUTE_VALUE = 'office:value';
public const XML_ATTRIBUTE_BOOLEAN_VALUE = 'office:boolean-value';
public const XML_ATTRIBUTE_DATE_VALUE = 'office:date-value';
public const XML_ATTRIBUTE_TIME_VALUE = 'office:time-value';
public const XML_ATTRIBUTE_CURRENCY = 'office:currency';
public const XML_ATTRIBUTE_C = 'text:c';
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
protected $shouldFormatDates;
@@ -64,7 +64,7 @@ class CellValueFormatter
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
* @see http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#refTable13
*
* @param \DOMNode $node
* @param \DOMElement $node
* @throws InvalidValueException If the node value is not valid
* @return string|int|float|bool|\DateTime|\DateInterval The value associated with the cell, empty string if cell's type is void/undefined
*/
@@ -96,7 +96,7 @@ class CellValueFormatter
/**
* Returns the cell String value.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return string The value associated with the cell
*/
protected function formatStringCellValue($node)
@@ -115,7 +115,7 @@ class CellValueFormatter
}
/**
* @param $pNode
* @param \DOMNode $pNode
* @return string
*/
private function extractTextValueFromNode($pNode)
@@ -159,7 +159,7 @@ class CellValueFormatter
*
* @see https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1415200_253892949
*
* @param \DOMNode $node The XML node representing a whitespace
* @param \DOMElement $node The XML node representing a whitespace
* @return string The corresponding whitespace value
*/
private function transformWhitespaceNode($node)
@@ -173,7 +173,7 @@ class CellValueFormatter
/**
* Returns the cell Numeric value from the given node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return int|float The value associated with the cell
*/
protected function formatFloatCellValue($node)
@@ -190,7 +190,7 @@ class CellValueFormatter
/**
* Returns the cell Boolean value from the given node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return bool The value associated with the cell
*/
protected function formatBooleanCellValue($node)
@@ -203,7 +203,7 @@ class CellValueFormatter
/**
* Returns the cell Date value from the given node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @throws InvalidValueException If the value is not a valid date
* @return \DateTime|string The value associated with the cell
*/
@@ -234,7 +234,7 @@ class CellValueFormatter
/**
* Returns the cell Time value from the given node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @throws InvalidValueException If the value is not a valid time
* @return \DateInterval|string The value associated with the cell
*/
@@ -265,7 +265,7 @@ class CellValueFormatter
/**
* Returns the cell Currency value from the given node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return string The value associated with the cell (e.g. "100 USD" or "9.99 EUR")
*/
protected function formatCurrencyCellValue($node)
@@ -279,7 +279,7 @@ class CellValueFormatter
/**
* Returns the cell Percentage value from the given node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return int|float The value associated with the cell
*/
protected function formatPercentageCellValue($node)
@@ -11,12 +11,12 @@ use Box\Spout\Reader\ODS\Creator\InternalEntityFactory;
*/
class SettingsHelper
{
const SETTINGS_XML_FILE_PATH = 'settings.xml';
public const SETTINGS_XML_FILE_PATH = 'settings.xml';
/** Definition of XML nodes name and attribute used to parse settings data */
const XML_NODE_CONFIG_ITEM = 'config:config-item';
const XML_ATTRIBUTE_CONFIG_NAME = 'config:name';
const XML_ATTRIBUTE_VALUE_ACTIVE_TABLE = 'ActiveTable';
public const XML_NODE_CONFIG_ITEM = 'config:config-item';
public const XML_ATTRIBUTE_CONFIG_NAME = 'config:name';
public const XML_ATTRIBUTE_VALUE_ACTIVE_TABLE = 'ActiveTable';
/** @var InternalEntityFactory Factory to create entities */
private $entityFactory;
+1 -1
View File
@@ -69,7 +69,7 @@ class Reader extends ReaderAbstract
*/
protected function closeReader()
{
if ($this->zip) {
if ($this->zip !== null) {
$this->zip->close();
}
}
+17 -16
View File
@@ -23,14 +23,14 @@ use Box\Spout\Reader\Wrapper\XMLReader;
class RowIterator implements IteratorInterface
{
/** Definition of XML nodes names used to parse data */
const XML_NODE_TABLE = 'table:table';
const XML_NODE_ROW = 'table:table-row';
const XML_NODE_CELL = 'table:table-cell';
const MAX_COLUMNS_EXCEL = 16384;
public const XML_NODE_TABLE = 'table:table';
public const XML_NODE_ROW = 'table:table-row';
public const XML_NODE_CELL = 'table:table-cell';
public const MAX_COLUMNS_EXCEL = 16384;
/** Definition of XML attribute used to parse data */
const XML_ATTRIBUTE_NUM_ROWS_REPEATED = 'table:number-rows-repeated';
const XML_ATTRIBUTE_NUM_COLUMNS_REPEATED = 'table:number-columns-repeated';
public const XML_ATTRIBUTE_NUM_ROWS_REPEATED = 'table:number-rows-repeated';
public const XML_ATTRIBUTE_NUM_COLUMNS_REPEATED = 'table:number-columns-repeated';
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
protected $xmlReader;
@@ -56,7 +56,7 @@ class RowIterator implements IteratorInterface
/** @var Row The currently processed row */
protected $currentlyProcessedRow;
/** @var Row Buffer used to store the current row, while checking if there are more rows to read */
/** @var Row|null Buffer used to store the current row, while checking if there are more rows to read */
protected $rowBuffer;
/** @var bool Indicates whether all rows have been read */
@@ -68,7 +68,7 @@ class RowIterator implements IteratorInterface
/** @var int Row index to be processed next (one-based) */
protected $nextRowIndexToBeProcessed = 1;
/** @var Cell Last processed cell (because when reading cell at column N+1, cell N is processed) */
/** @var Cell|null Last processed cell (because when reading cell at column N+1, cell N is processed) */
protected $lastProcessedCell;
/** @var int Number of times the last processed row should be repeated */
@@ -118,7 +118,7 @@ class RowIterator implements IteratorInterface
* @throws \Box\Spout\Reader\Exception\IteratorNotRewindableException If the iterator is rewound more than once
* @return void
*/
public function rewind()
public function rewind() : void
{
// Because sheet and row data is located in the file, we can't rewind both the
// sheet iterator and the row iterator, as XML file cannot be read backwards.
@@ -142,7 +142,7 @@ class RowIterator implements IteratorInterface
*
* @return bool
*/
public function valid()
public function valid() : bool
{
return (!$this->hasReachedEndOfFile);
}
@@ -155,7 +155,7 @@ class RowIterator implements IteratorInterface
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
* @return void
*/
public function next()
public function next() : void
{
if ($this->doesNeedDataForNextRowToBeProcessed()) {
$this->readDataForNextRow();
@@ -225,6 +225,7 @@ class RowIterator implements IteratorInterface
$currentNumColumnsRepeated = $this->getNumColumnsRepeatedForCurrentNode($xmlReader);
// NOTE: expand() will automatically decode all XML entities of the child nodes
/** @var \DOMElement $node */
$node = $xmlReader->expand();
$currentCell = $this->getCell($node);
@@ -316,7 +317,7 @@ class RowIterator implements IteratorInterface
/**
* Returns the cell with (unescaped) correctly marshalled, cell value associated to the given XML node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return Cell The cell set with the associated with the cell
*/
protected function getCell($node)
@@ -339,7 +340,7 @@ class RowIterator implements IteratorInterface
* row data yet (as we still need to apply the "num-columns-repeated" attribute).
*
* @param Row $currentRow
* @param Cell $lastReadCell The last read cell
* @param Cell|null $lastReadCell The last read cell
* @return bool Whether the row is empty
*/
protected function isEmptyRow($currentRow, $lastReadCell)
@@ -356,7 +357,7 @@ class RowIterator implements IteratorInterface
*
* @return Row
*/
public function current()
public function current() : Row
{
return $this->rowBuffer;
}
@@ -367,7 +368,7 @@ class RowIterator implements IteratorInterface
*
* @return int
*/
public function key()
public function key() : int
{
return $this->lastRowIndexProcessed;
}
@@ -377,7 +378,7 @@ class RowIterator implements IteratorInterface
*
* @return void
*/
public function end()
public function end() : void
{
$this->xmlReader->close();
}
+13 -11
View File
@@ -15,26 +15,26 @@ use Box\Spout\Reader\Wrapper\XMLReader;
*/
class SheetIterator implements IteratorInterface
{
const CONTENT_XML_FILE_PATH = 'content.xml';
public const CONTENT_XML_FILE_PATH = 'content.xml';
const XML_STYLE_NAMESPACE = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0';
public const XML_STYLE_NAMESPACE = 'urn:oasis:names:tc:opendocument:xmlns:style:1.0';
/** Definition of XML nodes name and attribute used to parse sheet data */
const XML_NODE_AUTOMATIC_STYLES = 'office:automatic-styles';
const XML_NODE_STYLE_TABLE_PROPERTIES = 'table-properties';
const XML_NODE_TABLE = 'table:table';
const XML_ATTRIBUTE_STYLE_NAME = 'style:name';
const XML_ATTRIBUTE_TABLE_NAME = 'table:name';
const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name';
const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display';
public const XML_NODE_AUTOMATIC_STYLES = 'office:automatic-styles';
public const XML_NODE_STYLE_TABLE_PROPERTIES = 'table-properties';
public const XML_NODE_TABLE = 'table:table';
public const XML_ATTRIBUTE_STYLE_NAME = 'style:name';
public const XML_ATTRIBUTE_TABLE_NAME = 'table:name';
public const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name';
public 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 */
@@ -108,6 +108,7 @@ class SheetIterator implements IteratorInterface
$sheetsVisibility = [];
$this->xmlReader->readUntilNodeFound(self::XML_NODE_AUTOMATIC_STYLES);
/** @var \DOMElement $automaticStylesNode */
$automaticStylesNode = $this->xmlReader->expand();
$tableStyleNodes = $automaticStylesNode->getElementsByTagNameNS(self::XML_STYLE_NAMESPACE, self::XML_NODE_STYLE_TABLE_PROPERTIES);
@@ -116,6 +117,7 @@ class SheetIterator implements IteratorInterface
foreach ($tableStyleNodes as $tableStyleNode) {
$isSheetVisible = ($tableStyleNode->getAttribute(self::XML_ATTRIBUTE_TABLE_DISPLAY) !== 'false');
/** @var \DOMElement $parentStyleNode */
$parentStyleNode = $tableStyleNode->parentNode;
$styleName = $parentStyleNode->getAttribute(self::XML_ATTRIBUTE_STYLE_NAME);
+2 -2
View File
@@ -53,7 +53,7 @@ abstract class ReaderAbstract implements ReaderInterface
/**
* Closes the reader. To be used after reading the file.
*
* @return ReaderAbstract
* @return void
*/
abstract protected function closeReader();
@@ -233,7 +233,7 @@ abstract class ReaderAbstract implements ReaderInterface
$this->closeReader();
$sheetIterator = $this->getConcreteSheetIterator();
if ($sheetIterator) {
if ($sheetIterator !== null) {
$sheetIterator->end();
}
+1 -1
View File
@@ -11,7 +11,7 @@ class XMLReader extends \XMLReader
{
use XMLInternalErrorsHelper;
const ZIP_WRAPPER = 'zip://';
public const ZIP_WRAPPER = 'zip://';
/**
* Opens the XML Reader to read a file located inside a ZIP file.
@@ -152,7 +152,7 @@ class InternalEntityFactory implements InternalEntityFactoryInterface
}
/**
* @param $xmlReader
* @param XMLReader $xmlReader
* @return XMLProcessor
*/
public function createXMLProcessor($xmlReader)
@@ -21,7 +21,7 @@ class ManagerFactory
/** @var CachingStrategyFactory */
private $cachingStrategyFactory;
/** @var WorkbookRelationshipsManager */
/** @var WorkbookRelationshipsManager|null */
private $cachedWorkbookRelationshipsManager;
/**
@@ -13,32 +13,24 @@ use Box\Spout\Reader\XLSX\Manager\StyleManager;
class CellValueFormatter
{
/** Definition of all possible cell types */
const CELL_TYPE_INLINE_STRING = 'inlineStr';
const CELL_TYPE_STR = 'str';
const CELL_TYPE_SHARED_STRING = 's';
const CELL_TYPE_BOOLEAN = 'b';
const CELL_TYPE_NUMERIC = 'n';
const CELL_TYPE_DATE = 'd';
const CELL_TYPE_ERROR = 'e';
public const CELL_TYPE_INLINE_STRING = 'inlineStr';
public const CELL_TYPE_STR = 'str';
public const CELL_TYPE_SHARED_STRING = 's';
public const CELL_TYPE_BOOLEAN = 'b';
public const CELL_TYPE_NUMERIC = 'n';
public const CELL_TYPE_DATE = 'd';
public const CELL_TYPE_ERROR = 'e';
/** Definition of XML nodes names used to parse data */
const XML_NODE_VALUE = 'v';
const XML_NODE_INLINE_STRING_VALUE = 't';
public const XML_NODE_VALUE = 'v';
public const XML_NODE_INLINE_STRING_VALUE = 't';
/** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_TYPE = 't';
const XML_ATTRIBUTE_STYLE_ID = 's';
public const XML_ATTRIBUTE_TYPE = 't';
public const XML_ATTRIBUTE_STYLE_ID = 's';
/** 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;
public const NUM_SECONDS_IN_ONE_DAY = 86400;
/** @var SharedStringsManager Manages shared strings */
protected $sharedStringsManager;
@@ -74,7 +66,7 @@ class CellValueFormatter
/**
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @throws InvalidValueException If the value is not valid
* @return string|int|float|bool|\DateTime The value associated with the cell
*/
@@ -110,7 +102,7 @@ class CellValueFormatter
/**
* Returns the cell's string value from a node's nested value node
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return string The value associated with the cell
*/
protected function getVNodeValue($node)
@@ -125,15 +117,20 @@ class CellValueFormatter
/**
* Returns the cell String value where string is inline.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return string The value associated with the cell
*/
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;
}
@@ -8,9 +8,9 @@ namespace Box\Spout\Reader\XLSX\Helper;
*/
class DateFormatHelper
{
const KEY_GENERAL = 'general';
const KEY_HOUR_12 = '12h';
const KEY_HOUR_24 = '24h';
public const KEY_GENERAL = 'general';
public const KEY_HOUR_12 = '12h';
public const KEY_HOUR_24 = '24h';
/**
* This map is used to replace Excel format characters by their PHP equivalent.
@@ -37,7 +37,7 @@ class CachingStrategyFactory
* This means that in order to store one shared string in memory, the memory amount needed is:
* => 20 * 600 ≈ 12KB
*/
const AMOUNT_MEMORY_NEEDED_PER_STRING_IN_KB = 12;
public const AMOUNT_MEMORY_NEEDED_PER_STRING_IN_KB = 12;
/**
* To avoid running out of memory when extracting a huge number of shared strings, they can be saved to temporary files
@@ -48,7 +48,7 @@ class CachingStrategyFactory
* best when the indexes of the shared strings are sorted in the sheet data.
* 10,000 was chosen because it creates small files that are fast to be loaded in memory.
*/
const MAX_NUM_STRINGS_PER_TEMP_FILE = 10000;
public const MAX_NUM_STRINGS_PER_TEMP_FILE = 10000;
/**
* Returns the best caching strategy, given the number of unique shared strings
@@ -84,7 +84,7 @@ class CachingStrategyFactory
$memoryAvailable = $this->getMemoryLimitInKB();
if ($memoryAvailable === -1) {
if ((int) $memoryAvailable === -1) {
// if cannot get memory limit or if memory limit set as unlimited, don't trust and play safe
$isInMemoryStrategyUsageSafe = ($sharedStringsUniqueCount < self::MAX_NUM_STRINGS_PER_TEMP_FILE);
} else {
@@ -15,7 +15,7 @@ use Box\Spout\Reader\XLSX\Creator\HelperFactory;
class FileBasedStrategy implements CachingStrategyInterface
{
/** Value to use to escape the line feed character ("\n") */
const ESCAPED_LINE_FEED_CHARACTER = '_x000A_';
public const ESCAPED_LINE_FEED_CHARACTER = '_x000A_';
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper;
@@ -32,7 +32,7 @@ class FileBasedStrategy implements CachingStrategyInterface
*/
protected $maxNumStringsPerTempFile;
/** @var resource Pointer to the last temp file a shared string was written to */
/** @var resource|null Pointer to the last temp file a shared string was written to */
protected $tempFilePointer;
/**
@@ -16,20 +16,17 @@ 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';
const XML_NODE_R = 'r';
const XML_NODE_T = 't';
public const XML_NODE_SST = 'sst';
public const XML_NODE_SI = 'si';
public const XML_NODE_R = 'r';
public const XML_NODE_T = 't';
/** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_COUNT = 'count';
const XML_ATTRIBUTE_UNIQUE_COUNT = 'uniqueCount';
const XML_ATTRIBUTE_XML_SPACE = 'xml:space';
const XML_ATTRIBUTE_VALUE_PRESERVE = 'preserve';
public const XML_ATTRIBUTE_COUNT = 'count';
public const XML_ATTRIBUTE_UNIQUE_COUNT = 'uniqueCount';
public const XML_ATTRIBUTE_XML_SPACE = 'xml:space';
public const XML_ATTRIBUTE_VALUE_PRESERVE = 'preserve';
/** @var string Path of the XLSX file being read */
protected $filePath;
@@ -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 */
@@ -182,6 +179,7 @@ class SharedStringsManager
$sharedStringValue = '';
// NOTE: expand() will automatically decode all XML entities of the child nodes
/** @var \DOMElement $siNode */
$siNode = $xmlReader->expand();
$textNodes = $siNode->getElementsByTagName(self::XML_NODE_T);
@@ -244,7 +242,7 @@ class SharedStringsManager
*/
public function cleanup()
{
if ($this->cachingStrategy) {
if ($this->cachingStrategy !== null) {
$this->cachingStrategy->clearCache();
}
}
+15 -15
View File
@@ -14,27 +14,27 @@ use Box\Spout\Reader\XLSX\Sheet;
class SheetManager
{
/** Paths of XML files relative to the XLSX file root */
const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
public const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
public const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
/** Definition of XML node names used to parse data */
const XML_NODE_WORKBOOK_PROPERTIES = 'workbookPr';
const XML_NODE_WORKBOOK_VIEW = 'workbookView';
const XML_NODE_SHEET = 'sheet';
const XML_NODE_SHEETS = 'sheets';
const XML_NODE_RELATIONSHIP = 'Relationship';
public const XML_NODE_WORKBOOK_PROPERTIES = 'workbookPr';
public const XML_NODE_WORKBOOK_VIEW = 'workbookView';
public const XML_NODE_SHEET = 'sheet';
public const XML_NODE_SHEETS = 'sheets';
public const XML_NODE_RELATIONSHIP = 'Relationship';
/** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_DATE_1904 = 'date1904';
const XML_ATTRIBUTE_ACTIVE_TAB = 'activeTab';
const XML_ATTRIBUTE_R_ID = 'r:id';
const XML_ATTRIBUTE_NAME = 'name';
const XML_ATTRIBUTE_STATE = 'state';
const XML_ATTRIBUTE_ID = 'Id';
const XML_ATTRIBUTE_TARGET = 'Target';
public const XML_ATTRIBUTE_DATE_1904 = 'date1904';
public const XML_ATTRIBUTE_ACTIVE_TAB = 'activeTab';
public const XML_ATTRIBUTE_R_ID = 'r:id';
public const XML_ATTRIBUTE_NAME = 'name';
public const XML_ATTRIBUTE_STATE = 'state';
public const XML_ATTRIBUTE_ID = 'Id';
public const XML_ATTRIBUTE_TARGET = 'Target';
/** State value to represent a hidden sheet */
const SHEET_STATE_HIDDEN = 'hidden';
public const SHEET_STATE_HIDDEN = 'hidden';
/** @var string Path of the XLSX file being read */
protected $filePath;
+11 -11
View File
@@ -11,20 +11,20 @@ use Box\Spout\Reader\XLSX\Creator\InternalEntityFactory;
class StyleManager
{
/** Nodes used to find relevant information in the styles XML file */
const XML_NODE_NUM_FMTS = 'numFmts';
const XML_NODE_NUM_FMT = 'numFmt';
const XML_NODE_CELL_XFS = 'cellXfs';
const XML_NODE_XF = 'xf';
public const XML_NODE_NUM_FMTS = 'numFmts';
public const XML_NODE_NUM_FMT = 'numFmt';
public const XML_NODE_CELL_XFS = 'cellXfs';
public const XML_NODE_XF = 'xf';
/** Attributes used to find relevant information in the styles XML file */
const XML_ATTRIBUTE_NUM_FMT_ID = 'numFmtId';
const XML_ATTRIBUTE_FORMAT_CODE = 'formatCode';
const XML_ATTRIBUTE_APPLY_NUMBER_FORMAT = 'applyNumberFormat';
public const XML_ATTRIBUTE_NUM_FMT_ID = 'numFmtId';
public const XML_ATTRIBUTE_FORMAT_CODE = 'formatCode';
public const XML_ATTRIBUTE_APPLY_NUMBER_FORMAT = 'applyNumberFormat';
/** By convention, default style ID is 0 */
const DEFAULT_STYLE_ID = 0;
public const DEFAULT_STYLE_ID = 0;
const NUMBER_FORMAT_GENERAL = 'General';
public const NUMBER_FORMAT_GENERAL = 'General';
/**
* @see https://msdn.microsoft.com/en-us/library/ff529597(v=office.12).aspx
@@ -60,10 +60,10 @@ class StyleManager
/** @var array Array containing the IDs of built-in number formats indicating a date */
protected $builtinNumFmtIdIndicatingDates;
/** @var array Array containing a mapping NUM_FMT_ID => FORMAT_CODE */
/** @var array|null Array containing a mapping NUM_FMT_ID => FORMAT_CODE */
protected $customNumberFormats;
/** @var array Array containing a mapping STYLE_ID => [STYLE_ATTRIBUTES] */
/** @var array|null Array containing a mapping STYLE_ID => [STYLE_ATTRIBUTES] */
protected $stylesAttributes;
/** @var array Cache containing a mapping NUM_FMT_ID => IS_DATE_FORMAT. Used to avoid lots of recalculations */
@@ -12,20 +12,21 @@ use Box\Spout\Reader\XLSX\Creator\InternalEntityFactory;
*/
class WorkbookRelationshipsManager
{
const BASE_PATH = 'xl/';
public const BASE_PATH = 'xl/';
/** Path of workbook relationships XML file inside the XLSX file */
const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
public const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
/** Relationships types */
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';
/** Relationships types - For Transitional and Strict OOXML */
public const RELATIONSHIP_TYPE_SHARED_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
public const RELATIONSHIP_TYPE_STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
public const RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT = 'http://purl.oclc.org/ooxml/officeDocument/relationships/sharedStrings';
public 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';
const XML_ATTRIBUTE_TYPE = 'Type';
const XML_ATTRIBUTE_TARGET = 'Target';
public const XML_NODE_RELATIONSHIP = 'Relationship';
public const XML_ATTRIBUTE_TYPE = 'Type';
public const XML_ATTRIBUTE_TARGET = 'Target';
/** @var string Path of the XLSX file being read */
private $filePath;
@@ -33,7 +34,7 @@ class WorkbookRelationshipsManager
/** @var InternalEntityFactory Factory to create entities */
private $entityFactory;
/** @var array Cache of the already read workbook relationships: [TYPE] => [FILE_NAME] */
/** @var array|null Cache of the already read workbook relationships: [TYPE] => [FILE_NAME] */
private $cachedWorkbookRelationships;
/**
@@ -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);
+2 -2
View File
@@ -119,11 +119,11 @@ class Reader extends ReaderAbstract
*/
protected function closeReader()
{
if ($this->zip) {
if ($this->zip !== null) {
$this->zip->close();
}
if ($this->sharedStringsManager) {
if ($this->sharedStringsManager !== null) {
$this->sharedStringsManager->cleanup();
}
}
+17 -16
View File
@@ -21,21 +21,21 @@ use Box\Spout\Reader\XLSX\Helper\CellValueFormatter;
class RowIterator implements IteratorInterface
{
/** Definition of XML nodes names used to parse data */
const XML_NODE_DIMENSION = 'dimension';
const XML_NODE_WORKSHEET = 'worksheet';
const XML_NODE_ROW = 'row';
const XML_NODE_CELL = 'c';
public const XML_NODE_DIMENSION = 'dimension';
public const XML_NODE_WORKSHEET = 'worksheet';
public const XML_NODE_ROW = 'row';
public const XML_NODE_CELL = 'c';
/** Definition of XML attributes used to parse data */
const XML_ATTRIBUTE_REF = 'ref';
const XML_ATTRIBUTE_SPANS = 'spans';
const XML_ATTRIBUTE_ROW_INDEX = 'r';
const XML_ATTRIBUTE_CELL_INDEX = 'r';
public const XML_ATTRIBUTE_REF = 'ref';
public const XML_ATTRIBUTE_SPANS = 'spans';
public const XML_ATTRIBUTE_ROW_INDEX = 'r';
public const XML_ATTRIBUTE_CELL_INDEX = 'r';
/** @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 */
@@ -139,7 +139,7 @@ class RowIterator implements IteratorInterface
* @throws \Box\Spout\Common\Exception\IOException If the sheet data XML cannot be read
* @return void
*/
public function rewind()
public function rewind() : void
{
$this->xmlReader->close();
@@ -163,7 +163,7 @@ class RowIterator implements IteratorInterface
*
* @return bool
*/
public function valid()
public function valid() : bool
{
return (!$this->hasReachedEndOfFile);
}
@@ -176,7 +176,7 @@ class RowIterator implements IteratorInterface
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
* @return void
*/
public function next()
public function next() : void
{
$this->nextRowIndexToBeProcessed++;
@@ -276,6 +276,7 @@ class RowIterator implements IteratorInterface
$currentColumnIndex = $this->getColumnIndex($xmlReader);
// NOTE: expand() will automatically decode all XML entities of the child nodes
/** @var \DOMElement $node */
$node = $xmlReader->expand();
$cell = $this->getCell($node);
@@ -352,7 +353,7 @@ class RowIterator implements IteratorInterface
/**
* Returns the cell with (unescaped) correctly marshalled, cell value associated to the given XML node.
*
* @param \DOMNode $node
* @param \DOMElement $node
* @return Cell The cell set with the associated with the cell
*/
protected function getCell($node)
@@ -374,7 +375,7 @@ class RowIterator implements IteratorInterface
*
* @return Row|null
*/
public function current()
public function current() : ?Row
{
$rowToBeProcessed = $this->rowBuffer;
@@ -399,7 +400,7 @@ class RowIterator implements IteratorInterface
*
* @return int
*/
public function key()
public function key() : int
{
// TODO: This should return $this->nextRowIndexToBeProcessed
// but to avoid a breaking change, the return value for
@@ -414,7 +415,7 @@ class RowIterator implements IteratorInterface
*
* @return void
*/
public function end()
public function end() : void
{
$this->xmlReader->close();
}
+1 -1
View File
@@ -15,7 +15,7 @@ use Box\Spout\Writer\WriterAbstract;
class Writer extends WriterAbstract
{
/** Number of rows to write before flushing */
const FLUSH_THRESHOLD = 500;
public const FLUSH_THRESHOLD = 500;
/** @var string Content-Type value for the header */
protected static $headerContentType = 'text/csv; charset=UTF-8';
@@ -50,6 +50,7 @@ class WriterEntityFactory
return WriterFactory::createFromType(Type::CSV);
} catch (UnsupportedTypeException $e) {
// should never happen
return null;
}
}
@@ -64,6 +65,7 @@ class WriterEntityFactory
return WriterFactory::createFromType(Type::XLSX);
} catch (UnsupportedTypeException $e) {
// should never happen
return null;
}
}
@@ -78,6 +80,7 @@ class WriterEntityFactory
return WriterFactory::createFromType(Type::ODS);
} catch (UnsupportedTypeException $e) {
// should never happen
return null;
}
}
+7 -7
View File
@@ -9,15 +9,15 @@ namespace Box\Spout\Writer\Common\Entity;
abstract class Options
{
// CSV specific options
const FIELD_DELIMITER = 'fieldDelimiter';
const FIELD_ENCLOSURE = 'fieldEnclosure';
const SHOULD_ADD_BOM = 'shouldAddBOM';
public const FIELD_DELIMITER = 'fieldDelimiter';
public const FIELD_ENCLOSURE = 'fieldEnclosure';
public const SHOULD_ADD_BOM = 'shouldAddBOM';
// Multisheets options
const TEMP_FOLDER = 'tempFolder';
const DEFAULT_ROW_STYLE = 'defaultRowStyle';
const SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY = 'shouldCreateNewSheetsAutomatically';
public const TEMP_FOLDER = 'tempFolder';
public const DEFAULT_ROW_STYLE = 'defaultRowStyle';
public const SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY = 'shouldCreateNewSheetsAutomatically';
// XLSX specific options
const SHOULD_USE_INLINE_STRINGS = 'shouldUseInlineStrings';
public const SHOULD_USE_INLINE_STRINGS = 'shouldUseInlineStrings';
}
+1 -1
View File
@@ -10,7 +10,7 @@ use Box\Spout\Writer\Common\Manager\SheetManager;
*/
class Sheet
{
const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
public const DEFAULT_SHEET_NAME_PREFIX = 'Sheet';
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
private $index;
+1 -1
View File
@@ -11,7 +11,7 @@ class Worksheet
/** @var string Path to the XML file that will contain the sheet data */
private $filePath;
/** @var resource Pointer to the sheet data file (e.g. xl/worksheets/sheet1.xml) */
/** @var resource|null Pointer to the sheet data file (e.g. xl/worksheets/sheet1.xml) */
private $filePointer;
/** @var Sheet The "external" sheet */
+3 -3
View File
@@ -10,11 +10,11 @@ use Box\Spout\Writer\Common\Creator\InternalEntityFactory;
*/
class ZipHelper
{
const ZIP_EXTENSION = '.zip';
public const ZIP_EXTENSION = '.zip';
/** Controls what to do when trying to add an existing file */
const EXISTING_FILES_SKIP = 'skip';
const EXISTING_FILES_OVERWRITE = 'overwrite';
public const EXISTING_FILES_SKIP = 'skip';
public const EXISTING_FILES_OVERWRITE = 'overwrite';
/** @var InternalEntityFactory Factory to create entities */
private $entityFactory;
@@ -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;
}
}
@@ -13,7 +13,7 @@ use Box\Spout\Writer\Exception\InvalidSheetNameException;
class SheetManager
{
/** Sheet name should not exceed 31 characters */
const MAX_LENGTH_SHEET_NAME = 31;
public const MAX_LENGTH_SHEET_NAME = 31;
/** @var array Invalid characters that cannot be contained in the sheet name */
private static $INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']'];
@@ -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;
}
@@ -88,10 +88,10 @@ class StyleMerger
if (!$style->hasSetCellAlignment() && $baseStyle->shouldApplyCellAlignment()) {
$styleToUpdate->setCellAlignment($baseStyle->getCellAlignment());
}
if (!$style->getBorder() && $baseStyle->shouldApplyBorder()) {
if ($style->getBorder() === null && $baseStyle->shouldApplyBorder()) {
$styleToUpdate->setBorder($baseStyle->getBorder());
}
if (!$style->getFormat() && $baseStyle->shouldApplyFormat()) {
if ($style->getFormat() === null && $baseStyle->shouldApplyFormat()) {
$styleToUpdate->setFormat($baseStyle->getFormat());
}
if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) {
@@ -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;
}
@@ -23,7 +23,7 @@ use Box\Spout\Writer\Exception\WriterException;
*/
abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
{
/** @var Workbook The workbook to manage */
/** @var Workbook|null The workbook to manage */
protected $workbook;
/** @var OptionsManagerInterface */
@@ -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 */
@@ -92,7 +92,7 @@ abstract class WorkbookManagerAbstract implements WorkbookManagerInterface
abstract protected function getWorksheetFilePath(Sheet $sheet);
/**
* @return Workbook
* @return Workbook|null
*/
public function getWorkbook()
{
@@ -17,7 +17,7 @@ use Box\Spout\Writer\Exception\WriterException;
interface WorkbookManagerInterface
{
/**
* @return Workbook
* @return Workbook|null
*/
public function getWorkbook();
@@ -30,7 +30,7 @@ class HelperFactory extends \Box\Spout\Common\Creator\HelperFactory
}
/**
* @param $entityFactory
* @param InternalEntityFactory $entityFactory
* @return ZipHelper
*/
private function createZipHelper($entityFactory)
@@ -22,7 +22,7 @@ class ManagerFactory implements ManagerFactoryInterface
/** @var InternalEntityFactory */
protected $entityFactory;
/** @var HelperFactory $helperFactory */
/** @var HelperFactory */
protected $helperFactory;
/**
@@ -15,17 +15,17 @@ use Box\Spout\Writer\ODS\Manager\WorksheetManager;
*/
class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper implements FileSystemWithRootFolderHelperInterface
{
const APP_NAME = 'Spout';
const MIMETYPE = 'application/vnd.oasis.opendocument.spreadsheet';
public const APP_NAME = 'Spout';
public const MIMETYPE = 'application/vnd.oasis.opendocument.spreadsheet';
const META_INF_FOLDER_NAME = 'META-INF';
const SHEETS_CONTENT_TEMP_FOLDER_NAME = 'worksheets-temp';
public const META_INF_FOLDER_NAME = 'META-INF';
public const SHEETS_CONTENT_TEMP_FOLDER_NAME = 'worksheets-temp';
const MANIFEST_XML_FILE_NAME = 'manifest.xml';
const CONTENT_XML_FILE_NAME = 'content.xml';
const META_XML_FILE_NAME = 'meta.xml';
const MIMETYPE_FILE_NAME = 'mimetype';
const STYLES_XML_FILE_NAME = 'styles.xml';
public const MANIFEST_XML_FILE_NAME = 'manifest.xml';
public const CONTENT_XML_FILE_NAME = 'content.xml';
public const META_XML_FILE_NAME = 'meta.xml';
public const MIMETYPE_FILE_NAME = 'mimetype';
public const STYLES_XML_FILE_NAME = 'styles.xml';
/** @var ZipHelper Helper to perform tasks with Zip archive */
private $zipHelper;
@@ -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;
@@ -85,7 +86,7 @@ class WorksheetManager implements WorksheetManagerInterface
* Returns the table XML root node as string.
*
* @param Worksheet $worksheet
* @return string <table> node as string
* @return string "<table>" node as string
*/
public function getTableElementStartAsString(Worksheet $worksheet)
{
@@ -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
+21 -4
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="' . rawurlencode($this->outputFilePath) . '"; ' .
'filename*=UTF-8\'\'' . rawurlencode($this->outputFilePath)
);
/*
* When forcing the download of a file over SSL,IE8 and lower browsers fail
@@ -152,7 +169,7 @@ abstract class WriterAbstract implements WriterInterface
*/
protected function throwIfFilePointerIsNotAvailable()
{
if (!$this->filePointer) {
if (!is_resource($this->filePointer)) {
throw new IOException('File pointer has not be opened');
}
}
@@ -9,7 +9,6 @@ use Box\Spout\Common\Manager\OptionsManagerInterface;
use Box\Spout\Writer\Common\Creator\ManagerFactoryInterface;
use Box\Spout\Writer\Common\Entity\Options;
use Box\Spout\Writer\Common\Entity\Sheet;
use Box\Spout\Writer\Common\Entity\Worksheet;
use Box\Spout\Writer\Common\Manager\WorkbookManagerInterface;
use Box\Spout\Writer\Exception\SheetNotFoundException;
use Box\Spout\Writer\Exception\WriterAlreadyOpenedException;
@@ -25,7 +24,7 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract
/** @var ManagerFactoryInterface */
private $managerFactory;
/** @var WorkbookManagerInterface */
/** @var WorkbookManagerInterface|null */
private $workbookManager;
/**
@@ -66,7 +65,7 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract
*/
protected function openWriter()
{
if (!$this->workbookManager) {
if ($this->workbookManager === null) {
$this->workbookManager = $this->managerFactory->createWorkbookManager($this->optionsManager);
$this->workbookManager->addNewSheetAndMakeItCurrent();
}
@@ -85,7 +84,6 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract
$externalSheets = [];
$worksheets = $this->workbookManager->getWorksheets();
/** @var Worksheet $worksheet */
foreach ($worksheets as $worksheet) {
$externalSheets[] = $worksheet->getExternalSheet();
}
@@ -143,7 +141,7 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract
*/
protected function throwIfWorkbookIsNotAvailable()
{
if (!$this->workbookManager->getWorkbook()) {
if ($this->workbookManager->getWorkbook() === null) {
throw new WriterNotOpenedException('The writer must be opened before performing this action.');
}
}
@@ -162,7 +160,7 @@ abstract class WriterMultiSheetsAbstract extends WriterAbstract
*/
protected function closeWriter()
{
if ($this->workbookManager) {
if ($this->workbookManager !== null) {
$this->workbookManager->close($this->filePointer);
}
}
@@ -24,7 +24,7 @@ class ManagerFactory implements ManagerFactoryInterface
/** @var InternalEntityFactory */
protected $entityFactory;
/** @var HelperFactory $helperFactory */
/** @var HelperFactory */
protected $helperFactory;
/**
@@ -91,8 +91,7 @@ class ManagerFactory implements ManagerFactoryInterface
$styleMerger,
$sharedStringsManager,
$stringsEscaper,
$stringsHelper,
$this->entityFactory
$stringsHelper
);
}
@@ -14,20 +14,20 @@ use Box\Spout\Writer\XLSX\Manager\Style\StyleManager;
*/
class FileSystemHelper extends \Box\Spout\Common\Helper\FileSystemHelper implements FileSystemWithRootFolderHelperInterface
{
const APP_NAME = 'Spout';
public const APP_NAME = 'Spout';
const RELS_FOLDER_NAME = '_rels';
const DOC_PROPS_FOLDER_NAME = 'docProps';
const XL_FOLDER_NAME = 'xl';
const WORKSHEETS_FOLDER_NAME = 'worksheets';
public const RELS_FOLDER_NAME = '_rels';
public const DOC_PROPS_FOLDER_NAME = 'docProps';
public const XL_FOLDER_NAME = 'xl';
public const WORKSHEETS_FOLDER_NAME = 'worksheets';
const RELS_FILE_NAME = '.rels';
const APP_XML_FILE_NAME = 'app.xml';
const CORE_XML_FILE_NAME = 'core.xml';
const CONTENT_TYPES_XML_FILE_NAME = '[Content_Types].xml';
const WORKBOOK_XML_FILE_NAME = 'workbook.xml';
const WORKBOOK_RELS_XML_FILE_NAME = 'workbook.xml.rels';
const STYLES_XML_FILE_NAME = 'styles.xml';
public const RELS_FILE_NAME = '.rels';
public const APP_XML_FILE_NAME = 'app.xml';
public const CORE_XML_FILE_NAME = 'core.xml';
public const CONTENT_TYPES_XML_FILE_NAME = '[Content_Types].xml';
public const WORKBOOK_XML_FILE_NAME = 'workbook.xml';
public const WORKBOOK_RELS_XML_FILE_NAME = 'workbook.xml.rels';
public const STYLES_XML_FILE_NAME = 'styles.xml';
/** @var ZipHelper Helper to perform tasks with Zip archive */
private $zipHelper;
@@ -13,8 +13,8 @@ use Box\Spout\Writer\Common\Entity\Options;
class OptionsManager extends OptionsManagerAbstract
{
/** Default style font values */
const DEFAULT_FONT_SIZE = 12;
const DEFAULT_FONT_NAME = 'Calibri';
public const DEFAULT_FONT_SIZE = 12;
public const DEFAULT_FONT_NAME = 'Calibri';
/** @var StyleBuilder Style builder */
protected $styleBuilder;
@@ -11,9 +11,9 @@ use Box\Spout\Common\Helper\Escaper;
*/
class SharedStringsManager
{
const SHARED_STRINGS_FILE_NAME = 'sharedStrings.xml';
public const SHARED_STRINGS_FILE_NAME = 'sharedStrings.xml';
const SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER = <<<'EOD'
public const SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER = <<<'EOD'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
EOD;
@@ -22,7 +22,7 @@ EOD;
* This number must be really big so that the no generated file will have more strings than that.
* If the strings number goes above, characters will be overwritten in an unwanted way and will corrupt the file.
*/
const DEFAULT_STRINGS_COUNT_PART = 'count="9999999999999" uniqueCount="9999999999999"';
public const DEFAULT_STRINGS_COUNT_PART = 'count="9999999999999" uniqueCount="9999999999999"';
/** @var resource Pointer to the sharedStrings.xml file */
protected $sharedStringsFilePointer;
@@ -59,7 +59,7 @@ EOD;
*/
protected function throwIfSharedStringsFilePointerIsNotAvailable()
{
if (!$this->sharedStringsFilePointer) {
if (!is_resource($this->sharedStringsFilePointer)) {
throw new IOException('Unable to open shared strings file for writing.');
}
}
@@ -2,6 +2,7 @@
namespace Box\Spout\Writer\XLSX\Manager\Style;
use Box\Spout\Common\Entity\Style\BorderPart;
use Box\Spout\Common\Entity\Style\Color;
use Box\Spout\Common\Entity\Style\Style;
use Box\Spout\Writer\XLSX\Helper\BorderHelper;
@@ -186,7 +187,7 @@ EOD;
$content .= '<border><left/><right/><top/><bottom/></border>';
foreach ($registeredBorders as $styleId) {
/** @var \Box\Spout\Common\Entity\Style\Style $style */
/** @var Style $style */
$style = $this->styleRegistry->getStyleFromStyleId($styleId);
$border = $style->getBorder();
$content .= '<border>';
@@ -196,7 +197,7 @@ EOD;
foreach ($sortOrder as $partName) {
if ($border->hasPart($partName)) {
/** @var $part \Box\Spout\Common\Entity\Style\BorderPart */
/** @var BorderPart $part */
$part = $border->getPart($partName);
$content .= BorderHelper::serializeBorderPart($part);
}
@@ -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);
@@ -10,10 +10,10 @@ use Box\Spout\Common\Exception\IOException;
use Box\Spout\Common\Helper\Escaper\XLSX as XLSXEscaper;
use Box\Spout\Common\Helper\StringHelper;
use Box\Spout\Common\Manager\OptionsManagerInterface;
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;
@@ -31,9 +31,9 @@ class WorksheetManager implements WorksheetManagerInterface
* @see https://support.office.com/en-us/article/Excel-specifications-and-limits-1672b34d-7043-467e-8e27-269d656771c3 [Excel 2010]
* @see https://support.office.com/en-us/article/Excel-specifications-and-limits-ca36e2dc-1f09-4620-b726-67c00b05040f [Excel 2013/2016]
*/
const MAX_CHARACTERS_PER_CELL = 32767;
public const MAX_CHARACTERS_PER_CELL = 32767;
const SHEET_XML_FILE_HEADER = <<<'EOD'
public const SHEET_XML_FILE_HEADER = <<<'EOD'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
EOD;
@@ -59,9 +59,6 @@ EOD;
/** @var StringHelper String helper */
private $stringHelper;
/** @var InternalEntityFactory Factory to create entities */
private $entityFactory;
/**
* WorksheetManager constructor.
*
@@ -72,7 +69,6 @@ EOD;
* @param SharedStringsManager $sharedStringsManager
* @param XLSXEscaper $stringsEscaper
* @param StringHelper $stringHelper
* @param InternalEntityFactory $entityFactory
*/
public function __construct(
OptionsManagerInterface $optionsManager,
@@ -81,8 +77,7 @@ EOD;
StyleMerger $styleMerger,
SharedStringsManager $sharedStringsManager,
XLSXEscaper $stringsEscaper,
StringHelper $stringHelper,
InternalEntityFactory $entityFactory
StringHelper $stringHelper
) {
$this->shouldUseInlineStrings = $optionsManager->getOption(Options::SHOULD_USE_INLINE_STRINGS);
$this->rowManager = $rowManager;
@@ -91,7 +86,6 @@ EOD;
$this->sharedStringsManager = $sharedStringsManager;
$this->stringsEscaper = $stringsEscaper;
$this->stringHelper = $stringHelper;
$this->entityFactory = $entityFactory;
}
/**
@@ -160,7 +154,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 +172,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 +233,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>';
+3 -3
View File
@@ -7,7 +7,7 @@ use Box\Spout\Common\Entity\Style\Style;
class RowTest extends \PHPUnit\Framework\TestCase
{
/**
* @return \PHPUnit_Framework_MockObject_MockObject|Style
* @return \PHPUnit\Framework\MockObject\MockObject|Style
*/
private function getStyleMock()
{
@@ -15,7 +15,7 @@ class RowTest extends \PHPUnit\Framework\TestCase
}
/**
* @return \PHPUnit_Framework_MockObject_MockObject|Cell
* @return \PHPUnit\Framework\MockObject\MockObject|Cell
*/
private function getCellMock()
{
@@ -122,6 +122,6 @@ class RowTest extends \PHPUnit\Framework\TestCase
->setStyle($this->getStyleMock())
->setCells([]);
$this->assertInternalType('object', $row);
$this->assertInstanceOf(Row::class, $row);
}
}
@@ -113,7 +113,7 @@ class BorderTest extends TestCase
$border->addPart($borderPart);
$this->assertCount(1, $border->getParts());
/** @var $part BorderPart */
/** @var BorderPart $part */
$part = $border->getParts()[$allowedName];
$this->assertEquals($allowedStyle, $part->getStyle());
@@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase;
class CellTypeHelperTest extends TestCase
{
/**
* @return array
* @return void
*/
public function testIsEmpty()
{
@@ -27,7 +27,7 @@ class CellTypeHelperTest extends TestCase
}
/**
* @return array
* @return void
*/
public function testIsNonEmptyString()
{
@@ -44,7 +44,7 @@ class CellTypeHelperTest extends TestCase
}
/**
* @return array
* @return void
*/
public function testIsNumeric()
{
@@ -66,7 +66,7 @@ class CellTypeHelperTest extends TestCase
}
/**
* @return array
* @return void
*/
public function testIsBoolean()
{
@@ -68,15 +68,15 @@ class EncodingHelperTest extends TestCase
$this->expectException(EncodingConversionException::class);
$helperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\GlobalFunctionsHelper')
->setMethods(['iconv', 'mb_convert_encoding'])
->onlyMethods(['iconv', 'mb_convert_encoding'])
->getMock();
$helperStub->method('iconv')->willReturn(false);
$helperStub->method('mb_convert_encoding')->willReturn(false);
/** @var EncodingHelper $encodingHelperStub */
/** @var EncodingHelper|\PHPUnit\Framework\MockObject\MockObject|\PHPUnit\Framework\MockObject\MockObject $encodingHelperStub */
$encodingHelperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\EncodingHelper')
->setConstructorArgs([$helperStub])
->setMethods(['canUseIconv', 'canUseMbString'])
->onlyMethods(['canUseIconv', 'canUseMbString'])
->getMock();
$encodingHelperStub->method('canUseIconv')->willReturn($shouldUseIconv);
$encodingHelperStub->method('canUseMbString')->willReturn(true);
@@ -91,10 +91,10 @@ class EncodingHelperTest extends TestCase
{
$this->expectException(EncodingConversionException::class);
/** @var EncodingHelper $encodingHelperStub */
/** @var EncodingHelper|\PHPUnit\Framework\MockObject\MockObject|\PHPUnit\Framework\MockObject\MockObject $encodingHelperStub */
$encodingHelperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\EncodingHelper')
->disableOriginalConstructor()
->setMethods(['canUseIconv', 'canUseMbString'])
->onlyMethods(['canUseIconv', 'canUseMbString'])
->getMock();
$encodingHelperStub->method('canUseIconv')->willReturn(false);
$encodingHelperStub->method('canUseMbString')->willReturn(false);
@@ -110,10 +110,10 @@ class EncodingHelperTest extends TestCase
*/
public function testAttemptConversionToUTF8ShouldReturnReencodedString($shouldUseIconv)
{
/** @var EncodingHelper $encodingHelperStub */
/** @var EncodingHelper|\PHPUnit\Framework\MockObject\MockObject $encodingHelperStub */
$encodingHelperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\EncodingHelper')
->setConstructorArgs([new GlobalFunctionsHelper()])
->setMethods(['canUseIconv', 'canUseMbString'])
->onlyMethods(['canUseIconv', 'canUseMbString'])
->getMock();
$encodingHelperStub->method('canUseIconv')->willReturn($shouldUseIconv);
$encodingHelperStub->method('canUseMbString')->willReturn(true);
@@ -129,10 +129,10 @@ class EncodingHelperTest extends TestCase
*/
public function testAttemptConversionToUTF8ShouldBeNoopWhenTargetIsUTF8()
{
/** @var EncodingHelper $encodingHelperStub */
/** @var EncodingHelper|\PHPUnit\Framework\MockObject\MockObject|\PHPUnit\Framework\MockObject\MockObject $encodingHelperStub */
$encodingHelperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\EncodingHelper')
->disableOriginalConstructor()
->setMethods(['canUseIconv'])
->onlyMethods(['canUseIconv'])
->getMock();
$encodingHelperStub->expects($this->never())->method('canUseIconv');
@@ -151,15 +151,15 @@ class EncodingHelperTest extends TestCase
$this->expectException(EncodingConversionException::class);
$helperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\GlobalFunctionsHelper')
->setMethods(['iconv', 'mb_convert_encoding'])
->onlyMethods(['iconv', 'mb_convert_encoding'])
->getMock();
$helperStub->method('iconv')->willReturn(false);
$helperStub->method('mb_convert_encoding')->willReturn(false);
/** @var EncodingHelper $encodingHelperStub */
/** @var EncodingHelper|\PHPUnit\Framework\MockObject\MockObject|\PHPUnit\Framework\MockObject\MockObject $encodingHelperStub */
$encodingHelperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\EncodingHelper')
->setConstructorArgs([$helperStub])
->setMethods(['canUseIconv', 'canUseMbString'])
->onlyMethods(['canUseIconv', 'canUseMbString'])
->getMock();
$encodingHelperStub->method('canUseIconv')->willReturn($shouldUseIconv);
$encodingHelperStub->method('canUseMbString')->willReturn(true);
@@ -174,10 +174,10 @@ class EncodingHelperTest extends TestCase
{
$this->expectException(EncodingConversionException::class);
/** @var EncodingHelper $encodingHelperStub */
/** @var EncodingHelper|\PHPUnit\Framework\MockObject\MockObject|\PHPUnit\Framework\MockObject\MockObject $encodingHelperStub */
$encodingHelperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\EncodingHelper')
->disableOriginalConstructor()
->setMethods(['canUseIconv', 'canUseMbString'])
->onlyMethods(['canUseIconv', 'canUseMbString'])
->getMock();
$encodingHelperStub->method('canUseIconv')->willReturn(false);
$encodingHelperStub->method('canUseMbString')->willReturn(false);
@@ -193,10 +193,10 @@ class EncodingHelperTest extends TestCase
*/
public function testAttemptConversionFromUTF8ShouldReturnReencodedString($shouldUseIconv)
{
/** @var EncodingHelper $encodingHelperStub */
/** @var EncodingHelper|\PHPUnit\Framework\MockObject\MockObject $encodingHelperStub */
$encodingHelperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\EncodingHelper')
->setConstructorArgs([new GlobalFunctionsHelper()])
->setMethods(['canUseIconv', 'canUseMbString'])
->onlyMethods(['canUseIconv', 'canUseMbString'])
->getMock();
$encodingHelperStub->method('canUseIconv')->willReturn($shouldUseIconv);
$encodingHelperStub->method('canUseMbString')->willReturn(true);
@@ -212,10 +212,10 @@ class EncodingHelperTest extends TestCase
*/
public function testAttemptConversionFromUTF8ShouldBeNoopWhenTargetIsUTF8()
{
/** @var EncodingHelper $encodingHelperStub */
/** @var EncodingHelper|\PHPUnit\Framework\MockObject\MockObject $encodingHelperStub */
$encodingHelperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\EncodingHelper')
->disableOriginalConstructor()
->setMethods(['canUseIconv'])
->onlyMethods(['canUseIconv'])
->getMock();
$encodingHelperStub->expects($this->never())->method('canUseIconv');
@@ -10,15 +10,15 @@ use PHPUnit\Framework\TestCase;
*/
class FileSystemHelperTest extends TestCase
{
/** @var \Box\Spout\Writer\XLSX\Helper\FileSystemHelper */
/** @var FileSystemHelper */
protected $fileSystemHelper;
/**
* @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()
+11 -8
View File
@@ -47,9 +47,9 @@ class ReaderTest extends TestCase
{
$this->expectException(IOException::class);
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper|\PHPUnit_Framework_MockObject_MockObject $helperStub */
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper|\PHPUnit\Framework\MockObject\MockObject $helperStub */
$helperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\GlobalFunctionsHelper')
->setMethods(['is_readable'])
->onlyMethods(['is_readable'])
->getMock();
$helperStub->method('is_readable')->willReturn(false);
@@ -66,9 +66,9 @@ class ReaderTest extends TestCase
{
$this->expectException(IOException::class);
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper|\PHPUnit_Framework_MockObject_MockObject $helperStub */
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper|\PHPUnit\Framework\MockObject\MockObject $helperStub */
$helperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\GlobalFunctionsHelper')
->setMethods(['fopen'])
->onlyMethods(['fopen'])
->getMock();
$helperStub->method('fopen')->willReturn(false);
@@ -237,7 +237,9 @@ class ReaderTest extends TestCase
public function testReadShouldNotTruncateLineBreak()
{
$allRows = $this->getAllRowsForFile('csv_with_line_breaks.csv');
$this->assertEquals("This is,\na comma", $allRows[0][0]);
$newLine = PHP_EOL; // to support both Unix and Windows
$this->assertEquals("This is,{$newLine}a comma", $allRows[0][0]);
}
/**
@@ -302,9 +304,9 @@ class ReaderTest extends TestCase
$allRows = [];
$resourcePath = $this->getResourcePath($fileName);
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper|\PHPUnit_Framework_MockObject_MockObject $helperStub */
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper|\PHPUnit\Framework\MockObject\MockObject $helperStub */
$helperStub = $this->getMockBuilder('\Box\Spout\Common\Helper\GlobalFunctionsHelper')
->setMethods(['function_exists'])
->onlyMethods(['function_exists'])
->getMock();
$returnValueMap = [
@@ -419,10 +421,11 @@ class ReaderTest extends TestCase
{
$allRows = $this->getAllRowsForFile('sheet_with_untrimmed_strings.csv');
$newLine = PHP_EOL; // to support both Unix and Windows
$expectedRows = [
['A'],
[' A '],
["\n\tA\n\t"],
["$newLine\tA$newLine\t"],
];
$this->assertEquals($expectedRows, $allRows, 'Cell values should not be trimmed');
+5 -5
View File
@@ -9,15 +9,15 @@ namespace Box\Spout\Reader\CSV;
*/
class SpoutTestStream
{
const CLASS_NAME = __CLASS__;
public const CLASS_NAME = __CLASS__;
const PATH_TO_CSV_RESOURCES = 'tests/resources/csv/';
const CSV_EXTENSION = '.csv';
public const PATH_TO_CSV_RESOURCES = 'tests/resources/csv/';
public 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';
@@ -79,7 +79,7 @@ class CellValueFormatterTest extends TestCase
->method('getAttribute')
->will($this->returnValueMap([
[CellValueFormatter::XML_ATTRIBUTE_TYPE, CellValueFormatter::CELL_TYPE_NUMERIC],
[CellValueFormatter::XML_ATTRIBUTE_STYLE_ID, 123],
[CellValueFormatter::XML_ATTRIBUTE_STYLE_ID, '123'],
]));
$nodeMock
@@ -88,7 +88,7 @@ class CellValueFormatterTest extends TestCase
->with(CellValueFormatter::XML_NODE_VALUE)
->will($this->returnValue($nodeListMock));
/** @var StyleManager|\PHPUnit_Framework_MockObject_MockObject $styleManagerMock */
/** @var StyleManager|\PHPUnit\Framework\MockObject\MockObject $styleManagerMock */
$styleManagerMock = $this->createMock(StyleManager::class);
$styleManagerMock
@@ -149,7 +149,7 @@ class CellValueFormatterTest extends TestCase
*/
public function testFormatNumericCellValueWithNumbers($value, $expectedFormattedValue, $expectedType)
{
/** @var StyleManager|\PHPUnit_Framework_MockObject_MockObject $styleManagerMock */
/** @var StyleManager|\PHPUnit\Framework\MockObject\MockObject $styleManagerMock */
$styleManagerMock = $this->createMock(StyleManager::class);
$styleManagerMock
->expects($this->once())
@@ -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);
@@ -36,17 +36,17 @@ class CachingStrategyFactoryTest extends TestCase
*/
public function testCreateBestCachingStrategy($sharedStringsUniqueCount, $memoryLimitInKB, $expectedStrategyClassName)
{
/** @var CachingStrategyFactory|\PHPUnit_Framework_MockObject_MockObject $factoryStub */
/** @var CachingStrategyFactory|\PHPUnit\Framework\MockObject\MockObject $factoryStub */
$factoryStub = $this
->getMockBuilder('\Box\Spout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyFactory')
->disableOriginalConstructor()
->setMethods(['getMemoryLimitInKB'])
->onlyMethods(['getMemoryLimitInKB'])
->getMock();
$factoryStub->method('getMemoryLimitInKB')->willReturn($memoryLimitInKB);
$tempFolder = sys_get_temp_dir();
$helperFactory = new HelperFactory($factoryStub);
$helperFactory = new HelperFactory();
$strategy = $factoryStub->createBestCachingStrategy($sharedStringsUniqueCount, $tempFolder, $helperFactory);
$fullExpectedStrategyClassName = 'Box\Spout\Reader\XLSX\Manager\SharedStringsCaching\\' . $expectedStrategyClassName;
@@ -84,11 +84,11 @@ class CachingStrategyFactoryTest extends TestCase
*/
public function testGetMemoryLimitInKB($memoryLimitFormatted, $expectedMemoryLimitInKB)
{
/** @var CachingStrategyFactory|\PHPUnit_Framework_MockObject_MockObject $factoryStub */
/** @var CachingStrategyFactory|\PHPUnit\Framework\MockObject\MockObject $factoryStub */
$factoryStub = $this
->getMockBuilder('\Box\Spout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyFactory')
->disableOriginalConstructor()
->setMethods(['getMemoryLimitFromIni'])
->onlyMethods(['getMemoryLimitFromIni'])
->getMock();
$factoryStub->method('getMemoryLimitFromIni')->willReturn($memoryLimitFormatted);
@@ -19,13 +19,13 @@ class SharedStringsManagerTest extends TestCase
{
use TestUsingResource;
/** @var SharedStringsManager */
/** @var SharedStringsManager|null */
private $sharedStringsManager;
/**
* @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();
@@ -21,10 +21,10 @@ class StyleManagerTest extends TestCase
$workbookRelationshipsManager = $this->createMock(WorkbookRelationshipsManager::class);
$workbookRelationshipsManager->method('hasStylesXMLFile')->willReturn(true);
/** @var StyleManager $styleManager */
/** @var StyleManager|\PHPUnit\Framework\MockObject\MockObject $styleManager */
$styleManager = $this->getMockBuilder('\Box\Spout\Reader\XLSX\Manager\StyleManager')
->setConstructorArgs(['/path/to/file.xlsx', $workbookRelationshipsManager, $entityFactory])
->setMethods(['getCustomNumberFormats', 'getStylesAttributes'])
->onlyMethods(['getCustomNumberFormats', 'getStylesAttributes'])
->getMock();
$styleManager->method('getStylesAttributes')->willReturn($styleAttributes);
+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
Loaded 100 of 120 files, more files were not shown because too many files have changed in this diff. Show more