Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8459666841 | ||
|
|
ec8d53b13c | ||
|
|
0739e044da | ||
|
|
cc42c1d29f | ||
|
|
5926207012 | ||
|
|
550a6831f3 | ||
|
|
6f1b67b39d | ||
|
|
ea0a67d283 | ||
|
|
e95e0eeefd | ||
|
|
64a09a748d | ||
|
|
7517e5c4de | ||
|
|
e75f6f7301 | ||
|
|
27c6845b4b | ||
|
|
2499dc46b7 | ||
|
|
6b7366bb6f | ||
|
|
6a10ec3586 | ||
|
|
9882bf0946 | ||
|
|
75c06807af | ||
|
|
0345b369c7 | ||
|
|
f8595e9d63 | ||
|
|
9533accd73 | ||
|
|
9bdb027d31 | ||
|
|
76017f0949 | ||
|
|
fde8a495ca | ||
|
|
69eeeff478 | ||
|
|
2ff515c306 | ||
|
|
0837d49c2b | ||
|
|
110876e32c | ||
|
|
8c1f0cc447 | ||
|
|
eb84ec9364 | ||
|
|
8a17d6c71f | ||
|
|
c6f596c776 | ||
|
|
11d91e1740 | ||
|
|
197fb9987a | ||
|
|
a58b340835 | ||
|
|
57b6e87a65 | ||
|
|
91f756be0b | ||
|
|
03e1ce438a | ||
|
|
df9d96366f | ||
|
|
0f20c99a7f | ||
|
|
9ab0b10a0f | ||
|
|
ed9322e309 | ||
|
|
73347517f0 | ||
|
|
ad913f0100 | ||
|
|
c29d1877b8 | ||
|
|
816596183f | ||
|
|
ab973cab34 | ||
|
|
b8eb2bb814 | ||
|
|
f54f7a400c | ||
|
|
7964dadc21 | ||
|
|
eb88bb4c3a | ||
|
|
94b654175c | ||
|
|
dbdf5f7f38 | ||
|
|
9f4c094fa0 | ||
|
|
0a0b1f7196 | ||
|
|
db32a7c7db | ||
|
|
859b8d336e | ||
|
|
8a2dcc946b | ||
|
|
74146c6224 | ||
|
|
6a6d1df9df | ||
|
|
52312b7045 | ||
|
|
d4e12b1812 | ||
|
|
e8c6d83104 | ||
|
|
40aecd7b90 | ||
|
|
16a2f91a22 | ||
|
|
2d297e954b | ||
|
|
2716d7eeed | ||
|
|
1bbfd45b82 |
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
github: adrilo
|
||||||
@@ -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
@@ -4,5 +4,5 @@
|
|||||||
/tests/resources/generated
|
/tests/resources/generated
|
||||||
/tests/coverage
|
/tests/coverage
|
||||||
/vendor
|
/vendor
|
||||||
/composer.lock
|
/.php-cs-fixer.cache
|
||||||
/.php_cs.cache
|
/.phpunit.result.cache
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$config = PhpCsFixer\Config::create()
|
$finder = PhpCsFixer\Finder::create()
|
||||||
|
->in(__DIR__)
|
||||||
|
->name('*.php')
|
||||||
|
->exclude('vendor');
|
||||||
|
|
||||||
|
$config = new PhpCsFixer\Config();
|
||||||
|
return $config
|
||||||
->setRiskyAllowed(true)
|
->setRiskyAllowed(true)
|
||||||
->setRules([
|
->setRules([
|
||||||
'@Symfony' => true,
|
'@Symfony' => true,
|
||||||
@@ -13,14 +19,15 @@ $config = PhpCsFixer\Config::create()
|
|||||||
'declare_equal_normalize' => ['space' => 'single'],
|
'declare_equal_normalize' => ['space' => 'single'],
|
||||||
'heredoc_to_nowdoc' => true,
|
'heredoc_to_nowdoc' => true,
|
||||||
'increment_style' => ['style' => 'post'],
|
'increment_style' => ['style' => 'post'],
|
||||||
'is_null' => ['use_yoda_style' => false],
|
'is_null' => true,
|
||||||
'method_argument_space' => ['ensure_fully_multiline' => true],
|
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
|
||||||
'modernize_types_casting' => true,
|
'modernize_types_casting' => true,
|
||||||
'no_break_comment' => ['comment_text' => 'do nothing'],
|
'no_break_comment' => ['comment_text' => 'do nothing'],
|
||||||
'no_empty_phpdoc' => false,
|
'no_empty_phpdoc' => false,
|
||||||
'no_null_property_initialization' => true,
|
'no_null_property_initialization' => true,
|
||||||
'no_short_echo_tag' => true,
|
'echo_tag_syntax' => false,
|
||||||
'no_superfluous_elseif' => true,
|
'no_superfluous_elseif' => true,
|
||||||
|
'no_superfluous_phpdoc_tags' => false,
|
||||||
'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'switch_case', 'yield']],
|
'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'switch_case', 'yield']],
|
||||||
'no_unneeded_curly_braces' => true,
|
'no_unneeded_curly_braces' => true,
|
||||||
'no_unneeded_final_method' => true,
|
'no_unneeded_final_method' => true,
|
||||||
@@ -36,23 +43,12 @@ $config = PhpCsFixer\Config::create()
|
|||||||
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
|
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
|
||||||
'phpdoc_separation' => false,
|
'phpdoc_separation' => false,
|
||||||
'protected_to_private' => true,
|
'protected_to_private' => true,
|
||||||
'psr4' => true,
|
'psr_autoloading' => true,
|
||||||
'return_type_declaration' => ['space_before' => 'one'],
|
'return_type_declaration' => ['space_before' => 'one'],
|
||||||
'semicolon_after_instruction' => true,
|
'semicolon_after_instruction' => true,
|
||||||
'simplified_null_return' => false,
|
'simplified_null_return' => false,
|
||||||
'single_line_comment_style' => ['comment_types' => ['hash']],
|
'single_line_comment_style' => ['comment_types' => ['hash']],
|
||||||
'strict_comparison' => true,
|
'strict_comparison' => true,
|
||||||
'yoda_style' => ['equal' => false, 'identical' => false],
|
'yoda_style' => ['equal' => false, 'identical' => false],
|
||||||
]);
|
])
|
||||||
|
->setFinder($finder);
|
||||||
$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;
|
|
||||||
@@ -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
@@ -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
|
|
||||||
+16
-2
@@ -6,7 +6,7 @@ All contributions are welcome to this project.
|
|||||||
|
|
||||||
Before a contribution can be merged into this project, please fill out the Contributor License Agreement (CLA) located at:
|
Before a contribution can be merged into this project, please fill out the Contributor License Agreement (CLA) located at:
|
||||||
|
|
||||||
http://opensource.box.com/cla
|
https://opensource.box.com/cla
|
||||||
|
|
||||||
To learn more about CLAs and why they are important to open source projects, please see the [Wikipedia entry](http://en.wikipedia.org/wiki/Contributor_License_Agreement).
|
To learn more about CLAs and why they are important to open source projects, please see the [Wikipedia entry](http://en.wikipedia.org/wiki/Contributor_License_Agreement).
|
||||||
|
|
||||||
@@ -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.
|
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.
|
Send the pull request from your feature branch to us. Be sure to include a description that lets us know what work you did.
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,39 @@
|
|||||||
# Spout
|
# Spout
|
||||||
|
|
||||||
[](https://packagist.org/packages/box/spout)
|
[](https://packagist.org/packages/box/spout)
|
||||||
[](http://opensource.box.com/badges)
|
[](https://opensource.box.com/badges)
|
||||||
[](https://travis-ci.org/box/spout)
|
[](https://github.com/box/spout/actions/workflows/ci.yml?query=branch%3Amaster)
|
||||||
[](https://scrutinizer-ci.com/g/box/spout/?branch=master)
|
[](https://coveralls.io/github/box/spout?branch=master)
|
||||||
[](https://scrutinizer-ci.com/g/box/spout/?branch=master)
|
|
||||||
[](https://packagist.org/packages/box/spout)
|
[](https://packagist.org/packages/box/spout)
|
||||||
|
|
||||||
Spout is a PHP library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way.
|
## 🪦 Archived project 🪦
|
||||||
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).
|
|
||||||
|
|
||||||
Join the community and come discuss about Spout: [](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: [](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Full documentation can be found at [http://opensource.box.com/spout/](http://opensource.box.com/spout/).
|
Full documentation can be found at [https://opensource.box.com/spout/](https://opensource.box.com/spout/).
|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* PHP version 7.1 or higher
|
* PHP version 7.2 or higher
|
||||||
* PHP extension `php_zip` enabled
|
* PHP extension `php_zip` enabled
|
||||||
* PHP extension `php_xmlreader` enabled
|
* PHP extension `php_xmlreader` enabled
|
||||||
|
|
||||||
@@ -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).
|
For information, the performance tests take about 10 minutes to run (processing 1 million rows files is not a quick thing).
|
||||||
|
|
||||||
> Performance tests status: [](https://travis-ci.org/box/spout)
|
|
||||||
|
|
||||||
|
|
||||||
## Support
|
## 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>
|
||||||
[](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
[](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||||
|
|
||||||
## Copyright and License
|
## Copyright and License
|
||||||
|
|
||||||
Copyright 2017 Box, Inc. All rights reserved.
|
Copyright 2022 Box, Inc. All rights reserved.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ With the 3.0 version, this is now possible: each cell can have its own style.
|
|||||||
|
|
||||||
Spout 3.0 tries to enforce better typing. For instance, instead of using/returning generic arrays, Spout now makes use of specific `Row` and `Cell` objects that can encapsulate more data such as type, style, value.
|
Spout 3.0 tries to enforce better typing. For instance, instead of using/returning generic arrays, Spout now makes use of specific `Row` and `Cell` objects that can encapsulate more data such as type, style, value.
|
||||||
|
|
||||||
Finally, **_Spout 3.0 only supports PHP 7.1 and above_**, as other PHP versions are no longer supported by the community.
|
Finally, **_Spout 3.2 only supports PHP 7.2 and above_**, as other PHP versions are no longer supported by the community.
|
||||||
|
|
||||||
Reader changes
|
Reader changes
|
||||||
--------------
|
--------------
|
||||||
|
|||||||
@@ -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
@@ -12,13 +12,15 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=7.1.0",
|
"php": ">=7.3.0",
|
||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"ext-xmlreader" : "*"
|
"ext-xmlreader": "*",
|
||||||
|
"ext-dom": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^7",
|
"phpunit/phpunit": "^9",
|
||||||
"friendsofphp/php-cs-fixer": "^2"
|
"friendsofphp/php-cs-fixer": "^3",
|
||||||
|
"phpstan/phpstan": "^1"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-iconv": "To handle non UTF-8 CSV files (if \"php-intl\" is not already installed or is too limited)",
|
"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": {
|
"config": {
|
||||||
"platform": {
|
"platform": {
|
||||||
"php": "7.1"
|
"php": "7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+4196
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -10,7 +10,7 @@ title: Spout
|
|||||||
email: oss@box.com
|
email: oss@box.com
|
||||||
description: "An open source PHP library to read and write spreadsheet files (XLSX, ODS and CSV), in a fast and scalable way."
|
description: "An open source PHP library to read and write spreadsheet files (XLSX, ODS and CSV), in a fast and scalable way."
|
||||||
baseurl: "" # the subpath of your site, e.g. /blog
|
baseurl: "" # the subpath of your site, e.g. /blog
|
||||||
url: "http://opensource.box.com" # the base hostname & protocol for your site
|
url: "https://opensource.box.com" # the base hostname & protocol for your site
|
||||||
|
|
||||||
# Build settings
|
# Build settings
|
||||||
markdown: kramdown
|
markdown: kramdown
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# Github Metadata plugin
|
||||||
|
repository: box/spout
|
||||||
|
plugins:
|
||||||
|
- "jekyll-github-metadata"
|
||||||
|
|
||||||
|
github:
|
||||||
|
url: http://localhost:8080
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<div class="footer-col footer-col-2">
|
<div class="footer-col footer-col-2">
|
||||||
<ul class="social-media-list">
|
<ul class="social-media-list">
|
||||||
<li><a href="https://github.com/box/spout/issues"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">GitHub Issues</span></a></li>
|
<li><a href="{{ site.github.issues_url }}"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">GitHub Issues</span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
|
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
|
||||||
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
|
<meta name="description" content="{% if page.excerpt %}{{ page.excerpt | strip_html | strip_newlines | truncate: 160 }}{% else %}{{ site.description }}{% endif %}">
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.github.url }}">
|
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site_url }}">
|
||||||
{% if site.algolia.enabled %}
|
{% if site.algolia.enabled %}
|
||||||
<link rel="stylesheet" href="//cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" />
|
<link rel="stylesheet" href="//cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<a class="site-title" href="{{ site.github.url }}/">{{ site.title }}</a>
|
<a class="site-title" href="{{ site_url }}/">{{ site.title }}</a>
|
||||||
|
|
||||||
<nav class="site-nav">
|
<nav class="site-nav">
|
||||||
<a href="#" class="menu-icon">
|
<a href="#" class="menu-icon">
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<div class="trigger">
|
<div class="trigger">
|
||||||
<a class="page-link" href="{{ site.github.url }}/getting-started/">Getting Started</a>
|
<a class="page-link" href="{{ site_url }}/getting-started/">Getting Started</a>
|
||||||
<a class="page-link" href="{{ site.github.url }}/docs/">Documentation</a>
|
<a class="page-link" href="{{ site_url }}/docs/">Documentation</a>
|
||||||
<a class="page-link" href="{{ site.github.url }}/guides/">Guides</a>
|
<a class="page-link" href="{{ site_url }}/guides/">Guides</a>
|
||||||
<a class="page-link" href="{{ site.github.url }}/faq/">FAQ</a>
|
<a class="page-link" href="{{ site_url }}/faq/">FAQ</a>
|
||||||
<a class="page-link" href="https://github.com/box/spout">
|
<a class="page-link" href="{{ site.github.repository_url }}">
|
||||||
<span class="icon icon--github">{% include icon-github.svg %}</span>
|
<span class="icon icon--github">{% include icon-github.svg %}</span>
|
||||||
<span class="username">GitHub</span>
|
<span class="username">GitHub</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -8,4 +8,4 @@
|
|||||||
Writing an ODS file with millions of rows? {{ site.spout_html }} can do it in no time!
|
Writing an ODS file with millions of rows? {{ site.spout_html }} can do it in no time!
|
||||||
{% endcapture %}
|
{% endcapture %}
|
||||||
|
|
||||||
{% include section.html %}
|
{% include section.html site_url = include.site_url %}
|
||||||
@@ -11,4 +11,4 @@
|
|||||||
Switching from one type to another is ridiculously easy!
|
Switching from one type to another is ridiculously easy!
|
||||||
{% endcapture %}
|
{% endcapture %}
|
||||||
|
|
||||||
{% include section.html %}
|
{% include section.html site_url=include.site_url %}
|
||||||
@@ -13,8 +13,9 @@
|
|||||||
<li class="feature-check">{{ site.spout_html }}'s API is {{emS}}developer-friendly{{emE}}.</li>
|
<li class="feature-check">{{ site.spout_html }}'s API is {{emS}}developer-friendly{{emE}}.</li>
|
||||||
</ul><br>
|
</ul><br>
|
||||||
<div class="btn-wrapper">
|
<div class="btn-wrapper">
|
||||||
<a href="{{ site.github.url }}/getting-started/" class="page-link"><button class="btn">Get started</button></a>
|
<a href="{{ include.site_url }}/getting-started/" class="page-link"><button class="btn">Get started</button></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endcapture %}
|
{% endcapture %}
|
||||||
|
|
||||||
{% include section.html %}
|
{% include section.html site_url=include.site_url %}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<div class="section {{ sectionClass }} vertical-align-middle">
|
<div class="section {{ sectionClass }} vertical-align-middle">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="description mbl">
|
<div class="description mbl">
|
||||||
@@ -9,7 +8,7 @@
|
|||||||
{% assign sectionIconsAsArray = sectionIcons | split:'~~' %}
|
{% assign sectionIconsAsArray = sectionIcons | split:'~~' %}
|
||||||
<div class="icons">
|
<div class="icons">
|
||||||
{% for icon in sectionIconsAsArray %}
|
{% for icon in sectionIconsAsArray %}
|
||||||
<img src="{{ site.github.url }}/images/{{ icon }}"/>
|
<img src="{{ include.site_url }}/images/{{ icon }}"/>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{% comment %}
|
||||||
|
This is a hack to force using HTTPS...
|
||||||
|
The URL of the docs should be changed in the repo settings instead
|
||||||
|
{% endcomment %}
|
||||||
|
{% assign protocol_scheme = page.url | absolute_url | truncate: 5, "" %}
|
||||||
|
{%- capture site_url -%}
|
||||||
|
{%- if protocol_scheme == "https" -%}
|
||||||
|
{{ site.github.url | replace: "http://", "https://" }}
|
||||||
|
{%- else -%}
|
||||||
|
{{ site.github.url }}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endcapture -%}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
|
{% include set-global-site-url.html %}
|
||||||
|
|
||||||
{% include head.html %}
|
{% include head.html %}
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -13,14 +15,6 @@
|
|||||||
|
|
||||||
{{ content }}
|
{{ content }}
|
||||||
|
|
||||||
<!--
|
|
||||||
<div class="page-content">
|
|
||||||
<div class="wrapper">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
|
|
||||||
{% include footer.html %}
|
{% include footer.html %}
|
||||||
{% include algolia.html %}
|
{% include algolia.html %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
layout: default
|
layout: default
|
||||||
---
|
---
|
||||||
|
|
||||||
<article class="post">
|
<article class="post">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<header class="post-header">
|
<header class="post-header">
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
---
|
|
||||||
layout: default
|
|
||||||
---
|
|
||||||
<article class="post" itemscope itemtype="http://schema.org/BlogPosting">
|
|
||||||
|
|
||||||
<header class="post-header">
|
|
||||||
<h1 class="post-title" itemprop="name headline">{{ page.title }}</h1>
|
|
||||||
<p class="post-meta"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time>{% if page.author %} • <span itemprop="author" itemscope itemtype="http://schema.org/Person"><span itemprop="name">{{ page.author }}</span></span>{% endif %}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="post-content" itemprop="articleBody">
|
|
||||||
{{ content }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
@@ -117,7 +117,7 @@ $reader->setShouldPreserveEmptyRows(true);
|
|||||||
For fonts and alignments, {{ site.spout_html }} does not support all the possible formatting options yet. But you can find the most important ones:
|
For fonts and alignments, {{ site.spout_html }} does not support all the possible formatting options yet. But you can find the most important ones:
|
||||||
|
|
||||||
| Category | Property | API
|
| Category | Property | API
|
||||||
|:----------|:--------------|:--------------------------------------
|
|:---------------------|:---------------|:--------------------------------------
|
||||||
| Font | Bold | `StyleBuilder::setFontBold()`
|
| Font | Bold | `StyleBuilder::setFontBold()`
|
||||||
| | Italic | `StyleBuilder::setFontItalic()`
|
| | Italic | `StyleBuilder::setFontItalic()`
|
||||||
| | Underline | `StyleBuilder::setFontUnderline()`
|
| | Underline | `StyleBuilder::setFontUnderline()`
|
||||||
@@ -125,8 +125,10 @@ For fonts and alignments, {{ site.spout_html }} does not support all the possibl
|
|||||||
| | Font name | `StyleBuilder::setFontName('Arial')`
|
| | Font name | `StyleBuilder::setFontName('Arial')`
|
||||||
| | Font size | `StyleBuilder::setFontSize(14)`
|
| | Font size | `StyleBuilder::setFontSize(14)`
|
||||||
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
|
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
|
||||||
| Alignment | Wrap text | `StyleBuilder::setShouldWrapText(true|false)`
|
| 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
|
### Styling rows
|
||||||
|
|
||||||
@@ -135,6 +137,7 @@ It is possible to apply some formatting options to a row. In this case, all cell
|
|||||||
```php
|
```php
|
||||||
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
|
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
|
||||||
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
|
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
|
||||||
|
use Box\Spout\Common\Entity\Style\CellAlignment;
|
||||||
use Box\Spout\Common\Entity\Style\Color;
|
use Box\Spout\Common\Entity\Style\Color;
|
||||||
|
|
||||||
$writer = WriterEntityFactory::createXLSXWriter();
|
$writer = WriterEntityFactory::createXLSXWriter();
|
||||||
@@ -146,6 +149,7 @@ $style = (new StyleBuilder())
|
|||||||
->setFontSize(15)
|
->setFontSize(15)
|
||||||
->setFontColor(Color::BLUE)
|
->setFontColor(Color::BLUE)
|
||||||
->setShouldWrapText()
|
->setShouldWrapText()
|
||||||
|
->setCellAlignment(CellAlignment::RIGHT)
|
||||||
->setBackgroundColor(Color::YELLOW)
|
->setBackgroundColor(Color::YELLOW)
|
||||||
->build();
|
->build();
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ title: Getting Started
|
|||||||
permalink: /getting-started/
|
permalink: /getting-started/
|
||||||
---
|
---
|
||||||
|
|
||||||
|
{% include set-global-site-url.html %}
|
||||||
|
|
||||||
This guide will help you install {{ site.spout_html }} and teach you how to use it.
|
This guide will help you install {{ site.spout_html }} and teach you how to use it.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
* PHP version 7.1 or higher
|
* PHP version 7.2 or higher
|
||||||
* PHP extension `ext-zip` enabled
|
* PHP extension `ext-zip` enabled
|
||||||
* PHP extension `ext-xmlreader` enabled
|
* PHP extension `ext-xmlreader` enabled
|
||||||
|
|
||||||
@@ -86,7 +88,9 @@ As with the reader, there is one common interface to write data to a file:
|
|||||||
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
|
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
|
||||||
use Box\Spout\Common\Entity\Row;
|
use Box\Spout\Common\Entity\Row;
|
||||||
|
|
||||||
$reader = ReaderEntityFactory::createReaderFromFile('/path/to/file.ext');
|
$writer = WriterEntityFactory::createXLSXWriter();
|
||||||
|
// $writer = WriterEntityFactory::createODSWriter();
|
||||||
|
// $writer = WriterEntityFactory::createCSVWriter();
|
||||||
|
|
||||||
$writer->openToFile($filePath); // write data to a file or to a PHP stream
|
$writer->openToFile($filePath); // write data to a file or to a PHP stream
|
||||||
//$writer->openToBrowser($fileName); // stream data directly to the browser
|
//$writer->openToBrowser($fileName); // stream data directly to the browser
|
||||||
@@ -132,4 +136,4 @@ For XLSX and ODS files, the number of rows per sheet is limited to *1,048,576*.
|
|||||||
|
|
||||||
## Advanced usage
|
## Advanced usage
|
||||||
|
|
||||||
You can do a lot more with {{ site.spout_html }}! Check out the [full documentation]({{ site.github.url }}/docs/) to learn about all the features.
|
You can do a lot more with {{ site.spout_html }}! Check out the [full documentation]({{ site_url }}/docs/) to learn about all the features.
|
||||||
@@ -4,15 +4,17 @@ title: Guides
|
|||||||
permalink: /guides/
|
permalink: /guides/
|
||||||
---
|
---
|
||||||
|
|
||||||
|
{% include set-global-site-url.html %}
|
||||||
|
|
||||||
These guides focus on common and more advanced usages of {{ site.spout_html }}.<br>
|
These guides focus on common and more advanced usages of {{ site.spout_html }}.<br>
|
||||||
If you are just starting with {{ site.spout_html }}, check out the [Getting Started page]({{ site.github.url }}/getting-started/) and the [Documentation]({{ site.github.url }}/docs/) first.
|
If you are just starting with {{ site.spout_html }}, check out the [Getting Started page]({{ site_url }}/getting-started/) and the [Documentation]({{ site_url }}/docs/) first.
|
||||||
|
|
||||||
{% assign pages=site.pages | sort: 'path' %}
|
{% assign pages=site.pages | sort: 'path' %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for page in pages %}
|
{% for page in pages %}
|
||||||
{% if page.title and page.category contains 'guide' %}
|
{% if page.title and page.category contains 'guide' %}
|
||||||
<li>
|
<li>
|
||||||
<a class="page-link" href="{{ page.url | prepend: site.github.url }}">{{ page.title }}</a>
|
<a class="page-link" href="{{ page.url | prepend: site_url }}">{{ page.title }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class MyStreamController extends Controller
|
|||||||
|
|
||||||
$i++;
|
$i++;
|
||||||
// Flushing the buffer every N rows to stream echo'ed content.
|
// Flushing the buffer every N rows to stream echo'ed content.
|
||||||
if ($i % FLUSH_THRESHOLD === 0) {
|
if ($i % self::FLUSH_THRESHOLD === 0) {
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ title: Spout - Read and write spreadsheets, quickly and at scale
|
|||||||
permalink: /
|
permalink: /
|
||||||
---
|
---
|
||||||
|
|
||||||
{% include section-supported-spreadsheet-types.html %}
|
{% include set-global-site-url.html %}
|
||||||
{% include section-fast-and-scalable.html %}
|
|
||||||
{% include section-why-use-spout.html %}
|
{% include section-supported-spreadsheet-types.html site_url=site_url %}
|
||||||
|
{% include section-fast-and-scalable.html site_url=site_url %}
|
||||||
|
{% include section-why-use-spout.html site_url=site_url %}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
services:
|
services:
|
||||||
site:
|
site:
|
||||||
command: jekyll serve
|
command: bash -c "
|
||||||
|
gem install 'jekyll-github-metadata'
|
||||||
|
&& jekyll serve --config _config.yml,_config_local.yml"
|
||||||
image: jekyll/jekyll:latest
|
image: jekyll/jekyll:latest
|
||||||
volumes:
|
volumes:
|
||||||
- $PWD:/srv/jekyll
|
- $PWD:/srv/jekyll
|
||||||
|
|||||||
@@ -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
|
||||||
+9
-7
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.5/phpunit.xsd"
|
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
||||||
bootstrap="tests/bootstrap.php"
|
bootstrap="tests/bootstrap.php"
|
||||||
colors="true"
|
colors="true"
|
||||||
convertErrorsToExceptions="false"
|
convertErrorsToExceptions="false"
|
||||||
@@ -8,12 +8,14 @@
|
|||||||
defaultTestSuite="unit-tests"
|
defaultTestSuite="unit-tests"
|
||||||
verbose="false">
|
verbose="false">
|
||||||
|
|
||||||
<testsuites>
|
<php>
|
||||||
|
<ini name="error_reporting" value="-1"/>
|
||||||
|
</php>
|
||||||
|
|
||||||
|
<testsuites>
|
||||||
<testsuite name="unit-tests">
|
<testsuite name="unit-tests">
|
||||||
<directory>tests/</directory>
|
<directory>tests/</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
|
|
||||||
</testsuites>
|
</testsuites>
|
||||||
|
|
||||||
<groups>
|
<groups>
|
||||||
@@ -22,13 +24,13 @@
|
|||||||
</exclude>
|
</exclude>
|
||||||
</groups>
|
</groups>
|
||||||
|
|
||||||
<filter>
|
<coverage>
|
||||||
<whitelist>
|
<include>
|
||||||
<directory suffix=".php">src/</directory>
|
<directory suffix=".php">src/</directory>
|
||||||
|
</include>
|
||||||
<exclude>
|
<exclude>
|
||||||
<directory>src/Spout/Autoloader</directory>
|
<directory>src/Spout/Autoloader</directory>
|
||||||
</exclude>
|
</exclude>
|
||||||
</whitelist>
|
</coverage>
|
||||||
</filter>
|
|
||||||
|
|
||||||
</phpunit>
|
</phpunit>
|
||||||
@@ -23,7 +23,7 @@ class Psr4Autoloader
|
|||||||
*/
|
*/
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
spl_autoload_register([$this, 'loadClass']);
|
\spl_autoload_register([$this, 'loadClass']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,10 +40,10 @@ class Psr4Autoloader
|
|||||||
public function addNamespace($prefix, $baseDir, $prepend = false)
|
public function addNamespace($prefix, $baseDir, $prepend = false)
|
||||||
{
|
{
|
||||||
// normalize namespace prefix
|
// normalize namespace prefix
|
||||||
$prefix = trim($prefix, '\\') . '\\';
|
$prefix = \trim($prefix, '\\') . '\\';
|
||||||
|
|
||||||
// normalize the base directory with a trailing separator
|
// normalize the base directory with a trailing separator
|
||||||
$baseDir = rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
|
$baseDir = \rtrim($baseDir, DIRECTORY_SEPARATOR) . '/';
|
||||||
|
|
||||||
// initialize the namespace prefix array
|
// initialize the namespace prefix array
|
||||||
if (isset($this->prefixes[$prefix]) === false) {
|
if (isset($this->prefixes[$prefix]) === false) {
|
||||||
@@ -52,9 +52,9 @@ class Psr4Autoloader
|
|||||||
|
|
||||||
// retain the base directory for the namespace prefix
|
// retain the base directory for the namespace prefix
|
||||||
if ($prepend) {
|
if ($prepend) {
|
||||||
array_unshift($this->prefixes[$prefix], $baseDir);
|
\array_unshift($this->prefixes[$prefix], $baseDir);
|
||||||
} else {
|
} else {
|
||||||
array_push($this->prefixes[$prefix], $baseDir);
|
\array_push($this->prefixes[$prefix], $baseDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +72,12 @@ class Psr4Autoloader
|
|||||||
|
|
||||||
// work backwards through the namespace names of the fully-qualified
|
// work backwards through the namespace names of the fully-qualified
|
||||||
// class name to find a mapped file name
|
// class name to find a mapped file name
|
||||||
while (($pos = strrpos($prefix, '\\')) !== false) {
|
while (($pos = \strrpos($prefix, '\\')) !== false) {
|
||||||
// retain the trailing namespace separator in the prefix
|
// retain the trailing namespace separator in the prefix
|
||||||
$prefix = substr($class, 0, $pos + 1);
|
$prefix = \substr($class, 0, $pos + 1);
|
||||||
|
|
||||||
// the rest is the relative class name
|
// the rest is the relative class name
|
||||||
$relativeClass = substr($class, $pos + 1);
|
$relativeClass = \substr($class, $pos + 1);
|
||||||
|
|
||||||
// try to load a mapped file for the prefix and relative class
|
// try to load a mapped file for the prefix and relative class
|
||||||
$mappedFile = $this->loadMappedFile($prefix, $relativeClass);
|
$mappedFile = $this->loadMappedFile($prefix, $relativeClass);
|
||||||
@@ -87,7 +87,7 @@ class Psr4Autoloader
|
|||||||
|
|
||||||
// remove the trailing namespace separator for the next iteration
|
// remove the trailing namespace separator for the next iteration
|
||||||
// of strrpos()
|
// of strrpos()
|
||||||
$prefix = rtrim($prefix, '\\');
|
$prefix = \rtrim($prefix, '\\');
|
||||||
}
|
}
|
||||||
|
|
||||||
// never found a mapped file
|
// never found a mapped file
|
||||||
@@ -115,7 +115,7 @@ class Psr4Autoloader
|
|||||||
// replace namespace separators with directory separators
|
// replace namespace separators with directory separators
|
||||||
// in the relative class name, append with .php
|
// in the relative class name, append with .php
|
||||||
$file = $baseDir
|
$file = $baseDir
|
||||||
. str_replace('\\', '/', $relativeClass)
|
. \str_replace('\\', '/', $relativeClass)
|
||||||
. '.php';
|
. '.php';
|
||||||
|
|
||||||
// if the mapped file exists, require it
|
// if the mapped file exists, require it
|
||||||
@@ -137,7 +137,7 @@ class Psr4Autoloader
|
|||||||
*/
|
*/
|
||||||
protected function requireFile($file)
|
protected function requireFile($file)
|
||||||
{
|
{
|
||||||
if (file_exists($file)) {
|
if (\file_exists($file)) {
|
||||||
require $file;
|
require $file;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ require_once 'Psr4Autoloader.php';
|
|||||||
* @var string
|
* @var string
|
||||||
* Full path to "src/Spout" which is what we want "Box\Spout" to map to.
|
* Full path to "src/Spout" which is what we want "Box\Spout" to map to.
|
||||||
*/
|
*/
|
||||||
$srcBaseDirectory = dirname(dirname(__FILE__));
|
$srcBaseDirectory = \dirname(\dirname(__FILE__));
|
||||||
|
|
||||||
$loader = new Psr4Autoloader();
|
$loader = new Psr4Autoloader();
|
||||||
$loader->register();
|
$loader->register();
|
||||||
|
|||||||
@@ -13,38 +13,38 @@ class Cell
|
|||||||
/**
|
/**
|
||||||
* Numeric cell type (whole numbers, fractional numbers, dates)
|
* Numeric cell type (whole numbers, fractional numbers, dates)
|
||||||
*/
|
*/
|
||||||
const TYPE_NUMERIC = 0;
|
public const TYPE_NUMERIC = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* String (text) cell type
|
* String (text) cell type
|
||||||
*/
|
*/
|
||||||
const TYPE_STRING = 1;
|
public const TYPE_STRING = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formula cell type
|
* Formula cell type
|
||||||
* Not used at the moment
|
* Not used at the moment
|
||||||
*/
|
*/
|
||||||
const TYPE_FORMULA = 2;
|
public const TYPE_FORMULA = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty cell type
|
* Empty cell type
|
||||||
*/
|
*/
|
||||||
const TYPE_EMPTY = 3;
|
public const TYPE_EMPTY = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean cell type
|
* Boolean cell type
|
||||||
*/
|
*/
|
||||||
const TYPE_BOOLEAN = 4;
|
public const TYPE_BOOLEAN = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date cell type
|
* Date cell type
|
||||||
*/
|
*/
|
||||||
const TYPE_DATE = 5;
|
public const TYPE_DATE = 5;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error cell type
|
* Error cell type
|
||||||
*/
|
*/
|
||||||
const TYPE_ERROR = 6;
|
public const TYPE_ERROR = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value of this cell
|
* The value of this cell
|
||||||
@@ -65,7 +65,7 @@ class Cell
|
|||||||
protected $style;
|
protected $style;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $value mixed
|
* @param mixed|null $value
|
||||||
* @param Style|null $style
|
* @param Style|null $style
|
||||||
*/
|
*/
|
||||||
public function __construct($value, Style $style = null)
|
public function __construct($value, Style $style = null)
|
||||||
@@ -91,6 +91,14 @@ class Cell
|
|||||||
return !$this->isError() ? $this->value : null;
|
return !$this->isError() ? $this->value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getValueEvenIfError()
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Style|null $style
|
* @param Style|null $style
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class Row
|
|||||||
*/
|
*/
|
||||||
public function getCellAtIndex($cellIndex)
|
public function getCellAtIndex($cellIndex)
|
||||||
{
|
{
|
||||||
return isset($this->cells[$cellIndex]) ? $this->cells[$cellIndex] : null;
|
return $this->cells[$cellIndex] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +89,13 @@ class Row
|
|||||||
*/
|
*/
|
||||||
public function getNumCells()
|
public function getNumCells()
|
||||||
{
|
{
|
||||||
return count($this->cells);
|
// When using "setCellAtIndex", it's possible to
|
||||||
|
// have "$this->cells" contain holes.
|
||||||
|
if (empty($this->cells)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return \max(\array_keys($this->cells)) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -116,7 +122,7 @@ class Row
|
|||||||
*/
|
*/
|
||||||
public function toArray()
|
public function toArray()
|
||||||
{
|
{
|
||||||
return array_map(function (Cell $cell) {
|
return \array_map(function (Cell $cell) {
|
||||||
return $cell->getValue();
|
return $cell->getValue();
|
||||||
}, $this->cells);
|
}, $this->cells);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,20 @@ namespace Box\Spout\Common\Entity\Style;
|
|||||||
*/
|
*/
|
||||||
class Border
|
class Border
|
||||||
{
|
{
|
||||||
const LEFT = 'left';
|
public const LEFT = 'left';
|
||||||
const RIGHT = 'right';
|
public const RIGHT = 'right';
|
||||||
const TOP = 'top';
|
public const TOP = 'top';
|
||||||
const BOTTOM = 'bottom';
|
public const BOTTOM = 'bottom';
|
||||||
|
|
||||||
const STYLE_NONE = 'none';
|
public const STYLE_NONE = 'none';
|
||||||
const STYLE_SOLID = 'solid';
|
public const STYLE_SOLID = 'solid';
|
||||||
const STYLE_DASHED = 'dashed';
|
public const STYLE_DASHED = 'dashed';
|
||||||
const STYLE_DOTTED = 'dotted';
|
public const STYLE_DOTTED = 'dotted';
|
||||||
const STYLE_DOUBLE = 'double';
|
public const STYLE_DOUBLE = 'double';
|
||||||
|
|
||||||
const WIDTH_THIN = 'thin';
|
public const WIDTH_THIN = 'thin';
|
||||||
const WIDTH_MEDIUM = 'medium';
|
public const WIDTH_MEDIUM = 'medium';
|
||||||
const WIDTH_THICK = 'thick';
|
public const WIDTH_THICK = 'thick';
|
||||||
|
|
||||||
/** @var array A list of BorderPart objects for this border. */
|
/** @var array A list of BorderPart objects for this border. */
|
||||||
private $parts = [];
|
private $parts = [];
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class BorderPart
|
|||||||
*/
|
*/
|
||||||
public function setName($name)
|
public function setName($name)
|
||||||
{
|
{
|
||||||
if (!in_array($name, self::$allowedNames)) {
|
if (!\in_array($name, self::$allowedNames)) {
|
||||||
throw new InvalidNameException($name);
|
throw new InvalidNameException($name);
|
||||||
}
|
}
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
@@ -114,7 +114,7 @@ class BorderPart
|
|||||||
*/
|
*/
|
||||||
public function setStyle($style)
|
public function setStyle($style)
|
||||||
{
|
{
|
||||||
if (!in_array($style, self::$allowedStyles)) {
|
if (!\in_array($style, self::$allowedStyles)) {
|
||||||
throw new InvalidStyleException($style);
|
throw new InvalidStyleException($style);
|
||||||
}
|
}
|
||||||
$this->style = $style;
|
$this->style = $style;
|
||||||
@@ -152,7 +152,7 @@ class BorderPart
|
|||||||
*/
|
*/
|
||||||
public function setWidth($width)
|
public function setWidth($width)
|
||||||
{
|
{
|
||||||
if (!in_array($width, self::$allowedWidths)) {
|
if (!\in_array($width, self::$allowedWidths)) {
|
||||||
throw new InvalidWidthException($width);
|
throw new InvalidWidthException($width);
|
||||||
}
|
}
|
||||||
$this->width = $width;
|
$this->width = $width;
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Box\Spout\Common\Entity\Style;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Alignment
|
||||||
|
* This class provides constants to work with text alignment.
|
||||||
|
*/
|
||||||
|
abstract class CellAlignment
|
||||||
|
{
|
||||||
|
public const LEFT = 'left';
|
||||||
|
public const RIGHT = 'right';
|
||||||
|
public const CENTER = 'center';
|
||||||
|
public const JUSTIFY = 'justify';
|
||||||
|
|
||||||
|
private static $VALID_ALIGNMENTS = [
|
||||||
|
self::LEFT => 1,
|
||||||
|
self::RIGHT => 1,
|
||||||
|
self::CENTER => 1,
|
||||||
|
self::JUSTIFY => 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $cellAlignment
|
||||||
|
*
|
||||||
|
* @return bool Whether the given cell alignment is valid
|
||||||
|
*/
|
||||||
|
public static function isValid($cellAlignment)
|
||||||
|
{
|
||||||
|
return isset(self::$VALID_ALIGNMENTS[$cellAlignment]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,21 +8,21 @@ use Box\Spout\Common\Exception\InvalidColorException;
|
|||||||
* Class Color
|
* Class Color
|
||||||
* This class provides constants and functions to work with colors
|
* This class provides constants and functions to work with colors
|
||||||
*/
|
*/
|
||||||
class Color
|
abstract class Color
|
||||||
{
|
{
|
||||||
/** Standard colors - based on Office Online */
|
/** Standard colors - based on Office Online */
|
||||||
const BLACK = '000000';
|
public const BLACK = '000000';
|
||||||
const WHITE = 'FFFFFF';
|
public const WHITE = 'FFFFFF';
|
||||||
const RED = 'FF0000';
|
public const RED = 'FF0000';
|
||||||
const DARK_RED = 'C00000';
|
public const DARK_RED = 'C00000';
|
||||||
const ORANGE = 'FFC000';
|
public const ORANGE = 'FFC000';
|
||||||
const YELLOW = 'FFFF00';
|
public const YELLOW = 'FFFF00';
|
||||||
const LIGHT_GREEN = '92D040';
|
public const LIGHT_GREEN = '92D040';
|
||||||
const GREEN = '00B050';
|
public const GREEN = '00B050';
|
||||||
const LIGHT_BLUE = '00B0E0';
|
public const LIGHT_BLUE = '00B0E0';
|
||||||
const BLUE = '0070C0';
|
public const BLUE = '0070C0';
|
||||||
const DARK_BLUE = '002060';
|
public const DARK_BLUE = '002060';
|
||||||
const PURPLE = '7030A0';
|
public const PURPLE = '7030A0';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an RGB color from R, G and B values
|
* Returns an RGB color from R, G and B values
|
||||||
@@ -38,7 +38,7 @@ class Color
|
|||||||
self::throwIfInvalidColorComponentValue($green);
|
self::throwIfInvalidColorComponentValue($green);
|
||||||
self::throwIfInvalidColorComponentValue($blue);
|
self::throwIfInvalidColorComponentValue($blue);
|
||||||
|
|
||||||
return strtoupper(
|
return \strtoupper(
|
||||||
self::convertColorComponentToHex($red) .
|
self::convertColorComponentToHex($red) .
|
||||||
self::convertColorComponentToHex($green) .
|
self::convertColorComponentToHex($green) .
|
||||||
self::convertColorComponentToHex($blue)
|
self::convertColorComponentToHex($blue)
|
||||||
@@ -54,7 +54,7 @@ class Color
|
|||||||
*/
|
*/
|
||||||
protected static function throwIfInvalidColorComponentValue($colorComponent)
|
protected static function throwIfInvalidColorComponentValue($colorComponent)
|
||||||
{
|
{
|
||||||
if (!is_int($colorComponent) || $colorComponent < 0 || $colorComponent > 255) {
|
if (!\is_int($colorComponent) || $colorComponent < 0 || $colorComponent > 255) {
|
||||||
throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent");
|
throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ class Color
|
|||||||
*/
|
*/
|
||||||
protected static function convertColorComponentToHex($colorComponent)
|
protected static function convertColorComponentToHex($colorComponent)
|
||||||
{
|
{
|
||||||
return str_pad(dechex($colorComponent), 2, '0', STR_PAD_LEFT);
|
return \str_pad(\dechex($colorComponent), 2, '0', STR_PAD_LEFT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ namespace Box\Spout\Common\Entity\Style;
|
|||||||
*/
|
*/
|
||||||
class Style
|
class Style
|
||||||
{
|
{
|
||||||
/** Default font values */
|
/** Default values */
|
||||||
const DEFAULT_FONT_SIZE = 11;
|
public const DEFAULT_FONT_SIZE = 11;
|
||||||
const DEFAULT_FONT_COLOR = Color::BLACK;
|
public const DEFAULT_FONT_COLOR = Color::BLACK;
|
||||||
const DEFAULT_FONT_NAME = 'Arial';
|
public const DEFAULT_FONT_NAME = 'Arial';
|
||||||
|
|
||||||
/** @var int|null Style ID */
|
/** @var int|null Style ID */
|
||||||
private $id;
|
private $id;
|
||||||
@@ -54,12 +54,19 @@ class Style
|
|||||||
/** @var bool Whether specific font properties should be applied */
|
/** @var bool Whether specific font properties should be applied */
|
||||||
private $shouldApplyFont = false;
|
private $shouldApplyFont = false;
|
||||||
|
|
||||||
|
/** @var bool Whether specific cell alignment should be applied */
|
||||||
|
private $shouldApplyCellAlignment = false;
|
||||||
|
/** @var string Cell alignment */
|
||||||
|
private $cellAlignment;
|
||||||
|
/** @var bool Whether the cell alignment property was set */
|
||||||
|
private $hasSetCellAlignment = false;
|
||||||
|
|
||||||
/** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */
|
/** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */
|
||||||
private $shouldWrapText = false;
|
private $shouldWrapText = false;
|
||||||
/** @var bool Whether the wrap text property was set */
|
/** @var bool Whether the wrap text property was set */
|
||||||
private $hasSetWrapText = false;
|
private $hasSetWrapText = false;
|
||||||
|
|
||||||
/** @var Border */
|
/** @var Border|null */
|
||||||
private $border;
|
private $border;
|
||||||
|
|
||||||
/** @var bool Whether border properties should be applied */
|
/** @var bool Whether border properties should be applied */
|
||||||
@@ -71,6 +78,18 @@ class Style
|
|||||||
/** @var bool */
|
/** @var bool */
|
||||||
private $hasSetBackgroundColor = false;
|
private $hasSetBackgroundColor = false;
|
||||||
|
|
||||||
|
/** @var string|null Format */
|
||||||
|
private $format;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $hasSetFormat = false;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $isRegistered = false;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $isEmpty = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int|null
|
* @return int|null
|
||||||
*/
|
*/
|
||||||
@@ -91,7 +110,7 @@ class Style
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Border
|
* @return Border|null
|
||||||
*/
|
*/
|
||||||
public function getBorder()
|
public function getBorder()
|
||||||
{
|
{
|
||||||
@@ -106,6 +125,7 @@ class Style
|
|||||||
{
|
{
|
||||||
$this->shouldApplyBorder = true;
|
$this->shouldApplyBorder = true;
|
||||||
$this->border = $border;
|
$this->border = $border;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -134,6 +154,7 @@ class Style
|
|||||||
$this->fontBold = true;
|
$this->fontBold = true;
|
||||||
$this->hasSetFontBold = true;
|
$this->hasSetFontBold = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -162,6 +183,7 @@ class Style
|
|||||||
$this->fontItalic = true;
|
$this->fontItalic = true;
|
||||||
$this->hasSetFontItalic = true;
|
$this->hasSetFontItalic = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -190,6 +212,7 @@ class Style
|
|||||||
$this->fontUnderline = true;
|
$this->fontUnderline = true;
|
||||||
$this->hasSetFontUnderline = true;
|
$this->hasSetFontUnderline = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -218,6 +241,7 @@ class Style
|
|||||||
$this->fontStrikethrough = true;
|
$this->fontStrikethrough = true;
|
||||||
$this->hasSetFontStrikethrough = true;
|
$this->hasSetFontStrikethrough = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -247,6 +271,7 @@ class Style
|
|||||||
$this->fontSize = $fontSize;
|
$this->fontSize = $fontSize;
|
||||||
$this->hasSetFontSize = true;
|
$this->hasSetFontSize = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -278,6 +303,7 @@ class Style
|
|||||||
$this->fontColor = $fontColor;
|
$this->fontColor = $fontColor;
|
||||||
$this->hasSetFontColor = true;
|
$this->hasSetFontColor = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -307,6 +333,7 @@ class Style
|
|||||||
$this->fontName = $fontName;
|
$this->fontName = $fontName;
|
||||||
$this->hasSetFontName = true;
|
$this->hasSetFontName = true;
|
||||||
$this->shouldApplyFont = true;
|
$this->shouldApplyFont = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -319,6 +346,45 @@ class Style
|
|||||||
return $this->hasSetFontName;
|
return $this->hasSetFontName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCellAlignment()
|
||||||
|
{
|
||||||
|
return $this->cellAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $cellAlignment The cell alignment
|
||||||
|
*
|
||||||
|
* @return Style
|
||||||
|
*/
|
||||||
|
public function setCellAlignment($cellAlignment)
|
||||||
|
{
|
||||||
|
$this->cellAlignment = $cellAlignment;
|
||||||
|
$this->hasSetCellAlignment = true;
|
||||||
|
$this->shouldApplyCellAlignment = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasSetCellAlignment()
|
||||||
|
{
|
||||||
|
return $this->hasSetCellAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool Whether specific cell alignment should be applied
|
||||||
|
*/
|
||||||
|
public function shouldApplyCellAlignment()
|
||||||
|
{
|
||||||
|
return $this->shouldApplyCellAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
@@ -335,6 +401,7 @@ class Style
|
|||||||
{
|
{
|
||||||
$this->shouldWrapText = $shouldWrap;
|
$this->shouldWrapText = $shouldWrap;
|
||||||
$this->hasSetWrapText = true;
|
$this->hasSetWrapText = true;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -364,6 +431,7 @@ class Style
|
|||||||
{
|
{
|
||||||
$this->hasSetBackgroundColor = true;
|
$this->hasSetBackgroundColor = true;
|
||||||
$this->backgroundColor = $color;
|
$this->backgroundColor = $color;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -383,4 +451,59 @@ class Style
|
|||||||
{
|
{
|
||||||
return $this->hasSetBackgroundColor;
|
return $this->hasSetBackgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets format
|
||||||
|
* @param string $format
|
||||||
|
* @return Style
|
||||||
|
*/
|
||||||
|
public function setFormat($format)
|
||||||
|
{
|
||||||
|
$this->hasSetFormat = true;
|
||||||
|
$this->format = $format;
|
||||||
|
$this->isEmpty = false;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getFormat()
|
||||||
|
{
|
||||||
|
return $this->format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool Whether format should be applied
|
||||||
|
*/
|
||||||
|
public function shouldApplyFormat()
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ namespace Box\Spout\Common\Helper;
|
|||||||
class CellTypeHelper
|
class CellTypeHelper
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param $value
|
* @param mixed|null $value
|
||||||
* @return bool Whether the given value is considered "empty"
|
* @return bool Whether the given value is considered "empty"
|
||||||
*/
|
*/
|
||||||
public static function isEmpty($value)
|
public static function isEmpty($value)
|
||||||
@@ -18,24 +18,24 @@ class CellTypeHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $value
|
* @param mixed $value
|
||||||
* @return bool Whether the given value is a non empty string
|
* @return bool Whether the given value is a non empty string
|
||||||
*/
|
*/
|
||||||
public static function isNonEmptyString($value)
|
public static function isNonEmptyString($value)
|
||||||
{
|
{
|
||||||
return (gettype($value) === 'string' && $value !== '');
|
return (\gettype($value) === 'string' && $value !== '');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the given value is numeric.
|
* Returns whether the given value is numeric.
|
||||||
* A numeric value is from type "integer" or "double" ("float" is not returned by gettype).
|
* 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
|
* @return bool Whether the given value is numeric
|
||||||
*/
|
*/
|
||||||
public static function isNumeric($value)
|
public static function isNumeric($value)
|
||||||
{
|
{
|
||||||
$valueType = gettype($value);
|
$valueType = \gettype($value);
|
||||||
|
|
||||||
return ($valueType === 'integer' || $valueType === 'double');
|
return ($valueType === 'integer' || $valueType === 'double');
|
||||||
}
|
}
|
||||||
@@ -44,24 +44,24 @@ class CellTypeHelper
|
|||||||
* Returns whether the given value is boolean.
|
* Returns whether the given value is boolean.
|
||||||
* "true"/"false" and 0/1 are not booleans.
|
* "true"/"false" and 0/1 are not booleans.
|
||||||
*
|
*
|
||||||
* @param $value
|
* @param mixed $value
|
||||||
* @return bool Whether the given value is boolean
|
* @return bool Whether the given value is boolean
|
||||||
*/
|
*/
|
||||||
public static function isBoolean($value)
|
public static function isBoolean($value)
|
||||||
{
|
{
|
||||||
return gettype($value) === 'boolean';
|
return \gettype($value) === 'boolean';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the given value is a DateTime or DateInterval object.
|
* 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
|
* @return bool Whether the given value is a DateTime or DateInterval object
|
||||||
*/
|
*/
|
||||||
public static function isDateTimeOrDateInterval($value)
|
public static function isDateTimeOrDateInterval($value)
|
||||||
{
|
{
|
||||||
return (
|
return (
|
||||||
$value instanceof \DateTime ||
|
$value instanceof \DateTimeInterface ||
|
||||||
$value instanceof \DateInterval
|
$value instanceof \DateInterval
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,18 +11,18 @@ use Box\Spout\Common\Exception\EncodingConversionException;
|
|||||||
class EncodingHelper
|
class EncodingHelper
|
||||||
{
|
{
|
||||||
/** Definition of the encodings that can have a BOM */
|
/** Definition of the encodings that can have a BOM */
|
||||||
const ENCODING_UTF8 = 'UTF-8';
|
public const ENCODING_UTF8 = 'UTF-8';
|
||||||
const ENCODING_UTF16_LE = 'UTF-16LE';
|
public const ENCODING_UTF16_LE = 'UTF-16LE';
|
||||||
const ENCODING_UTF16_BE = 'UTF-16BE';
|
public const ENCODING_UTF16_BE = 'UTF-16BE';
|
||||||
const ENCODING_UTF32_LE = 'UTF-32LE';
|
public const ENCODING_UTF32_LE = 'UTF-32LE';
|
||||||
const ENCODING_UTF32_BE = 'UTF-32BE';
|
public const ENCODING_UTF32_BE = 'UTF-32BE';
|
||||||
|
|
||||||
/** Definition of the BOMs for the different encodings */
|
/** Definition of the BOMs for the different encodings */
|
||||||
const BOM_UTF8 = "\xEF\xBB\xBF";
|
public const BOM_UTF8 = "\xEF\xBB\xBF";
|
||||||
const BOM_UTF16_LE = "\xFF\xFE";
|
public const BOM_UTF16_LE = "\xFF\xFE";
|
||||||
const BOM_UTF16_BE = "\xFE\xFF";
|
public const BOM_UTF16_BE = "\xFE\xFF";
|
||||||
const BOM_UTF32_LE = "\xFF\xFE\x00\x00";
|
public const BOM_UTF32_LE = "\xFF\xFE\x00\x00";
|
||||||
const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
|
public const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
|
||||||
|
|
||||||
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
||||||
protected $globalFunctionsHelper;
|
protected $globalFunctionsHelper;
|
||||||
@@ -61,7 +61,7 @@ class EncodingHelper
|
|||||||
$bomUsed = $this->supportedEncodingsWithBom[$encoding];
|
$bomUsed = $this->supportedEncodingsWithBom[$encoding];
|
||||||
|
|
||||||
// we skip the N first bytes
|
// we skip the N first bytes
|
||||||
$byteOffsetToSkipBom = strlen($bomUsed);
|
$byteOffsetToSkipBom = \strlen($bomUsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $byteOffsetToSkipBom;
|
return $byteOffsetToSkipBom;
|
||||||
@@ -80,9 +80,9 @@ class EncodingHelper
|
|||||||
|
|
||||||
$this->globalFunctionsHelper->rewind($filePointer);
|
$this->globalFunctionsHelper->rewind($filePointer);
|
||||||
|
|
||||||
if (array_key_exists($encoding, $this->supportedEncodingsWithBom)) {
|
if (\array_key_exists($encoding, $this->supportedEncodingsWithBom)) {
|
||||||
$potentialBom = $this->supportedEncodingsWithBom[$encoding];
|
$potentialBom = $this->supportedEncodingsWithBom[$encoding];
|
||||||
$numBytesInBom = strlen($potentialBom);
|
$numBytesInBom = \strlen($potentialBom);
|
||||||
|
|
||||||
$hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom);
|
$hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ class ODS implements EscaperInterface
|
|||||||
{
|
{
|
||||||
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
|
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
|
||||||
// single/double quotes (for XML attributes) need to be encoded.
|
// single/double quotes (for XML attributes) need to be encoded.
|
||||||
if (defined('ENT_DISALLOWED')) {
|
if (\defined('ENT_DISALLOWED')) {
|
||||||
// 'ENT_DISALLOWED' ensures that invalid characters in the given document type are replaced.
|
// 'ENT_DISALLOWED' ensures that invalid characters in the given document type are replaced.
|
||||||
// Otherwise control characters like a vertical tab "\v" will make the XML document unreadable by the XML processor
|
// Otherwise control characters like a vertical tab "\v" will make the XML document unreadable by the XML processor
|
||||||
// @link https://github.com/box/spout/issues/329
|
// @link https://github.com/box/spout/issues/329
|
||||||
$replacedString = htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8');
|
$replacedString = \htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8');
|
||||||
} else {
|
} else {
|
||||||
// We are on hhvm or any other engine that does not support ENT_DISALLOWED.
|
// We are on hhvm or any other engine that does not support ENT_DISALLOWED.
|
||||||
$escapedString = htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
|
$escapedString = \htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
// control characters values are from 0 to 1F (hex values) in the ASCII table
|
// control characters values are from 0 to 1F (hex values) in the ASCII table
|
||||||
// some characters should not be escaped though: "\t", "\r" and "\n".
|
// some characters should not be escaped though: "\t", "\r" and "\n".
|
||||||
@@ -34,7 +34,7 @@ class ODS implements EscaperInterface
|
|||||||
'\x0B-\x0C' .
|
'\x0B-\x0C' .
|
||||||
// skipping "\r" (0xD)
|
// skipping "\r" (0xD)
|
||||||
'\x0E-\x1F]';
|
'\x0E-\x1F]';
|
||||||
$replacedString = preg_replace("/$regexPattern/", '�', $escapedString);
|
$replacedString = \preg_replace("/$regexPattern/", '�', $escapedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $replacedString;
|
return $replacedString;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class XLSX implements EscaperInterface
|
|||||||
if (!$this->isAlreadyInitialized) {
|
if (!$this->isAlreadyInitialized) {
|
||||||
$this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern();
|
$this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern();
|
||||||
$this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
|
$this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
|
||||||
$this->controlCharactersEscapingReverseMap = array_flip($this->controlCharactersEscapingMap);
|
$this->controlCharactersEscapingReverseMap = \array_flip($this->controlCharactersEscapingMap);
|
||||||
|
|
||||||
$this->isAlreadyInitialized = true;
|
$this->isAlreadyInitialized = true;
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ class XLSX implements EscaperInterface
|
|||||||
$escapedString = $this->escapeControlCharacters($string);
|
$escapedString = $this->escapeControlCharacters($string);
|
||||||
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
|
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
|
||||||
// single/double quotes (for XML attributes) need to be encoded.
|
// single/double quotes (for XML attributes) need to be encoded.
|
||||||
$escapedString = htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8');
|
$escapedString = \htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8');
|
||||||
|
|
||||||
return $escapedString;
|
return $escapedString;
|
||||||
}
|
}
|
||||||
@@ -103,10 +103,10 @@ class XLSX implements EscaperInterface
|
|||||||
|
|
||||||
// control characters values are from 0 to 1F (hex values) in the ASCII table
|
// control characters values are from 0 to 1F (hex values) in the ASCII table
|
||||||
for ($charValue = 0x00; $charValue <= 0x1F; $charValue++) {
|
for ($charValue = 0x00; $charValue <= 0x1F; $charValue++) {
|
||||||
$character = chr($charValue);
|
$character = \chr($charValue);
|
||||||
if (preg_match("/{$this->escapableControlCharactersPattern}/", $character)) {
|
if (\preg_match("/{$this->escapableControlCharactersPattern}/", $character)) {
|
||||||
$charHexValue = dechex($charValue);
|
$charHexValue = \dechex($charValue);
|
||||||
$escapedChar = '_x' . sprintf('%04s', strtoupper($charHexValue)) . '_';
|
$escapedChar = '_x' . \sprintf('%04s', \strtoupper($charHexValue)) . '_';
|
||||||
$controlCharactersEscapingMap[$escapedChar] = $character;
|
$controlCharactersEscapingMap[$escapedChar] = $character;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,11 +132,11 @@ class XLSX implements EscaperInterface
|
|||||||
$escapedString = $this->escapeEscapeCharacter($string);
|
$escapedString = $this->escapeEscapeCharacter($string);
|
||||||
|
|
||||||
// if no control characters
|
// if no control characters
|
||||||
if (!preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) {
|
if (!\preg_match("/{$this->escapableControlCharactersPattern}/", $escapedString)) {
|
||||||
return $escapedString;
|
return $escapedString;
|
||||||
}
|
}
|
||||||
|
|
||||||
return preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) {
|
return \preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) {
|
||||||
return $this->controlCharactersEscapingReverseMap[$matches[0]];
|
return $this->controlCharactersEscapingReverseMap[$matches[0]];
|
||||||
}, $escapedString);
|
}, $escapedString);
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ class XLSX implements EscaperInterface
|
|||||||
*/
|
*/
|
||||||
protected function escapeEscapeCharacter($string)
|
protected function escapeEscapeCharacter($string)
|
||||||
{
|
{
|
||||||
return preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string);
|
return \preg_replace('/_(x[\dA-F]{4})_/', '_x005F_$1_', $string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -171,7 +171,7 @@ class XLSX implements EscaperInterface
|
|||||||
|
|
||||||
foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) {
|
foreach ($this->controlCharactersEscapingMap as $escapedCharValue => $charValue) {
|
||||||
// only unescape characters that don't contain the escaped escape character for now
|
// only unescape characters that don't contain the escaped escape character for now
|
||||||
$unescapedString = preg_replace("/(?<!_x005F)($escapedCharValue)/", $charValue, $unescapedString);
|
$unescapedString = \preg_replace("/(?<!_x005F)($escapedCharValue)/", $charValue, $unescapedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->unescapeEscapeCharacter($unescapedString);
|
return $this->unescapeEscapeCharacter($unescapedString);
|
||||||
@@ -185,6 +185,6 @@ class XLSX implements EscaperInterface
|
|||||||
*/
|
*/
|
||||||
protected function unescapeEscapeCharacter($string)
|
protected function unescapeEscapeCharacter($string)
|
||||||
{
|
{
|
||||||
return preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string);
|
return \preg_replace('/_x005F(_x[\dA-F]{4}_)/', '$1', $string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,9 +17,9 @@ class FileSystemHelper implements FileSystemHelperInterface
|
|||||||
/**
|
/**
|
||||||
* @param string $baseFolderPath The path of the base folder where all the I/O can occur
|
* @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);
|
$this->baseFolderRealPath = \realpath($baseFolderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,7 +36,7 @@ class FileSystemHelper implements FileSystemHelperInterface
|
|||||||
|
|
||||||
$folderPath = $parentFolderPath . '/' . $folderName;
|
$folderPath = $parentFolderPath . '/' . $folderName;
|
||||||
|
|
||||||
$wasCreationSuccessful = mkdir($folderPath, 0777, true);
|
$wasCreationSuccessful = \mkdir($folderPath, 0777, true);
|
||||||
if (!$wasCreationSuccessful) {
|
if (!$wasCreationSuccessful) {
|
||||||
throw new IOException("Unable to create folder: $folderPath");
|
throw new IOException("Unable to create folder: $folderPath");
|
||||||
}
|
}
|
||||||
@@ -60,7 +60,7 @@ class FileSystemHelper implements FileSystemHelperInterface
|
|||||||
|
|
||||||
$filePath = $parentFolderPath . '/' . $fileName;
|
$filePath = $parentFolderPath . '/' . $fileName;
|
||||||
|
|
||||||
$wasCreationSuccessful = file_put_contents($filePath, $fileContents);
|
$wasCreationSuccessful = \file_put_contents($filePath, $fileContents);
|
||||||
if ($wasCreationSuccessful === false) {
|
if ($wasCreationSuccessful === false) {
|
||||||
throw new IOException("Unable to create file: $filePath");
|
throw new IOException("Unable to create file: $filePath");
|
||||||
}
|
}
|
||||||
@@ -79,8 +79,8 @@ class FileSystemHelper implements FileSystemHelperInterface
|
|||||||
{
|
{
|
||||||
$this->throwIfOperationNotInBaseFolder($filePath);
|
$this->throwIfOperationNotInBaseFolder($filePath);
|
||||||
|
|
||||||
if (file_exists($filePath) && is_file($filePath)) {
|
if (\file_exists($filePath) && \is_file($filePath)) {
|
||||||
unlink($filePath);
|
\unlink($filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,13 +102,13 @@ class FileSystemHelper implements FileSystemHelperInterface
|
|||||||
|
|
||||||
foreach ($itemIterator as $item) {
|
foreach ($itemIterator as $item) {
|
||||||
if ($item->isDir()) {
|
if ($item->isDir()) {
|
||||||
rmdir($item->getPathname());
|
\rmdir($item->getPathname());
|
||||||
} else {
|
} else {
|
||||||
unlink($item->getPathname());
|
\unlink($item->getPathname());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rmdir($folderPath);
|
\rmdir($folderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,13 +117,17 @@ class FileSystemHelper implements FileSystemHelperInterface
|
|||||||
* should occur is not inside the base folder.
|
* should occur is not inside the base folder.
|
||||||
*
|
*
|
||||||
* @param string $operationFolderPath The path of the folder where the I/O operation should occur
|
* @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
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function throwIfOperationNotInBaseFolder($operationFolderPath)
|
protected function throwIfOperationNotInBaseFolder(string $operationFolderPath)
|
||||||
{
|
{
|
||||||
$operationFolderRealPath = realpath($operationFolderPath);
|
$operationFolderRealPath = \realpath($operationFolderPath);
|
||||||
$isInBaseFolder = (strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0);
|
if (!$this->baseFolderRealPath) {
|
||||||
|
throw new IOException("The base folder path is invalid: {$this->baseFolderRealPath}");
|
||||||
|
}
|
||||||
|
$isInBaseFolder = (\strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0);
|
||||||
if (!$isInBaseFolder) {
|
if (!$isInBaseFolder) {
|
||||||
throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}");
|
throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function fopen($fileName, $mode)
|
public function fopen($fileName, $mode)
|
||||||
{
|
{
|
||||||
return fopen($fileName, $mode);
|
return \fopen($fileName, $mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,7 +33,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function fgets($handle, $length = null)
|
public function fgets($handle, $length = null)
|
||||||
{
|
{
|
||||||
return fgets($handle, $length);
|
return \fgets($handle, $length);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,7 +46,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function fputs($handle, $string)
|
public function fputs($handle, $string)
|
||||||
{
|
{
|
||||||
return fputs($handle, $string);
|
return \fputs($handle, $string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,7 +58,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function fflush($handle)
|
public function fflush($handle)
|
||||||
{
|
{
|
||||||
return fflush($handle);
|
return \fflush($handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,7 +71,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function fseek($handle, $offset)
|
public function fseek($handle, $offset)
|
||||||
{
|
{
|
||||||
return fseek($handle, $offset);
|
return \fseek($handle, $offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,7 +82,7 @@ class GlobalFunctionsHelper
|
|||||||
* @param int|null $length
|
* @param int|null $length
|
||||||
* @param string|null $delimiter
|
* @param string|null $delimiter
|
||||||
* @param string|null $enclosure
|
* @param string|null $enclosure
|
||||||
* @return array
|
* @return array|false
|
||||||
*/
|
*/
|
||||||
public function fgetcsv($handle, $length = null, $delimiter = null, $enclosure = null)
|
public function fgetcsv($handle, $length = null, $delimiter = null, $enclosure = null)
|
||||||
{
|
{
|
||||||
@@ -90,9 +90,9 @@ class GlobalFunctionsHelper
|
|||||||
// To fix that, simply disable the escape character.
|
// To fix that, simply disable the escape character.
|
||||||
// @see https://bugs.php.net/bug.php?id=43225
|
// @see https://bugs.php.net/bug.php?id=43225
|
||||||
// @see http://tools.ietf.org/html/rfc4180
|
// @see http://tools.ietf.org/html/rfc4180
|
||||||
$escapeCharacter = "\0";
|
$escapeCharacter = PHP_VERSION_ID >= 70400 ? '' : "\0";
|
||||||
|
|
||||||
return fgetcsv($handle, $length, $delimiter, $enclosure, $escapeCharacter);
|
return \fgetcsv($handle, $length, $delimiter, $enclosure, $escapeCharacter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,7 +103,7 @@ class GlobalFunctionsHelper
|
|||||||
* @param array $fields
|
* @param array $fields
|
||||||
* @param string|null $delimiter
|
* @param string|null $delimiter
|
||||||
* @param string|null $enclosure
|
* @param string|null $enclosure
|
||||||
* @return int
|
* @return int|false
|
||||||
*/
|
*/
|
||||||
public function fputcsv($handle, array $fields, $delimiter = null, $enclosure = null)
|
public function fputcsv($handle, array $fields, $delimiter = null, $enclosure = null)
|
||||||
{
|
{
|
||||||
@@ -111,9 +111,9 @@ class GlobalFunctionsHelper
|
|||||||
// To fix that, simply disable the escape character.
|
// To fix that, simply disable the escape character.
|
||||||
// @see https://bugs.php.net/bug.php?id=43225
|
// @see https://bugs.php.net/bug.php?id=43225
|
||||||
// @see http://tools.ietf.org/html/rfc4180
|
// @see http://tools.ietf.org/html/rfc4180
|
||||||
$escapeCharacter = "\0";
|
$escapeCharacter = PHP_VERSION_ID >= 70400 ? '' : "\0";
|
||||||
|
|
||||||
return fputcsv($handle, $fields, $delimiter, $enclosure, $escapeCharacter);
|
return \fputcsv($handle, $fields, $delimiter, $enclosure, $escapeCharacter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -126,7 +126,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function fwrite($handle, $string)
|
public function fwrite($handle, $string)
|
||||||
{
|
{
|
||||||
return fwrite($handle, $string);
|
return \fwrite($handle, $string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -138,7 +138,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function fclose($handle)
|
public function fclose($handle)
|
||||||
{
|
{
|
||||||
return fclose($handle);
|
return \fclose($handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,7 +150,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function rewind($handle)
|
public function rewind($handle)
|
||||||
{
|
{
|
||||||
return rewind($handle);
|
return \rewind($handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,7 +162,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function file_exists($fileName)
|
public function file_exists($fileName)
|
||||||
{
|
{
|
||||||
return file_exists($fileName);
|
return \file_exists($fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -176,7 +176,7 @@ class GlobalFunctionsHelper
|
|||||||
{
|
{
|
||||||
$realFilePath = $this->convertToUseRealPath($filePath);
|
$realFilePath = $this->convertToUseRealPath($filePath);
|
||||||
|
|
||||||
return file_get_contents($realFilePath);
|
return \file_get_contents($realFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,13 +191,13 @@ class GlobalFunctionsHelper
|
|||||||
$realFilePath = $filePath;
|
$realFilePath = $filePath;
|
||||||
|
|
||||||
if ($this->isZipStream($filePath)) {
|
if ($this->isZipStream($filePath)) {
|
||||||
if (preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) {
|
if (\preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) {
|
||||||
$documentPath = $matches[1];
|
$documentPath = $matches[1];
|
||||||
$documentInsideZipPath = $matches[2];
|
$documentInsideZipPath = $matches[2];
|
||||||
$realFilePath = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath;
|
$realFilePath = 'zip://' . \realpath($documentPath) . '#' . $documentInsideZipPath;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$realFilePath = realpath($filePath);
|
$realFilePath = \realpath($filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $realFilePath;
|
return $realFilePath;
|
||||||
@@ -211,7 +211,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
protected function isZipStream($path)
|
protected function isZipStream($path)
|
||||||
{
|
{
|
||||||
return (strpos($path, 'zip://') === 0);
|
return (\strpos($path, 'zip://') === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -223,7 +223,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function feof($handle)
|
public function feof($handle)
|
||||||
{
|
{
|
||||||
return feof($handle);
|
return \feof($handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -235,7 +235,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function is_readable($fileName)
|
public function is_readable($fileName)
|
||||||
{
|
{
|
||||||
return is_readable($fileName);
|
return \is_readable($fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,12 +243,12 @@ class GlobalFunctionsHelper
|
|||||||
* @see basename()
|
* @see basename()
|
||||||
*
|
*
|
||||||
* @param string $path
|
* @param string $path
|
||||||
* @param string|null $suffix
|
* @param string $suffix
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function basename($path, $suffix = null)
|
public function basename($path, $suffix = '')
|
||||||
{
|
{
|
||||||
return basename($path, $suffix);
|
return \basename($path, $suffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -260,7 +260,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function header($string)
|
public function header($string)
|
||||||
{
|
{
|
||||||
header($string);
|
\header($string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,8 +271,8 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function ob_end_clean()
|
public function ob_end_clean()
|
||||||
{
|
{
|
||||||
if (ob_get_length() > 0) {
|
if (\ob_get_length() > 0) {
|
||||||
ob_end_clean();
|
\ob_end_clean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function iconv($string, $sourceEncoding, $targetEncoding)
|
public function iconv($string, $sourceEncoding, $targetEncoding)
|
||||||
{
|
{
|
||||||
return iconv($sourceEncoding, $targetEncoding, $string);
|
return \iconv($sourceEncoding, $targetEncoding, $string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -301,7 +301,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function mb_convert_encoding($string, $sourceEncoding, $targetEncoding)
|
public function mb_convert_encoding($string, $sourceEncoding, $targetEncoding)
|
||||||
{
|
{
|
||||||
return mb_convert_encoding($string, $targetEncoding, $sourceEncoding);
|
return \mb_convert_encoding($string, $targetEncoding, $sourceEncoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -312,7 +312,7 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function stream_get_wrappers()
|
public function stream_get_wrappers()
|
||||||
{
|
{
|
||||||
return stream_get_wrappers();
|
return \stream_get_wrappers();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -324,6 +324,6 @@ class GlobalFunctionsHelper
|
|||||||
*/
|
*/
|
||||||
public function function_exists($functionName)
|
public function function_exists($functionName)
|
||||||
{
|
{
|
||||||
return function_exists($functionName);
|
return \function_exists($functionName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,12 +13,20 @@ class StringHelper
|
|||||||
/** @var bool Whether the mbstring extension is loaded */
|
/** @var bool Whether the mbstring extension is loaded */
|
||||||
protected $hasMbstringSupport;
|
protected $hasMbstringSupport;
|
||||||
|
|
||||||
|
/** @var bool Whether the code is running with PHP7 or older versions */
|
||||||
|
private $isRunningPhp7OrOlder;
|
||||||
|
|
||||||
|
/** @var array Locale info, used for number formatting */
|
||||||
|
private $localeInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->hasMbstringSupport = extension_loaded('mbstring');
|
$this->hasMbstringSupport = \extension_loaded('mbstring');
|
||||||
|
$this->isRunningPhp7OrOlder = \version_compare(PHP_VERSION, '8.0.0') < 0;
|
||||||
|
$this->localeInfo = \localeconv();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,7 +40,7 @@ class StringHelper
|
|||||||
*/
|
*/
|
||||||
public function getStringLength($string)
|
public function getStringLength($string)
|
||||||
{
|
{
|
||||||
return $this->hasMbstringSupport ? mb_strlen($string) : strlen($string);
|
return $this->hasMbstringSupport ? \mb_strlen($string) : \strlen($string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +55,7 @@ class StringHelper
|
|||||||
*/
|
*/
|
||||||
public function getCharFirstOccurrencePosition($char, $string)
|
public function getCharFirstOccurrencePosition($char, $string)
|
||||||
{
|
{
|
||||||
$position = $this->hasMbstringSupport ? mb_strpos($string, $char) : strpos($string, $char);
|
$position = $this->hasMbstringSupport ? \mb_strpos($string, $char) : \strpos($string, $char);
|
||||||
|
|
||||||
return ($position !== false) ? $position : -1;
|
return ($position !== false) ? $position : -1;
|
||||||
}
|
}
|
||||||
@@ -64,8 +72,34 @@ class StringHelper
|
|||||||
*/
|
*/
|
||||||
public function getCharLastOccurrencePosition($char, $string)
|
public function getCharLastOccurrencePosition($char, $string)
|
||||||
{
|
{
|
||||||
$position = $this->hasMbstringSupport ? mb_strrpos($string, $char) : strrpos($string, $char);
|
$position = $this->hasMbstringSupport ? \mb_strrpos($string, $char) : \strrpos($string, $char);
|
||||||
|
|
||||||
return ($position !== false) ? $position : -1;
|
return ($position !== false) ? $position : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a numeric value (int or float) in a way that's compatible with the expected spreadsheet format.
|
||||||
|
*
|
||||||
|
* Formatting of float values is locale dependent in PHP < 8.
|
||||||
|
* Thousands separators and decimal points vary from locale to locale (en_US: 12.34 vs pl_PL: 12,34).
|
||||||
|
* However, float values must be formatted with no thousands separator and a "." as decimal point
|
||||||
|
* to work properly. This method can be used to convert the value to the correct format before storing it.
|
||||||
|
*
|
||||||
|
* @see https://wiki.php.net/rfc/locale_independent_float_to_string for the changed behavior in PHP8.
|
||||||
|
*
|
||||||
|
* @param int|float $numericValue
|
||||||
|
* @return 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
|
abstract class OptionsManagerAbstract implements OptionsManagerInterface
|
||||||
{
|
{
|
||||||
const PREFIX_OPTION = 'OPTION_';
|
public const PREFIX_OPTION = 'OPTION_';
|
||||||
|
|
||||||
/** @var string[] List of all supported option names */
|
/** @var string[] List of all supported option names */
|
||||||
private $supportedOptions = [];
|
private $supportedOptions = [];
|
||||||
@@ -46,7 +46,7 @@ abstract class OptionsManagerAbstract implements OptionsManagerInterface
|
|||||||
*/
|
*/
|
||||||
public function setOption($optionName, $optionValue)
|
public function setOption($optionName, $optionValue)
|
||||||
{
|
{
|
||||||
if (in_array($optionName, $this->supportedOptions)) {
|
if (\in_array($optionName, $this->supportedOptions)) {
|
||||||
$this->options[$optionName] = $optionValue;
|
$this->options[$optionName] = $optionValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Box\Spout\Common;
|
|||||||
*/
|
*/
|
||||||
abstract class Type
|
abstract class Type
|
||||||
{
|
{
|
||||||
const CSV = 'csv';
|
public const CSV = 'csv';
|
||||||
const XLSX = 'xlsx';
|
public const XLSX = 'xlsx';
|
||||||
const ODS = 'ods';
|
public const ODS = 'ods';
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ class InternalEntityFactory implements InternalEntityFactoryInterface
|
|||||||
*/
|
*/
|
||||||
public function createRowFromArray(array $cellValues = [])
|
public function createRowFromArray(array $cellValues = [])
|
||||||
{
|
{
|
||||||
$cells = array_map(function ($cellValue) {
|
$cells = \array_map(function ($cellValue) {
|
||||||
return $this->createCell($cellValue);
|
return $this->createCell($cellValue);
|
||||||
}, $cellValues);
|
}, $cellValues);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
namespace Box\Spout\Reader\CSV;
|
namespace Box\Spout\Reader\CSV;
|
||||||
|
|
||||||
use Box\Spout\Common\Exception\IOException;
|
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\Common\Entity\Options;
|
||||||
use Box\Spout\Reader\CSV\Creator\InternalEntityFactory;
|
use Box\Spout\Reader\CSV\Creator\InternalEntityFactory;
|
||||||
use Box\Spout\Reader\ReaderAbstract;
|
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 */
|
/** @var string Original value for the "auto_detect_line_endings" INI value */
|
||||||
protected $originalAutoDetectLineEndings;
|
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.
|
* Sets the field delimiter for the CSV.
|
||||||
* Needs to be called before opening the reader.
|
* Needs to be called before opening the reader.
|
||||||
@@ -84,8 +104,11 @@ class Reader extends ReaderAbstract
|
|||||||
*/
|
*/
|
||||||
protected function openReader($filePath)
|
protected function openReader($filePath)
|
||||||
{
|
{
|
||||||
$this->originalAutoDetectLineEndings = ini_get('auto_detect_line_endings');
|
// "auto_detect_line_endings" is deprecated in PHP 8.1
|
||||||
ini_set('auto_detect_line_endings', '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');
|
$this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r');
|
||||||
if (!$this->filePointer) {
|
if (!$this->filePointer) {
|
||||||
@@ -119,10 +142,13 @@ class Reader extends ReaderAbstract
|
|||||||
*/
|
*/
|
||||||
protected function closeReader()
|
protected function closeReader()
|
||||||
{
|
{
|
||||||
if ($this->filePointer) {
|
if (is_resource($this->filePointer)) {
|
||||||
$this->globalFunctionsHelper->fclose($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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,9 +19,9 @@ class RowIterator implements IteratorInterface
|
|||||||
/**
|
/**
|
||||||
* Value passed to fgetcsv. 0 means "unlimited" (slightly slower but accomodates for very long lines).
|
* 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;
|
protected $filePointer;
|
||||||
|
|
||||||
/** @var int Number of read rows */
|
/** @var int Number of read rows */
|
||||||
@@ -84,7 +84,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function rewind()
|
public function rewind() : void
|
||||||
{
|
{
|
||||||
$this->rewindAndSkipBom();
|
$this->rewindAndSkipBom();
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function valid()
|
public function valid() : bool
|
||||||
{
|
{
|
||||||
return ($this->filePointer && !$this->hasReachedEndOfFile);
|
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
|
* @throws \Box\Spout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function next()
|
public function next() : void
|
||||||
{
|
{
|
||||||
$this->hasReachedEndOfFile = $this->globalFunctionsHelper->feof($this->filePointer);
|
$this->hasReachedEndOfFile = $this->globalFunctionsHelper->feof($this->filePointer);
|
||||||
|
|
||||||
@@ -146,8 +146,8 @@ class RowIterator implements IteratorInterface
|
|||||||
} while ($this->shouldReadNextRow($rowData));
|
} while ($this->shouldReadNextRow($rowData));
|
||||||
|
|
||||||
if ($rowData !== false) {
|
if ($rowData !== false) {
|
||||||
// str_replace will replace NULL values by empty strings
|
// array_map will replace NULL values by empty strings
|
||||||
$rowDataBufferAsArray = str_replace(null, null, $rowData);
|
$rowDataBufferAsArray = array_map(function ($value) { return (string) $value; }, $rowData);
|
||||||
$this->rowBuffer = $this->entityFactory->createRowFromArray($rowDataBufferAsArray);
|
$this->rowBuffer = $this->entityFactory->createRowFromArray($rowDataBufferAsArray);
|
||||||
$this->numReadRows++;
|
$this->numReadRows++;
|
||||||
} else {
|
} else {
|
||||||
@@ -193,13 +193,13 @@ class RowIterator implements IteratorInterface
|
|||||||
case EncodingHelper::ENCODING_UTF16_LE:
|
case EncodingHelper::ENCODING_UTF16_LE:
|
||||||
case EncodingHelper::ENCODING_UTF32_LE:
|
case EncodingHelper::ENCODING_UTF32_LE:
|
||||||
// remove whitespace from the beginning of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
|
// remove whitespace from the beginning of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
|
||||||
$cellValue = ltrim($cellValue);
|
$cellValue = \ltrim($cellValue);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EncodingHelper::ENCODING_UTF16_BE:
|
case EncodingHelper::ENCODING_UTF16_BE:
|
||||||
case EncodingHelper::ENCODING_UTF32_BE:
|
case EncodingHelper::ENCODING_UTF32_BE:
|
||||||
// remove whitespace from the end of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
|
// remove whitespace from the end of a string as fgetcsv() add extra whitespace when it try to explode non UTF-8 data
|
||||||
$cellValue = rtrim($cellValue);
|
$cellValue = \rtrim($cellValue);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*/
|
*/
|
||||||
protected function isEmptyLine($lineData)
|
protected function isEmptyLine($lineData)
|
||||||
{
|
{
|
||||||
return (is_array($lineData) && count($lineData) === 1 && $lineData[0] === null);
|
return (\is_array($lineData) && \count($lineData) === 1 && $lineData[0] === null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -224,7 +224,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return Row|null
|
* @return Row|null
|
||||||
*/
|
*/
|
||||||
public function current()
|
public function current() : ?Row
|
||||||
{
|
{
|
||||||
return $this->rowBuffer;
|
return $this->rowBuffer;
|
||||||
}
|
}
|
||||||
@@ -235,7 +235,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function key()
|
public function key() : int
|
||||||
{
|
{
|
||||||
return $this->numReadRows;
|
return $this->numReadRows;
|
||||||
}
|
}
|
||||||
@@ -245,7 +245,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function end()
|
public function end() : void
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use Box\Spout\Reader\IteratorInterface;
|
|||||||
*/
|
*/
|
||||||
class SheetIterator implements IteratorInterface
|
class SheetIterator implements IteratorInterface
|
||||||
{
|
{
|
||||||
/** @var \Box\Spout\Reader\CSV\Sheet The CSV unique "sheet" */
|
/** @var Sheet The CSV unique "sheet" */
|
||||||
protected $sheet;
|
protected $sheet;
|
||||||
|
|
||||||
/** @var bool Whether the unique "sheet" has already been read */
|
/** @var bool Whether the unique "sheet" has already been read */
|
||||||
@@ -30,7 +30,7 @@ class SheetIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function rewind()
|
public function rewind() : void
|
||||||
{
|
{
|
||||||
$this->hasReadUniqueSheet = false;
|
$this->hasReadUniqueSheet = false;
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ class SheetIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function valid()
|
public function valid() : bool
|
||||||
{
|
{
|
||||||
return (!$this->hasReadUniqueSheet);
|
return (!$this->hasReadUniqueSheet);
|
||||||
}
|
}
|
||||||
@@ -52,7 +52,7 @@ class SheetIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function next()
|
public function next() : void
|
||||||
{
|
{
|
||||||
$this->hasReadUniqueSheet = true;
|
$this->hasReadUniqueSheet = true;
|
||||||
}
|
}
|
||||||
@@ -61,9 +61,9 @@ class SheetIterator implements IteratorInterface
|
|||||||
* Return the current element
|
* Return the current element
|
||||||
* @see http://php.net/manual/en/iterator.current.php
|
* @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;
|
return $this->sheet;
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ class SheetIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function key()
|
public function key() : int
|
||||||
{
|
{
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@ class SheetIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function end()
|
public function end() : void
|
||||||
{
|
{
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class ReaderEntityFactory
|
|||||||
return ReaderFactory::createFromType(Type::CSV);
|
return ReaderFactory::createFromType(Type::CSV);
|
||||||
} catch (UnsupportedTypeException $e) {
|
} catch (UnsupportedTypeException $e) {
|
||||||
// should never happen
|
// should never happen
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ class ReaderEntityFactory
|
|||||||
return ReaderFactory::createFromType(Type::XLSX);
|
return ReaderFactory::createFromType(Type::XLSX);
|
||||||
} catch (UnsupportedTypeException $e) {
|
} catch (UnsupportedTypeException $e) {
|
||||||
// should never happen
|
// should never happen
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,6 +65,7 @@ class ReaderEntityFactory
|
|||||||
return ReaderFactory::createFromType(Type::ODS);
|
return ReaderFactory::createFromType(Type::ODS);
|
||||||
} catch (UnsupportedTypeException $e) {
|
} catch (UnsupportedTypeException $e) {
|
||||||
// should never happen
|
// should never happen
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ class ReaderFactory
|
|||||||
*/
|
*/
|
||||||
public static function createFromFile(string $path)
|
public static function createFromFile(string $path)
|
||||||
{
|
{
|
||||||
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
$extension = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
return self::createFromType($extension);
|
return self::createFromType($extension);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ namespace Box\Spout\Reader\Common\Entity;
|
|||||||
abstract class Options
|
abstract class Options
|
||||||
{
|
{
|
||||||
// Common options
|
// Common options
|
||||||
const SHOULD_FORMAT_DATES = 'shouldFormatDates';
|
public const SHOULD_FORMAT_DATES = 'shouldFormatDates';
|
||||||
const SHOULD_PRESERVE_EMPTY_ROWS = 'shouldPreserveEmptyRows';
|
public const SHOULD_PRESERVE_EMPTY_ROWS = 'shouldPreserveEmptyRows';
|
||||||
|
|
||||||
// CSV specific options
|
// CSV specific options
|
||||||
const FIELD_DELIMITER = 'fieldDelimiter';
|
public const FIELD_DELIMITER = 'fieldDelimiter';
|
||||||
const FIELD_ENCLOSURE = 'fieldEnclosure';
|
public const FIELD_ENCLOSURE = 'fieldEnclosure';
|
||||||
const ENCODING = 'encoding';
|
public const ENCODING = 'encoding';
|
||||||
|
|
||||||
// XLSX specific options
|
// XLSX specific options
|
||||||
const TEMP_FOLDER = 'tempFolder';
|
public const TEMP_FOLDER = 'tempFolder';
|
||||||
const SHOULD_USE_1904_DATES = 'shouldUse1904Dates';
|
public const SHOULD_USE_1904_DATES = 'shouldUse1904Dates';
|
||||||
}
|
}
|
||||||
@@ -47,19 +47,34 @@ class RowManager
|
|||||||
*/
|
*/
|
||||||
public function fillMissingIndexesWithEmptyCells(Row $row)
|
public function fillMissingIndexesWithEmptyCells(Row $row)
|
||||||
{
|
{
|
||||||
if ($row->getNumCells() === 0) {
|
$numCells = $row->getNumCells();
|
||||||
|
|
||||||
|
if ($numCells === 0) {
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rowCells = $row->getCells();
|
$rowCells = $row->getCells();
|
||||||
$maxCellIndex = max(array_keys($rowCells));
|
$maxCellIndex = $numCells;
|
||||||
|
|
||||||
|
// If the row has empty cells, calling "setCellAtIndex" will add the cell
|
||||||
|
// but in the wrong place (the new cell is added at the end of the array).
|
||||||
|
// Therefore, we need to sort the array using keys to have proper order.
|
||||||
|
// @see https://github.com/box/spout/issues/740
|
||||||
|
$needsSorting = false;
|
||||||
|
|
||||||
for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) {
|
for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) {
|
||||||
if (!isset($rowCells[$cellIndex])) {
|
if (!isset($rowCells[$cellIndex])) {
|
||||||
$row->setCellAtIndex($this->entityFactory->createCell(''), $cellIndex);
|
$row->setCellAtIndex($this->entityFactory->createCell(''), $cellIndex);
|
||||||
|
$needsSorting = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($needsSorting) {
|
||||||
|
$rowCells = $row->getCells();
|
||||||
|
ksort($rowCells);
|
||||||
|
$row->setCells($rowCells);
|
||||||
|
}
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,16 +11,16 @@ use Box\Spout\Reader\Wrapper\XMLReader;
|
|||||||
class XMLProcessor
|
class XMLProcessor
|
||||||
{
|
{
|
||||||
/* Node types */
|
/* Node types */
|
||||||
const NODE_TYPE_START = XMLReader::ELEMENT;
|
public const NODE_TYPE_START = XMLReader::ELEMENT;
|
||||||
const NODE_TYPE_END = XMLReader::END_ELEMENT;
|
public const NODE_TYPE_END = XMLReader::END_ELEMENT;
|
||||||
|
|
||||||
/* Keys associated to reflection attributes to invoke a callback */
|
/* Keys associated to reflection attributes to invoke a callback */
|
||||||
const CALLBACK_REFLECTION_METHOD = 'reflectionMethod';
|
public const CALLBACK_REFLECTION_METHOD = 'reflectionMethod';
|
||||||
const CALLBACK_REFLECTION_OBJECT = 'reflectionObject';
|
public const CALLBACK_REFLECTION_OBJECT = 'reflectionObject';
|
||||||
|
|
||||||
/* Values returned by the callbacks to indicate what the processor should do next */
|
/* Values returned by the callbacks to indicate what the processor should do next */
|
||||||
const PROCESSING_CONTINUE = 1;
|
public const PROCESSING_CONTINUE = 1;
|
||||||
const PROCESSING_STOP = 2;
|
public const PROCESSING_STOP = 2;
|
||||||
|
|
||||||
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
||||||
protected $xmlReader;
|
protected $xmlReader;
|
||||||
@@ -73,7 +73,7 @@ class XMLProcessor
|
|||||||
{
|
{
|
||||||
$callbackObject = $callback[0];
|
$callbackObject = $callback[0];
|
||||||
$callbackMethodName = $callback[1];
|
$callbackMethodName = $callback[1];
|
||||||
$reflectionMethod = new \ReflectionMethod(get_class($callbackObject), $callbackMethodName);
|
$reflectionMethod = new \ReflectionMethod(\get_class($callbackObject), $callbackMethodName);
|
||||||
$reflectionMethod->setAccessible(true);
|
$reflectionMethod->setAccessible(true);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class InternalEntityFactory implements InternalEntityFactoryInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $xmlReader
|
* @param XMLReader $xmlReader
|
||||||
* @return XMLProcessor
|
* @return XMLProcessor
|
||||||
*/
|
*/
|
||||||
private function createXMLProcessor($xmlReader)
|
private function createXMLProcessor($xmlReader)
|
||||||
|
|||||||
@@ -11,29 +11,31 @@ use Box\Spout\Reader\Exception\InvalidValueException;
|
|||||||
class CellValueFormatter
|
class CellValueFormatter
|
||||||
{
|
{
|
||||||
/** Definition of all possible cell types */
|
/** Definition of all possible cell types */
|
||||||
const CELL_TYPE_STRING = 'string';
|
public const CELL_TYPE_STRING = 'string';
|
||||||
const CELL_TYPE_FLOAT = 'float';
|
public const CELL_TYPE_FLOAT = 'float';
|
||||||
const CELL_TYPE_BOOLEAN = 'boolean';
|
public const CELL_TYPE_BOOLEAN = 'boolean';
|
||||||
const CELL_TYPE_DATE = 'date';
|
public const CELL_TYPE_DATE = 'date';
|
||||||
const CELL_TYPE_TIME = 'time';
|
public const CELL_TYPE_TIME = 'time';
|
||||||
const CELL_TYPE_CURRENCY = 'currency';
|
public const CELL_TYPE_CURRENCY = 'currency';
|
||||||
const CELL_TYPE_PERCENTAGE = 'percentage';
|
public const CELL_TYPE_PERCENTAGE = 'percentage';
|
||||||
const CELL_TYPE_VOID = 'void';
|
public const CELL_TYPE_VOID = 'void';
|
||||||
|
|
||||||
/** Definition of XML nodes names used to parse data */
|
/** Definition of XML nodes names used to parse data */
|
||||||
const XML_NODE_P = 'p';
|
public const XML_NODE_P = 'p';
|
||||||
const XML_NODE_S = 'text:s';
|
public const XML_NODE_TEXT_A = 'text:a';
|
||||||
const XML_NODE_A = 'text:a';
|
public const XML_NODE_TEXT_SPAN = 'text:span';
|
||||||
const XML_NODE_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 */
|
/** Definition of XML attributes used to parse data */
|
||||||
const XML_ATTRIBUTE_TYPE = 'office:value-type';
|
public const XML_ATTRIBUTE_TYPE = 'office:value-type';
|
||||||
const XML_ATTRIBUTE_VALUE = 'office:value';
|
public const XML_ATTRIBUTE_VALUE = 'office:value';
|
||||||
const XML_ATTRIBUTE_BOOLEAN_VALUE = 'office:boolean-value';
|
public const XML_ATTRIBUTE_BOOLEAN_VALUE = 'office:boolean-value';
|
||||||
const XML_ATTRIBUTE_DATE_VALUE = 'office:date-value';
|
public const XML_ATTRIBUTE_DATE_VALUE = 'office:date-value';
|
||||||
const XML_ATTRIBUTE_TIME_VALUE = 'office:time-value';
|
public const XML_ATTRIBUTE_TIME_VALUE = 'office:time-value';
|
||||||
const XML_ATTRIBUTE_CURRENCY = 'office:currency';
|
public const XML_ATTRIBUTE_CURRENCY = 'office:currency';
|
||||||
const XML_ATTRIBUTE_C = 'text:c';
|
public const XML_ATTRIBUTE_C = 'text:c';
|
||||||
|
|
||||||
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
|
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
|
||||||
protected $shouldFormatDates;
|
protected $shouldFormatDates;
|
||||||
@@ -41,6 +43,13 @@ class CellValueFormatter
|
|||||||
/** @var \Box\Spout\Common\Helper\Escaper\ODS Used to unescape XML data */
|
/** @var \Box\Spout\Common\Helper\Escaper\ODS Used to unescape XML data */
|
||||||
protected $escaper;
|
protected $escaper;
|
||||||
|
|
||||||
|
/** @var array List of XML nodes representing whitespaces and their corresponding value */
|
||||||
|
private static $WHITESPACE_XML_NODES = [
|
||||||
|
self::XML_NODE_TEXT_S => ' ',
|
||||||
|
self::XML_NODE_TEXT_TAB => "\t",
|
||||||
|
self::XML_NODE_TEXT_LINE_BREAK => "\n",
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
|
* @param bool $shouldFormatDates Whether date/time values should be returned as PHP objects or be formatted as strings
|
||||||
* @param \Box\Spout\Common\Helper\Escaper\ODS $escaper Used to unescape XML data
|
* @param \Box\Spout\Common\Helper\Escaper\ODS $escaper Used to unescape XML data
|
||||||
@@ -55,7 +64,7 @@ class CellValueFormatter
|
|||||||
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
* 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
|
* @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
|
* @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
|
* @return string|int|float|bool|\DateTime|\DateInterval The value associated with the cell, empty string if cell's type is void/undefined
|
||||||
*/
|
*/
|
||||||
@@ -87,7 +96,7 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns the cell String value.
|
* Returns the cell String value.
|
||||||
*
|
*
|
||||||
* @param \DOMNode $node
|
* @param \DOMElement $node
|
||||||
* @return string The value associated with the cell
|
* @return string The value associated with the cell
|
||||||
*/
|
*/
|
||||||
protected function formatStringCellValue($node)
|
protected function formatStringCellValue($node)
|
||||||
@@ -96,33 +105,75 @@ class CellValueFormatter
|
|||||||
$pNodes = $node->getElementsByTagName(self::XML_NODE_P);
|
$pNodes = $node->getElementsByTagName(self::XML_NODE_P);
|
||||||
|
|
||||||
foreach ($pNodes as $pNode) {
|
foreach ($pNodes as $pNode) {
|
||||||
$currentPValue = '';
|
$pNodeValues[] = $this->extractTextValueFromNode($pNode);
|
||||||
|
|
||||||
foreach ($pNode->childNodes as $childNode) {
|
|
||||||
if ($childNode instanceof \DOMText) {
|
|
||||||
$currentPValue .= $childNode->nodeValue;
|
|
||||||
} elseif ($childNode->nodeName === self::XML_NODE_S) {
|
|
||||||
$spaceAttribute = $childNode->getAttribute(self::XML_ATTRIBUTE_C);
|
|
||||||
$numSpaces = (!empty($spaceAttribute)) ? (int) $spaceAttribute : 1;
|
|
||||||
$currentPValue .= str_repeat(' ', $numSpaces);
|
|
||||||
} elseif ($childNode->nodeName === self::XML_NODE_A || $childNode->nodeName === self::XML_NODE_SPAN) {
|
|
||||||
$currentPValue .= $childNode->nodeValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$pNodeValues[] = $currentPValue;
|
$escapedCellValue = \implode("\n", $pNodeValues);
|
||||||
}
|
|
||||||
|
|
||||||
$escapedCellValue = implode("\n", $pNodeValues);
|
|
||||||
$cellValue = $this->escaper->unescape($escapedCellValue);
|
$cellValue = $this->escaper->unescape($escapedCellValue);
|
||||||
|
|
||||||
return $cellValue;
|
return $cellValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \DOMNode $pNode
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function extractTextValueFromNode($pNode)
|
||||||
|
{
|
||||||
|
$textValue = '';
|
||||||
|
|
||||||
|
foreach ($pNode->childNodes as $childNode) {
|
||||||
|
if ($childNode instanceof \DOMText) {
|
||||||
|
$textValue .= $childNode->nodeValue;
|
||||||
|
} elseif ($this->isWhitespaceNode($childNode->nodeName)) {
|
||||||
|
$textValue .= $this->transformWhitespaceNode($childNode);
|
||||||
|
} elseif ($childNode->nodeName === self::XML_NODE_TEXT_A || $childNode->nodeName === self::XML_NODE_TEXT_SPAN) {
|
||||||
|
$textValue .= $this->extractTextValueFromNode($childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $textValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the given node is a whitespace node. It must be one of these:
|
||||||
|
* - <text:s />
|
||||||
|
* - <text:tab />
|
||||||
|
* - <text:line-break />
|
||||||
|
*
|
||||||
|
* @param string $nodeName
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function isWhitespaceNode($nodeName)
|
||||||
|
{
|
||||||
|
return isset(self::$WHITESPACE_XML_NODES[$nodeName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The "<text:p>" node can contain the string value directly
|
||||||
|
* or contain child elements. In this case, whitespaces contain in
|
||||||
|
* the child elements should be replaced by their XML equivalent:
|
||||||
|
* - space => <text:s />
|
||||||
|
* - tab => <text:tab />
|
||||||
|
* - line break => <text:line-break />
|
||||||
|
*
|
||||||
|
* @see https://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1415200_253892949
|
||||||
|
*
|
||||||
|
* @param \DOMElement $node The XML node representing a whitespace
|
||||||
|
* @return string The corresponding whitespace value
|
||||||
|
*/
|
||||||
|
private function transformWhitespaceNode($node)
|
||||||
|
{
|
||||||
|
$countAttribute = $node->getAttribute(self::XML_ATTRIBUTE_C); // only defined for "<text:s>"
|
||||||
|
$numWhitespaces = (!empty($countAttribute)) ? (int) $countAttribute : 1;
|
||||||
|
|
||||||
|
return \str_repeat(self::$WHITESPACE_XML_NODES[$node->nodeName], $numWhitespaces);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cell Numeric value from the given node.
|
* Returns the cell Numeric value from the given node.
|
||||||
*
|
*
|
||||||
* @param \DOMNode $node
|
* @param \DOMElement $node
|
||||||
* @return int|float The value associated with the cell
|
* @return int|float The value associated with the cell
|
||||||
*/
|
*/
|
||||||
protected function formatFloatCellValue($node)
|
protected function formatFloatCellValue($node)
|
||||||
@@ -139,7 +190,7 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns the cell Boolean value from the given node.
|
* Returns the cell Boolean value from the given node.
|
||||||
*
|
*
|
||||||
* @param \DOMNode $node
|
* @param \DOMElement $node
|
||||||
* @return bool The value associated with the cell
|
* @return bool The value associated with the cell
|
||||||
*/
|
*/
|
||||||
protected function formatBooleanCellValue($node)
|
protected function formatBooleanCellValue($node)
|
||||||
@@ -152,7 +203,7 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns the cell Date value from the given node.
|
* 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
|
* @throws InvalidValueException If the value is not a valid date
|
||||||
* @return \DateTime|string The value associated with the cell
|
* @return \DateTime|string The value associated with the cell
|
||||||
*/
|
*/
|
||||||
@@ -183,7 +234,7 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns the cell Time value from the given node.
|
* 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
|
* @throws InvalidValueException If the value is not a valid time
|
||||||
* @return \DateInterval|string The value associated with the cell
|
* @return \DateInterval|string The value associated with the cell
|
||||||
*/
|
*/
|
||||||
@@ -214,7 +265,7 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns the cell Currency value from the given node.
|
* 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")
|
* @return string The value associated with the cell (e.g. "100 USD" or "9.99 EUR")
|
||||||
*/
|
*/
|
||||||
protected function formatCurrencyCellValue($node)
|
protected function formatCurrencyCellValue($node)
|
||||||
@@ -228,7 +279,7 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns the cell Percentage value from the given node.
|
* Returns the cell Percentage value from the given node.
|
||||||
*
|
*
|
||||||
* @param \DOMNode $node
|
* @param \DOMElement $node
|
||||||
* @return int|float The value associated with the cell
|
* @return int|float The value associated with the cell
|
||||||
*/
|
*/
|
||||||
protected function formatPercentageCellValue($node)
|
protected function formatPercentageCellValue($node)
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ use Box\Spout\Reader\ODS\Creator\InternalEntityFactory;
|
|||||||
*/
|
*/
|
||||||
class SettingsHelper
|
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 */
|
/** Definition of XML nodes name and attribute used to parse settings data */
|
||||||
const XML_NODE_CONFIG_ITEM = 'config:config-item';
|
public const XML_NODE_CONFIG_ITEM = 'config:config-item';
|
||||||
const XML_ATTRIBUTE_CONFIG_NAME = 'config:name';
|
public const XML_ATTRIBUTE_CONFIG_NAME = 'config:name';
|
||||||
const XML_ATTRIBUTE_VALUE_ACTIVE_TABLE = 'ActiveTable';
|
public const XML_ATTRIBUTE_VALUE_ACTIVE_TABLE = 'ActiveTable';
|
||||||
|
|
||||||
/** @var InternalEntityFactory Factory to create entities */
|
/** @var InternalEntityFactory Factory to create entities */
|
||||||
private $entityFactory;
|
private $entityFactory;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Reader extends ReaderAbstract
|
|||||||
*/
|
*/
|
||||||
protected function closeReader()
|
protected function closeReader()
|
||||||
{
|
{
|
||||||
if ($this->zip) {
|
if ($this->zip !== null) {
|
||||||
$this->zip->close();
|
$this->zip->close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ use Box\Spout\Reader\Wrapper\XMLReader;
|
|||||||
class RowIterator implements IteratorInterface
|
class RowIterator implements IteratorInterface
|
||||||
{
|
{
|
||||||
/** Definition of XML nodes names used to parse data */
|
/** Definition of XML nodes names used to parse data */
|
||||||
const XML_NODE_TABLE = 'table:table';
|
public const XML_NODE_TABLE = 'table:table';
|
||||||
const XML_NODE_ROW = 'table:table-row';
|
public const XML_NODE_ROW = 'table:table-row';
|
||||||
const XML_NODE_CELL = 'table:table-cell';
|
public const XML_NODE_CELL = 'table:table-cell';
|
||||||
const MAX_COLUMNS_EXCEL = 16384;
|
public const MAX_COLUMNS_EXCEL = 16384;
|
||||||
|
|
||||||
/** Definition of XML attribute used to parse data */
|
/** Definition of XML attribute used to parse data */
|
||||||
const XML_ATTRIBUTE_NUM_ROWS_REPEATED = 'table:number-rows-repeated';
|
public 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_COLUMNS_REPEATED = 'table:number-columns-repeated';
|
||||||
|
|
||||||
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
||||||
protected $xmlReader;
|
protected $xmlReader;
|
||||||
@@ -56,7 +56,7 @@ class RowIterator implements IteratorInterface
|
|||||||
/** @var Row The currently processed row */
|
/** @var Row The currently processed row */
|
||||||
protected $currentlyProcessedRow;
|
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;
|
protected $rowBuffer;
|
||||||
|
|
||||||
/** @var bool Indicates whether all rows have been read */
|
/** @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) */
|
/** @var int Row index to be processed next (one-based) */
|
||||||
protected $nextRowIndexToBeProcessed = 1;
|
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;
|
protected $lastProcessedCell;
|
||||||
|
|
||||||
/** @var int Number of times the last processed row should be repeated */
|
/** @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
|
* @throws \Box\Spout\Reader\Exception\IteratorNotRewindableException If the iterator is rewound more than once
|
||||||
* @return void
|
* @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
|
// 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.
|
// sheet iterator and the row iterator, as XML file cannot be read backwards.
|
||||||
@@ -142,7 +142,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function valid()
|
public function valid() : bool
|
||||||
{
|
{
|
||||||
return (!$this->hasReachedEndOfFile);
|
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
|
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function next()
|
public function next() : void
|
||||||
{
|
{
|
||||||
if ($this->doesNeedDataForNextRowToBeProcessed()) {
|
if ($this->doesNeedDataForNextRowToBeProcessed()) {
|
||||||
$this->readDataForNextRow();
|
$this->readDataForNextRow();
|
||||||
@@ -225,6 +225,7 @@ class RowIterator implements IteratorInterface
|
|||||||
$currentNumColumnsRepeated = $this->getNumColumnsRepeatedForCurrentNode($xmlReader);
|
$currentNumColumnsRepeated = $this->getNumColumnsRepeatedForCurrentNode($xmlReader);
|
||||||
|
|
||||||
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
||||||
|
/** @var \DOMElement $node */
|
||||||
$node = $xmlReader->expand();
|
$node = $xmlReader->expand();
|
||||||
$currentCell = $this->getCell($node);
|
$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.
|
* 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
|
* @return Cell The cell set with the associated with the cell
|
||||||
*/
|
*/
|
||||||
protected function getCell($node)
|
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).
|
* row data yet (as we still need to apply the "num-columns-repeated" attribute).
|
||||||
*
|
*
|
||||||
* @param Row $currentRow
|
* @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
|
* @return bool Whether the row is empty
|
||||||
*/
|
*/
|
||||||
protected function isEmptyRow($currentRow, $lastReadCell)
|
protected function isEmptyRow($currentRow, $lastReadCell)
|
||||||
@@ -356,7 +357,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return Row
|
* @return Row
|
||||||
*/
|
*/
|
||||||
public function current()
|
public function current() : Row
|
||||||
{
|
{
|
||||||
return $this->rowBuffer;
|
return $this->rowBuffer;
|
||||||
}
|
}
|
||||||
@@ -367,7 +368,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function key()
|
public function key() : int
|
||||||
{
|
{
|
||||||
return $this->lastRowIndexProcessed;
|
return $this->lastRowIndexProcessed;
|
||||||
}
|
}
|
||||||
@@ -377,7 +378,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function end()
|
public function end() : void
|
||||||
{
|
{
|
||||||
$this->xmlReader->close();
|
$this->xmlReader->close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,26 +15,26 @@ use Box\Spout\Reader\Wrapper\XMLReader;
|
|||||||
*/
|
*/
|
||||||
class SheetIterator implements IteratorInterface
|
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 */
|
/** Definition of XML nodes name and attribute used to parse sheet data */
|
||||||
const XML_NODE_AUTOMATIC_STYLES = 'office:automatic-styles';
|
public const XML_NODE_AUTOMATIC_STYLES = 'office:automatic-styles';
|
||||||
const XML_NODE_STYLE_TABLE_PROPERTIES = 'table-properties';
|
public const XML_NODE_STYLE_TABLE_PROPERTIES = 'table-properties';
|
||||||
const XML_NODE_TABLE = 'table:table';
|
public const XML_NODE_TABLE = 'table:table';
|
||||||
const XML_ATTRIBUTE_STYLE_NAME = 'style:name';
|
public const XML_ATTRIBUTE_STYLE_NAME = 'style:name';
|
||||||
const XML_ATTRIBUTE_TABLE_NAME = 'table:name';
|
public const XML_ATTRIBUTE_TABLE_NAME = 'table:name';
|
||||||
const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name';
|
public const XML_ATTRIBUTE_TABLE_STYLE_NAME = 'table:style-name';
|
||||||
const XML_ATTRIBUTE_TABLE_DISPLAY = 'table:display';
|
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;
|
protected $filePath;
|
||||||
|
|
||||||
/** @var \Box\Spout\Common\Manager\OptionsManagerInterface Reader's options manager */
|
/** @var \Box\Spout\Common\Manager\OptionsManagerInterface Reader's options manager */
|
||||||
protected $optionsManager;
|
protected $optionsManager;
|
||||||
|
|
||||||
/** @var InternalEntityFactory $entityFactory Factory to create entities */
|
/** @var InternalEntityFactory Factory to create entities */
|
||||||
protected $entityFactory;
|
protected $entityFactory;
|
||||||
|
|
||||||
/** @var XMLReader The XMLReader object that will help read sheet's XML data */
|
/** @var XMLReader The XMLReader object that will help read sheet's XML data */
|
||||||
@@ -108,6 +108,7 @@ class SheetIterator implements IteratorInterface
|
|||||||
$sheetsVisibility = [];
|
$sheetsVisibility = [];
|
||||||
|
|
||||||
$this->xmlReader->readUntilNodeFound(self::XML_NODE_AUTOMATIC_STYLES);
|
$this->xmlReader->readUntilNodeFound(self::XML_NODE_AUTOMATIC_STYLES);
|
||||||
|
/** @var \DOMElement $automaticStylesNode */
|
||||||
$automaticStylesNode = $this->xmlReader->expand();
|
$automaticStylesNode = $this->xmlReader->expand();
|
||||||
|
|
||||||
$tableStyleNodes = $automaticStylesNode->getElementsByTagNameNS(self::XML_STYLE_NAMESPACE, self::XML_NODE_STYLE_TABLE_PROPERTIES);
|
$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) {
|
foreach ($tableStyleNodes as $tableStyleNode) {
|
||||||
$isSheetVisible = ($tableStyleNode->getAttribute(self::XML_ATTRIBUTE_TABLE_DISPLAY) !== 'false');
|
$isSheetVisible = ($tableStyleNode->getAttribute(self::XML_ATTRIBUTE_TABLE_DISPLAY) !== 'false');
|
||||||
|
|
||||||
|
/** @var \DOMElement $parentStyleNode */
|
||||||
$parentStyleNode = $tableStyleNode->parentNode;
|
$parentStyleNode = $tableStyleNode->parentNode;
|
||||||
$styleName = $parentStyleNode->getAttribute(self::XML_ATTRIBUTE_STYLE_NAME);
|
$styleName = $parentStyleNode->getAttribute(self::XML_ATTRIBUTE_STYLE_NAME);
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ abstract class ReaderAbstract implements ReaderInterface
|
|||||||
/**
|
/**
|
||||||
* Closes the reader. To be used after reading the file.
|
* Closes the reader. To be used after reading the file.
|
||||||
*
|
*
|
||||||
* @return ReaderAbstract
|
* @return void
|
||||||
*/
|
*/
|
||||||
abstract protected function closeReader();
|
abstract protected function closeReader();
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ abstract class ReaderAbstract implements ReaderInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Need to use realpath to fix "Can't open file" on some Windows setup
|
// Need to use realpath to fix "Can't open file" on some Windows setup
|
||||||
return realpath($filePath);
|
return \realpath($filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,7 +158,7 @@ abstract class ReaderAbstract implements ReaderInterface
|
|||||||
protected function getStreamWrapperScheme($filePath)
|
protected function getStreamWrapperScheme($filePath)
|
||||||
{
|
{
|
||||||
$streamScheme = null;
|
$streamScheme = null;
|
||||||
if (preg_match('/^(\w+):\/\//', $filePath, $matches)) {
|
if (\preg_match('/^(\w+):\/\//', $filePath, $matches)) {
|
||||||
$streamScheme = $matches[1];
|
$streamScheme = $matches[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ abstract class ReaderAbstract implements ReaderInterface
|
|||||||
$streamScheme = $this->getStreamWrapperScheme($filePath);
|
$streamScheme = $this->getStreamWrapperScheme($filePath);
|
||||||
|
|
||||||
return ($streamScheme !== null) ?
|
return ($streamScheme !== null) ?
|
||||||
in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) :
|
\in_array($streamScheme, $this->globalFunctionsHelper->stream_get_wrappers()) :
|
||||||
true;
|
true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ abstract class ReaderAbstract implements ReaderInterface
|
|||||||
$this->closeReader();
|
$this->closeReader();
|
||||||
|
|
||||||
$sheetIterator = $this->getConcreteSheetIterator();
|
$sheetIterator = $this->getConcreteSheetIterator();
|
||||||
if ($sheetIterator) {
|
if ($sheetIterator !== null) {
|
||||||
$sheetIterator->end();
|
$sheetIterator->end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ trait XMLInternalErrorsHelper
|
|||||||
*/
|
*/
|
||||||
protected function useXMLInternalErrors()
|
protected function useXMLInternalErrors()
|
||||||
{
|
{
|
||||||
libxml_clear_errors();
|
\libxml_clear_errors();
|
||||||
$this->initialUseInternalErrorsValue = libxml_use_internal_errors(true);
|
$this->initialUseInternalErrorsValue = \libxml_use_internal_errors(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +48,7 @@ trait XMLInternalErrorsHelper
|
|||||||
*/
|
*/
|
||||||
private function hasXMLErrorOccured()
|
private function hasXMLErrorOccured()
|
||||||
{
|
{
|
||||||
return (libxml_get_last_error() !== false);
|
return (\libxml_get_last_error() !== false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,10 +60,10 @@ trait XMLInternalErrorsHelper
|
|||||||
private function getLastXMLErrorMessage()
|
private function getLastXMLErrorMessage()
|
||||||
{
|
{
|
||||||
$errorMessage = null;
|
$errorMessage = null;
|
||||||
$error = libxml_get_last_error();
|
$error = \libxml_get_last_error();
|
||||||
|
|
||||||
if ($error !== false) {
|
if ($error !== false) {
|
||||||
$errorMessage = trim($error->message);
|
$errorMessage = \trim($error->message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $errorMessage;
|
return $errorMessage;
|
||||||
@@ -74,6 +74,6 @@ trait XMLInternalErrorsHelper
|
|||||||
*/
|
*/
|
||||||
protected function resetXMLInternalErrorsSetting()
|
protected function resetXMLInternalErrorsSetting()
|
||||||
{
|
{
|
||||||
libxml_use_internal_errors($this->initialUseInternalErrorsValue);
|
\libxml_use_internal_errors($this->initialUseInternalErrorsValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ class XMLReader extends \XMLReader
|
|||||||
{
|
{
|
||||||
use XMLInternalErrorsHelper;
|
use XMLInternalErrorsHelper;
|
||||||
|
|
||||||
const ZIP_WRAPPER = 'zip://';
|
public const ZIP_WRAPPER = 'zip://';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the XML Reader to read a file located inside a ZIP file.
|
* Opens the XML Reader to read a file located inside a ZIP file.
|
||||||
@@ -46,9 +46,9 @@ class XMLReader extends \XMLReader
|
|||||||
public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
|
public function getRealPathURIForFileInZip($zipFilePath, $fileInsideZipPath)
|
||||||
{
|
{
|
||||||
// The file path should not start with a '/', otherwise it won't be found
|
// The file path should not start with a '/', otherwise it won't be found
|
||||||
$fileInsideZipPathWithoutLeadingSlash = ltrim($fileInsideZipPath, '/');
|
$fileInsideZipPathWithoutLeadingSlash = \ltrim($fileInsideZipPath, '/');
|
||||||
|
|
||||||
return (self::ZIP_WRAPPER . realpath($zipFilePath) . '#' . $fileInsideZipPathWithoutLeadingSlash);
|
return (self::ZIP_WRAPPER . \realpath($zipFilePath) . '#' . $fileInsideZipPathWithoutLeadingSlash);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,7 +62,7 @@ class XMLReader extends \XMLReader
|
|||||||
$doesFileExists = false;
|
$doesFileExists = false;
|
||||||
|
|
||||||
$pattern = '/zip:\/\/([^#]+)#(.*)/';
|
$pattern = '/zip:\/\/([^#]+)#(.*)/';
|
||||||
if (preg_match($pattern, $zipStreamURI, $matches)) {
|
if (\preg_match($pattern, $zipStreamURI, $matches)) {
|
||||||
$zipFilePath = $matches[1];
|
$zipFilePath = $matches[1];
|
||||||
$innerFilePath = $matches[2];
|
$innerFilePath = $matches[2];
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ class XMLReader extends \XMLReader
|
|||||||
// In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
|
// In some cases, the node has a prefix (for instance, "<sheet>" can also be "<x:sheet>").
|
||||||
// So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
|
// So if the given node name does not have a prefix, we need to look at the unprefixed name ("localName").
|
||||||
// @see https://github.com/box/spout/issues/233
|
// @see https://github.com/box/spout/issues/233
|
||||||
$hasPrefix = (strpos($nodeName, ':') !== false);
|
$hasPrefix = (\strpos($nodeName, ':') !== false);
|
||||||
$currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
|
$currentNodeName = ($hasPrefix) ? $this->name : $this->localName;
|
||||||
|
|
||||||
return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);
|
return ($this->nodeType === $nodeType && $currentNodeName === $nodeName);
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class InternalEntityFactory implements InternalEntityFactoryInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $xmlReader
|
* @param XMLReader $xmlReader
|
||||||
* @return XMLProcessor
|
* @return XMLProcessor
|
||||||
*/
|
*/
|
||||||
public function createXMLProcessor($xmlReader)
|
public function createXMLProcessor($xmlReader)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ManagerFactory
|
|||||||
/** @var CachingStrategyFactory */
|
/** @var CachingStrategyFactory */
|
||||||
private $cachingStrategyFactory;
|
private $cachingStrategyFactory;
|
||||||
|
|
||||||
/** @var WorkbookRelationshipsManager */
|
/** @var WorkbookRelationshipsManager|null */
|
||||||
private $cachedWorkbookRelationshipsManager;
|
private $cachedWorkbookRelationshipsManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class CellHelper
|
|||||||
$columnIndex = 0;
|
$columnIndex = 0;
|
||||||
|
|
||||||
// Remove row information
|
// Remove row information
|
||||||
$columnLetters = preg_replace('/\d/', '', $cellIndex);
|
$columnLetters = \preg_replace('/\d/', '', $cellIndex);
|
||||||
|
|
||||||
// strlen() is super slow too... Using isset() is way faster and not too unreadable,
|
// strlen() is super slow too... Using isset() is way faster and not too unreadable,
|
||||||
// since we checked before that there are between 1 and 3 letters.
|
// since we checked before that there are between 1 and 3 letters.
|
||||||
@@ -75,6 +75,6 @@ class CellHelper
|
|||||||
*/
|
*/
|
||||||
protected static function isValidCellIndex($cellIndex)
|
protected static function isValidCellIndex($cellIndex)
|
||||||
{
|
{
|
||||||
return (preg_match('/^[A-Z]{1,3}\d+$/', $cellIndex) === 1);
|
return (\preg_match('/^[A-Z]{1,3}\d+$/', $cellIndex) === 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,32 +13,24 @@ use Box\Spout\Reader\XLSX\Manager\StyleManager;
|
|||||||
class CellValueFormatter
|
class CellValueFormatter
|
||||||
{
|
{
|
||||||
/** Definition of all possible cell types */
|
/** Definition of all possible cell types */
|
||||||
const CELL_TYPE_INLINE_STRING = 'inlineStr';
|
public const CELL_TYPE_INLINE_STRING = 'inlineStr';
|
||||||
const CELL_TYPE_STR = 'str';
|
public const CELL_TYPE_STR = 'str';
|
||||||
const CELL_TYPE_SHARED_STRING = 's';
|
public const CELL_TYPE_SHARED_STRING = 's';
|
||||||
const CELL_TYPE_BOOLEAN = 'b';
|
public const CELL_TYPE_BOOLEAN = 'b';
|
||||||
const CELL_TYPE_NUMERIC = 'n';
|
public const CELL_TYPE_NUMERIC = 'n';
|
||||||
const CELL_TYPE_DATE = 'd';
|
public const CELL_TYPE_DATE = 'd';
|
||||||
const CELL_TYPE_ERROR = 'e';
|
public const CELL_TYPE_ERROR = 'e';
|
||||||
|
|
||||||
/** Definition of XML nodes names used to parse data */
|
/** Definition of XML nodes names used to parse data */
|
||||||
const XML_NODE_VALUE = 'v';
|
public const XML_NODE_VALUE = 'v';
|
||||||
const XML_NODE_INLINE_STRING_VALUE = 't';
|
public const XML_NODE_INLINE_STRING_VALUE = 't';
|
||||||
|
|
||||||
/** Definition of XML attributes used to parse data */
|
/** Definition of XML attributes used to parse data */
|
||||||
const XML_ATTRIBUTE_TYPE = 't';
|
public const XML_ATTRIBUTE_TYPE = 't';
|
||||||
const XML_ATTRIBUTE_STYLE_ID = 's';
|
public const XML_ATTRIBUTE_STYLE_ID = 's';
|
||||||
|
|
||||||
/** Constants used for date formatting */
|
/** Constants used for date formatting */
|
||||||
const NUM_SECONDS_IN_ONE_DAY = 86400;
|
public const NUM_SECONDS_IN_ONE_DAY = 86400;
|
||||||
const NUM_SECONDS_IN_ONE_HOUR = 3600;
|
|
||||||
const NUM_SECONDS_IN_ONE_MINUTE = 60;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* February 29th, 1900 is NOT a leap year but Excel thinks it is...
|
|
||||||
* @see https://en.wikipedia.org/wiki/Year_1900_problem#Microsoft_Excel
|
|
||||||
*/
|
|
||||||
const ERRONEOUS_EXCEL_LEAP_YEAR_DAY = 60;
|
|
||||||
|
|
||||||
/** @var SharedStringsManager Manages shared strings */
|
/** @var SharedStringsManager Manages shared strings */
|
||||||
protected $sharedStringsManager;
|
protected $sharedStringsManager;
|
||||||
@@ -74,7 +66,7 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns the (unescaped) correctly marshalled, cell value associated to the given XML node.
|
* 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
|
* @throws InvalidValueException If the value is not valid
|
||||||
* @return string|int|float|bool|\DateTime The value associated with the cell
|
* @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
|
* 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
|
* @return string The value associated with the cell
|
||||||
*/
|
*/
|
||||||
protected function getVNodeValue($node)
|
protected function getVNodeValue($node)
|
||||||
@@ -125,15 +117,20 @@ class CellValueFormatter
|
|||||||
/**
|
/**
|
||||||
* Returns the cell String value where string is inline.
|
* Returns the cell String value where string is inline.
|
||||||
*
|
*
|
||||||
* @param \DOMNode $node
|
* @param \DOMElement $node
|
||||||
* @return string The value associated with the cell
|
* @return string The value associated with the cell
|
||||||
*/
|
*/
|
||||||
protected function formatInlineStringCellValue($node)
|
protected function formatInlineStringCellValue($node)
|
||||||
{
|
{
|
||||||
// inline strings are formatted this way:
|
// inline strings are formatted this way (they can contain any number of <t> nodes):
|
||||||
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t></is></c>
|
// <c r="A1" t="inlineStr"><is><t>[INLINE_STRING]</t><t>[INLINE_STRING_2]</t></is></c>
|
||||||
$tNode = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE)->item(0);
|
$tNodes = $node->getElementsByTagName(self::XML_NODE_INLINE_STRING_VALUE);
|
||||||
$cellValue = $this->escaper->unescape($tNode->nodeValue);
|
|
||||||
|
$cellValue = '';
|
||||||
|
for ($i = 0; $i < $tNodes->count(); $i++) {
|
||||||
|
$tNode = $tNodes->item($i);
|
||||||
|
$cellValue .= $this->escaper->unescape($tNode->nodeValue);
|
||||||
|
}
|
||||||
|
|
||||||
return $cellValue;
|
return $cellValue;
|
||||||
}
|
}
|
||||||
@@ -163,7 +160,7 @@ class CellValueFormatter
|
|||||||
*/
|
*/
|
||||||
protected function formatStrCellValue($nodeValue)
|
protected function formatStrCellValue($nodeValue)
|
||||||
{
|
{
|
||||||
$escapedCellValue = trim($nodeValue);
|
$escapedCellValue = \trim($nodeValue);
|
||||||
$cellValue = $this->escaper->unescape($escapedCellValue);
|
$cellValue = $this->escaper->unescape($escapedCellValue);
|
||||||
|
|
||||||
return $cellValue;
|
return $cellValue;
|
||||||
@@ -248,8 +245,8 @@ class CellValueFormatter
|
|||||||
$baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30';
|
$baseDate = $this->shouldUse1904Dates ? '1904-01-01' : '1899-12-30';
|
||||||
|
|
||||||
$daysSinceBaseDate = (int) $nodeValue;
|
$daysSinceBaseDate = (int) $nodeValue;
|
||||||
$timeRemainder = fmod($nodeValue, 1);
|
$timeRemainder = \fmod($nodeValue, 1);
|
||||||
$secondsRemainder = round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0);
|
$secondsRemainder = \round($timeRemainder * self::NUM_SECONDS_IN_ONE_DAY, 0);
|
||||||
|
|
||||||
$dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate);
|
$dateObj = \DateTime::createFromFormat('|Y-m-d', $baseDate);
|
||||||
$dateObj->modify('+' . $daysSinceBaseDate . 'days');
|
$dateObj->modify('+' . $daysSinceBaseDate . 'days');
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ namespace Box\Spout\Reader\XLSX\Helper;
|
|||||||
*/
|
*/
|
||||||
class DateFormatHelper
|
class DateFormatHelper
|
||||||
{
|
{
|
||||||
const KEY_GENERAL = 'general';
|
public const KEY_GENERAL = 'general';
|
||||||
const KEY_HOUR_12 = '12h';
|
public const KEY_HOUR_12 = '12h';
|
||||||
const KEY_HOUR_24 = '24h';
|
public const KEY_HOUR_24 = '24h';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This map is used to replace Excel format characters by their PHP equivalent.
|
* This map is used to replace Excel format characters by their PHP equivalent.
|
||||||
@@ -62,13 +62,13 @@ class DateFormatHelper
|
|||||||
// Remove brackets potentially present at the beginning of the format string
|
// Remove brackets potentially present at the beginning of the format string
|
||||||
// and text portion of the format at the end of it (starting with ";")
|
// and text portion of the format at the end of it (starting with ";")
|
||||||
// See §18.8.31 of ECMA-376 for more detail.
|
// See §18.8.31 of ECMA-376 for more detail.
|
||||||
$dateFormat = preg_replace('/^(?:\[\$[^\]]+?\])?([^;]*).*/', '$1', $excelDateFormat);
|
$dateFormat = \preg_replace('/^(?:\[\$[^\]]+?\])?([^;]*).*/', '$1', $excelDateFormat);
|
||||||
|
|
||||||
// Double quotes are used to escape characters that must not be interpreted.
|
// Double quotes are used to escape characters that must not be interpreted.
|
||||||
// For instance, ["Day " dd] should result in "Day 13" and we should not try to interpret "D", "a", "y"
|
// For instance, ["Day " dd] should result in "Day 13" and we should not try to interpret "D", "a", "y"
|
||||||
// By exploding the format string using double quote as a delimiter, we can get all parts
|
// By exploding the format string using double quote as a delimiter, we can get all parts
|
||||||
// that must be transformed (even indexes) and all parts that must not be (odd indexes).
|
// that must be transformed (even indexes) and all parts that must not be (odd indexes).
|
||||||
$dateFormatParts = explode('"', $dateFormat);
|
$dateFormatParts = \explode('"', $dateFormat);
|
||||||
|
|
||||||
foreach ($dateFormatParts as $partIndex => $dateFormatPart) {
|
foreach ($dateFormatParts as $partIndex => $dateFormatPart) {
|
||||||
// do not look at odd indexes
|
// do not look at odd indexes
|
||||||
@@ -77,19 +77,19 @@ class DateFormatHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make sure all characters are lowercase, as the mapping table is using lowercase characters
|
// Make sure all characters are lowercase, as the mapping table is using lowercase characters
|
||||||
$transformedPart = strtolower($dateFormatPart);
|
$transformedPart = \strtolower($dateFormatPart);
|
||||||
|
|
||||||
// Remove escapes related to non-format characters
|
// Remove escapes related to non-format characters
|
||||||
$transformedPart = str_replace('\\', '', $transformedPart);
|
$transformedPart = \str_replace('\\', '', $transformedPart);
|
||||||
|
|
||||||
// Apply general transformation first...
|
// Apply general transformation first...
|
||||||
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_GENERAL]);
|
$transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_GENERAL]);
|
||||||
|
|
||||||
// ... then apply hour transformation, for 12-hour or 24-hour format
|
// ... then apply hour transformation, for 12-hour or 24-hour format
|
||||||
if (self::has12HourFormatMarker($dateFormatPart)) {
|
if (self::has12HourFormatMarker($dateFormatPart)) {
|
||||||
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]);
|
$transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_12]);
|
||||||
} else {
|
} else {
|
||||||
$transformedPart = strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_24]);
|
$transformedPart = \strtr($transformedPart, self::$excelDateFormatToPHPDateFormatMapping[self::KEY_HOUR_24]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// overwrite the parts array with the new transformed part
|
// overwrite the parts array with the new transformed part
|
||||||
@@ -97,16 +97,16 @@ class DateFormatHelper
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Merge all transformed parts back together
|
// Merge all transformed parts back together
|
||||||
$phpDateFormat = implode('"', $dateFormatParts);
|
$phpDateFormat = \implode('"', $dateFormatParts);
|
||||||
|
|
||||||
// Finally, to have the date format compatible with the DateTime::format() function, we need to escape
|
// Finally, to have the date format compatible with the DateTime::format() function, we need to escape
|
||||||
// all characters that are inside double quotes (and double quotes must be removed).
|
// all characters that are inside double quotes (and double quotes must be removed).
|
||||||
// For instance, ["Day " dd] should become [\D\a\y\ dd]
|
// For instance, ["Day " dd] should become [\D\a\y\ dd]
|
||||||
$phpDateFormat = preg_replace_callback('/"(.+?)"/', function ($matches) {
|
$phpDateFormat = \preg_replace_callback('/"(.+?)"/', function ($matches) {
|
||||||
$stringToEscape = $matches[1];
|
$stringToEscape = $matches[1];
|
||||||
$letters = preg_split('//u', $stringToEscape, -1, PREG_SPLIT_NO_EMPTY);
|
$letters = \preg_split('//u', $stringToEscape, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
return '\\' . implode('\\', $letters);
|
return '\\' . \implode('\\', $letters);
|
||||||
}, $phpDateFormat);
|
}, $phpDateFormat);
|
||||||
|
|
||||||
return $phpDateFormat;
|
return $phpDateFormat;
|
||||||
@@ -118,6 +118,6 @@ class DateFormatHelper
|
|||||||
*/
|
*/
|
||||||
private static function has12HourFormatMarker($excelDateFormat)
|
private static function has12HourFormatMarker($excelDateFormat)
|
||||||
{
|
{
|
||||||
return (stripos($excelDateFormat, 'am/pm') !== false);
|
return (\stripos($excelDateFormat, 'am/pm') !== false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ class OptionsManager extends OptionsManagerAbstract
|
|||||||
*/
|
*/
|
||||||
protected function setDefaultOptions()
|
protected function setDefaultOptions()
|
||||||
{
|
{
|
||||||
$this->setOption(Options::TEMP_FOLDER, sys_get_temp_dir());
|
$this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
|
||||||
$this->setOption(Options::SHOULD_FORMAT_DATES, false);
|
$this->setOption(Options::SHOULD_FORMAT_DATES, false);
|
||||||
$this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
|
$this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
|
||||||
$this->setOption(Options::SHOULD_USE_1904_DATES, false);
|
$this->setOption(Options::SHOULD_USE_1904_DATES, false);
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class CachingStrategyFactory
|
|||||||
* This means that in order to store one shared string in memory, the memory amount needed is:
|
* This means that in order to store one shared string in memory, the memory amount needed is:
|
||||||
* => 20 * 600 ≈ 12KB
|
* => 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
|
* 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.
|
* 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.
|
* 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
|
* Returns the best caching strategy, given the number of unique shared strings
|
||||||
@@ -84,7 +84,7 @@ class CachingStrategyFactory
|
|||||||
|
|
||||||
$memoryAvailable = $this->getMemoryLimitInKB();
|
$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
|
// 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);
|
$isInMemoryStrategyUsageSafe = ($sharedStringsUniqueCount < self::MAX_NUM_STRINGS_PER_TEMP_FILE);
|
||||||
} else {
|
} else {
|
||||||
@@ -103,14 +103,14 @@ class CachingStrategyFactory
|
|||||||
protected function getMemoryLimitInKB()
|
protected function getMemoryLimitInKB()
|
||||||
{
|
{
|
||||||
$memoryLimitFormatted = $this->getMemoryLimitFromIni();
|
$memoryLimitFormatted = $this->getMemoryLimitFromIni();
|
||||||
$memoryLimitFormatted = strtolower(trim($memoryLimitFormatted));
|
$memoryLimitFormatted = \strtolower(\trim($memoryLimitFormatted));
|
||||||
|
|
||||||
// No memory limit
|
// No memory limit
|
||||||
if ($memoryLimitFormatted === '-1') {
|
if ($memoryLimitFormatted === '-1') {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) {
|
if (\preg_match('/(\d+)([bkmgt])b?/', $memoryLimitFormatted, $matches)) {
|
||||||
$amount = (int) ($matches[1]);
|
$amount = (int) ($matches[1]);
|
||||||
$unit = $matches[2];
|
$unit = $matches[2];
|
||||||
|
|
||||||
@@ -133,6 +133,6 @@ class CachingStrategyFactory
|
|||||||
*/
|
*/
|
||||||
protected function getMemoryLimitFromIni()
|
protected function getMemoryLimitFromIni()
|
||||||
{
|
{
|
||||||
return ini_get('memory_limit');
|
return \ini_get('memory_limit');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ use Box\Spout\Reader\XLSX\Creator\HelperFactory;
|
|||||||
class FileBasedStrategy implements CachingStrategyInterface
|
class FileBasedStrategy implements CachingStrategyInterface
|
||||||
{
|
{
|
||||||
/** Value to use to escape the line feed character ("\n") */
|
/** 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 */
|
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
|
||||||
protected $globalFunctionsHelper;
|
protected $globalFunctionsHelper;
|
||||||
@@ -32,7 +32,7 @@ class FileBasedStrategy implements CachingStrategyInterface
|
|||||||
*/
|
*/
|
||||||
protected $maxNumStringsPerTempFile;
|
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;
|
protected $tempFilePointer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +55,7 @@ class FileBasedStrategy implements CachingStrategyInterface
|
|||||||
public function __construct($tempFolder, $maxNumStringsPerTempFile, $helperFactory)
|
public function __construct($tempFolder, $maxNumStringsPerTempFile, $helperFactory)
|
||||||
{
|
{
|
||||||
$this->fileSystemHelper = $helperFactory->createFileSystemHelper($tempFolder);
|
$this->fileSystemHelper = $helperFactory->createFileSystemHelper($tempFolder);
|
||||||
$this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, uniqid('sharedstrings'));
|
$this->tempFolder = $this->fileSystemHelper->createFolder($tempFolder, \uniqid('sharedstrings'));
|
||||||
|
|
||||||
$this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile;
|
$this->maxNumStringsPerTempFile = $maxNumStringsPerTempFile;
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ class FileBasedStrategy implements CachingStrategyInterface
|
|||||||
// free memory
|
// free memory
|
||||||
unset($this->inMemoryTempFileContents);
|
unset($this->inMemoryTempFileContents);
|
||||||
|
|
||||||
$this->inMemoryTempFileContents = explode(PHP_EOL, $this->globalFunctionsHelper->file_get_contents($tempFilePath));
|
$this->inMemoryTempFileContents = \explode(PHP_EOL, $this->globalFunctionsHelper->file_get_contents($tempFilePath));
|
||||||
$this->inMemoryTempFilePath = $tempFilePath;
|
$this->inMemoryTempFilePath = $tempFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ class FileBasedStrategy implements CachingStrategyInterface
|
|||||||
throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
|
throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
|
||||||
}
|
}
|
||||||
|
|
||||||
return rtrim($sharedString, PHP_EOL);
|
return \rtrim($sharedString, PHP_EOL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,7 +162,7 @@ class FileBasedStrategy implements CachingStrategyInterface
|
|||||||
*/
|
*/
|
||||||
private function escapeLineFeed($unescapedString)
|
private function escapeLineFeed($unescapedString)
|
||||||
{
|
{
|
||||||
return str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString);
|
return \str_replace("\n", self::ESCAPED_LINE_FEED_CHARACTER, $unescapedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,7 +173,7 @@ class FileBasedStrategy implements CachingStrategyInterface
|
|||||||
*/
|
*/
|
||||||
private function unescapeLineFeed($escapedString)
|
private function unescapeLineFeed($escapedString)
|
||||||
{
|
{
|
||||||
return str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString);
|
return \str_replace(self::ESCAPED_LINE_FEED_CHARACTER, "\n", $escapedString);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -16,20 +16,17 @@ use Box\Spout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyInterface;
|
|||||||
*/
|
*/
|
||||||
class SharedStringsManager
|
class SharedStringsManager
|
||||||
{
|
{
|
||||||
/** Main namespace for the sharedStrings.xml file */
|
|
||||||
const MAIN_NAMESPACE_FOR_SHARED_STRINGS_XML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
|
||||||
|
|
||||||
/** Definition of XML nodes names used to parse data */
|
/** Definition of XML nodes names used to parse data */
|
||||||
const XML_NODE_SST = 'sst';
|
public const XML_NODE_SST = 'sst';
|
||||||
const XML_NODE_SI = 'si';
|
public const XML_NODE_SI = 'si';
|
||||||
const XML_NODE_R = 'r';
|
public const XML_NODE_R = 'r';
|
||||||
const XML_NODE_T = 't';
|
public const XML_NODE_T = 't';
|
||||||
|
|
||||||
/** Definition of XML attributes used to parse data */
|
/** Definition of XML attributes used to parse data */
|
||||||
const XML_ATTRIBUTE_COUNT = 'count';
|
public const XML_ATTRIBUTE_COUNT = 'count';
|
||||||
const XML_ATTRIBUTE_UNIQUE_COUNT = 'uniqueCount';
|
public const XML_ATTRIBUTE_UNIQUE_COUNT = 'uniqueCount';
|
||||||
const XML_ATTRIBUTE_XML_SPACE = 'xml:space';
|
public const XML_ATTRIBUTE_XML_SPACE = 'xml:space';
|
||||||
const XML_ATTRIBUTE_VALUE_PRESERVE = 'preserve';
|
public const XML_ATTRIBUTE_VALUE_PRESERVE = 'preserve';
|
||||||
|
|
||||||
/** @var string Path of the XLSX file being read */
|
/** @var string Path of the XLSX file being read */
|
||||||
protected $filePath;
|
protected $filePath;
|
||||||
@@ -43,7 +40,7 @@ class SharedStringsManager
|
|||||||
/** @var InternalEntityFactory Factory to create entities */
|
/** @var InternalEntityFactory Factory to create entities */
|
||||||
protected $entityFactory;
|
protected $entityFactory;
|
||||||
|
|
||||||
/** @var HelperFactory $helperFactory Factory to create helpers */
|
/** @var HelperFactory Factory to create helpers */
|
||||||
protected $helperFactory;
|
protected $helperFactory;
|
||||||
|
|
||||||
/** @var CachingStrategyFactory Factory to create shared strings caching strategies */
|
/** @var CachingStrategyFactory Factory to create shared strings caching strategies */
|
||||||
@@ -182,6 +179,7 @@ class SharedStringsManager
|
|||||||
$sharedStringValue = '';
|
$sharedStringValue = '';
|
||||||
|
|
||||||
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
||||||
|
/** @var \DOMElement $siNode */
|
||||||
$siNode = $xmlReader->expand();
|
$siNode = $xmlReader->expand();
|
||||||
$textNodes = $siNode->getElementsByTagName(self::XML_NODE_T);
|
$textNodes = $siNode->getElementsByTagName(self::XML_NODE_T);
|
||||||
|
|
||||||
@@ -190,7 +188,7 @@ class SharedStringsManager
|
|||||||
$textNodeValue = $textNode->nodeValue;
|
$textNodeValue = $textNode->nodeValue;
|
||||||
$shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode);
|
$shouldPreserveWhitespace = $this->shouldPreserveWhitespace($textNode);
|
||||||
|
|
||||||
$sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : trim($textNodeValue);
|
$sharedStringValue .= ($shouldPreserveWhitespace) ? $textNodeValue : \trim($textNodeValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +242,7 @@ class SharedStringsManager
|
|||||||
*/
|
*/
|
||||||
public function cleanup()
|
public function cleanup()
|
||||||
{
|
{
|
||||||
if ($this->cachingStrategy) {
|
if ($this->cachingStrategy !== null) {
|
||||||
$this->cachingStrategy->clearCache();
|
$this->cachingStrategy->clearCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,27 +14,27 @@ use Box\Spout\Reader\XLSX\Sheet;
|
|||||||
class SheetManager
|
class SheetManager
|
||||||
{
|
{
|
||||||
/** Paths of XML files relative to the XLSX file root */
|
/** Paths of XML files relative to the XLSX file root */
|
||||||
const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
|
public const WORKBOOK_XML_RELS_FILE_PATH = 'xl/_rels/workbook.xml.rels';
|
||||||
const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
|
public const WORKBOOK_XML_FILE_PATH = 'xl/workbook.xml';
|
||||||
|
|
||||||
/** Definition of XML node names used to parse data */
|
/** Definition of XML node names used to parse data */
|
||||||
const XML_NODE_WORKBOOK_PROPERTIES = 'workbookPr';
|
public const XML_NODE_WORKBOOK_PROPERTIES = 'workbookPr';
|
||||||
const XML_NODE_WORKBOOK_VIEW = 'workbookView';
|
public const XML_NODE_WORKBOOK_VIEW = 'workbookView';
|
||||||
const XML_NODE_SHEET = 'sheet';
|
public const XML_NODE_SHEET = 'sheet';
|
||||||
const XML_NODE_SHEETS = 'sheets';
|
public const XML_NODE_SHEETS = 'sheets';
|
||||||
const XML_NODE_RELATIONSHIP = 'Relationship';
|
public const XML_NODE_RELATIONSHIP = 'Relationship';
|
||||||
|
|
||||||
/** Definition of XML attributes used to parse data */
|
/** Definition of XML attributes used to parse data */
|
||||||
const XML_ATTRIBUTE_DATE_1904 = 'date1904';
|
public const XML_ATTRIBUTE_DATE_1904 = 'date1904';
|
||||||
const XML_ATTRIBUTE_ACTIVE_TAB = 'activeTab';
|
public const XML_ATTRIBUTE_ACTIVE_TAB = 'activeTab';
|
||||||
const XML_ATTRIBUTE_R_ID = 'r:id';
|
public const XML_ATTRIBUTE_R_ID = 'r:id';
|
||||||
const XML_ATTRIBUTE_NAME = 'name';
|
public const XML_ATTRIBUTE_NAME = 'name';
|
||||||
const XML_ATTRIBUTE_STATE = 'state';
|
public const XML_ATTRIBUTE_STATE = 'state';
|
||||||
const XML_ATTRIBUTE_ID = 'Id';
|
public const XML_ATTRIBUTE_ID = 'Id';
|
||||||
const XML_ATTRIBUTE_TARGET = 'Target';
|
public const XML_ATTRIBUTE_TARGET = 'Target';
|
||||||
|
|
||||||
/** State value to represent a hidden sheet */
|
/** 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 */
|
/** @var string Path of the XLSX file being read */
|
||||||
protected $filePath;
|
protected $filePath;
|
||||||
@@ -116,7 +116,7 @@ class SheetManager
|
|||||||
{
|
{
|
||||||
// Using "filter_var($x, FILTER_VALIDATE_BOOLEAN)" here because the value of the "date1904" attribute
|
// Using "filter_var($x, FILTER_VALIDATE_BOOLEAN)" here because the value of the "date1904" attribute
|
||||||
// may be the string "false", that is not mapped to the boolean "false" by default...
|
// may be the string "false", that is not mapped to the boolean "false" by default...
|
||||||
$shouldUse1904Dates = filter_var($xmlReader->getAttribute(self::XML_ATTRIBUTE_DATE_1904), FILTER_VALIDATE_BOOLEAN);
|
$shouldUse1904Dates = \filter_var($xmlReader->getAttribute(self::XML_ATTRIBUTE_DATE_1904), FILTER_VALIDATE_BOOLEAN);
|
||||||
$this->optionsManager->setOption(Options::SHOULD_USE_1904_DATES, $shouldUse1904Dates);
|
$this->optionsManager->setOption(Options::SHOULD_USE_1904_DATES, $shouldUse1904Dates);
|
||||||
|
|
||||||
return XMLProcessor::PROCESSING_CONTINUE;
|
return XMLProcessor::PROCESSING_CONTINUE;
|
||||||
@@ -211,7 +211,7 @@ class SheetManager
|
|||||||
$sheetDataXMLFilePath = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET);
|
$sheetDataXMLFilePath = $xmlReader->getAttribute(self::XML_ATTRIBUTE_TARGET);
|
||||||
|
|
||||||
// sometimes, the sheet data file path already contains "/xl/"...
|
// sometimes, the sheet data file path already contains "/xl/"...
|
||||||
if (strpos($sheetDataXMLFilePath, '/xl/') !== 0) {
|
if (\strpos($sheetDataXMLFilePath, '/xl/') !== 0) {
|
||||||
$sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath;
|
$sheetDataXMLFilePath = '/xl/' . $sheetDataXMLFilePath;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,20 @@ use Box\Spout\Reader\XLSX\Creator\InternalEntityFactory;
|
|||||||
class StyleManager
|
class StyleManager
|
||||||
{
|
{
|
||||||
/** Nodes used to find relevant information in the styles XML file */
|
/** Nodes used to find relevant information in the styles XML file */
|
||||||
const XML_NODE_NUM_FMTS = 'numFmts';
|
public const XML_NODE_NUM_FMTS = 'numFmts';
|
||||||
const XML_NODE_NUM_FMT = 'numFmt';
|
public const XML_NODE_NUM_FMT = 'numFmt';
|
||||||
const XML_NODE_CELL_XFS = 'cellXfs';
|
public const XML_NODE_CELL_XFS = 'cellXfs';
|
||||||
const XML_NODE_XF = 'xf';
|
public const XML_NODE_XF = 'xf';
|
||||||
|
|
||||||
/** Attributes used to find relevant information in the styles XML file */
|
/** Attributes used to find relevant information in the styles XML file */
|
||||||
const XML_ATTRIBUTE_NUM_FMT_ID = 'numFmtId';
|
public const XML_ATTRIBUTE_NUM_FMT_ID = 'numFmtId';
|
||||||
const XML_ATTRIBUTE_FORMAT_CODE = 'formatCode';
|
public const XML_ATTRIBUTE_FORMAT_CODE = 'formatCode';
|
||||||
const XML_ATTRIBUTE_APPLY_NUMBER_FORMAT = 'applyNumberFormat';
|
public const XML_ATTRIBUTE_APPLY_NUMBER_FORMAT = 'applyNumberFormat';
|
||||||
|
|
||||||
/** By convention, default style ID is 0 */
|
/** 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
|
* @see https://msdn.microsoft.com/en-us/library/ff529597(v=office.12).aspx
|
||||||
@@ -48,7 +48,10 @@ class StyleManager
|
|||||||
/** @var string Path of the XLSX file being read */
|
/** @var string Path of the XLSX file being read */
|
||||||
protected $filePath;
|
protected $filePath;
|
||||||
|
|
||||||
/** @var string Path of the styles XML file */
|
/** @var bool Whether the XLSX file contains a styles XML file */
|
||||||
|
protected $hasStylesXMLFile;
|
||||||
|
|
||||||
|
/** @var string|null Path of the styles XML file */
|
||||||
protected $stylesXMLFilePath;
|
protected $stylesXMLFilePath;
|
||||||
|
|
||||||
/** @var InternalEntityFactory Factory to create entities */
|
/** @var InternalEntityFactory Factory to create entities */
|
||||||
@@ -57,10 +60,10 @@ class StyleManager
|
|||||||
/** @var array Array containing the IDs of built-in number formats indicating a date */
|
/** @var array Array containing the IDs of built-in number formats indicating a date */
|
||||||
protected $builtinNumFmtIdIndicatingDates;
|
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;
|
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;
|
protected $stylesAttributes;
|
||||||
|
|
||||||
/** @var array Cache containing a mapping NUM_FMT_ID => IS_DATE_FORMAT. Used to avoid lots of recalculations */
|
/** @var array Cache containing a mapping NUM_FMT_ID => IS_DATE_FORMAT. Used to avoid lots of recalculations */
|
||||||
@@ -75,9 +78,12 @@ class StyleManager
|
|||||||
{
|
{
|
||||||
$this->filePath = $filePath;
|
$this->filePath = $filePath;
|
||||||
$this->entityFactory = $entityFactory;
|
$this->entityFactory = $entityFactory;
|
||||||
$this->builtinNumFmtIdIndicatingDates = array_keys(self::$builtinNumFmtIdToNumFormatMapping);
|
$this->builtinNumFmtIdIndicatingDates = \array_keys(self::$builtinNumFmtIdToNumFormatMapping);
|
||||||
|
$this->hasStylesXMLFile = $workbookRelationshipsManager->hasStylesXMLFile();
|
||||||
|
if ($this->hasStylesXMLFile) {
|
||||||
$this->stylesXMLFilePath = $workbookRelationshipsManager->getStylesXMLFilePath();
|
$this->stylesXMLFilePath = $workbookRelationshipsManager->getStylesXMLFilePath();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the style with the given ID should consider
|
* Returns whether the style with the given ID should consider
|
||||||
@@ -88,6 +94,10 @@ class StyleManager
|
|||||||
*/
|
*/
|
||||||
public function shouldFormatNumericValueAsDate($styleId)
|
public function shouldFormatNumericValueAsDate($styleId)
|
||||||
{
|
{
|
||||||
|
if (!$this->hasStylesXMLFile) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$stylesAttributes = $this->getStylesAttributes();
|
$stylesAttributes = $this->getStylesAttributes();
|
||||||
|
|
||||||
// Default style (0) does not format numeric values as timestamps. Only custom styles do.
|
// Default style (0) does not format numeric values as timestamps. Only custom styles do.
|
||||||
@@ -263,7 +273,7 @@ class StyleManager
|
|||||||
*/
|
*/
|
||||||
protected function isNumFmtIdBuiltInDateFormat($numFmtId)
|
protected function isNumFmtIdBuiltInDateFormat($numFmtId)
|
||||||
{
|
{
|
||||||
return in_array($numFmtId, $this->builtinNumFmtIdIndicatingDates);
|
return \in_array($numFmtId, $this->builtinNumFmtIdIndicatingDates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -273,7 +283,7 @@ class StyleManager
|
|||||||
protected function isFormatCodeCustomDateFormat($formatCode)
|
protected function isFormatCodeCustomDateFormat($formatCode)
|
||||||
{
|
{
|
||||||
// if no associated format code or if using the default "General" format
|
// if no associated format code or if using the default "General" format
|
||||||
if ($formatCode === null || strcasecmp($formatCode, self::NUMBER_FORMAT_GENERAL) === 0) {
|
if ($formatCode === null || \strcasecmp($formatCode, self::NUMBER_FORMAT_GENERAL) === 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +298,7 @@ class StyleManager
|
|||||||
{
|
{
|
||||||
// Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\")
|
// Remove extra formatting (what's between [ ], the brackets should not be preceded by a "\")
|
||||||
$pattern = '((?<!\\\)\[.+?(?<!\\\)\])';
|
$pattern = '((?<!\\\)\[.+?(?<!\\\)\])';
|
||||||
$formatCode = preg_replace($pattern, '', $formatCode);
|
$formatCode = \preg_replace($pattern, '', $formatCode);
|
||||||
|
|
||||||
// custom date formats contain specific characters to represent the date:
|
// custom date formats contain specific characters to represent the date:
|
||||||
// e - yy - m - d - h - s
|
// e - yy - m - d - h - s
|
||||||
@@ -300,7 +310,7 @@ class StyleManager
|
|||||||
// character not preceded by "\" (case insensitive)
|
// character not preceded by "\" (case insensitive)
|
||||||
$pattern = '/(?<!\\\)' . $dateFormatCharacter . '/i';
|
$pattern = '/(?<!\\\)' . $dateFormatCharacter . '/i';
|
||||||
|
|
||||||
if (preg_match($pattern, $formatCode)) {
|
if (\preg_match($pattern, $formatCode)) {
|
||||||
$hasFoundDateFormatCharacter = true;
|
$hasFoundDateFormatCharacter = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,20 +12,21 @@ use Box\Spout\Reader\XLSX\Creator\InternalEntityFactory;
|
|||||||
*/
|
*/
|
||||||
class WorkbookRelationshipsManager
|
class WorkbookRelationshipsManager
|
||||||
{
|
{
|
||||||
const BASE_PATH = 'xl/';
|
public const BASE_PATH = 'xl/';
|
||||||
|
|
||||||
/** Path of workbook relationships XML file inside the XLSX file */
|
/** Path of workbook relationships XML file inside the XLSX file */
|
||||||
const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
|
public const WORKBOOK_RELS_XML_FILE_PATH = 'xl/_rels/workbook.xml.rels';
|
||||||
|
|
||||||
/** Relationships types */
|
/** Relationships types - For Transitional and Strict OOXML */
|
||||||
const RELATIONSHIP_TYPE_SHARED_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
|
public 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';
|
public const RELATIONSHIP_TYPE_STYLES = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles';
|
||||||
const RELATIONSHIP_TYPE_WORKSHEET = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
|
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 */
|
/** Nodes and attributes used to find relevant information in the workbook relationships XML file */
|
||||||
const XML_NODE_RELATIONSHIP = 'Relationship';
|
public const XML_NODE_RELATIONSHIP = 'Relationship';
|
||||||
const XML_ATTRIBUTE_TYPE = 'Type';
|
public const XML_ATTRIBUTE_TYPE = 'Type';
|
||||||
const XML_ATTRIBUTE_TARGET = 'Target';
|
public const XML_ATTRIBUTE_TARGET = 'Target';
|
||||||
|
|
||||||
/** @var string Path of the XLSX file being read */
|
/** @var string Path of the XLSX file being read */
|
||||||
private $filePath;
|
private $filePath;
|
||||||
@@ -33,7 +34,7 @@ class WorkbookRelationshipsManager
|
|||||||
/** @var InternalEntityFactory Factory to create entities */
|
/** @var InternalEntityFactory Factory to create entities */
|
||||||
private $entityFactory;
|
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;
|
private $cachedWorkbookRelationships;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,10 +53,11 @@ class WorkbookRelationshipsManager
|
|||||||
public function getSharedStringsXMLFilePath()
|
public function getSharedStringsXMLFilePath()
|
||||||
{
|
{
|
||||||
$workbookRelationships = $this->getWorkbookRelationships();
|
$workbookRelationships = $this->getWorkbookRelationships();
|
||||||
$sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS];
|
$sharedStringsXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]
|
||||||
|
?? $workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT];
|
||||||
|
|
||||||
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
|
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
|
||||||
$doesContainBasePath = (strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
|
$doesContainBasePath = (\strpos($sharedStringsXMLFilePath, self::BASE_PATH) !== false);
|
||||||
if (!$doesContainBasePath) {
|
if (!$doesContainBasePath) {
|
||||||
// make sure we return an absolute file path
|
// make sure we return an absolute file path
|
||||||
$sharedStringsXMLFilePath = self::BASE_PATH . $sharedStringsXMLFilePath;
|
$sharedStringsXMLFilePath = self::BASE_PATH . $sharedStringsXMLFilePath;
|
||||||
@@ -71,19 +73,32 @@ class WorkbookRelationshipsManager
|
|||||||
{
|
{
|
||||||
$workbookRelationships = $this->getWorkbookRelationships();
|
$workbookRelationships = $this->getWorkbookRelationships();
|
||||||
|
|
||||||
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS]);
|
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS])
|
||||||
|
|| isset($workbookRelationships[self::RELATIONSHIP_TYPE_SHARED_STRINGS_STRICT]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string|null The path of the styles XML file
|
* @return bool Whether the XLSX file contains a styles XML file
|
||||||
|
*/
|
||||||
|
public function hasStylesXMLFile()
|
||||||
|
{
|
||||||
|
$workbookRelationships = $this->getWorkbookRelationships();
|
||||||
|
|
||||||
|
return isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES])
|
||||||
|
|| isset($workbookRelationships[self::RELATIONSHIP_TYPE_STYLES_STRICT]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string The path of the styles XML file
|
||||||
*/
|
*/
|
||||||
public function getStylesXMLFilePath()
|
public function getStylesXMLFilePath()
|
||||||
{
|
{
|
||||||
$workbookRelationships = $this->getWorkbookRelationships();
|
$workbookRelationships = $this->getWorkbookRelationships();
|
||||||
$stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES];
|
$stylesXMLFilePath = $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES]
|
||||||
|
?? $workbookRelationships[self::RELATIONSHIP_TYPE_STYLES_STRICT];
|
||||||
|
|
||||||
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
|
// the file path can be relative (e.g. "styles.xml") or absolute (e.g. "/xl/styles.xml")
|
||||||
$doesContainBasePath = (strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
|
$doesContainBasePath = (\strpos($stylesXMLFilePath, self::BASE_PATH) !== false);
|
||||||
if (!$doesContainBasePath) {
|
if (!$doesContainBasePath) {
|
||||||
// make sure we return a full path
|
// make sure we return a full path
|
||||||
$stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath;
|
$stylesXMLFilePath = self::BASE_PATH . $stylesXMLFilePath;
|
||||||
|
|||||||
@@ -119,11 +119,11 @@ class Reader extends ReaderAbstract
|
|||||||
*/
|
*/
|
||||||
protected function closeReader()
|
protected function closeReader()
|
||||||
{
|
{
|
||||||
if ($this->zip) {
|
if ($this->zip !== null) {
|
||||||
$this->zip->close();
|
$this->zip->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->sharedStringsManager) {
|
if ($this->sharedStringsManager !== null) {
|
||||||
$this->sharedStringsManager->cleanup();
|
$this->sharedStringsManager->cleanup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,21 +21,21 @@ use Box\Spout\Reader\XLSX\Helper\CellValueFormatter;
|
|||||||
class RowIterator implements IteratorInterface
|
class RowIterator implements IteratorInterface
|
||||||
{
|
{
|
||||||
/** Definition of XML nodes names used to parse data */
|
/** Definition of XML nodes names used to parse data */
|
||||||
const XML_NODE_DIMENSION = 'dimension';
|
public const XML_NODE_DIMENSION = 'dimension';
|
||||||
const XML_NODE_WORKSHEET = 'worksheet';
|
public const XML_NODE_WORKSHEET = 'worksheet';
|
||||||
const XML_NODE_ROW = 'row';
|
public const XML_NODE_ROW = 'row';
|
||||||
const XML_NODE_CELL = 'c';
|
public const XML_NODE_CELL = 'c';
|
||||||
|
|
||||||
/** Definition of XML attributes used to parse data */
|
/** Definition of XML attributes used to parse data */
|
||||||
const XML_ATTRIBUTE_REF = 'ref';
|
public const XML_ATTRIBUTE_REF = 'ref';
|
||||||
const XML_ATTRIBUTE_SPANS = 'spans';
|
public const XML_ATTRIBUTE_SPANS = 'spans';
|
||||||
const XML_ATTRIBUTE_ROW_INDEX = 'r';
|
public const XML_ATTRIBUTE_ROW_INDEX = 'r';
|
||||||
const XML_ATTRIBUTE_CELL_INDEX = 'r';
|
public const XML_ATTRIBUTE_CELL_INDEX = 'r';
|
||||||
|
|
||||||
/** @var string Path of the XLSX file being read */
|
/** @var string Path of the XLSX file being read */
|
||||||
protected $filePath;
|
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;
|
protected $sheetDataXMLFilePath;
|
||||||
|
|
||||||
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
|
||||||
@@ -127,7 +127,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*/
|
*/
|
||||||
protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath)
|
protected function normalizeSheetDataXMLFilePath($sheetDataXMLFilePath)
|
||||||
{
|
{
|
||||||
return ltrim($sheetDataXMLFilePath, '/');
|
return \ltrim($sheetDataXMLFilePath, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -139,7 +139,7 @@ class RowIterator implements IteratorInterface
|
|||||||
* @throws \Box\Spout\Common\Exception\IOException If the sheet data XML cannot be read
|
* @throws \Box\Spout\Common\Exception\IOException If the sheet data XML cannot be read
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function rewind()
|
public function rewind() : void
|
||||||
{
|
{
|
||||||
$this->xmlReader->close();
|
$this->xmlReader->close();
|
||||||
|
|
||||||
@@ -163,7 +163,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function valid()
|
public function valid() : bool
|
||||||
{
|
{
|
||||||
return (!$this->hasReachedEndOfFile);
|
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
|
* @throws \Box\Spout\Common\Exception\IOException If unable to read the sheet data XML
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function next()
|
public function next() : void
|
||||||
{
|
{
|
||||||
$this->nextRowIndexToBeProcessed++;
|
$this->nextRowIndexToBeProcessed++;
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ class RowIterator implements IteratorInterface
|
|||||||
{
|
{
|
||||||
// Read dimensions of the sheet
|
// Read dimensions of the sheet
|
||||||
$dimensionRef = $xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet)
|
$dimensionRef = $xmlReader->getAttribute(self::XML_ATTRIBUTE_REF); // returns 'A1:M13' for instance (or 'A1' for empty sheet)
|
||||||
if (preg_match('/[A-Z]+\d+:([A-Z]+\d+)/', $dimensionRef, $matches)) {
|
if (\preg_match('/[A-Z]+\d+:([A-Z]+\d+)/', $dimensionRef, $matches)) {
|
||||||
$this->numColumns = CellHelper::getColumnIndexFromCellIndex($matches[1]) + 1;
|
$this->numColumns = CellHelper::getColumnIndexFromCellIndex($matches[1]) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,11 +257,11 @@ class RowIterator implements IteratorInterface
|
|||||||
$numberOfColumnsForRow = $this->numColumns;
|
$numberOfColumnsForRow = $this->numColumns;
|
||||||
$spans = $xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance
|
$spans = $xmlReader->getAttribute(self::XML_ATTRIBUTE_SPANS); // returns '1:5' for instance
|
||||||
if ($spans) {
|
if ($spans) {
|
||||||
list(, $numberOfColumnsForRow) = explode(':', $spans);
|
list(, $numberOfColumnsForRow) = \explode(':', $spans);
|
||||||
$numberOfColumnsForRow = (int) $numberOfColumnsForRow;
|
$numberOfColumnsForRow = (int) $numberOfColumnsForRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
$cells = array_fill(0, $numberOfColumnsForRow, $this->entityFactory->createCell(''));
|
$cells = \array_fill(0, $numberOfColumnsForRow, $this->entityFactory->createCell(''));
|
||||||
$this->currentlyProcessedRow->setCells($cells);
|
$this->currentlyProcessedRow->setCells($cells);
|
||||||
|
|
||||||
return XMLProcessor::PROCESSING_CONTINUE;
|
return XMLProcessor::PROCESSING_CONTINUE;
|
||||||
@@ -276,6 +276,7 @@ class RowIterator implements IteratorInterface
|
|||||||
$currentColumnIndex = $this->getColumnIndex($xmlReader);
|
$currentColumnIndex = $this->getColumnIndex($xmlReader);
|
||||||
|
|
||||||
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
// NOTE: expand() will automatically decode all XML entities of the child nodes
|
||||||
|
/** @var \DOMElement $node */
|
||||||
$node = $xmlReader->expand();
|
$node = $xmlReader->expand();
|
||||||
$cell = $this->getCell($node);
|
$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.
|
* 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
|
* @return Cell The cell set with the associated with the cell
|
||||||
*/
|
*/
|
||||||
protected function getCell($node)
|
protected function getCell($node)
|
||||||
@@ -374,7 +375,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return Row|null
|
* @return Row|null
|
||||||
*/
|
*/
|
||||||
public function current()
|
public function current() : ?Row
|
||||||
{
|
{
|
||||||
$rowToBeProcessed = $this->rowBuffer;
|
$rowToBeProcessed = $this->rowBuffer;
|
||||||
|
|
||||||
@@ -399,7 +400,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return int
|
* @return int
|
||||||
*/
|
*/
|
||||||
public function key()
|
public function key() : int
|
||||||
{
|
{
|
||||||
// TODO: This should return $this->nextRowIndexToBeProcessed
|
// TODO: This should return $this->nextRowIndexToBeProcessed
|
||||||
// but to avoid a breaking change, the return value for
|
// but to avoid a breaking change, the return value for
|
||||||
@@ -414,7 +415,7 @@ class RowIterator implements IteratorInterface
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function end()
|
public function end() : void
|
||||||
{
|
{
|
||||||
$this->xmlReader->close();
|
$this->xmlReader->close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class SheetIterator implements IteratorInterface
|
|||||||
// Fetch all available sheets
|
// Fetch all available sheets
|
||||||
$this->sheets = $sheetManager->getSheets();
|
$this->sheets = $sheetManager->getSheets();
|
||||||
|
|
||||||
if (count($this->sheets) === 0) {
|
if (\count($this->sheets) === 0) {
|
||||||
throw new NoSheetsFoundException('The file must contain at least one sheet.');
|
throw new NoSheetsFoundException('The file must contain at least one sheet.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ class SheetIterator implements IteratorInterface
|
|||||||
*/
|
*/
|
||||||
public function valid()
|
public function valid()
|
||||||
{
|
{
|
||||||
return ($this->currentSheetIndex < count($this->sheets));
|
return ($this->currentSheetIndex < \count($this->sheets));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use Box\Spout\Writer\WriterAbstract;
|
|||||||
class Writer extends WriterAbstract
|
class Writer extends WriterAbstract
|
||||||
{
|
{
|
||||||
/** Number of rows to write before flushing */
|
/** Number of rows to write before flushing */
|
||||||
const FLUSH_THRESHOLD = 500;
|
public const FLUSH_THRESHOLD = 500;
|
||||||
|
|
||||||
/** @var string Content-Type value for the header */
|
/** @var string Content-Type value for the header */
|
||||||
protected static $headerContentType = 'text/csv; charset=UTF-8';
|
protected static $headerContentType = 'text/csv; charset=UTF-8';
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
namespace Box\Spout\Writer\Common\Creator\Style;
|
namespace Box\Spout\Writer\Common\Creator\Style;
|
||||||
|
|
||||||
use Box\Spout\Common\Entity\Style\Border;
|
use Box\Spout\Common\Entity\Style\Border;
|
||||||
|
use Box\Spout\Common\Entity\Style\CellAlignment;
|
||||||
use Box\Spout\Common\Entity\Style\Style;
|
use Box\Spout\Common\Entity\Style\Style;
|
||||||
|
use Box\Spout\Common\Exception\InvalidArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class StyleBuilder
|
* Class StyleBuilder
|
||||||
@@ -122,6 +124,25 @@ class StyleBuilder
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cell alignment.
|
||||||
|
*
|
||||||
|
* @param string $cellAlignment The cell alignment
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the given cell alignment is not valid
|
||||||
|
* @return StyleBuilder
|
||||||
|
*/
|
||||||
|
public function setCellAlignment($cellAlignment)
|
||||||
|
{
|
||||||
|
if (!CellAlignment::isValid($cellAlignment)) {
|
||||||
|
throw new InvalidArgumentException('Invalid cell alignment value');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->style->setCellAlignment($cellAlignment);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a border
|
* Set a border
|
||||||
*
|
*
|
||||||
@@ -148,6 +169,20 @@ class StyleBuilder
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a format
|
||||||
|
*
|
||||||
|
* @param string $format Format
|
||||||
|
* @return StyleBuilder
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
public function setFormat($format)
|
||||||
|
{
|
||||||
|
$this->style->setFormat($format);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the configured style. The style is cached and can be reused.
|
* Returns the configured style. The style is cached and can be reused.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ class WriterEntityFactory
|
|||||||
return WriterFactory::createFromType(Type::CSV);
|
return WriterFactory::createFromType(Type::CSV);
|
||||||
} catch (UnsupportedTypeException $e) {
|
} catch (UnsupportedTypeException $e) {
|
||||||
// should never happen
|
// should never happen
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ class WriterEntityFactory
|
|||||||
return WriterFactory::createFromType(Type::XLSX);
|
return WriterFactory::createFromType(Type::XLSX);
|
||||||
} catch (UnsupportedTypeException $e) {
|
} catch (UnsupportedTypeException $e) {
|
||||||
// should never happen
|
// should never happen
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,6 +80,7 @@ class WriterEntityFactory
|
|||||||
return WriterFactory::createFromType(Type::ODS);
|
return WriterFactory::createFromType(Type::ODS);
|
||||||
} catch (UnsupportedTypeException $e) {
|
} catch (UnsupportedTypeException $e) {
|
||||||
// should never happen
|
// should never happen
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +101,7 @@ class WriterEntityFactory
|
|||||||
*/
|
*/
|
||||||
public static function createRowFromArray(array $cellValues = [], Style $rowStyle = null)
|
public static function createRowFromArray(array $cellValues = [], Style $rowStyle = null)
|
||||||
{
|
{
|
||||||
$cells = array_map(function ($cellValue) {
|
$cells = \array_map(function ($cellValue) {
|
||||||
return new Cell($cellValue);
|
return new Cell($cellValue);
|
||||||
}, $cellValues);
|
}, $cellValues);
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class WriterFactory
|
|||||||
*/
|
*/
|
||||||
public static function createFromFile(string $path)
|
public static function createFromFile(string $path)
|
||||||
{
|
{
|
||||||
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
$extension = \strtolower(\pathinfo($path, PATHINFO_EXTENSION));
|
||||||
|
|
||||||
return self::createFromType($extension);
|
return self::createFromType($extension);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,15 +9,15 @@ namespace Box\Spout\Writer\Common\Entity;
|
|||||||
abstract class Options
|
abstract class Options
|
||||||
{
|
{
|
||||||
// CSV specific options
|
// CSV specific options
|
||||||
const FIELD_DELIMITER = 'fieldDelimiter';
|
public const FIELD_DELIMITER = 'fieldDelimiter';
|
||||||
const FIELD_ENCLOSURE = 'fieldEnclosure';
|
public const FIELD_ENCLOSURE = 'fieldEnclosure';
|
||||||
const SHOULD_ADD_BOM = 'shouldAddBOM';
|
public const SHOULD_ADD_BOM = 'shouldAddBOM';
|
||||||
|
|
||||||
// Multisheets options
|
// Multisheets options
|
||||||
const TEMP_FOLDER = 'tempFolder';
|
public const TEMP_FOLDER = 'tempFolder';
|
||||||
const DEFAULT_ROW_STYLE = 'defaultRowStyle';
|
public const DEFAULT_ROW_STYLE = 'defaultRowStyle';
|
||||||
const SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY = 'shouldCreateNewSheetsAutomatically';
|
public const SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY = 'shouldCreateNewSheetsAutomatically';
|
||||||
|
|
||||||
// XLSX specific options
|
// XLSX specific options
|
||||||
const SHOULD_USE_INLINE_STRINGS = 'shouldUseInlineStrings';
|
public const SHOULD_USE_INLINE_STRINGS = 'shouldUseInlineStrings';
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ use Box\Spout\Writer\Common\Manager\SheetManager;
|
|||||||
*/
|
*/
|
||||||
class Sheet
|
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) */
|
/** @var int Index of the sheet, based on order in the workbook (zero-based) */
|
||||||
private $index;
|
private $index;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Workbook
|
|||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->internalId = uniqid();
|
$this->internalId = \uniqid();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class Worksheet
|
|||||||
/** @var string Path to the XML file that will contain the sheet data */
|
/** @var string Path to the XML file that will contain the sheet data */
|
||||||
private $filePath;
|
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;
|
private $filePointer;
|
||||||
|
|
||||||
/** @var Sheet The "external" sheet */
|
/** @var Sheet The "external" sheet */
|
||||||
|
|||||||
@@ -8,38 +8,39 @@ namespace Box\Spout\Writer\Common\Helper;
|
|||||||
*/
|
*/
|
||||||
class CellHelper
|
class CellHelper
|
||||||
{
|
{
|
||||||
/** @var array Cache containing the mapping column index => cell index */
|
/** @var array Cache containing the mapping column index => column letters */
|
||||||
private static $columnIndexToCellIndexCache = [];
|
private static $columnIndexToColumnLettersCache = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cell index (base 26) associated to the base 10 column index.
|
* Returns the column letters (base 26) associated to the base 10 column index.
|
||||||
* Excel uses A to Z letters for column indexing, where A is the 1st column,
|
* Excel uses A to Z letters for column indexing, where A is the 1st column,
|
||||||
* Z is the 26th and AA is the 27th.
|
* Z is the 26th and AA is the 27th.
|
||||||
* The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26.
|
* The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26.
|
||||||
*
|
*
|
||||||
* @param int $columnIndex The Excel column index (0, 42, ...)
|
* @param int $columnIndexZeroBased The Excel column index (0, 42, ...)
|
||||||
|
*
|
||||||
* @return string The associated cell index ('A', 'BC', ...)
|
* @return string The associated cell index ('A', 'BC', ...)
|
||||||
*/
|
*/
|
||||||
public static function getCellIndexFromColumnIndex($columnIndex)
|
public static function getColumnLettersFromColumnIndex($columnIndexZeroBased)
|
||||||
{
|
{
|
||||||
$originalColumnIndex = $columnIndex;
|
$originalColumnIndex = $columnIndexZeroBased;
|
||||||
|
|
||||||
// Using isset here because it is way faster than array_key_exists...
|
// Using isset here because it is way faster than array_key_exists...
|
||||||
if (!isset(self::$columnIndexToCellIndexCache[$originalColumnIndex])) {
|
if (!isset(self::$columnIndexToColumnLettersCache[$originalColumnIndex])) {
|
||||||
$cellIndex = '';
|
$columnLetters = '';
|
||||||
$capitalAAsciiValue = ord('A');
|
$capitalAAsciiValue = \ord('A');
|
||||||
|
|
||||||
do {
|
do {
|
||||||
$modulus = $columnIndex % 26;
|
$modulus = $columnIndexZeroBased % 26;
|
||||||
$cellIndex = chr($capitalAAsciiValue + $modulus) . $cellIndex;
|
$columnLetters = \chr($capitalAAsciiValue + $modulus) . $columnLetters;
|
||||||
|
|
||||||
// substracting 1 because it's zero-based
|
// substracting 1 because it's zero-based
|
||||||
$columnIndex = (int) ($columnIndex / 26) - 1;
|
$columnIndexZeroBased = (int) ($columnIndexZeroBased / 26) - 1;
|
||||||
} while ($columnIndex >= 0);
|
} while ($columnIndexZeroBased >= 0);
|
||||||
|
|
||||||
self::$columnIndexToCellIndexCache[$originalColumnIndex] = $cellIndex;
|
self::$columnIndexToColumnLettersCache[$originalColumnIndex] = $columnLetters;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self::$columnIndexToCellIndexCache[$originalColumnIndex];
|
return self::$columnIndexToColumnLettersCache[$originalColumnIndex];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,11 @@ use Box\Spout\Writer\Common\Creator\InternalEntityFactory;
|
|||||||
*/
|
*/
|
||||||
class ZipHelper
|
class ZipHelper
|
||||||
{
|
{
|
||||||
const ZIP_EXTENSION = '.zip';
|
public const ZIP_EXTENSION = '.zip';
|
||||||
|
|
||||||
/** Controls what to do when trying to add an existing file */
|
/** Controls what to do when trying to add an existing file */
|
||||||
const EXISTING_FILES_SKIP = 'skip';
|
public const EXISTING_FILES_SKIP = 'skip';
|
||||||
const EXISTING_FILES_OVERWRITE = 'overwrite';
|
public const EXISTING_FILES_OVERWRITE = 'overwrite';
|
||||||
|
|
||||||
/** @var InternalEntityFactory Factory to create entities */
|
/** @var InternalEntityFactory Factory to create entities */
|
||||||
private $entityFactory;
|
private $entityFactory;
|
||||||
@@ -135,7 +135,7 @@ class ZipHelper
|
|||||||
public static function canChooseCompressionMethod()
|
public static function canChooseCompressionMethod()
|
||||||
{
|
{
|
||||||
// setCompressionName() is a PHP7+ method...
|
// setCompressionName() is a PHP7+ method...
|
||||||
return (method_exists(new \ZipArchive(), 'setCompressionName'));
|
return (\method_exists(new \ZipArchive(), 'setCompressionName'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,7 +151,7 @@ class ZipHelper
|
|||||||
|
|
||||||
foreach ($itemIterator as $itemInfo) {
|
foreach ($itemIterator as $itemInfo) {
|
||||||
$itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
|
$itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
|
||||||
$itemLocalPath = str_replace($folderRealPath, '', $itemRealPath);
|
$itemLocalPath = \str_replace($folderRealPath, '', $itemRealPath);
|
||||||
|
|
||||||
if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) {
|
if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) {
|
||||||
$zip->addFile($itemRealPath, $itemLocalPath);
|
$zip->addFile($itemRealPath, $itemLocalPath);
|
||||||
@@ -181,9 +181,9 @@ class ZipHelper
|
|||||||
*/
|
*/
|
||||||
protected function getNormalizedRealPath($path)
|
protected function getNormalizedRealPath($path)
|
||||||
{
|
{
|
||||||
$realPath = realpath($path);
|
$realPath = \realpath($path);
|
||||||
|
|
||||||
return str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
|
return \str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -210,8 +210,8 @@ class ZipHelper
|
|||||||
*/
|
*/
|
||||||
protected function copyZipToStream($zipFilePath, $pointer)
|
protected function copyZipToStream($zipFilePath, $pointer)
|
||||||
{
|
{
|
||||||
$zipFilePointer = fopen($zipFilePath, 'r');
|
$zipFilePointer = \fopen($zipFilePath, 'r');
|
||||||
stream_copy_to_stream($zipFilePointer, $pointer);
|
\stream_copy_to_stream($zipFilePointer, $pointer);
|
||||||
fclose($zipFilePointer);
|
\fclose($zipFilePointer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
class SheetManager
|
||||||
{
|
{
|
||||||
/** Sheet name should not exceed 31 characters */
|
/** 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 */
|
/** @var array Invalid characters that cannot be contained in the sheet name */
|
||||||
private static $INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']'];
|
private static $INVALID_CHARACTERS_IN_SHEET_NAME = ['\\', '/', '?', '*', ':', '[', ']'];
|
||||||
@@ -45,8 +45,8 @@ class SheetManager
|
|||||||
*/
|
*/
|
||||||
public function throwIfNameIsInvalid($name, Sheet $sheet)
|
public function throwIfNameIsInvalid($name, Sheet $sheet)
|
||||||
{
|
{
|
||||||
if (!is_string($name)) {
|
if (!\is_string($name)) {
|
||||||
$actualType = gettype($name);
|
$actualType = \gettype($name);
|
||||||
$errorMessage = "The sheet's name is invalid. It must be a string ($actualType given).";
|
$errorMessage = "The sheet's name is invalid. It must be a string ($actualType given).";
|
||||||
throw new InvalidSheetNameException($errorMessage);
|
throw new InvalidSheetNameException($errorMessage);
|
||||||
}
|
}
|
||||||
@@ -74,9 +74,9 @@ class SheetManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($failedRequirements) !== 0) {
|
if (\count($failedRequirements) !== 0) {
|
||||||
$errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - ";
|
$errorMessage = "The sheet's name (\"$name\") is invalid. It did not respect these rules:\n - ";
|
||||||
$errorMessage .= implode("\n - ", $failedRequirements);
|
$errorMessage .= \implode("\n - ", $failedRequirements);
|
||||||
throw new InvalidSheetNameException($errorMessage);
|
throw new InvalidSheetNameException($errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ class SheetManager
|
|||||||
*/
|
*/
|
||||||
private function doesContainInvalidCharacters($name)
|
private function doesContainInvalidCharacters($name)
|
||||||
{
|
{
|
||||||
return (str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $name);
|
return (\str_replace(self::$INVALID_CHARACTERS_IN_SHEET_NAME, '', $name) !== $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.
|
* Typically, set "wrap text" if a cell contains a new line.
|
||||||
*
|
*
|
||||||
* @param Cell $cell
|
* @param Cell $cell
|
||||||
* @return Style
|
* @return PossiblyUpdatedStyle The eventually updated style
|
||||||
*/
|
*/
|
||||||
public function applyExtraStylesIfNeeded(Cell $cell)
|
public function applyExtraStylesIfNeeded(Cell $cell) : PossiblyUpdatedStyle
|
||||||
{
|
{
|
||||||
$updatedStyle = $this->applyWrapTextIfCellContainsNewLine($cell);
|
return $this->applyWrapTextIfCellContainsNewLine($cell);
|
||||||
|
|
||||||
return $updatedStyle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,21 +67,19 @@ class StyleManager implements StyleManagerInterface
|
|||||||
* on the Windows version of Excel...
|
* on the Windows version of Excel...
|
||||||
*
|
*
|
||||||
* @param Cell $cell The cell the style should be applied to
|
* @param Cell $cell The cell the style should be applied to
|
||||||
* @return \Box\Spout\Common\Entity\Style\Style The eventually updated style
|
* @return PossiblyUpdatedStyle The eventually updated style
|
||||||
*/
|
*/
|
||||||
protected function applyWrapTextIfCellContainsNewLine(Cell $cell)
|
protected function applyWrapTextIfCellContainsNewLine(Cell $cell) : PossiblyUpdatedStyle
|
||||||
{
|
{
|
||||||
$cellStyle = $cell->getStyle();
|
$cellStyle = $cell->getStyle();
|
||||||
|
|
||||||
// if the "wrap text" option is already set, no-op
|
// if the "wrap text" option is already set, no-op
|
||||||
if ($cellStyle->hasSetWrapText()) {
|
if (!$cellStyle->hasSetWrapText() && $cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
|
||||||
return $cellStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($cell->isString() && strpos($cell->getValue(), "\n") !== false) {
|
|
||||||
$cellStyle->setShouldWrapText();
|
$cellStyle->setShouldWrapText();
|
||||||
|
|
||||||
|
return new PossiblyUpdatedStyle($cellStyle, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $cellStyle;
|
return new PossiblyUpdatedStyle($cellStyle, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ interface StyleManagerInterface
|
|||||||
* Typically, set "wrap text" if a cell contains a new line.
|
* Typically, set "wrap text" if a cell contains a new line.
|
||||||
*
|
*
|
||||||
* @param Cell $cell
|
* @param Cell $cell
|
||||||
* @return Style The updated style
|
* @return PossiblyUpdatedStyle The eventually updated style
|
||||||
*/
|
*/
|
||||||
public function applyExtraStylesIfNeeded(Cell $cell);
|
public function applyExtraStylesIfNeeded(Cell $cell) : PossiblyUpdatedStyle;
|
||||||
}
|
}
|
||||||
@@ -85,9 +85,15 @@ class StyleMerger
|
|||||||
if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) {
|
if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) {
|
||||||
$styleToUpdate->setShouldWrapText();
|
$styleToUpdate->setShouldWrapText();
|
||||||
}
|
}
|
||||||
if (!$style->getBorder() && $baseStyle->shouldApplyBorder()) {
|
if (!$style->hasSetCellAlignment() && $baseStyle->shouldApplyCellAlignment()) {
|
||||||
|
$styleToUpdate->setCellAlignment($baseStyle->getCellAlignment());
|
||||||
|
}
|
||||||
|
if ($style->getBorder() === null && $baseStyle->shouldApplyBorder()) {
|
||||||
$styleToUpdate->setBorder($baseStyle->getBorder());
|
$styleToUpdate->setBorder($baseStyle->getBorder());
|
||||||
}
|
}
|
||||||
|
if ($style->getFormat() === null && $baseStyle->shouldApplyFormat()) {
|
||||||
|
$styleToUpdate->setFormat($baseStyle->getFormat());
|
||||||
|
}
|
||||||
if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) {
|
if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) {
|
||||||
$styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor());
|
$styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor());
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user