Compare commits

...
97 Commits
Author SHA1 Message Date
Adrien Loison 6c4086cf97 Fix reading of 1904 dates option
Whether the spreadsheet is using 1904 dates or not is controlled by a XML property. Its value can be the string "false" that is not mapped to the boolean "false" but to the boolean "true"... Therefore Spout was previously using the wrong date system when this property was set.
2019-06-04 09:36:51 +02:00
Adrien Loison a296f73a98 Fix Github icon in docs 2019-05-24 10:12:35 +02:00
Adrien Loison 0f0bf64802 Merge branch 'master' into develop_3.0 2019-05-24 09:30:41 +02:00
Adrien Loison 9d33fcdd00 Update FAQ 2019-05-24 09:24:34 +02:00
Adrien Loison 4ff9717b0a Update FAQ in docs 2019-05-23 09:00:46 +02:00
Adrien Loison c62177f0e4 Move documentation from gh-pages branch to 'docs' folder
To prepare the migration to 3.0, we need to change the location where the documentation is generated from.
Having a gh-pages branch makes it hard to synchronize the code and the docs. Having a "docs" folder in the repo itself simplifies this.
2019-05-22 09:45:45 +02:00
Adrien Loison 3beaa32021 Fix docblock 2019-05-21 19:43:54 +02:00
Adrien Loison 5ce5a488d1 Force UTF-8 encoding in htmlspecialchars 2019-05-21 19:37:09 +02:00
Adrien Loison 69b0fb9eaf Add methods to SheetInterface
The SheetInterface was missing methods common to all Sheets (getIndex, getName, isActive, isVisible).
2019-05-17 21:38:29 +02:00
Adrien Loison 40ee386edd Add helper functions to create specific readers and writers
Removed the `ReaderEntityFactory::createReader(Type)` method and replaced it by 3 methods:
- `ReaderEntityFactory::createCSVReader()`
- `ReaderEntityFactory::createXLSXReader()`
- `ReaderEntityFactory::createODSReader()`

This has the advantage of enabling autocomplete in the IDE, as the return type is no longer the interface but the concrete type. Since readers may expose different options, this is pretty useful.

Similarly, removed the `WriterEntityFactory::createWriter(Type)` method and replaced it by 3 methods:
- `WriterEntityFactory::createCSVWriter()`
- `WriterEntityFactory::createXLSXWriter()`
- `WriterEntityFactory::createODSWriter()`

Since this is a breaking change, I also updated the Upgrade guide.
Finally, the doc is up to date too.
2019-05-17 21:22:03 +02:00
Adrien Loison 6104d41857 Add tests runs on PHP 7.3 in Travis 2019-05-17 13:37:25 +02:00
Adrien Loison 4260c46b11 Update documentation for 3.0 2019-05-17 13:25:49 +02:00
Adrien Loison 4a9d0398ad Update Reader/WriterEntityFactory
Add `WriterEntityFactory::createWriterFromFile`, working like `ReaderEntityFactory::createReaderFromFile` (guessing writer type from file name).
Use static functions when needed.
2019-05-17 13:22:27 +02:00
madflow e8693834a0 perf tests in development branch 2019-03-24 22:53:28 +01:00
Adrien Loison 171a2fab10 Fix test failure message 2019-02-02 09:55:16 +01:00
madflow 3d577197d2 upgrade guide 2019-01-30 16:01:25 +01:00
Adrien Loison 71cf0fe339 Fix sheet name escaping
Sheet names are stored as attributes of an XML entity. We therefore need a different escaping strategy, escaping quotes.
2019-01-26 16:14:15 +01:00
Adrien Loison ee998f7173 For PHP 7.1 for Composer
If local PHP is 7.2, we still want to download dependencies compatible with PHP 7.1
2019-01-04 19:24:28 +01:00
madflow 6c8344c025 PHP version in readme 2019-01-04 19:23:27 +01:00
Yannick ROGER a420e3fffa Fix phpunit (#604)
* Enforce PHP 5.4 on composer update
* Ran composer update
2018-12-03 16:42:00 +01:00
madflow e99c80b3ad create a reader by file type #569 2018-10-23 13:33:39 +02:00
madflow 8f7f106555 doc update with new classes and signatures 2018-10-12 20:16:30 +02:00
madflow 738ea30f35 use expectNotToPerformAssertions 2018-10-08 10:09:47 +02:00
madflow 8a1c48b6b0 rename EntityFactory for writers and readers #526 2018-09-03 11:15:09 +02:00
madflow e1acdc1fc5 (docs) removed Bower, use a CDN, Docker usage, updated Readme 2018-08-09 23:52:14 +02:00
madflow b105d15f08 some migrations to PHP 7.1 2018-06-12 18:28:04 +02:00
madflow b05ce01d3c delete unused ReaderOptions 2018-06-12 18:27:00 +02:00
Adrien Loison 195b0d4bda Upgrade to 3.0 guide 2018-06-04 08:46:37 +02:00
Adrien Loison f7c483adbd Better support for errored cells 2018-06-03 22:31:24 +02:00
Adrien Loison 1b64a06fbe Move ReaderFactory into Common/Creator 2018-06-03 21:13:38 +02:00
Adrien Loison d25a4ebd6d Add docs to .gitattributes 2018-06-03 21:08:21 +02:00
Adrien Loison e83ac423dc Force PHP 7.1 2018-06-03 20:43:49 +02:00
Adrien Loison 799ad93d23 Force UTF-8 encoding in htmlspecialchars 2018-05-09 22:56:06 +02:00
madflow 5c0030854f appveyor 2018-03-28 09:50:56 +02:00
madflow 01ad5af2c5 fix risky tests and assert true for silent tests 2018-03-25 15:35:19 +02:00
madflow 29cf6245a1 added Row::getCellAtIndex method 2018-02-14 21:32:10 +01:00
madflow 21e0e9e6b1 implement Cell:isDate() for unification 2018-01-16 14:22:37 +01:00
madflow e135b71473 remove unused import 2018-01-16 14:22:10 +01:00
madflow d84f5168ec RowManager constructor does not take any arguments 2018-01-16 14:22:10 +01:00
madflow 88eee3be72 wrong parameter count in method call 2018-01-16 14:22:10 +01:00
Gabriel Caruso 4c7adbb33f Refactoring tests 2017-12-15 10:09:18 +01:00
Gabriel Caruso 9f4e28b3fd Clean else 2017-12-15 10:06:19 +01:00
Gabriel Caruso 0efdf48119 Support PHPUnit 6 2017-11-27 00:24:13 +01:00
madflow cd0831ea8e #463, move gh-pages to docs folder 2017-11-26 20:02:20 +01:00
madflow 5b1bcc1303 (chore) add PHP 7.2 2017-11-20 22:24:01 +01:00
Adrien Loison f5168114d0 Merge Reader and Writer entities
Merged Cell/Row/Style entities
2017-11-19 02:54:17 +01:00
Adrien Loison 4d1d1c1e87 various improvements 2017-11-19 02:41:07 +01:00
Adrien Loison 102e17159c Make ODS reader return Row object 2017-11-19 01:36:18 +01:00
Adrien Loison 68a96367a8 Remove StyleMerger from RowManager 2017-11-18 21:19:31 +01:00
Adrien Loison 78b6639480 Make XLSX reader return Row objects 2017-11-18 20:53:22 +01:00
Adrien Loison a665b974fa Make CSV reader return Row objects 2017-11-18 19:08:27 +01:00
Adrien Loison 139f7fdfb3 Remove StyleMerger from Cell entity 2017-11-18 17:37:52 +01:00
Adrien Loison c826d15472 Fix charachters escaping with CSV reader/writer
PHP's built-in functions fputcsv and fgetcsv are not RFC-4180 compliant and include an escape character that's not defined in the spec.
This results in escaping characters that should not be escaped.
This commit disables this escaping mechanism.
2017-11-11 16:21:05 +01:00
Adrien Loison e2b519d6f9 Fetch XML file paths from Workbook Relationships 2017-11-11 15:25:12 +01:00
Adrien Loison 0c8a53c821 Fix isEmptyRow() check 2017-11-11 12:35:03 +01:00
Adrien Loison 5a470188a9 Merge remote-tracking branch 'origin/master' into develop_3.0 2017-11-11 12:20:28 +01:00
Adrien Loison 1c69dee9c9 Sheet visibility - ODS writer and reader 2017-11-11 11:11:47 +01:00
Adrien Loison b9206fcb4b Sheet visibility - XLSX writer and reader 2017-11-11 11:11:47 +01:00
Adrien Loison ddfa40e8b3 StyleMerger and RowManager changes
Move DI of StyleMerger to get rid of business logic in the entities Cell and Row
Simplify RowManager
2017-11-10 22:45:57 +01:00
Adrien Loison 111f82d35f Add tests for cell styling 2017-11-10 21:35:59 +01:00
madflow 8dd6487ea3 add upgrade documentation #492 2017-11-10 11:21:57 +01:00
madflow 7367b89384 always write rows even with no cells #492 2017-11-10 11:21:57 +01:00
Adrien Loison 8aec9ea992 Apply default row style in WorkbookManager
Instead of doing it in the Writer
2017-11-05 14:13:22 +01:00
Adrien Loison 3d0f108b1d Consolidate external EntityFactory
All entities will now be created through a single factory (including the Writers).
Also, added a EntityFactory::createRowFromArray() to make it easier to create rows
2017-11-05 13:18:29 +01:00
Adrien Loison 2d2151ac8d Split Writer EntityFactory into interal and external ones 2017-11-05 13:04:03 +01:00
Adrien Loison ca5962271e Default row style should only apply to existing cells 2017-11-05 12:17:45 +01:00
Adrien Loison 727a90fd06 Remove @api annotation 2017-11-05 02:23:26 +01:00
Adrien Loison 3851e05f83 Remove @expectedException annotation 2017-11-05 02:21:09 +01:00
Adrien Loison 7274226b75 Row objects and Cell styling
This commit introduces Row and Cell entities, that will replace the arrays passed in previously.
It also adds support for Cell styling (instead of Row styling only).
2017-11-05 02:12:28 +01:00
Adrien Loison fec27e9056 Update .php_cs.dist
Remove "void_return" rule
2017-11-04 16:43:36 +01:00
Adrien Loison c74c0d9127 Add support for 1904 dates
This commit adds support for dates using the 1904 calendar (starting 1904-01-01 00:00:00).
It also fixes some issues with the dates in 1900 calendar (which now correctly start at 1899-12-30 00:00:00).
Finally, it is now possible to have negative timestamps, representing dates before the base date (and up to 0000-01-01 00:00:00), as per the SpreadsheetML specs. Note that some versions of Excel don't support negative dates...
2017-11-04 16:33:46 +01:00
Adrien Loison e1ae3c8a81 Update php-cs config: increment_style 2017-11-04 13:32:22 +01:00
madflow 0ab053dc6e (chore) remove hhvm from the test grid 2017-10-13 18:50:10 +02:00
Adrien Loison 28c1bea28c Code style should not follow Yoda style 2017-10-04 00:53:05 +02:00
Adrien Loison b968513cb9 Fix code style 2017-09-06 00:33:43 +02:00
Adrien Loison 740fcfb8c1 Fix code before applying PHP CS Fixer 2017-09-06 00:33:43 +02:00
Adrien Loison 554ebf987b Setup PHP CS fixer
- Add PHP CS Fixer as a dev dependencies.
- Add PHP CS Fixer cache file to gitignore/gitattributes
- Add custom code style config
- Update TravisCI config to check code style
2017-09-06 00:33:43 +02:00
Adrien Loison 668c10a30d Introduce Managers for readers
Some helper classes were more managers than helpers...
2017-08-27 03:56:17 +02:00
Adrien Loison 61f2addefa Favor object creation in factories (#459)
Instead of passing factories in the constructors and let objects call the factory method, create all dependencies directly in the factories.
2017-08-27 02:40:39 +02:00
Adrien Loison 4ec3a21170 Random DI improvements (#458)
* Add random DI improvements

Fixing things that were previously missed

* Split InternalFactory into Manager and Helper factories
2017-08-27 01:44:20 +02:00
Adrien Loison b7e46740ce Refactor readers for better di (#457)
* Refactor readers to get a proper DI

Similar to what was done with writers, readers also needed to be updated to match the new way of doing things.
This commits promotes a better DI (factories, injection through constructors).

* Escapers should not be singletons

Instead, they should be proper object that can be injected where needed.
2017-08-27 00:01:17 +02:00
madflow 4d6437fa77 merge master, resolve conflicts (#447) 2017-07-13 09:52:15 +02:00
Adrien Loison 3bb4fd3d48 Move Sheet to Common/Entity (#438)
... and introduce a SheetManager
2017-05-30 16:49:07 +02:00
Adrien Loison 30366e6a5d Inject ZipHelper through constructor (#437) 2017-05-30 15:19:05 +02:00
Adrien Loison 762dd1573a Inject GlobalFunctionsHelper through constructor for Writers (#436) 2017-05-30 14:37:38 +02:00
Adrien Loison 7ec0f565fd Rename SharedStringsHelper to SharedStringsManager (#435) 2017-05-30 14:11:18 +02:00
Adrien Loison cc9a0b526b Refactory Writer Styles to match new code organization (#433)
Decomposed old StyleHelper into StyleManager, StyleRegistry and StyleMerger.
2017-05-30 13:05:18 +02:00
Adrien Loison 238756ab6e Move Style classes into Common folder (#432) 2017-05-30 00:56:50 +02:00
Adrien Loison c4e25a168e Move entities and managers back to Common (#431) 2017-05-29 23:28:10 +02:00
Adrien Loison 878c4a9c8b Rename Factory folder to Creator (#430) 2017-05-29 23:10:24 +02:00
Adrien Loison cebffbe80c Move Cell and Options to Entity folder (#429) 2017-05-29 22:51:12 +02:00
Adrien Loison 69b091b37c Move OptionManager from Common/Manager to Manager (#428) 2017-05-29 22:34:25 +02:00
Adrien Loison bc17311f5f Refactor writers for better DI (#427)
This commit is a big refactor that improves the code organization.
It focuses on how dependencies are injected into the different classes. This is now done via some factories.

Also, the code is now built around entities (data model that only exposes getters and setters), managers (used to manage an entity) and helpers (used by the managers to perform some specific tasks).

The refactoring is not fully complete, as some dependencies are still hidden...
2017-05-29 22:18:40 +02:00
Adrien Loison a366d0d0af Introduce an options manager for all writers (#423)
This will improve the management of options and simplify some methods' signatures.
This commit will also help moving the code to a better state regarding Dependency Injection.
2017-05-24 13:17:50 +02:00
madflow f9d8ad8be3 bump phpunit to the latest supported release, travis setup, ignore composer.lock (#419) 2017-05-22 10:39:03 +02:00
Adrien Loison 80553c6c52 Bump min PHP version to 5.6 (#415) 2017-05-05 15:14:47 +02:00
madflow 4acd9ad087 Cell value objects (#383)
* first stab at cell objects, #182
* removed comment parameter, streamlined cell detection, more tests #182
* shorter constant names, missing isFormula() #182
* first batch of changes #182
* documentation #182
2017-05-01 12:09:24 +02:00
288 changed files with 1012041 additions and 6466 deletions

No files matched your search

+3 -2
View File
@@ -1,11 +1,12 @@
# Ignore all test and documentation for archive
# Ignore all tests, documentation and dot files for archive
/docs export-ignore
/tests export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.php_cs.dist export-ignore
/.scrutinizer.yml export-ignore
/.travis.yml export-ignore
/composer.lock export-ignore
/CONTRIBUTING.md export-ignore
/logo.png export-ignore
/phpunit.xml export-ignore
+2
View File
@@ -4,3 +4,5 @@
/tests/resources/generated
/tests/coverage
/vendor
/composer.lock
/.php_cs.cache
+58
View File
@@ -0,0 +1,58 @@
<?php
$config = PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules([
'@Symfony' => true,
'align_multiline_comment' => false,
'array_syntax' => ['syntax' => 'short'],
'binary_operator_spaces' => ['default' => null],
'blank_line_before_statement' => ['statements' => ['return']],
'combine_consecutive_unsets' => true,
'concat_space' => ['spacing' => 'one'],
'declare_equal_normalize' => ['space' => 'single'],
'heredoc_to_nowdoc' => true,
'increment_style' => ['style' => 'post'],
'is_null' => ['use_yoda_style' => false],
'method_argument_space' => ['ensure_fully_multiline' => true],
'modernize_types_casting' => true,
'no_break_comment' => ['comment_text' => 'do nothing'],
'no_empty_phpdoc' => false,
'no_null_property_initialization' => true,
'no_short_echo_tag' => true,
'no_superfluous_elseif' => true,
'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'switch_case', 'yield']],
'no_unneeded_curly_braces' => true,
'no_unneeded_final_method' => true,
'no_useless_else' => false,
'no_useless_return' => true,
'ordered_imports' => true,
'phpdoc_add_missing_param_annotation' => true,
'phpdoc_align' => false,
'phpdoc_annotation_without_dot' => false,
'phpdoc_no_empty_return' => false,
'phpdoc_order' => true,
'phpdoc_summary' => false,
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'phpdoc_separation' => false,
'protected_to_private' => true,
'psr4' => true,
'return_type_declaration' => ['space_before' => 'one'],
'semicolon_after_instruction' => true,
'simplified_null_return' => false,
'single_line_comment_style' => ['comment_types' => ['hash']],
'strict_comparison' => true,
'yoda_style' => ['equal' => false, 'identical' => false],
]);
$config->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
->name('*.php')
);
$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__;
$config->setCacheFile($cacheDir . '/.php_cs.cache');
return $config;
+1 -1
View File
@@ -4,7 +4,7 @@ filter:
tools:
external_code_coverage:
timeout: 600 # Wait 10 minutes for results
runs: 1 # Results are only coming from the PHP 7.1 job
runs: 1 # Merge results for the 7.1 job
php_mess_detector: true
php_code_sniffer:
config:
+41 -12
View File
@@ -1,29 +1,58 @@
dist: trusty
sudo: false
language: php
dist: trusty
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- hhvm-3.6
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
script:
before_script:
- mkdir -p "$HOME/.php-cs-fixer"
- mkdir -p build/logs
- php vendor/bin/phpunit --coverage-clover=build/logs/coverage.clover
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 [[ "$TRAVIS_PHP_VERSION" == '7.1' ]]; then
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
+8 -8
View File
@@ -20,23 +20,23 @@ Full documentation can be found at [http://opensource.box.com/spout/](http://ope
## Requirements
* PHP version 5.4.0 or higher
* PHP version 7.1 or higher
* PHP extension `php_zip` enabled
* PHP extension `php_xmlreader` enabled
## Upgrade guide
Version 3 introduced new functionality but also some breaking changes. If you want to upgrade your Spout codebase from version 2 please consult the [Upgrade guide](UPGRADE-3.0.md).
## Running tests
On the `master` branch, only unit and functional tests are included. The performance tests require very large files and have been excluded.
If you just want to check that everything is working as expected, executing the tests of the `master` branch is enough.
The `master` branch includes unit, functional and performance tests.
If you just want to check that everything is working as expected, executing the unit and functional tests is enough.
If you want to run performance tests, you will need to checkout the `perf-tests` branch. Multiple test suites can then be run, depending on the expected output:
* `phpunit` - runs the whole test suite (unit + functional + performance tests)
* `phpunit --exclude-group perf-tests` - only runs the unit and functional tests
* `phpunit` - runs unit and functional tests
* `phpunit --group perf-tests` - only runs the performance tests
For information, the performance tests take about 30 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: [![Build Status](https://travis-ci.org/box/spout.svg?branch=perf-tests)](https://travis-ci.org/box/spout)
+89
View File
@@ -0,0 +1,89 @@
Upgrading from 2.x to 3.0
=========================
Spout 3.0 introduced several backwards-incompatible changes. The upgrade from Spout 2.x to 3.0 must therefore be done with caution.
This guide is meant to ease this process.
Most notable changes
--------------------
In 2.x, styles were applied per row; it was therefore impossible to apply different styles to cells in the same row.
With the 3.0 version, this is now possible: each cell can have its own style.
Spout 3.0 tries to enforce better typing. For instance, instead of using/returning generic arrays, Spout now makes use of specific `Row` and `Cell` objects that can encapsulate more data such as type, style, value.
Finally, **_Spout 3.0 only supports PHP 7.1 and above_**, as other PHP versions are no longer supported by the community.
Reader changes
--------------
Creating a reader should now be done through the Reader `ReaderEntityFactory`, instead of using the `ReaderFactory`.
Also, the `ReaderFactory::create($type)` method was removed and replaced by methods for each reader:
```php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory; // namespace is no longer "Box\Spout\Reader"
...
$reader = ReaderEntityFactory::createXLSXReader(); // replaces ReaderFactory::create(Type::XLSX)
$reader = ReaderEntityFactory::createCSVReader(); // replaces ReaderFactory::create(Type::CSV)
$reader = ReaderEntityFactory::createODSReader(); // replaces ReaderFactory::create(Type::ODS)
```
When iterating over the spreadsheet rows, Spout now returns `Row` objects, instead of an array containing row values. Accessing the row values should now be done this way:
```php
...
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) { // $row is a "Row" object, not an array
$rowAsArray = $row->toArray(); // this is the 2.x equivalent
// OR
$cellsArray = $row->getCells(); // this can be used to get access to cells' details
...
}
}
```
Writer changes
--------------
Writer creation follows the same change as the reader. It should now be done through the Writer `WriterEntityFactory`, instead of using the `WriterFactory`.
Also, the `WriterFactory::create($type)` method was removed and replaced by methods for each writer:
```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory; // namespace is no longer "Box\Spout\Writer"
...
$writer = WriterEntityFactory::createXLSXWriter(); // replaces WriterFactory::create(Type::XLSX)
$writer = WriterEntityFactory::createCSVWriter(); // replaces WriterFactory::create(Type::CSV)
$writer = WriterEntityFactory::createODSWriter(); // replaces WriterFactory::create(Type::ODS)
```
Adding rows is also done differently: instead of passing an array, the writer now takes in a `Row` object (or an array of `Row`). Creating such objects can easily be done this way:
```php
// Adding a row from an array of values (2.x equivalent)
$cellValues = ['foo', 12345];
$row1 = WriterEntityFactory::createRowFromArray($cellValues, $rowStyle);
// Adding a row from an array of Cell
$cell1 = WriterEntityFactory::createCell('foo', $cellStyle1); // this cell has its own style
$cell2 = WriterEntityFactory::createCell(12345, $cellStyle2); // this cell has its own style
$row2 = WriterEntityFactory::createRow([$cell1, $cell2]);
$writer->addRows([$row1, $row2]);
```
Namespace changes for styles
-----------------
The namespaces for styles have changed. Styles are still created by using a `builder` class.
For the builder, please update your import statements to use the following namespaces:
Box\Spout\Writer\Common\Creator\Style\StyleBuilder
Box\Spout\Writer\Common\Creator\Style\BorderBuilder
The `Style` base class and style definitions like `Border`, `BorderPart` and `Color` also have a new namespace.
If your are using these classes directly via an import statement in your code, please use the following namespaces:
Box\Spout\Common\Entity\Style\Border
Box\Spout\Common\Entity\Style\BorderPart
Box\Spout\Common\Entity\Style\Color
Box\Spout\Common\Entity\Style\Style
Handling of empty rows
----------------------
In 2.x, empty rows were not added to the spreadsheet.
In 3.0, `addRow` now always writes a row to the spreadsheet: when the row does not contain any cells, an empty row is created in the sheet.
+35
View File
@@ -0,0 +1,35 @@
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
+9 -4
View File
@@ -12,12 +12,13 @@
}
],
"require": {
"php": ">=5.4.0",
"php": ">=7.1.0",
"ext-zip": "*",
"ext-xmlreader" : "*"
},
"require-dev": {
"phpunit/phpunit": "^4.8.0"
"phpunit/phpunit": "^7",
"friendsofphp/php-cs-fixer": "^2"
},
"suggest": {
"ext-iconv": "To handle non UTF-8 CSV files (if \"php-intl\" is not already installed or is too limited)",
@@ -30,8 +31,12 @@
},
"extra": {
"branch-alias": {
"dev-master": "2.8.x-dev"
"dev-master": "3.1.x-dev"
}
},
"config": {
"platform": {
"php": "7.1"
}
}
}
Generated
-980
View File
@@ -1,980 +0,0 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "8957b9da742e28d7250c02fca8f9a5a7",
"content-hash": "973b8a4a1d8c520dd99fcd32cb5e022f",
"packages": [],
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
"reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
"shasum": ""
},
"require": {
"php": ">=5.3,<8.0-DEV"
},
"require-dev": {
"athletic/athletic": "~0.1.8",
"ext-pdo": "*",
"ext-phar": "*",
"phpunit/phpunit": "~4.0",
"squizlabs/php_codesniffer": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com",
"homepage": "http://ocramius.github.com/"
}
],
"description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
"homepage": "https://github.com/doctrine/instantiator",
"keywords": [
"constructor",
"instantiate"
],
"time": "2015-06-14 21:17:01"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
"reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
},
"suggest": {
"dflydev/markdown": "~1.0",
"erusev/parsedown": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-0": {
"phpDocumentor": [
"src/"
]
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "mike.vanriel@naenius.com"
}
],
"time": "2015-02-03 12:10:50"
},
{
"name": "phpspec/prophecy",
"version": "v1.6.0",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
"reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "~2.0",
"sebastian/comparator": "~1.1",
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"phpspec/phpspec": "~2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5.x-dev"
}
},
"autoload": {
"psr-0": {
"Prophecy\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Konstantin Kudryashov",
"email": "ever.zet@gmail.com",
"homepage": "http://everzet.com"
},
{
"name": "Marcello Duarte",
"email": "marcello.duarte@gmail.com"
}
],
"description": "Highly opinionated mocking framework for PHP 5.3+",
"homepage": "https://github.com/phpspec/prophecy",
"keywords": [
"Double",
"Dummy",
"fake",
"mock",
"spy",
"stub"
],
"time": "2016-02-15 07:46:21"
},
{
"name": "phpunit/php-code-coverage",
"version": "2.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"phpunit/php-file-iterator": "~1.3",
"phpunit/php-text-template": "~1.2",
"phpunit/php-token-stream": "~1.3",
"sebastian/environment": "^1.3.2",
"sebastian/version": "~1.0"
},
"require-dev": {
"ext-xdebug": ">=2.1.4",
"phpunit/phpunit": "~4"
},
"suggest": {
"ext-dom": "*",
"ext-xdebug": ">=2.2.1",
"ext-xmlwriter": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"time": "2015-10-06 15:47:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
"reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
"time": "2015-06-21 13:08:43"
},
{
"name": "phpunit/php-text-template",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"time": "2015-06-21 13:50:34"
},
{
"name": "phpunit/php-timer",
"version": "1.0.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
"reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4|~5"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
"timer"
],
"time": "2016-05-12 18:03:57"
},
{
"name": "phpunit/php-token-stream",
"version": "1.4.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
"reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Wrapper around PHP's tokenizer extension.",
"homepage": "https://github.com/sebastianbergmann/php-token-stream/",
"keywords": [
"tokenizer"
],
"time": "2015-09-15 10:49:45"
},
{
"name": "phpunit/phpunit",
"version": "4.8.26",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fc1d8cd5b5de11625979125c5639347896ac2c74"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74",
"reference": "fc1d8cd5b5de11625979125c5639347896ac2c74",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-json": "*",
"ext-pcre": "*",
"ext-reflection": "*",
"ext-spl": "*",
"php": ">=5.3.3",
"phpspec/prophecy": "^1.3.1",
"phpunit/php-code-coverage": "~2.1",
"phpunit/php-file-iterator": "~1.4",
"phpunit/php-text-template": "~1.2",
"phpunit/php-timer": "^1.0.6",
"phpunit/phpunit-mock-objects": "~2.3",
"sebastian/comparator": "~1.1",
"sebastian/diff": "~1.2",
"sebastian/environment": "~1.3",
"sebastian/exporter": "~1.2",
"sebastian/global-state": "~1.0",
"sebastian/version": "~1.0",
"symfony/yaml": "~2.1|~3.0"
},
"suggest": {
"phpunit/php-invoker": "~1.1"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.8.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"time": "2016-05-17 03:09:28"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "2.3.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": ">=5.3.3",
"phpunit/php-text-template": "~1.2",
"sebastian/exporter": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"suggest": {
"ext-soap": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sb@sebastian-bergmann.de",
"role": "lead"
}
],
"description": "Mock Object library for PHPUnit",
"homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
"keywords": [
"mock",
"xunit"
],
"time": "2015-10-02 06:51:40"
},
{
"name": "sebastian/comparator",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
"reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/diff": "~1.2",
"sebastian/exporter": "~1.2"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "http://www.github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"time": "2015-07-26 15:48:44"
},
{
"name": "sebastian/diff",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
"reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
"diff"
],
"time": "2015-12-08 07:14:41"
},
{
"name": "sebastian/environment",
"version": "1.3.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716",
"reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "http://www.github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
"hhvm"
],
"time": "2016-05-17 03:18:57"
},
{
"name": "sebastian/exporter",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "7ae5513327cb536431847bcc0c10edba2701064e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
"reference": "7ae5513327cb536431847bcc0c10edba2701064e",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"sebastian/recursion-context": "~1.0"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"homepage": "http://www.github.com/sebastianbergmann/exporter",
"keywords": [
"export",
"exporter"
],
"time": "2015-06-21 07:55:53"
},
{
"name": "sebastian/global-state",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.2"
},
"suggest": {
"ext-uopz": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Snapshotting of global state",
"homepage": "http://www.github.com/sebastianbergmann/global-state",
"keywords": [
"global state"
],
"time": "2015-10-12 03:26:01"
},
{
"name": "sebastian/recursion-context",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "913401df809e99e4f47b27cdd781f4a258d58791"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
"reference": "913401df809e99e4f47b27cdd781f4a258d58791",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"require-dev": {
"phpunit/phpunit": "~4.4"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"time": "2015-11-11 19:50:13"
},
{
"name": "sebastian/version",
"version": "1.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/version.git",
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
"shasum": ""
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that helps with managing the version number of Git-hosted PHP projects",
"homepage": "https://github.com/sebastianbergmann/version",
"time": "2015-06-21 13:59:46"
},
{
"name": "symfony/yaml",
"version": "v2.8.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/e4fbcc65f90909c999ac3b4dfa699ee6563a9940",
"reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2016-03-29 19:00:15"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=5.4.0",
"ext-zip": "*",
"ext-xmlreader": "*",
"ext-simplexml": "*"
},
"platform-dev": []
}
+9
View File
@@ -0,0 +1,9 @@
/.idea
*.iml
/tests/resources/generated
/tests/coverage
/vendor
/.sass-cache
/_site
Executable
+17
View File
@@ -0,0 +1,17 @@
# Spout Documentation
## Quickstart
1. Install `jekyll`: `sudo gem install jekyll`
2. Run the site locally: `jekyll serve`
## Usage with Docker and Docker Compose
* Install Docker and Docker Compose
* Run ```docker-compose up```
* Point your browser at [http://127.0.0.1:8080/](http://127.0.0.1:8080/)
## Editing the documentation
* All documents relevant for the documentation are located in the ```_pages``` folder.
+37
View File
@@ -0,0 +1,37 @@
# Welcome to Jekyll!
#
# This config file is meant for settings that affect your whole blog, values
# which you are expected to set up once and rarely need to edit after that.
# For technical reasons, this file is *NOT* reloaded automatically when you use
# 'jekyll serve'. If you change this file, please restart the server process.
# Site settings
title: Spout
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."
baseurl: "" # the subpath of your site, e.g. /blog
url: "http://opensource.box.com" # the base hostname & protocol for your site
# Build settings
markdown: kramdown
highlighter: rouge
kramdown:
input: GFM
syntax_highlighter: rouge
exclude: ["README.md"]
collections:
pages:
output: true
sections:
output: true
# 3rd parties
#google_analytics:
algolia:
enabled: false
# apiKey:
# indexName:
# Misc
spout_html: <span class="spout">Spout</span>
+9
View File
@@ -0,0 +1,9 @@
{% if site.algolia.enabled %}
<script type="text/javascript" src="https://cdn.jsdelivr.net/docsearch.js/1/docsearch.min.js"></script>
<script type="text/javascript"> docsearch({
apiKey: '{{ site.algolia.apiKey }}',
indexName: '{{ site.algolia.indexName }}',
inputSelector: '#algolia-doc-search'
});
</script>
{% endif %}
+12
View File
@@ -0,0 +1,12 @@
{% if site.google_analytics %}
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{{ site.google_analytics }}', 'auto');
ga('send', 'pageview');
</script>
{% endif %}
+9
View File
@@ -0,0 +1,9 @@
<div class="site-banner">
<div class="wrapper vertical-align-middle">
<p class="tag-line">
Read and write spreadsheets
<br />
<strong>quickly</strong> and <strong>at scale</strong>
</p>
</div>
</div>
+9
View File
@@ -0,0 +1,9 @@
<!-- http://ricostacruz.com/til/relative-paths-in-jekyll.html -->
<!-- _includes/base.html -->
{% assign base = '' %}
{% assign depth = page.url | split: '/' | size | minus: 1 %}
{% if depth == 1 %}{% assign base = '.' %}
{% elsif depth == 2 %}{% assign base = '..' %}
{% elsif depth == 3 %}{% assign base = '../..' %}
{% elsif depth == 4 %}{% assign base = '../../..' %}{% endif %}
+25
View File
@@ -0,0 +1,25 @@
<footer class="site-footer">
<div class="wrapper">
<div class="footer-col-wrapper">
<div class="footer-col footer-col-1">
<ul class="contact-list">
<li>Need to contact us?<br><a href="https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"><img src="https://badges.gitter.im/box/spout.svg" alt="Join the chat at https://gitter.im/box/spout" /></a></li>
</ul>
</div>
<div class="footer-col footer-col-2">
<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>
</ul>
</div>
<div class="footer-col footer-col-3">
<p>{{ site.title }} - {{ site.description }}</p>
</div>
</div>
</div>
</footer>
+17
View File
@@ -0,0 +1,17 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<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 %}">
<link rel="stylesheet" href="{{ "/css/main.css" | prepend: site.github.url }}">
{% if site.algolia.enabled %}
<link rel="stylesheet" href="//cdn.jsdelivr.net/docsearch.js/1/docsearch.min.css" />
{% endif %}
{% if jekyll.environment == 'production' %}
{% include analytics.html %}
{% endif %}
</head>
+33
View File
@@ -0,0 +1,33 @@
<header class="site-header">
<div class="wrapper">
<a class="site-title" href="{{ site.github.url }}/">{{ site.title }}</a>
<nav class="site-nav">
<a href="#" class="menu-icon">
<svg viewBox="0 0 18 15">
<path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/>
<path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/>
<path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/>
</svg>
</a>
<div class="trigger">
<a class="page-link" href="{{ site.github.url }}/getting-started/">Getting Started</a>
<a class="page-link" href="{{ site.github.url }}/docs/">Documentation</a>
<a class="page-link" href="{{ site.github.url }}/guides/">Guides</a>
<a class="page-link" href="{{ site.github.url }}/faq/">FAQ</a>
<a class="page-link" href="https://github.com/box/spout">
<span class="icon icon--github">{% include icon-github.svg %}</span>
<span class="username">GitHub</span>
</a>
{% if site.algolia.enabled %}
<span class="site-search">
<input type="text" id="algolia-doc-search">
</span>
{% endif %}
</div>
</nav>
</div>
</header>
+1
View File
@@ -0,0 +1 @@
<a href="https://github.com/{{ include.username }}"><span class="icon icon--github">{% include icon-github.svg %}</span><span class="username">{{ include.username }}</span></a>
+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 16 16"><path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z"/></svg>

After

Width:  |  Height:  |  Size: 926 B

@@ -0,0 +1,11 @@
{% assign sectionClass = "section-even centered" %}
{% assign sectionTitle = "Fast and Scalable" %}
{% assign sectionIcons = "icon-lightning-bolt.png" %}
{% capture sectionContent %}
Reading a small CSV file? No problem!<br>
Reading a huge XLSX file? No extra code needed!<br>
Writing an ODS file with millions of rows? {{ site.spout_html }} can do it in no time!
{% endcapture %}
{% include section.html %}
@@ -0,0 +1,14 @@
{% assign sectionClass = "section-odd centered" %}
{% assign sectionTitle = "Supported Spreadsheet Types" %}
{% assign sectionIcons = "icon-xlsx.png~~icon-ods.png~~icon-csv.png" %}
{% assign emS = '<span class="light-em">' %}
{% assign emE = '</span>' %}
{% capture sectionContent %}
{{ site.spout_html }} supports 3 types of spreadsheets: {{emS}}XLSX{{emE}}, {{emS}}ODS{{emE}} and {{emS}}CSV{{emE}}.<br>
{{ site.spout_html }} provides a simple and unified API to read or create these different types of spreadsheets.
Switching from one type to another is ridiculously easy!
{% endcapture %}
{% include section.html %}
+20
View File
@@ -0,0 +1,20 @@
{% assign sectionClass = "section-odd last-section" %}
{% capture sectionTitle %}Why use {{ site.spout_html }}?{% endcapture %}
{% assign sectionIcons = "" %}
{% assign emS = '<span class="light-em">' %}
{% assign emE = '</span>' %}
{% capture sectionContent %}
<ul class="feature-list">
<li class="feature-check">{{ site.spout_html }} is capable of processing files of {{emS}}any size{{emE}}.</li>
<li class="feature-check">{{ site.spout_html }} needs {{emS}}only 3MB{{emE}} of memory to process any file.</li>
<li class="feature-check">{{ site.spout_html }}'s streaming mechanism makes it {{emS}}incredibly fast{{emE}}.</li>
<li class="feature-check">{{ site.spout_html }}'s API is {{emS}}developer-friendly{{emE}}.</li>
</ul><br>
<div class="btn-wrapper">
<a href="{{ site.github.url }}/getting-started/" class="page-link"><button class="btn">Get started</button></a>
</div>
{% endcapture %}
{% include section.html %}
+17
View File
@@ -0,0 +1,17 @@
<div class="section {{ sectionClass }} vertical-align-middle">
<div class="wrapper">
<div class="description mbl">
<h2 class="section-title">{{ sectionTitle }}</h2>
{{ sectionContent }}
</div>
{% if sectionIcons %}
{% assign sectionIconsAsArray = sectionIcons | split:'~~' %}
<div class="icons">
{% for icon in sectionIconsAsArray %}
<img src="{{ site.github.url }}/images/{{ icon }}"/>
{% endfor %}
</div>
{% endif %}
</div>
</div>
+29
View File
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
{% include head.html %}
<body>
{% include header.html %}
{% if page.banner %}
{% include banner.html %}
{% endif %}
{{ content }}
<!--
<div class="page-content">
<div class="wrapper">
</div>
</div>
-->
{% include footer.html %}
{% include algolia.html %}
</body>
</html>
+40
View File
@@ -0,0 +1,40 @@
---
layout: default
---
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/anchor-js/4.1.0/anchor.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function() {
anchors.add();
$('.post-title, .doc-content h1, .doc-content h2, .doc-content h3').each(function(index, element) {
var el = $(element);
var title = el.text();
var id = el.attr('id');
var tag = el.prop('tagName');
var titleEl = $('<div>').append(
$('<a>')
.attr('href', '#' + id)
.addClass(tag.toLowerCase())
.text(title));
$('.table-of-content').append(titleEl);
});
});
</script>
<div class="table-of-content" data-spy="affix" data-offset-top="40" data-offset-bottom="300">
</div>
<article class="post">
<div class="wrapper">
<header class="post-header">
<h1 class="post-title">{{ page.title }}</h1>
</header>
<div class="post-content">
<div class="doc-content">
{{ content }}
</div>
</div>
</div>
+14
View File
@@ -0,0 +1,14 @@
---
layout: default
---
<article class="post">
<div class="wrapper">
<header class="post-header">
<h1 class="post-title">{{ page.title }}</h1>
</header>
<div class="post-content">
{{ content }}
</div>
</div>
</article>
+15
View File
@@ -0,0 +1,15 @@
---
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>
+319
View File
@@ -0,0 +1,319 @@
---
layout: doc
title: Documentation
permalink: /docs/
---
## Configuration for CSV
It is possible to configure both the CSV reader and writer to adapt them to your requirements:
```php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createReaderFromFile('/path/to/file.csv');
/** All of these methods have to be called before opening the reader. */
$reader->setFieldDelimiter('|');
$reader->setFieldEnclosure('@');
```
Additionally, if you need to read non UTF-8 files, you can specify the encoding of your file this way:
```php
$reader->setEncoding('UTF-16LE');
```
By default, the writer generates CSV files encoded in UTF-8, with a BOM.
It is however possible to not include the BOM:
```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
$writer = WriterEntityFactory::createWriterFromFile('/path/to/file.csv');
$writer->setShouldAddBOM(false);
```
## Configuration for XLSX and ODS
### New sheet creation
It is possible to change the behavior of the writers when the maximum number of rows (*1,048,576*) has been written in the current sheet. By default, a new sheet is automatically created so that writing can keep going but that may not always be preferable.
```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
$writer = WriterEntityFactory::createODSWriter();
$writer->setShouldCreateNewSheetsAutomatically(true); // default value
$writer->setShouldCreateNewSheetsAutomatically(false); // will stop writing new data when limit is reached
```
### Using a custom temporary folder
Processing XLSX and ODS files requires temporary files to be created. By default, {{ site.spout_html }} will use the system default temporary folder (as returned by `sys_get_temp_dir()`). It is possible to override this by explicitly setting it on the reader or writer:
```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
$writer = WriterEntityFactory::createXLSXWriter();
$writer->setTempFolder($customTempFolderPath);
```
### Strings storage (XLSX writer)
XLSX files support different ways to store the string values:
* Shared strings are meant to optimize file size by separating strings from the sheet representation and ignoring strings duplicates (if a string is used three times, only one string will be stored)
* Inline strings are less optimized (as duplicate strings are all stored) but is faster to process
In order to keep the memory usage really low, {{ site.spout_html }} does not de-duplicate strings when using shared strings. It is nevertheless possible to use this mode.
```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
$writer = WriterEntityFactory::createXLSXWriter();
$writer->setShouldUseInlineStrings(true); // default (and recommended) value
$writer->setShouldUseInlineStrings(false); // will use shared strings
```
> #### Note on Apple Numbers and iOS support
>
> Apple's products (Numbers and the iOS previewer) don't support inline strings and display empty cells instead. Therefore, if these platforms need to be supported, make sure to use shared strings!
### Date/Time formatting
When reading a spreadsheet containing dates or times, {{ site.spout_html }} returns the values by default as `DateTime` objects.
It is possible to change this behavior and have a formatted date returned instead (e.g. "2016-11-29 1:22 AM"). The format of the date corresponds to what is specified in the spreadsheet.
```php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createXLSXReader();
$reader->setShouldFormatDates(false); // default value
$reader->setShouldFormatDates(true); // will return formatted dates
```
## Empty rows
By default, when {{ site.spout_html }} reads a spreadsheet it skips empty rows and only return rows containing data.
This behavior can be changed so that {{ site.spout_html }} returns all rows:
```php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createReaderFromFile('/path/to/file.ext');
$reader->setShouldPreserveEmptyRows(true);
```
## Styling
### Available styles
{{ site.spout_html }} supports styling at a row and cell level. It is possible to customize the fonts, backgrounds, alignment as well as borders.
For fonts and alignments, {{ site.spout_html }} does not support all the possible formatting options yet. But you can find the most important ones:
| Category | Property | API
|:----------|:--------------|:--------------------------------------
| Font | Bold | `StyleBuilder::setFontBold()`
| | Italic | `StyleBuilder::setFontItalic()`
| | Underline | `StyleBuilder::setFontUnderline()`
| | Strikethrough | `StyleBuilder::setFontStrikethrough()`
| | Font name | `StyleBuilder::setFontName('Arial')`
| | Font size | `StyleBuilder::setFontSize(14)`
| | Font color | `StyleBuilder::setFontColor(Color::BLUE)`<br>`StyleBuilder::setFontColor(Color::rgb(0, 128, 255))`
| Alignment | Wrap text | `StyleBuilder::setShouldWrapText(true|false)`
### Styling rows
It is possible to apply some formatting options to a row. In this case, all cells of the row will have the same style:
```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
use Box\Spout\Common\Entity\Style\Color;
$writer = WriterEntityFactory::createXLSXWriter();
$writer->openToFile($filePath);
/** Create a style with the StyleBuilder */
$style = (new StyleBuilder())
->setFontBold()
->setFontSize(15)
->setFontColor(Color::BLUE)
->setShouldWrapText()
->setBackgroundColor(Color::YELLOW)
->build();
/** Create a row with cells and apply the style to all cells */
$row = WriterEntityFactory::createRowFromArray(['Carl', 'is', 'great'], $style);
/** Add the row to the writer */
$writer->addRow($row);
$writer->close();
```
Adding borders to a row requires a ```Border``` object.
```php
use Box\Spout\Common\Entity\Style\Border;
use Box\Spout\Writer\Common\Creator\Style\BorderBuilder;
use Box\Spout\Common\Entity\Style\Color;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
$border = (new BorderBuilder())
->setBorderBottom(Color::GREEN, Border::WIDTH_THIN, Border::STYLE_DASHED)
->build();
$style = (new StyleBuilder())
->setBorder($border)
->build();
$writer = WriterEntityFactory::createXLSXWriter();
$writer->openToFile($filePath);
$cells = WriterEntityFactory::createCell('Border Bottom Green Thin Dashed');
$row = WriterEntityFactory::createRow($cells);
$row->setStyle($style);
$writer->addRow($row);
$writer->close();
```
### Styling cells
The same styling techniques as described in [Styling rows](#styling-rows) can be applied to individual cells of a row as well.
Cell styles are inherited from the parent row and the default row style respectively.
The styles applied to a specific cell will override any parent styles if present.
Example:
```php
use Box\Spout\Common\Entity\Style\Color;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
$defaultStyle = (new StyleBuilder())
->setFontSize(8)
->build();
$writer = WriterEntityFactory::createXLSXWriter();
$writer->setDefaultRowStyle($defaultStyle)
->openToFile($filePath);
$zebraBlackStyle = (new StyleBuilder())
->setBackgroundColor(Color::BLACK)
->setFontColor(Color::WHITE)
->setFontSize(10)
->build();
$zebraWhiteStyle = (new StyleBuilder())
->setBackgroundColor(Color::WHITE)
->setFontColor(Color::BLACK)
->setFontItalic()
->build();
$cells = [
WriterEntityFactory::createCell('Ze', $zebraBlackStyle),
WriterEntityFactory::createCell('bra', $zebraWhiteStyle)
];
$rowStyle = (new StyleBuilder())
->setFontBold()
->build();
$row = WriterEntityFactory::createRow($cells, $rowStyle);
$writer->addRow($row);
$writer->close();
```
### Default style
{{ site.spout_html }} will use a default style for all created rows. This style can be overridden this way:
```php
$defaultStyle = (new StyleBuilder())
->setFontName('Arial')
->setFontSize(11)
->build();
$writer = WriterEntityFactory::createXLSXWriter();
$writer->setDefaultRowStyle($defaultStyle)
->openToFile($filePath);
```
## Playing with sheets
When creating a XLSX or ODS file, it is possible to control which sheet the data will be written into. At any time, you can retrieve or set the current sheet:
```php
$firstSheet = $writer->getCurrentSheet();
$writer->addRow($rowForSheet1); // writes the row to the first sheet
$newSheet = $writer->addNewSheetAndMakeItCurrent();
$writer->addRow($rowForSheet2); // writes the row to the new sheet
$writer->setCurrentSheet($firstSheet);
$writer->addRow($anotherRowForSheet1); // append the row to the first sheet
```
It is also possible to retrieve all the sheets currently created:
```php
$sheets = $writer->getSheets();
```
It is possible to retrieve some sheet's attributes when reading:
```php
foreach ($reader->getSheetIterator() as $sheet) {
$sheetName = $sheet->getName();
$isSheetVisible = $sheet->isVisible();
$isSheetActive = $sheet->isActive(); // active sheet when spreadsheet last saved
}
```
If you rely on the sheet's name in your application, you can customize it this way:
```php
// Accessing the sheet name when writing
$sheet = $writer->getCurrentSheet();
$sheetName = $sheet->getName();
// Customizing the sheet name when writing
$sheet = $writer->getCurrentSheet();
$sheet->setName('My custom name');
```
> Please note that Excel has some restrictions on the sheet's name:
> * it must not be blank
> * it must not exceed 31 characters
> * it must not contain these characters: \ / ? * : [ or ]
> * it must not start or end with a single quote
> * it must be unique
>
> Handling these restrictions is the developer's responsibility. {{ site.spout_html }} does not try to automatically change the sheet's name, as one may rely on this name to be exactly what was passed in.
## Fluent interface
Because fluent interfaces are great, you can use them with {{ site.spout_html }}:
```php
use Box\Spout\Writer\WriterEntityFactory;
$writer = WriterEntityFactory::createWriterFromFile('path/to/file.ext');
$writer->setTempFolder($customTempFolderPath)
->setShouldUseInlineStrings(true)
->openToFile($filePath)
->addRow($headerRow)
->addRows($dataRows)
->close();
```
+30
View File
@@ -0,0 +1,30 @@
---
layout: page
title: Frequently Asked Questions
permalink: /faq/
---
### How can {{ site.spout_html }} handle such large data sets and still use less than 3MB of memory?
When writing data, {{ site.spout_html }} is streaming the data to files, one or few lines at a time. That means that it only keeps in memory the few rows that it needs to write. Once written, the memory is freed.
Same goes with reading. Only one row at a time is stored in memory. A special technique is used to handle shared strings in XLSX, storing them - if needed - into several small temporary files that allows fast access.
### How long does it take to generate a file with X rows?
Here are a few numbers regarding the performance of {{ site.spout_html }}:
| Type | Action | 2,000 rows (6,000 cells) | 200,000 rows (600,000 cells) | 2,000,000 rows (6,000,000 cells) |
|------|-------------------------------|--------------------------|------------------------------|----------------------------------|
| CSV | Read | < 1 second | 4 seconds | 2-3 minutes |
| | Write | < 1 second | 2 seconds | 2-3 minutes |
| XLSX | Read<br>*inline&nbsp;strings* | < 1 second | 35-40 seconds | 18-20 minutes |
| | Read<br>*shared&nbsp;strings* | 1 second | 1-2 minutes | 35-40 minutes |
| | Write | 1 second | 20-25 seconds | 8-10 minutes |
| ODS | Read | 1 second | 1-2 minutes | 5-6 minutes |
| | Write | < 1 second | 35-40 seconds | 5-6 minutes |
### Does {{ site.spout_html }} support charts or formulas?
No. This is a compromise to keep memory usage low. Charts and formulas requires data to be kept in memory in order to be used.
So the larger the file would be, the more memory would be consumed, preventing your code to scale well.
+135
View File
@@ -0,0 +1,135 @@
---
layout: doc
title: Getting Started
permalink: /getting-started/
---
This guide will help you install {{ site.spout_html }} and teach you how to use it.
## Requirements
* PHP version 7.1 or higher
* PHP extension `ext-zip` enabled
* PHP extension `ext-xmlreader` enabled
## Installation
### Composer (recommended)
{{ site.spout_html }} can be installed directly from [Composer](https://getcomposer.org/).
Run the following command:
```powershell
$ composer require box/spout
```
### Manual installation
If you can't use Composer, no worries! You can still install {{ site.spout_html }} manually.
> Before starting, make sure your system meets the [requirements](#requirements).
1. Download the source code from the [Releases page](https://github.com/box/spout/releases)
2. Extract the downloaded content into your project.
3. Add this code to the top controller (e.g. index.php) or wherever it may be more appropriate:
```php
// don't forget to change the path!
require_once '[PATH/TO]/src/Spout/Autoloader/autoload.php';
```
## Basic usage
### Reader
Regardless of the file type, the interface to read a file is always the same:
```php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createReaderFromFile('/path/to/file.ext');
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
foreach ($sheet->getRowIterator() as $row) {
// do stuff with the row
$cells = $row->getCells();
...
}
}
$reader->close();
```
If there are multiple sheets in the file, the reader will read all of them sequentially.
Note that {{ site.spout_html }} guesses the reader type based on the file extension. If the extension is not standard (`.csv`, `.ods`, `.xlsx` _- lower/uppercase_), a specific reader can be created directly:
```php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createXLSXReader();
// $reader = ReaderEntityFactory::createODSReader();
// $reader = ReaderEntityFactory::createCSVReader();
```
### Writer
As with the reader, there is one common interface to write data to a file:
```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Common\Entity\Row;
$reader = ReaderEntityFactory::createReaderFromFile('/path/to/file.ext');
$writer->openToFile($filePath); // write data to a file or to a PHP stream
//$writer->openToBrowser($fileName); // stream data directly to the browser
$cells = [
WriterEntityFactory::createCell('Carl'),
WriterEntityFactory::createCell('is'),
WriterEntityFactory::createCell('great!'),
];
/** add a row at a time */
$singleRow = WriterEntityFactory::createRow($cells);
$writer->addRow($singleRow);
/** add multiple rows at a time */
$multipleRows = [
WriterEntityFactory::createRow($cells),
WriterEntityFactory::createRow($cells),
];
$writer->addRows($multipleRows);
/** Shortcut: add a row from an array of values */
$values = ['Carl', 'is', 'great!'];
$rowFromValues = WriterEntityFactory::createRowFromArray($values);
$writer->addRow($rowFromValues);
$writer->close();
```
Similar to the reader, if the file extension of the file to be written is not standard, specific writers can be created this way:
```php
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Common\Entity\Row;
$writer = WriterEntityFactory::createXLSXWriter();
// $writer = WriterEntityFactory::createODSWriter();
// $writer = WriterEntityFactory::createCSVWriter();
```
For XLSX and ODS files, the number of rows per sheet is limited to *1,048,576*. By default, once this limit is reached, the writer will automatically create a new sheet and continue writing data into it.
## 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.
+19
View File
@@ -0,0 +1,19 @@
---
layout: page
title: Guides
permalink: /guides/
---
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.
{% assign pages=site.pages | sort: 'path' %}
<ul>
{% for page in pages %}
{% if page.title and page.category contains 'guide' %}
<li>
<a class="page-link" href="{{ page.url | prepend: site.github.url }}">{{ page.title }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
BIN
View File
Binary file not shown.
@@ -0,0 +1,62 @@
---
layout: page
title: "Add data to an existing spreadsheet"
category: guide
permalink: /guides/add-data-to-existing-spreadsheet/
---
A common use case when using spreadsheets is to add data to an existing spreadsheet. For instance, let's assume you built a spreadsheet containing the last orders on your favorite website and want to update it as you make a new order.
We'll start with a file called "orders.xlsx" and add a new row, containing the last order's info, at the end.
In order to avoid memory issues when dealing with large spreadsheets, {{ site.spout_html }} does not hold the whole representation of the spreadsheet in memory. So to alter an existing spreadsheet, we'll have to create a new one that is similar to the existing one and add the new data in the new one.
```php
<?php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
$existingFilePath = 'path/to/orders.xlsx';
$newFilePath = 'path/to/new-orders.xlsx';
// we need a reader to read the existing file...
$reader = ReaderEntityFactory::createReaderFromFile($existingFilePath);
$reader->setShouldFormatDates(true); // this is to be able to copy dates
$reader->open($existingFilePath);
// ... and a writer to create the new file
$writer = WriterEntityFactory::createWriterFromFile($newFilePath);
$writer->openToFile($newFilePath);
// let's read the entire spreadsheet...
foreach ($reader->getSheetIterator() as $sheetIndex => $sheet) {
// Add sheets in the new file, as we read new sheets in the existing one
if ($sheetIndex !== 1) {
$writer->addNewSheetAndMakeItCurrent();
}
foreach ($sheet->getRowIterator() as $row) {
// ... and copy each row into the new spreadsheet
$writer->addRow($row);
}
}
// At this point, the new spreadsheet contains the same data as the existing one.
// So let's add the new data:
$writer->addRow(
WriterEntityFactory::createRowFromArray(['2015-12-25', 'Christmas gift', 29, 'USD'])
);
$reader->close();
$writer->close();
```
Optionally, if you rely on the file name or want to keep only one file, simple remove the old file and rename the new one:
```php?start_inline=1
unlink($existingFilePath);
rename($newFilePath, $existingFilePath);
```
That's it! The created file now contains the updated data and is ready to be used.
@@ -0,0 +1,89 @@
---
layout: page
title: "Edit an existing spreadsheet"
category: guide
permalink: /guides/edit-existing-spreadsheet/
---
Editing an existing spreadsheet is a pretty common task that {{ site.spout_html }} is totally capable of doing.
With {{ site.spout_html }}, it is not possible to do things like `deleteRow(3)` or `insertRowAfter(5, $newRow)`. This is because {{ site.spout_html }} does not keep an in-memory representation of the entire spreadsheet, to avoid consuming all the memory available with large spreadsheets. This means, {{ site.spout_html }} does not know how to jump to the 3rd row directly and has especially no way of moving backwards (changing row 3 after having changed row 5). So let's see how this can be done, in a scalable way.
For this example, let's assume we have an existing ODS spreadsheet called "my-music.ods" that looks like this:
| Song title | Artist | Album | Year |
| ---------------- | --------------- | --------------- | ---- |
| Yesterday | The Beatles | The White Album | 1968 |
| Yellow Submarine | The Beatles | Unknown | 1968 |
| Space Oddity | David Bowie | David Bowie | 1969 |
| Thriller | Michael Jackson | Thriller | 1982 |
| No Woman No Cry | Bob Marley | Legend | 1984 |
| Buffalo Soldier | Bob Marley | Legend | 1984 |
> Note that the album for "Yellow Submarine" is "Unknown" and that the songs are ordered by year (most recent last).
We'd like to update the missing album for "Yellow Submarine", remove the Bob Marley's songs and add a new song: "Hotel California" from "The Eagles", released in 1976. Here is how this can be done:
```php
<?php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
$existingFilePath = '/path/to/my-music.ods';
$newFilePath = '/path/to/my-new-music.ods';
// we need a reader to read the existing file...
$reader = ReaderEntityFactory::createReaderFromFile($existingFilePath);
$reader->open($existingFilePath);
$reader->setShouldFormatDates(true); // this is to be able to copy dates
// ... and a writer to create the new file
$writer = WriterEntityFactory::createWriterFromFile($newFilePath);
$writer->openToFile($newFilePath);
// let's read the entire spreadsheet
foreach ($reader->getSheetIterator() as $sheetIndex => $sheet) {
// Add sheets in the new file, as you read new sheets in the existing one
if ($sheetIndex !== 1) {
$writer->addNewSheetAndMakeItCurrent();
}
foreach ($sheet->getRowIterator() as $rowIndex => $row) {
$songTitle = $row->getCellAtIndex(0);
$artist = $row->getCellAtIndex(1);
// Change the album name for "Yellow Submarine"
if ($songTitle === 'Yellow Submarine') {
$row->setCellAtIndex(WriterEntityFactory::createCell('The White Album'), 2);
}
// skip Bob Marley's songs
if ($artist === 'Bob Marley') {
continue;
}
// write the edited row to the new file
$writer->addRow($row);
// insert new song at the right position, between the 3rd and 4th rows
if ($rowIndex === 3) {
$writer->addRow(
WriterEntityFactory::createRowFromArray(['Hotel California', 'The Eagles', 'Hotel California', 1976])
);
}
}
}
$reader->close();
$writer->close();
```
Optionally, if you rely on the file name or want to keep only one file, simple remove the old file and rename the new one:
```php?start_inline=1
unlink($existingFilePath);
rename($newFilePath, $existingFilePath);
```
That's it! The created file now contains the updated data and is ready to be used.
@@ -0,0 +1,54 @@
---
layout: page
title: "Read data from a specific sheet only"
category: guide
permalink: /guides/read-data-from-specific-sheet/
---
Even though a spreadsheet contains multiple sheets, you may be interested in reading only one of them and skip the other ones. Here is how you can do it with {{ site.spout_html }}:
* If you know the name of the sheet
```php
<?php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createXLSXReader();
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
// only read data from "summary" sheet
if ($sheet->getName() === 'summary') {
foreach ($sheet->getRowIterator() as $row) {
// do something with the row
}
break; // no need to read more sheets
}
}
$reader->close();
```
* If you know the position of the sheet
```php
<?php
use Box\Spout\Reader\Common\Creator\ReaderEntityFactory;
$reader = ReaderEntityFactory::createXLSXReader();
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
// only read data from 3rd sheet
if ($sheet->getIndex() === 2) { // index is 0-based
foreach ($sheet->getRowIterator() as $row) {
// do something with the row
}
break; // no need to read more sheets
}
}
$reader->close();
```
@@ -0,0 +1,110 @@
---
layout: page
title: "[Symfony] Stream content of a large spreadsheet"
category: guide
permalink: /guides/symfony-stream-content-large-spreadsheet/
---
> This tutorial is for the PHP framework [Symfony](http://symfony.com/).
The main benefit of streaming content is that this content can be rendered as soon as it is available. No matter how big the content is, the browser will be able to start rendering it as soon as the first byte is sent.
Reading a static spreadsheet to display its content to a user is a great use case for streaming. The spreadsheet can contain from a few rows to thousands of them and we don't want to wait until the whole file has been read (which can take a long time) before showing something to the user. Let's see how [Symfony's StreamedResponse](http://symfony.com/doc/current/components/http_foundation/introduction.html#streaming-a-response) let us easily stream the content of the spreadsheet.
A regular controller usually builds the content to be displayed and encapsulate it into a `Response` object. Everything happens synchronously. Such a controller may look like this:
```php?start_inline=1
class MyRegularController extends Controller
{
/**
* @Route("/spreadsheet/read")
*/
public function readAction()
{
$filePath = '/path/to/static/file.xlsx';
// The content to be displayed has to be built entirely
// before it can be sent to the browser.
$content = '';
$reader = ReaderEntityFactory::createReaderFromFile($filePath);
$reader->open($filePath);
foreach ($reader->getSheetIterator() as $sheet) {
$content .= '<table>';
foreach ($sheet->getRowIterator() as $row) {
$content .= '<tr>';
$content .= implode(array_map(function($cell) {
return '<td>' . $cell . '</td>';
}, $row->getCells()));
$content .= '</tr>';
}
$content .= '</table><br>';
}
$reader->close();
// The response is sent to the browser
// once the entire file has been read.
$response = new Response($content);
$response->headers->set('Content-Type', 'text/html');
return $response;
}
}
```
Converting a regular controller to return a `StreamedResponse` is super easy! This is what it looks like after conversion:
```php?start_inline=1
class MyStreamController extends Controller
{
// See below how it is used.
const FLUSH_THRESHOLD = 100;
/**
* @Route("/spreadsheet/stream")
*/
public function readAction()
{
$filePath = '/path/to/static/file.xlsx';
// We'll now return a StreamedResponse.
$response = new StreamedResponse();
$response->headers->set('Content-Type', 'text/html');
// Instead of a string, the streamed response will execute
// a callback function to retrieve data chunks.
$response->setCallback(function() use ($filePath) {
// Same code goes inside the callback.
$reader = ReaderEntityFactory::createXLSXReader();
$reader->open($filePath);
$i = 0;
foreach ($reader->getSheetIterator() as $sheet) {
// The main difference with the regular response is
// that the content is now echo'ed, not appended.
echo '<table>';
foreach ($sheet->getRowIterator() as $row) {
echo '<tr>';
echo implode(array_map(function($cell) {
return '<td>' . $cell . '</td>';
}, $row->getCells()));
echo '</tr>';
$i++;
// Flushing the buffer every N rows to stream echo'ed content.
if ($i % FLUSH_THRESHOLD === 0) {
flush();
}
}
echo '</table><br>';
}
$reader->close();
});
return $response;
}
}
```
+10
View File
@@ -0,0 +1,10 @@
---
layout: default
banner: true
title: Spout - Read and write spreadsheets, quickly and at scale
permalink: /
---
{% include section-supported-spreadsheet-types.html %}
{% include section-fast-and-scalable.html %}
{% include section-why-use-spout.html %}
+278
View File
@@ -0,0 +1,278 @@
/**
* Reset some basic elements
*/
body, h1, h2, h3, h4, h5, h6,
p, blockquote, pre, hr,
dl, dd, ol, ul, figure {
margin: 0;
padding: 0;
}
/**
* Basic styling
*/
body {
font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family;
color: $text-color;
background-color: $background-color;
-webkit-text-size-adjust: 100%;
-webkit-font-feature-settings: "kern" 1;
-moz-font-feature-settings: "kern" 1;
-o-font-feature-settings: "kern" 1;
font-feature-settings: "kern" 1;
font-kerning: normal;
}
/**
* Set `margin-bottom` to maintain vertical rhythm
*/
h1, h2, h3, h4, h5, h6,
p, blockquote, pre,
ul, ol, dl, figure,
%vertical-rhythm {
margin-bottom: $spacing-unit / 2;
}
h1:not(:nth-child(1)), h2:not(:nth-child(1)), h3:not(:nth-child(1)), h4:not(:nth-child(1)), h5:not(:nth-child(1)), h6:not(:nth-child(1)) {
margin-top: $spacing-unit;
}
/**
* Tables
*/
table {
display: block;
width: 100%;
overflow: auto;
margin-top: 0;
margin-bottom: 16px;
border-spacing: 0;
border-collapse: collapse;
th, td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
}
tr {
background-color: #fff;
border-top: 1px solid #c6cbd1;
&:nth-child(2n) {
background-color: #f6f8fa;
}
}
th {
font-weight: 500;
}
}
/**
* Images
*/
img {
max-width: 100%;
vertical-align: middle;
}
/**
* Figures
*/
figure > img {
display: block;
}
figcaption {
font-size: $small-font-size;
}
/**
* Lists
*/
ul, ol {
margin-left: $spacing-unit;
}
li {
> ul,
> ol {
margin-bottom: 0;
}
}
/**
* Headings
*/
h1, h2, h3, h4, h5, h6 {
font-weight: $base-font-weight;
}
h1 {
font-size: 3em;
}
h2 {
font-size: 2em;
}
/**
* Links
*/
a {
color: $brand-color;
text-decoration: none;
font-weight: $base-font-weight;
&:visited {
color: lighten($brand-color, 15%);
}
&:hover {
color: $text-color;
text-decoration: underline;
}
}
/**
* Blockquotes
*/
blockquote {
color: $grey-color;
border-left: 4px solid lighten($brand-color, 65%);
padding: $spacing-unit / 3 $spacing-unit / 2;
font-style: italic;
font-weight: 200;
> :last-child {
margin-bottom: 0;
}
}
/**
* Code formatting
*/
pre,
code {
font-size: 15px;
// border: 1px solid darken($code-color, 5%);
border-radius: 3px;
// background-color: $code-color;
}
code {
padding: 1px 5px;
}
pre {
padding: 8px 12px;
overflow-x: auto;
> code {
border: 0;
padding-right: 0;
padding-left: 0;
}
}
/**
* Wrapper
*/
.wrapper {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
margin-right: auto;
margin-left: auto;
padding-right: $spacing-unit;
padding-left: $spacing-unit;
@extend %clearfix;
@include media-query($on-laptop) {
max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
max-width: calc(#{$content-width} - (#{$spacing-unit}));
padding-right: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
}
}
/**
* Clearfix
*/
%clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
/**
* Icons
*/
.icon {
> svg {
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle;
path {
fill: $grey-color;
}
}
}
.pull-left {
float: left;
}
.pull-right {
float: right;
}
.vertical-align-middle {
// position: relative;
// top: 50%;
// transform: translateY(50%);
display: flex;
justify-content: center;
align-items: center;
}
.mrl { margin-right: 20px; }
.mll { margin-left: 20px; }
.mbl { margin-bottom: 20px; }
.text-center { text-align: center; }
.light-em {
font-weight: $base-font-weight + 100;
}
+79
View File
@@ -0,0 +1,79 @@
.spout {
font-variant: small-caps;
}
.section {
padding: $spacing-unit 0;
border-bottom: 1px solid $grey-color-light;
&.last-section {
border-bottom: none;
}
.section-title {
text-align: center;
}
.description {
width: 600px;
}
&.centered .description {
text-align: center;
}
.icons {
text-align: center;
img {
margin: 10px 20px;
}
}
&.section-odd {
background-color: rgba($grey-color, 0.0);
}
&.section-even {
background-color: $grey-color-very-light;
}
.btn-wrapper {
margin: $spacing-unit 0;
text-align: center;
.btn {
border-radius: 5px;
border: 1px solid;
background: $brand-color;
box-shadow: none;
color: white;
font-size: 20px;
padding: 15px 30px;
text-decoration: none;
cursor: pointer;
font-weight: 500;
}
.btn:hover {
background: darken($brand-color, 10%);
}
.page-link:hover {
text-decoration: none;
}
}
}
.feature-list {
list-style: none;
padding: 0;
margin: 0;
}
.feature-check {
background: url("../images/blue-check-mark.png") no-repeat left top;
background-size: 15px 15px;
background-position-y: 4px;
padding-left: 25px;
}
+410
View File
@@ -0,0 +1,410 @@
/**
* Site header
*/
.site-header {
border-bottom: 1px solid lighten($brand-color, 15%);
min-height: 56px;
// Positioning context for the mobile navigation icon
position: relative;
}
.site-search {
padding-right: 10px;
}
.site-title {
font-size: 26px;
font-weight: 300;
line-height: 56px;
letter-spacing: -1px;
margin-bottom: 0;
float: left;
&,
&:visited {
color: $grey-color-dark;
}
}
.site-nav {
float: right;
line-height: 56px;
.menu-icon {
display: none;
}
.page-link {
color: $grey-color;
line-height: $base-line-height;
// Gaps between nav items, but not on the last one
&:not(:last-child) {
margin-right: 20px;
}
svg {
padding-bottom: 3px;
}
}
@include media-query($on-palm) {
position: absolute;
top: 9px;
right: $spacing-unit / 2;
background-color: $background-color;
border: 1px solid $grey-color-light;
border-radius: 5px;
text-align: right;
.menu-icon {
display: block;
float: right;
width: 36px;
height: 26px;
line-height: 0;
padding-top: 10px;
text-align: center;
> svg {
width: 18px;
height: 15px;
path {
fill: $grey-color-dark;
}
}
}
.trigger {
clear: both;
display: none;
}
&:hover .trigger {
display: block;
padding-bottom: 5px;
}
.page-link {
display: block;
padding: 5px 10px;
&:not(:last-child) {
margin-right: 0;
}
margin-left: 20px;
}
}
}
.site-banner {
width: 100%;
background: $brand-color;
height: 340px;
color: white;
.wrapper {
height: 100%;
background: url('../images/logo.png');
background-size: 30%;
background-repeat: no-repeat;
background-position: 95% center;
justify-content: flex-start;
}
.tag-line {
font-size: 2.8em;
line-height: 1.1em;
width: 60%;
}
@include media-query($on-palm) {
height: 240px;
.tag-line {
font-size: 1.8em;
}
}
}
/**
* Site footer
*/
.site-footer {
background: $grey-color-dark;
color: $grey-color-light;
border-top: 1px solid $grey-color-light;
padding: $spacing-unit 0;
margin-top: $spacing-unit;
a {
color: $grey-color-very-light;
}
.contact-list,
.social-media-list {
list-style: none;
margin-left: 0;
li {
margin-bottom: 2px;
}
}
.footer-col-wrapper {
font-size: 15px;
// color: $grey-color;
margin-left: -$spacing-unit / 2;
@extend %clearfix;
}
.footer-col {
float: left;
margin-bottom: $spacing-unit / 2;
padding-left: $spacing-unit / 2;
}
.footer-col-1 {
width: -webkit-calc(35% - (#{$spacing-unit} / 2));
width: calc(35% - (#{$spacing-unit} / 2));
}
.footer-col-2 {
width: -webkit-calc(25% - (#{$spacing-unit} / 2));
width: calc(25% - (#{$spacing-unit} / 2));
}
.footer-col-3 {
width: -webkit-calc(40% - (#{$spacing-unit} / 2));
width: calc(40% - (#{$spacing-unit} / 2));
}
@include media-query($on-laptop) {
.footer-col-1,
.footer-col-2 {
width: -webkit-calc(50% - (#{$spacing-unit} / 2));
width: calc(50% - (#{$spacing-unit} / 2));
}
.footer-col-3 {
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
width: calc(100% - (#{$spacing-unit} / 2));
}
}
@include media-query($on-palm) {
.footer-col {
float: none;
width: -webkit-calc(100% - (#{$spacing-unit} / 2));
width: calc(100% - (#{$spacing-unit} / 2));
}
}
}
/**
* Page content
*/
.page-content {
padding: $spacing-unit 0;
}
.page-heading {
font-size: 20px;
}
.post-list {
margin-left: 0;
list-style: none;
> li {
margin-bottom: $spacing-unit;
}
}
.post-meta {
font-size: $small-font-size;
color: $grey-color;
}
.post-link {
display: block;
font-size: 24px;
}
/**
* Posts
*/
.post-header {
margin-bottom: $spacing-unit;
padding-top: $spacing-unit;
}
.post-title {
font-size: 42px;
letter-spacing: -1px;
line-height: 1;
@include media-query($on-laptop) {
font-size: 36px;
}
}
.post-content {
margin-bottom: $spacing-unit;
h2 {
font-size: 32px;
@include media-query($on-laptop) {
font-size: 28px;
}
}
h3 {
font-size: 26px;
@include media-query($on-laptop) {
font-size: 22px;
}
}
h4 {
font-size: 20px;
@include media-query($on-laptop) {
font-size: 18px;
}
}
}
$table-of-content-width: 250px;
.table-of-content {
position: absolute;
left: 100px;
width: $table-of-content-width;
}
.table-of-content.affix-top {
position: absolute;
top: 70px;
}
.table-of-content.affix-bottom {
position: absolute;
bottom: 300;
}
.table-of-content.affix {
position: fixed;
top: 30px;
}
.table-of-content a.h1 {
font-weight: 600;
}
.table-of-content a.h2 {
font-weight: 400;
font-size: 14px;
position: relative;
left: 10px;
}
.table-of-content a.h3 {
font-size: 12px;
position: relative;
left: 20px;
}
// TODO
@include media-query($on-laptop-big+$table-of-content-width) {
.table-of-content {
left: 20px;
}
}
@include media-query($on-laptop-big) {
.table-of-content {
display: none;
}
}
@include media-query($on-laptop) {
.table-of-content {
display: none;
}
}
/* search */
input#algolia-doc-search {
background: transparent url("/images/search.png") no-repeat 10px center;
background-size: 16px 16px;
position: relative;
vertical-align: top;
margin-left: 10px;
padding: 0 10px;
padding-left: 35px;
height: 30px;
margin-top: 10px;
font-size: 16px;
line-height: 20px;
background-color: #fff;
border-radius: 4px;
color: #333;
outline: none;
width: 100px;
transition: width .2s ease;
box-shadow: none;
border: 1px solid #ddd;
}
input#algolia-doc-search:focus {
width: 200px;
}
/* Bottom border of each suggestion */
.algolia-docsearch-suggestion {
border-bottom-color: $brand-color;
}
/* Main category headers */
.algolia-docsearch-suggestion--category-header {
background-color: lighten($brand-color, 20%);
}
/* Highlighted search terms */
.algolia-docsearch-suggestion--highlight {
color: lighten($brand-color, 10%);
}
/* Highligted search terms in the main category headers */
.algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight {
background-color: $brand-color;
}
/* Currently selected suggestion */
.aa-cursor .algolia-docsearch-suggestion--content {
color: darken($brand-color, 10%);
}
.aa-cursor .algolia-docsearch-suggestion {
background: ligten($brand-color, 30%);
}
/* For bigger screens, when displaying results in two columns */
@media (min-width: 768px) {
/* Bottom border of each suggestion */
.algolia-docsearch-suggestion {
border-bottom-color: lighten($brand-color, 15%);
}
/* Left column, with secondary category header */
.algolia-docsearch-suggestion--subcategory-column {
border-right-color: lighten($brand-color, 25%);
background-color: lighten($brand-color, 45%);
color: darken($brand-color, 35%);
}
}
+109
View File
@@ -0,0 +1,109 @@
/**
* Syntax highlighting styles
*/
.highlight {
// markdown editing
$default: #989898;
$bg: #f5f5f5;
$caret: #00bdff;
$black: #000;
$white: #fff;
$invisible: #E0E0E0;
$highlight: #e6e6e6;
$inserted: #DDFFDD;
$output: #7F7F7F;
$promt: #555;
$traceback: #F93232;
$deleted: #fdd;
$selection: #C2E8FF;
$found: #FFE792;
$shadow: #808080;
$comment: #bbbaba;
$invalid: #F9F2CE;
$operator: #626FC9;
$keyword: #7aad36;
$symbol: #E8FFD5;
$type: #6700B9;
$constant: #9870EC;
$var: #4C8FC7;
$attribute: #d42a57;
$function: $keyword;
$built-in: $attribute;
$class: #3A1D72;
$exception: #F93232;
$section: #333333;
$number: $constant;
$literal: #de8325;
$string_re: #699D36;
$tag: $var;
$name-class: #3A77BF;
$entity: #6d98cf;
$punctuation: $black;
color: $default;
background-color: $bg;
//border: 1px solid darken($bg, 5%);
.bp { color: $caret; } // Name.Builtin.Pseudo
.c { color: $comment; } // Comment
.c1 { @extend .c; } // Comment.Single
.cm { @extend .c; } // Comment.Multiline
.cp { color: $shadow } // Comment.Preproc
.cs { @extend .c; } // Comment.Special
.err { color: $exception; background-color: $invalid } // Error
.gd { color: $black; background-color: $deleted; } // Generic.Deleted
.ge { color: $default; background-color: $highlight; } // Generic.Emph
.gh { color: $section;} // Generic.Heading
.gi { color: $black; background-color: $inserted; } // Generic.Inserted
.go { color: $output } // Generic.Output
.gp { color: $promt } // Generic.Prompt
.gr { color: $exception } // Generic.Error
.gs { background-color: $white; } // Generic.Strong
.gt { color: $traceback } // Generic.Traceback
.gu { color: $black; } // Generic.Subheading
.hll { background-color: $found }
.k { color: $keyword } // Keyword
.kc { color: $constant } // Keyword.Constant
.kd { @extend .k; } // Keyword.Declaration
.kn { @extend .k; } // Keyword.Namespace
.kp { @extend .k; } // Keyword.Pseudo
.kr { @extend .k; } // Keyword.Reserved
.kt { color: $type } // Keyword.Type
.m { color: $number; } // Literal.Number
.mf { @extend .m; } // Literal.Number.Float
.mh { @extend .m; } // Literal.Number.Hex
.mi { @extend .m; } // Literal.Number.Integer
.mo { @extend .m; } // Literal.Number.Oct
.il { @extend .m; } // Literal.Number.Integer.Long
.n { color: $default } // Name
.na { color: $attribute } // Name.Attribute
.nb { color: $built-in } // Name.Builtin
.nc { color: $name-class; } // Name.Class
.nd { color: $shadow } // Name.Decorator
.nf { color: $function } // Name.Function
.ni { color: $entity;} // Name.Entity
.nn { color: $class; text-decoration: underline } // Name.Namespace
.no { color: $constant } // Name.Constant
.nt { color: $tag;} // Name.Tag
.nv { @extend .v; } // Name.Variable
.nx { color: $default; }
.o { color: $punctuation } // Operator.Word
.ow { color: $operator } // Operator.Word
.s { color: $literal } // Literal.String
.s1 { @extend .s; } // Literal.String.Single
.s2 { @extend .s; } // Literal.String.Double
.sb { @extend .s; } // Literal.String.Backtick
.sc { @extend .s; } // Literal.String.Char
.sd { @extend .s; } // Literal.String.Doc
.se { @extend .s; } // Literal.String.Escape
.sh { @extend .s; } // Literal.String.Heredoc
.si { @extend .s; } // Literal.String.Interpol
.sr { color: $string_re; } // Literal.String.Regex
.ss { color: $caret; } // Literal.String.Symbol
.sx { @extend .s; } // Literal.String.Other
.v { color: $var }
.vc { color: $class; } // Name.Variable.Class
.vg { @extend .v; } // Name.Variable.Global
.vi { @extend .v; } // Name.Variable.Instance
.w { color: $invisible; } // Text.Whitespace
}
+56
View File
@@ -0,0 +1,56 @@
---
# Only the main Sass file needs front matter (the dashes are enough)
---
@charset "utf-8";
// Our variables
$base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
$base-font-size: 16px;
$base-font-weight: 300;
$small-font-size: $base-font-size * 0.875;
$base-line-height: 1.5;
$spacing-unit: 30px;
$text-color: #111;
$background-color: #fdfdfd;
$brand-color: #104c81;
$grey-color: #828282;
$grey-color-light: lighten($grey-color, 40%);
$grey-color-very-light: lighten($grey-color, 45%);
$grey-color-dark: darken($grey-color, 25%);
// Width of the content area
$content-width: 800px;
$on-palm: 600px;
$on-laptop: 800px;
$on-laptop-big: 1400px;
// Use media queries like this:
// @include media-query($on-palm) {
// .wrapper {
// padding-right: $spacing-unit / 2;
// padding-left: $spacing-unit / 2;
// }
// }
@mixin media-query($device) {
@media screen and (max-width: $device) {
@content;
}
}
// Import partials from `sass_dir` (defaults to `_sass`)
@import
"base",
"layout",
"syntax-highlighting",
"index"
;
+13
View File
@@ -0,0 +1,13 @@
version: "3"
services:
site:
command: jekyll serve
image: jekyll/jekyll:latest
volumes:
- $PWD:/srv/jekyll
- $PWD/vendor/bundle:/usr/local/bundle
ports:
- 4000:4000
- 35729:35729
- 3000:3000
- 8080:4000
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+13 -4
View File
@@ -1,18 +1,27 @@
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.3/phpunit.xsd"
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/7.5/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="false"
convertWarningsToExceptions="false"
defaultTestSuite="unit-tests"
verbose="false">
<testsuites>
<testsuite name="all-tests">
<testsuite name="unit-tests">
<directory>tests/</directory>
</testsuite>
</testsuites>
<groups>
<exclude>
<group>perf-tests</group>
</exclude>
</groups>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
+6 -8
View File
@@ -5,8 +5,6 @@ namespace Box\Spout\Autoloader;
/**
* Class Psr4Autoloader
* @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md#class-example
*
* @package Box\Spout\Autoloader
*/
class Psr4Autoloader
{
@@ -16,7 +14,7 @@ class Psr4Autoloader
*
* @var array
*/
protected $prefixes = array();
protected $prefixes = [];
/**
* Register loader with SPL autoloader stack.
@@ -25,7 +23,7 @@ class Psr4Autoloader
*/
public function register()
{
spl_autoload_register(array($this, 'loadClass'));
spl_autoload_register([$this, 'loadClass']);
}
/**
@@ -49,7 +47,7 @@ class Psr4Autoloader
// initialize the namespace prefix array
if (isset($this->prefixes[$prefix]) === false) {
$this->prefixes[$prefix] = array();
$this->prefixes[$prefix] = [];
}
// retain the base directory for the namespace prefix
@@ -74,8 +72,7 @@ class Psr4Autoloader
// work backwards through the namespace names of the fully-qualified
// class name to find a mapped file name
while (false !== $pos = strrpos($prefix, '\\')) {
while (($pos = strrpos($prefix, '\\')) !== false) {
// retain the trailing namespace separator in the prefix
$prefix = substr($class, 0, $pos + 1);
@@ -114,7 +111,6 @@ class Psr4Autoloader
// look through base directories for this namespace prefix
foreach ($this->prefixes[$prefix] as $baseDir) {
// replace the namespace prefix with the base directory,
// replace namespace separators with directory separators
// in the relative class name, append with .php
@@ -143,8 +139,10 @@ class Psr4Autoloader
{
if (file_exists($file)) {
require $file;
return true;
}
return false;
}
}
+1 -1
View File
@@ -5,7 +5,7 @@ namespace Box\Spout\Autoloader;
require_once 'Psr4Autoloader.php';
/**
* @var string $srcBaseDirectory
* @var string
* Full path to "src/Spout" which is what we want "Box\Spout" to map to.
*/
$srcBaseDirectory = dirname(dirname(__FILE__));
@@ -0,0 +1,49 @@
<?php
namespace Box\Spout\Common\Creator;
use Box\Spout\Common\Helper\EncodingHelper;
use Box\Spout\Common\Helper\FileSystemHelper;
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
use Box\Spout\Common\Helper\StringHelper;
/**
* Class HelperFactory
* Factory to create helpers
*/
class HelperFactory
{
/**
* @return GlobalFunctionsHelper
*/
public function createGlobalFunctionsHelper()
{
return new GlobalFunctionsHelper();
}
/**
* @param string $baseFolderPath The path of the base folder where all the I/O can occur
* @return FileSystemHelper
*/
public function createFileSystemHelper($baseFolderPath)
{
return new FileSystemHelper($baseFolderPath);
}
/**
* @param GlobalFunctionsHelper $globalFunctionsHelper
* @return EncodingHelper
*/
public function createEncodingHelper(GlobalFunctionsHelper $globalFunctionsHelper)
{
return new EncodingHelper($globalFunctionsHelper);
}
/**
* @return StringHelper
*/
public function createStringHelper()
{
return new StringHelper();
}
}
+208
View File
@@ -0,0 +1,208 @@
<?php
namespace Box\Spout\Common\Entity;
use Box\Spout\Common\Entity\Style\Style;
use Box\Spout\Common\Helper\CellTypeHelper;
/**
* Class Cell
*/
class Cell
{
/**
* Numeric cell type (whole numbers, fractional numbers, dates)
*/
const TYPE_NUMERIC = 0;
/**
* String (text) cell type
*/
const TYPE_STRING = 1;
/**
* Formula cell type
* Not used at the moment
*/
const TYPE_FORMULA = 2;
/**
* Empty cell type
*/
const TYPE_EMPTY = 3;
/**
* Boolean cell type
*/
const TYPE_BOOLEAN = 4;
/**
* Date cell type
*/
const TYPE_DATE = 5;
/**
* Error cell type
*/
const TYPE_ERROR = 6;
/**
* The value of this cell
* @var mixed|null
*/
protected $value;
/**
* The cell type
* @var int|null
*/
protected $type;
/**
* The cell style
* @var Style
*/
protected $style;
/**
* @param $value mixed
* @param Style|null $style
*/
public function __construct($value, Style $style = null)
{
$this->setValue($value);
$this->setStyle($style);
}
/**
* @param mixed|null $value
*/
public function setValue($value)
{
$this->value = $value;
$this->type = $this->detectType($value);
}
/**
* @return mixed|null
*/
public function getValue()
{
return !$this->isError() ? $this->value : null;
}
/**
* @param Style|null $style
*/
public function setStyle($style)
{
$this->style = $style ?: new Style();
}
/**
* @return Style
*/
public function getStyle()
{
return $this->style;
}
/**
* @return int|null
*/
public function getType()
{
return $this->type;
}
/**
* @param int $type
*/
public function setType($type)
{
$this->type = $type;
}
/**
* Get the current value type
*
* @param mixed|null $value
* @return int
*/
protected function detectType($value)
{
if (CellTypeHelper::isBoolean($value)) {
return self::TYPE_BOOLEAN;
}
if (CellTypeHelper::isEmpty($value)) {
return self::TYPE_EMPTY;
}
if (CellTypeHelper::isNumeric($value)) {
return self::TYPE_NUMERIC;
}
if (CellTypeHelper::isDateTimeOrDateInterval($value)) {
return self::TYPE_DATE;
}
if (CellTypeHelper::isNonEmptyString($value)) {
return self::TYPE_STRING;
}
return self::TYPE_ERROR;
}
/**
* @return bool
*/
public function isBoolean()
{
return $this->type === self::TYPE_BOOLEAN;
}
/**
* @return bool
*/
public function isEmpty()
{
return $this->type === self::TYPE_EMPTY;
}
/**
* @return bool
*/
public function isNumeric()
{
return $this->type === self::TYPE_NUMERIC;
}
/**
* @return bool
*/
public function isString()
{
return $this->type === self::TYPE_STRING;
}
/**
* @return bool
*/
public function isDate()
{
return $this->type === self::TYPE_DATE;
}
/**
* @return bool
*/
public function isError()
{
return $this->type === self::TYPE_ERROR;
}
/**
* @return string
*/
public function __toString()
{
return (string) $this->getValue();
}
}
+123
View File
@@ -0,0 +1,123 @@
<?php
namespace Box\Spout\Common\Entity;
use Box\Spout\Common\Entity\Style\Style;
class Row
{
/**
* The cells in this row
* @var Cell[]
*/
protected $cells = [];
/**
* The row style
* @var Style
*/
protected $style;
/**
* Row constructor.
* @param Cell[] $cells
* @param Style|null $style
*/
public function __construct(array $cells, $style)
{
$this
->setCells($cells)
->setStyle($style);
}
/**
* @return Cell[] $cells
*/
public function getCells()
{
return $this->cells;
}
/**
* @param Cell[] $cells
* @return Row
*/
public function setCells(array $cells)
{
$this->cells = [];
foreach ($cells as $cell) {
$this->addCell($cell);
}
return $this;
}
/**
* @param Cell $cell
* @param int $cellIndex
* @return Row
*/
public function setCellAtIndex(Cell $cell, $cellIndex)
{
$this->cells[$cellIndex] = $cell;
return $this;
}
/**
* @param int $cellIndex
* @return Cell|null
*/
public function getCellAtIndex($cellIndex)
{
return isset($this->cells[$cellIndex]) ? $this->cells[$cellIndex] : null;
}
/**
* @param Cell $cell
* @return Row
*/
public function addCell(Cell $cell)
{
$this->cells[] = $cell;
return $this;
}
/**
* @return int
*/
public function getNumCells()
{
return count($this->cells);
}
/**
* @return Style
*/
public function getStyle()
{
return $this->style;
}
/**
* @param Style|null $style
* @return Row
*/
public function setStyle($style)
{
$this->style = $style ?: new Style();
return $this;
}
/**
* @return array The row values, as array
*/
public function toArray()
{
return array_map(function (Cell $cell) {
return $cell->getValue();
}, $this->cells);
}
}
@@ -1,6 +1,6 @@
<?php
namespace Box\Spout\Writer\Style;
namespace Box\Spout\Common\Entity\Style;
/**
* Class Border
@@ -22,13 +22,11 @@ class Border
const WIDTH_MEDIUM = 'medium';
const WIDTH_THICK = 'thick';
/**
* @var array A list of BorderPart objects for this border.
*/
protected $parts = [];
/** @var array A list of BorderPart objects for this border. */
private $parts = [];
/**
* @param array|void $borderParts
* @param array $borderParts
*/
public function __construct(array $borderParts = [])
{
@@ -36,8 +34,8 @@ class Border
}
/**
* @param $name The name of the border part
* @return null|BorderPart
* @param string $name The name of the border part
* @return BorderPart|null
*/
public function getPart($name)
{
@@ -45,7 +43,7 @@ class Border
}
/**
* @param $name The name of the border part
* @param string $name The name of the border part
* @return bool
*/
public function hasPart($name)
@@ -64,6 +62,7 @@ class Border
/**
* Set BorderParts
* @param array $parts
* @return void
*/
public function setParts($parts)
{
@@ -75,11 +74,12 @@ class Border
/**
* @param BorderPart $borderPart
* @return self
* @return Border
*/
public function addPart(BorderPart $borderPart)
{
$this->parts[$borderPart->getName()] = $borderPart;
return $this;
}
}
@@ -1,6 +1,6 @@
<?php
namespace Box\Spout\Writer\Style;
namespace Box\Spout\Common\Entity\Style;
use Box\Spout\Writer\Exception\Border\InvalidNameException;
use Box\Spout\Writer\Exception\Border\InvalidStyleException;
@@ -39,7 +39,7 @@ class BorderPart
'solid',
'dashed',
'dotted',
'double'
'double',
];
/**
@@ -1,14 +1,12 @@
<?php
namespace Box\Spout\Writer\Style;
namespace Box\Spout\Common\Entity\Style;
use Box\Spout\Writer\Exception\InvalidColorException;
use Box\Spout\Common\Exception\InvalidColorException;
/**
* Class Color
* This class provides constants and functions to work with colors
*
* @package Box\Spout\Writer\Style
*/
class Color
{
@@ -29,7 +27,6 @@ class Color
/**
* Returns an RGB color from R, G and B values
*
* @api
* @param int $red Red component, 0 - 255
* @param int $green Green component, 0 - 255
* @param int $blue Blue component, 0 - 255
@@ -52,8 +49,8 @@ class Color
* Throws an exception is the color component value is outside of bounds (0 - 255)
*
* @param int $colorComponent
* @throws \Box\Spout\Common\Exception\InvalidColorException
* @return void
* @throws \Box\Spout\Writer\Exception\InvalidColorException
*/
protected static function throwIfInvalidColorComponentValue($colorComponent)
{
@@ -1,12 +1,10 @@
<?php
namespace Box\Spout\Writer\Style;
namespace Box\Spout\Common\Entity\Style;
/**
* Class Style
* Represents a style to be applied to a cell
*
* @package Box\Spout\Writer\Style
*/
class Style
{
@@ -16,67 +14,62 @@ class Style
const DEFAULT_FONT_NAME = 'Arial';
/** @var int|null Style ID */
protected $id = null;
private $id;
/** @var bool Whether the font should be bold */
protected $fontBold = false;
private $fontBold = false;
/** @var bool Whether the bold property was set */
protected $hasSetFontBold = false;
private $hasSetFontBold = false;
/** @var bool Whether the font should be italic */
protected $fontItalic = false;
private $fontItalic = false;
/** @var bool Whether the italic property was set */
protected $hasSetFontItalic = false;
private $hasSetFontItalic = false;
/** @var bool Whether the font should be underlined */
protected $fontUnderline = false;
private $fontUnderline = false;
/** @var bool Whether the underline property was set */
protected $hasSetFontUnderline = false;
private $hasSetFontUnderline = false;
/** @var bool Whether the font should be struck through */
protected $fontStrikethrough = false;
private $fontStrikethrough = false;
/** @var bool Whether the strikethrough property was set */
protected $hasSetFontStrikethrough = false;
private $hasSetFontStrikethrough = false;
/** @var int Font size */
protected $fontSize = self::DEFAULT_FONT_SIZE;
private $fontSize = self::DEFAULT_FONT_SIZE;
/** @var bool Whether the font size property was set */
protected $hasSetFontSize = false;
private $hasSetFontSize = false;
/** @var string Font color */
protected $fontColor = self::DEFAULT_FONT_COLOR;
private $fontColor = self::DEFAULT_FONT_COLOR;
/** @var bool Whether the font color property was set */
protected $hasSetFontColor = false;
private $hasSetFontColor = false;
/** @var string Font name */
protected $fontName = self::DEFAULT_FONT_NAME;
private $fontName = self::DEFAULT_FONT_NAME;
/** @var bool Whether the font name property was set */
protected $hasSetFontName = false;
private $hasSetFontName = false;
/** @var bool Whether specific font properties should be applied */
protected $shouldApplyFont = false;
private $shouldApplyFont = false;
/** @var bool Whether the text should wrap in the cell (useful for long or multi-lines text) */
protected $shouldWrapText = false;
private $shouldWrapText = false;
/** @var bool Whether the wrap text property was set */
protected $hasSetWrapText = false;
private $hasSetWrapText = false;
/**
* @var Border
*/
protected $border = null;
/** @var Border */
private $border;
/**
* @var bool Whether border properties should be applied
*/
protected $shouldApplyBorder = false;
/** @var bool Whether border properties should be applied */
private $shouldApplyBorder = false;
/** @var string Background color */
protected $backgroundColor = null;
private $backgroundColor;
/** @var bool */
protected $hasSetBackgroundColor = false;
private $hasSetBackgroundColor = false;
/**
* @return int|null
@@ -93,6 +86,7 @@ class Style
public function setId($id)
{
$this->id = $id;
return $this;
}
@@ -112,6 +106,7 @@ class Style
{
$this->shouldApplyBorder = true;
$this->border = $border;
return $this;
}
@@ -139,9 +134,18 @@ class Style
$this->fontBold = true;
$this->hasSetFontBold = true;
$this->shouldApplyFont = true;
return $this;
}
/**
* @return bool
*/
public function hasSetFontBold()
{
return $this->hasSetFontBold;
}
/**
* @return bool
*/
@@ -158,9 +162,18 @@ class Style
$this->fontItalic = true;
$this->hasSetFontItalic = true;
$this->shouldApplyFont = true;
return $this;
}
/**
* @return bool
*/
public function hasSetFontItalic()
{
return $this->hasSetFontItalic;
}
/**
* @return bool
*/
@@ -177,9 +190,18 @@ class Style
$this->fontUnderline = true;
$this->hasSetFontUnderline = true;
$this->shouldApplyFont = true;
return $this;
}
/**
* @return bool
*/
public function hasSetFontUnderline()
{
return $this->hasSetFontUnderline;
}
/**
* @return bool
*/
@@ -196,9 +218,18 @@ class Style
$this->fontStrikethrough = true;
$this->hasSetFontStrikethrough = true;
$this->shouldApplyFont = true;
return $this;
}
/**
* @return bool
*/
public function hasSetFontStrikethrough()
{
return $this->hasSetFontStrikethrough;
}
/**
* @return int
*/
@@ -216,9 +247,18 @@ class Style
$this->fontSize = $fontSize;
$this->hasSetFontSize = true;
$this->shouldApplyFont = true;
return $this;
}
/**
* @return bool
*/
public function hasSetFontSize()
{
return $this->hasSetFontSize;
}
/**
* @return string
*/
@@ -238,9 +278,18 @@ class Style
$this->fontColor = $fontColor;
$this->hasSetFontColor = true;
$this->shouldApplyFont = true;
return $this;
}
/**
* @return bool
*/
public function hasSetFontColor()
{
return $this->hasSetFontColor;
}
/**
* @return string
*/
@@ -258,9 +307,18 @@ class Style
$this->fontName = $fontName;
$this->hasSetFontName = true;
$this->shouldApplyFont = true;
return $this;
}
/**
* @return bool
*/
public function hasSetFontName()
{
return $this->hasSetFontName;
}
/**
* @return bool
*/
@@ -270,13 +328,14 @@ class Style
}
/**
* @param bool|void $shouldWrap Should the text be wrapped
* @param bool $shouldWrap Should the text be wrapped
* @return Style
*/
public function setShouldWrapText($shouldWrap = true)
{
$this->shouldWrapText = $shouldWrap;
$this->hasSetWrapText = true;
return $this;
}
@@ -305,6 +364,7 @@ class Style
{
$this->hasSetBackgroundColor = true;
$this->backgroundColor = $color;
return $this;
}
@@ -317,110 +377,10 @@ class Style
}
/**
*
* @return bool Whether the background color should be applied
*/
public function shouldApplyBackgroundColor()
{
return $this->hasSetBackgroundColor;
}
/**
* Serializes the style for future comparison with other styles.
* The ID is excluded from the comparison, as we only care about
* actual style properties.
*
* @return string The serialized style
*/
public function serialize()
{
// In order to be able to properly compare style, set static ID value
$currentId = $this->id;
$this->setId(0);
$serializedStyle = serialize($this);
$this->setId($currentId);
return $serializedStyle;
}
/**
* Merges the current style with the given style, using the given style as a base. This means that:
* - if current style and base style both have property A set, use current style property's value
* - if current style has property A set but base style does not, use current style property's value
* - if base style has property A set but current style does not, use base style property's value
*
* @NOTE: This function returns a new style.
*
* @param Style $baseStyle
* @return Style New style corresponding to the merge of the 2 styles
*/
public function mergeWith($baseStyle)
{
$mergedStyle = clone $this;
$this->mergeFontStyles($mergedStyle, $baseStyle);
$this->mergeOtherFontProperties($mergedStyle, $baseStyle);
$this->mergeCellProperties($mergedStyle, $baseStyle);
return $mergedStyle;
}
/**
* @param Style $styleToUpdate (passed as reference)
* @param Style $baseStyle
* @return void
*/
private function mergeFontStyles($styleToUpdate, $baseStyle)
{
if (!$this->hasSetFontBold && $baseStyle->isFontBold()) {
$styleToUpdate->setFontBold();
}
if (!$this->hasSetFontItalic && $baseStyle->isFontItalic()) {
$styleToUpdate->setFontItalic();
}
if (!$this->hasSetFontUnderline && $baseStyle->isFontUnderline()) {
$styleToUpdate->setFontUnderline();
}
if (!$this->hasSetFontStrikethrough && $baseStyle->isFontStrikethrough()) {
$styleToUpdate->setFontStrikethrough();
}
}
/**
* @param Style $styleToUpdate Style to update (passed as reference)
* @param Style $baseStyle
* @return void
*/
private function mergeOtherFontProperties($styleToUpdate, $baseStyle)
{
if (!$this->hasSetFontSize && $baseStyle->getFontSize() !== self::DEFAULT_FONT_SIZE) {
$styleToUpdate->setFontSize($baseStyle->getFontSize());
}
if (!$this->hasSetFontColor && $baseStyle->getFontColor() !== self::DEFAULT_FONT_COLOR) {
$styleToUpdate->setFontColor($baseStyle->getFontColor());
}
if (!$this->hasSetFontName && $baseStyle->getFontName() !== self::DEFAULT_FONT_NAME) {
$styleToUpdate->setFontName($baseStyle->getFontName());
}
}
/**
* @param Style $styleToUpdate Style to update (passed as reference)
* @param Style $baseStyle
* @return void
*/
private function mergeCellProperties($styleToUpdate, $baseStyle)
{
if (!$this->hasSetWrapText && $baseStyle->shouldWrapText()) {
$styleToUpdate->setShouldWrapText();
}
if (!$this->getBorder() && $baseStyle->shouldApplyBorder()) {
$styleToUpdate->setBorder($baseStyle->getBorder());
}
if (!$this->hasSetBackgroundColor && $baseStyle->shouldApplyBackgroundColor()) {
$styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor());
}
}
}
@@ -4,9 +4,6 @@ namespace Box\Spout\Common\Exception;
/**
* Class EncodingConversionException
*
* @api
* @package Box\Spout\Common\Exception
*/
class EncodingConversionException extends SpoutException
{
@@ -4,9 +4,6 @@ namespace Box\Spout\Common\Exception;
/**
* Class IOException
*
* @api
* @package Box\Spout\Common\Exception
*/
class IOException extends SpoutException
{
@@ -4,9 +4,6 @@ namespace Box\Spout\Common\Exception;
/**
* Class InvalidArgumentException
*
* @api
* @package Box\Spout\Common\Exception
*/
class InvalidArgumentException extends SpoutException
{
@@ -0,0 +1,10 @@
<?php
namespace Box\Spout\Common\Exception;
/**
* Class InvalidColorException
*/
class InvalidColorException extends SpoutException
{
}
@@ -5,7 +5,6 @@ namespace Box\Spout\Common\Exception;
/**
* Class SpoutException
*
* @package Box\Spout\Common\Exception
* @abstract
*/
abstract class SpoutException extends \Exception
@@ -4,9 +4,6 @@ namespace Box\Spout\Common\Exception;
/**
* Class UnsupportedTypeException
*
* @api
* @package Box\Spout\Common\Exception
*/
class UnsupportedTypeException extends SpoutException
{
@@ -0,0 +1,68 @@
<?php
namespace Box\Spout\Common\Helper;
/**
* Class CellTypeHelper
* This class provides helper functions to determine the type of the cell value
*/
class CellTypeHelper
{
/**
* @param $value
* @return bool Whether the given value is considered "empty"
*/
public static function isEmpty($value)
{
return ($value === null || $value === '');
}
/**
* @param $value
* @return bool Whether the given value is a non empty string
*/
public static function isNonEmptyString($value)
{
return (gettype($value) === 'string' && $value !== '');
}
/**
* Returns whether the given value is numeric.
* A numeric value is from type "integer" or "double" ("float" is not returned by gettype).
*
* @param $value
* @return bool Whether the given value is numeric
*/
public static function isNumeric($value)
{
$valueType = gettype($value);
return ($valueType === 'integer' || $valueType === 'double');
}
/**
* Returns whether the given value is boolean.
* "true"/"false" and 0/1 are not booleans.
*
* @param $value
* @return bool Whether the given value is boolean
*/
public static function isBoolean($value)
{
return gettype($value) === 'boolean';
}
/**
* Returns whether the given value is a DateTime or DateInterval object.
*
* @param $value
* @return bool Whether the given value is a DateTime or DateInterval object
*/
public static function isDateTimeOrDateInterval($value)
{
return (
$value instanceof \DateTime ||
$value instanceof \DateInterval
);
}
}
+4 -6
View File
@@ -7,8 +7,6 @@ use Box\Spout\Common\Exception\EncodingConversionException;
/**
* Class EncodingHelper
* This class provides helper functions to work with encodings.
*
* @package Box\Spout\Common\Helper
*/
class EncodingHelper
{
@@ -97,8 +95,8 @@ class EncodingHelper
*
* @param string $string Non UTF-8 string to be converted
* @param string $sourceEncoding The encoding used to encode the source string
* @return string The converted, UTF-8 string
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
* @return string The converted, UTF-8 string
*/
public function attemptConversionToUTF8($string, $sourceEncoding)
{
@@ -110,8 +108,8 @@ class EncodingHelper
*
* @param string $string UTF-8 string to be converted
* @param string $targetEncoding The encoding the string should be re-encoded into
* @return string The converted string, encoded with the given encoding
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
* @return string The converted string, encoded with the given encoding
*/
public function attemptConversionFromUTF8($string, $targetEncoding)
{
@@ -125,8 +123,8 @@ class EncodingHelper
* @param string $string string to be converted
* @param string $sourceEncoding The encoding used to encode the source string
* @param string $targetEncoding The encoding the string should be re-encoded into
* @return string The converted string, encoded with the given encoding
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
* @return string The converted string, encoded with the given encoding
*/
protected function attemptConversion($string, $sourceEncoding, $targetEncoding)
{
@@ -139,7 +137,7 @@ class EncodingHelper
if ($this->canUseIconv()) {
$convertedString = $this->globalFunctionsHelper->iconv($string, $sourceEncoding, $targetEncoding);
} else if ($this->canUseMbString()) {
} elseif ($this->canUseMbString()) {
$convertedString = $this->globalFunctionsHelper->mb_convert_encoding($string, $sourceEncoding, $targetEncoding);
} else {
throw new EncodingConversionException("The conversion from $sourceEncoding to $targetEncoding is not supported. Please install \"iconv\" or \"PHP Intl\".");
@@ -1,12 +1,10 @@
<?php
namespace Box\Spout\Common\Escaper;
namespace Box\Spout\Common\Helper\Escaper;
/**
* Class CSV
* Provides functions to escape and unescape data for CSV files
*
* @package Box\Spout\Common\Escaper
*/
class CSV implements EscaperInterface
{
@@ -1,11 +1,9 @@
<?php
namespace Box\Spout\Common\Escaper;
namespace Box\Spout\Common\Helper\Escaper;
/**
* Interface EscaperInterface
*
* @package Box\Spout\Common\Escaper
*/
interface EscaperInterface
{
@@ -1,19 +1,13 @@
<?php
namespace Box\Spout\Common\Escaper;
use Box\Spout\Common\Singleton;
namespace Box\Spout\Common\Helper\Escaper;
/**
* Class ODS
* Provides functions to escape and unescape data for ODS files
*
* @package Box\Spout\Common\Escaper
*/
class ODS implements EscaperInterface
{
use Singleton;
/**
* Escapes the given string to make it compatible with XLSX
*
@@ -22,17 +16,16 @@ class ODS implements EscaperInterface
*/
public function escape($string)
{
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
// single/double quotes (for XML attributes) need to be encoded.
if (defined('ENT_DISALLOWED')) {
// '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
// @link https://github.com/box/spout/issues/329
$replacedString = htmlspecialchars($string, ENT_NOQUOTES | ENT_DISALLOWED);
$replacedString = htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8');
} else {
// We are on hhvm or any other engine that does not support ENT_DISALLOWED.
//
// @NOTE: Using ENT_NOQUOTES as only XML entities ('<', '>', '&') need to be encoded.
// Single and double quotes can be left as is.
$escapedString = htmlspecialchars($string, ENT_NOQUOTES);
$escapedString = htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
// 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".
@@ -1,36 +1,37 @@
<?php
namespace Box\Spout\Common\Escaper;
use Box\Spout\Common\Singleton;
namespace Box\Spout\Common\Helper\Escaper;
/**
* Class XLSX
* Provides functions to escape and unescape data for XLSX files
*
* @package Box\Spout\Common\Escaper
*/
class XLSX implements EscaperInterface
{
use Singleton;
/** @var bool Whether the escaper has already been initialized */
private $isAlreadyInitialized = false;
/** @var string Regex pattern to detect control characters that need to be escaped */
protected $escapableControlCharactersPattern;
private $escapableControlCharactersPattern;
/** @var string[] Map containing control characters to be escaped (key) and their escaped value (value) */
protected $controlCharactersEscapingMap;
private $controlCharactersEscapingMap;
/** @var string[] Map containing control characters to be escaped (value) and their escaped value (key) */
protected $controlCharactersEscapingReverseMap;
private $controlCharactersEscapingReverseMap;
/**
* Initializes the singleton instance
* Initializes the control characters if not already done
*/
protected function init()
protected function initIfNeeded()
{
$this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern();
$this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
$this->controlCharactersEscapingReverseMap = array_flip($this->controlCharactersEscapingMap);
if (!$this->isAlreadyInitialized) {
$this->escapableControlCharactersPattern = $this->getEscapableControlCharactersPattern();
$this->controlCharactersEscapingMap = $this->getControlCharactersEscapingMap();
$this->controlCharactersEscapingReverseMap = array_flip($this->controlCharactersEscapingMap);
$this->isAlreadyInitialized = true;
}
}
/**
@@ -41,10 +42,12 @@ class XLSX implements EscaperInterface
*/
public function escape($string)
{
$this->initIfNeeded();
$escapedString = $this->escapeControlCharacters($string);
// @NOTE: Using ENT_NOQUOTES as only XML entities ('<', '>', '&') need to be encoded.
// Single and double quotes can be left as is.
$escapedString = htmlspecialchars($escapedString, ENT_NOQUOTES);
// @NOTE: Using ENT_QUOTES as XML entities ('<', '>', '&') as well as
// single/double quotes (for XML attributes) need to be encoded.
$escapedString = htmlspecialchars($escapedString, ENT_QUOTES, 'UTF-8');
return $escapedString;
}
@@ -57,6 +60,8 @@ class XLSX implements EscaperInterface
*/
public function unescape($string)
{
$this->initIfNeeded();
// ==============
// = WARNING =
// ==============
@@ -88,7 +93,7 @@ class XLSX implements EscaperInterface
* "\t", "\r" and "\n" don't need to be escaped.
*
* NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
* @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
* @see https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
*
* @return string[]
*/
@@ -101,7 +106,7 @@ class XLSX implements EscaperInterface
$character = chr($charValue);
if (preg_match("/{$this->escapableControlCharactersPattern}/", $character)) {
$charHexValue = dechex($charValue);
$escapedChar = '_x' . sprintf('%04s' , strtoupper($charHexValue)) . '_';
$escapedChar = '_x' . sprintf('%04s', strtoupper($charHexValue)) . '_';
$controlCharactersEscapingMap[$escapedChar] = $character;
}
}
@@ -117,7 +122,7 @@ class XLSX implements EscaperInterface
* So "\0" -> _x0000_ and "_x0000_" -> _x005F_x0000_.
*
* NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
* @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
* @see https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
*
* @param string $string String to escape
* @return string
@@ -131,7 +136,7 @@ class XLSX implements EscaperInterface
return $escapedString;
}
return preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function($matches) {
return preg_replace_callback("/({$this->escapableControlCharactersPattern})/", function ($matches) {
return $this->controlCharactersEscapingReverseMap[$matches[0]];
}, $escapedString);
}
@@ -155,7 +160,7 @@ class XLSX implements EscaperInterface
* So "_x0000_" -> "\0" and "_x005F_x0000_" -> "_x0000_"
*
* NOTE: the logic has been adapted from the XlsxWriter library (BSD License)
* @link https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
* @see https://github.com/jmcnamara/XlsxWriter/blob/f1e610f29/xlsxwriter/sharedstrings.py#L89
*
* @param string $string String to unescape
* @return string
+6 -8
View File
@@ -8,10 +8,8 @@ use Box\Spout\Common\Exception\IOException;
* Class FileSystemHelper
* This class provides helper functions to help with the file system operations
* like files/folders creation & deletion
*
* @package Box\Spout\Common\Helper
*/
class FileSystemHelper
class FileSystemHelper implements FileSystemHelperInterface
{
/** @var string Real path of the base folder where all the I/O can occur */
protected $baseFolderRealPath;
@@ -29,8 +27,8 @@ class FileSystemHelper
*
* @param string $parentFolderPath The parent folder path under which the folder is going to be created
* @param string $folderName The name of the folder to create
* @return string Path of the created folder
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or if the folder path is not inside of the base folder
* @return string Path of the created folder
*/
public function createFolder($parentFolderPath, $folderName)
{
@@ -53,8 +51,8 @@ class FileSystemHelper
* @param string $parentFolderPath The parent folder path where the file is going to be created
* @param string $fileName The name of the file to create
* @param string $fileContents The contents of the file to create
* @return string Path of the created file
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file or if the file path is not inside of the base folder
* @return string Path of the created file
*/
public function createFileWithContents($parentFolderPath, $fileName, $fileContents)
{
@@ -74,8 +72,8 @@ class FileSystemHelper
* Delete the file at the given path
*
* @param string $filePath Path of the file to delete
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the file path is not inside of the base folder
* @return void
*/
public function deleteFile($filePath)
{
@@ -90,8 +88,8 @@ class FileSystemHelper
* Delete the folder at the given path as well as all its contents
*
* @param string $folderPath Path of the folder to delete
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the folder path is not inside of the base folder
* @return void
*/
public function deleteFolderRecursively($folderPath)
{
@@ -119,8 +117,8 @@ class FileSystemHelper
* should occur is not inside the base folder.
*
* @param string $operationFolderPath The path of the folder where the I/O operation should occur
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the folder where the I/O operation should occur is not inside the base folder
* @return void
*/
protected function throwIfOperationNotInBaseFolder($operationFolderPath)
{
@@ -0,0 +1,51 @@
<?php
namespace Box\Spout\Common\Helper;
/**
* Class FileSystemHelperInterface
* This interface describes helper functions to help with the file system operations
* like files/folders creation & deletion
*/
interface FileSystemHelperInterface
{
/**
* Creates an empty folder with the given name under the given parent folder.
*
* @param string $parentFolderPath The parent folder path under which the folder is going to be created
* @param string $folderName The name of the folder to create
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or if the folder path is not inside of the base folder
* @return string Path of the created folder
*/
public function createFolder($parentFolderPath, $folderName);
/**
* Creates a file with the given name and content in the given folder.
* The parent folder must exist.
*
* @param string $parentFolderPath The parent folder path where the file is going to be created
* @param string $fileName The name of the file to create
* @param string $fileContents The contents of the file to create
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file or if the file path is not inside of the base folder
* @return string Path of the created file
*/
public function createFileWithContents($parentFolderPath, $fileName, $fileContents);
/**
* Delete the file at the given path
*
* @param string $filePath Path of the file to delete
* @throws \Box\Spout\Common\Exception\IOException If the file path is not inside of the base folder
* @return void
*/
public function deleteFile($filePath);
/**
* Delete the folder at the given path as well as all its contents
*
* @param string $folderPath Path of the folder to delete
* @throws \Box\Spout\Common\Exception\IOException If the folder path is not inside of the base folder
* @return void
*/
public function deleteFolderRecursively($folderPath);
}
@@ -7,8 +7,6 @@ namespace Box\Spout\Common\Helper;
* This class wraps global functions to facilitate testing
*
* @codeCoverageIgnore
*
* @package Box\Spout\Common\Helper
*/
class GlobalFunctionsHelper
{
@@ -30,7 +28,7 @@ class GlobalFunctionsHelper
* @see fgets()
*
* @param resource $handle
* @param int|void $length
* @param int|null $length
* @return string
*/
public function fgets($handle, $length = null)
@@ -81,14 +79,20 @@ class GlobalFunctionsHelper
* @see fgetcsv()
*
* @param resource $handle
* @param int|void $length
* @param string|void $delimiter
* @param string|void $enclosure
* @param int|null $length
* @param string|null $delimiter
* @param string|null $enclosure
* @return array
*/
public function fgetcsv($handle, $length = null, $delimiter = null, $enclosure = null)
{
return fgetcsv($handle, $length, $delimiter, $enclosure);
// PHP uses '\' as the default escape character. This is not RFC-4180 compliant...
// To fix that, simply disable the escape character.
// @see https://bugs.php.net/bug.php?id=43225
// @see http://tools.ietf.org/html/rfc4180
$escapeCharacter = "\0";
return fgetcsv($handle, $length, $delimiter, $enclosure, $escapeCharacter);
}
/**
@@ -97,13 +101,19 @@ class GlobalFunctionsHelper
*
* @param resource $handle
* @param array $fields
* @param string|void $delimiter
* @param string|void $enclosure
* @param string|null $delimiter
* @param string|null $enclosure
* @return int
*/
public function fputcsv($handle, array $fields, $delimiter = null, $enclosure = null)
{
return fputcsv($handle, $fields, $delimiter, $enclosure);
// PHP uses '\' as the default escape character. This is not RFC-4180 compliant...
// To fix that, simply disable the escape character.
// @see https://bugs.php.net/bug.php?id=43225
// @see http://tools.ietf.org/html/rfc4180
$escapeCharacter = "\0";
return fputcsv($handle, $fields, $delimiter, $enclosure, $escapeCharacter);
}
/**
@@ -165,6 +175,7 @@ class GlobalFunctionsHelper
public function file_get_contents($filePath)
{
$realFilePath = $this->convertToUseRealPath($filePath);
return file_get_contents($realFilePath);
}
@@ -207,7 +218,7 @@ class GlobalFunctionsHelper
* Wrapper around global function feof()
* @see feof()
*
* @param resource
* @param resource $handle
* @return bool
*/
public function feof($handle)
@@ -232,7 +243,7 @@ class GlobalFunctionsHelper
* @see basename()
*
* @param string $path
* @param string|void $suffix
* @param string|null $suffix
* @return string
*/
public function basename($path, $suffix = null)
+2 -2
View File
@@ -7,8 +7,6 @@ namespace Box\Spout\Common\Helper;
* This class provides helper functions to work with strings and multibyte strings.
*
* @codeCoverageIgnore
*
* @package Box\Spout\Common\Helper
*/
class StringHelper
{
@@ -50,6 +48,7 @@ class StringHelper
public function getCharFirstOccurrencePosition($char, $string)
{
$position = $this->hasMbstringSupport ? mb_strpos($string, $char) : strpos($string, $char);
return ($position !== false) ? $position : -1;
}
@@ -66,6 +65,7 @@ class StringHelper
public function getCharLastOccurrencePosition($char, $string)
{
$position = $this->hasMbstringSupport ? mb_strrpos($string, $char) : strrpos($string, $char);
return ($position !== false) ? $position : -1;
}
}
@@ -0,0 +1,68 @@
<?php
namespace Box\Spout\Common\Manager;
/**
* Class OptionsManager
*/
abstract class OptionsManagerAbstract implements OptionsManagerInterface
{
const PREFIX_OPTION = 'OPTION_';
/** @var string[] List of all supported option names */
private $supportedOptions = [];
/** @var array Associative array [OPTION_NAME => OPTION_VALUE] */
private $options = [];
/**
* OptionsManagerAbstract constructor.
*/
public function __construct()
{
$this->supportedOptions = $this->getSupportedOptions();
$this->setDefaultOptions();
}
/**
* @return array List of supported options
*/
abstract protected function getSupportedOptions();
/**
* Sets the default options.
* To be overriden by child classes
*
* @return void
*/
abstract protected function setDefaultOptions();
/**
* Sets the given option, if this option is supported.
*
* @param string $optionName
* @param mixed $optionValue
* @return void
*/
public function setOption($optionName, $optionValue)
{
if (in_array($optionName, $this->supportedOptions)) {
$this->options[$optionName] = $optionValue;
}
}
/**
* @param string $optionName
* @return mixed|null The set option or NULL if no option with given name found
*/
public function getOption($optionName)
{
$optionValue = null;
if (isset($this->options[$optionName])) {
$optionValue = $this->options[$optionName];
}
return $optionValue;
}
}
@@ -0,0 +1,22 @@
<?php
namespace Box\Spout\Common\Manager;
/**
* Interface OptionsManagerInterface
*/
interface OptionsManagerInterface
{
/**
* @param string $optionName
* @param mixed $optionValue
* @return void
*/
public function setOption($optionName, $optionValue);
/**
* @param string $optionName
* @return mixed|null The set option or NULL if no option with given name found
*/
public function getOption($optionName);
}
-41
View File
@@ -1,41 +0,0 @@
<?php
namespace Box\Spout\Common;
/**
* Class Singleton
* Defines a class as a singleton.
*
* @package Box\Spout\Common
*/
trait Singleton
{
protected static $instance;
/**
* @return static
*/
final public static function getInstance()
{
return isset(static::$instance)
? static::$instance
: static::$instance = new static;
}
/**
* Singleton constructor.
*/
final private function __construct()
{
$this->init();
}
/**
* Initializes the singleton
* @return void
*/
protected function init() {}
final private function __wakeup() {}
final private function __clone() {}
}
-2
View File
@@ -5,8 +5,6 @@ namespace Box\Spout\Common;
/**
* Class Type
* This class references the supported types
*
* @api
*/
abstract class Type
{
@@ -0,0 +1,98 @@
<?php
namespace Box\Spout\Reader\CSV\Creator;
use Box\Spout\Common\Creator\HelperFactory;
use Box\Spout\Common\Entity\Cell;
use Box\Spout\Common\Entity\Row;
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
use Box\Spout\Common\Manager\OptionsManagerInterface;
use Box\Spout\Reader\Common\Creator\InternalEntityFactoryInterface;
use Box\Spout\Reader\CSV\RowIterator;
use Box\Spout\Reader\CSV\Sheet;
use Box\Spout\Reader\CSV\SheetIterator;
/**
* Class EntityFactory
* Factory to create entities
*/
class InternalEntityFactory implements InternalEntityFactoryInterface
{
/** @var HelperFactory */
private $helperFactory;
/**
* @param HelperFactory $helperFactory
*/
public function __construct(HelperFactory $helperFactory)
{
$this->helperFactory = $helperFactory;
}
/**
* @param resource $filePointer Pointer to the CSV file to read
* @param OptionsManagerInterface $optionsManager
* @param GlobalFunctionsHelper $globalFunctionsHelper
* @return SheetIterator
*/
public function createSheetIterator($filePointer, $optionsManager, $globalFunctionsHelper)
{
$rowIterator = $this->createRowIterator($filePointer, $optionsManager, $globalFunctionsHelper);
$sheet = $this->createSheet($rowIterator);
return new SheetIterator($sheet);
}
/**
* @param RowIterator $rowIterator
* @return Sheet
*/
private function createSheet($rowIterator)
{
return new Sheet($rowIterator);
}
/**
* @param resource $filePointer Pointer to the CSV file to read
* @param OptionsManagerInterface $optionsManager
* @param GlobalFunctionsHelper $globalFunctionsHelper
* @return RowIterator
*/
private function createRowIterator($filePointer, $optionsManager, $globalFunctionsHelper)
{
$encodingHelper = $this->helperFactory->createEncodingHelper($globalFunctionsHelper);
return new RowIterator($filePointer, $optionsManager, $encodingHelper, $this, $globalFunctionsHelper);
}
/**
* @param Cell[] $cells
* @return Row
*/
public function createRow(array $cells = [])
{
return new Row($cells, null);
}
/**
* @param mixed $cellValue
* @return Cell
*/
public function createCell($cellValue)
{
return new Cell($cellValue);
}
/**
* @param array $cellValues
* @return Row
*/
public function createRowFromArray(array $cellValues = [])
{
$cells = array_map(function ($cellValue) {
return $this->createCell($cellValue);
}, $cellValues);
return $this->createRow($cells);
}
}
@@ -0,0 +1,40 @@
<?php
namespace Box\Spout\Reader\CSV\Manager;
use Box\Spout\Common\Helper\EncodingHelper;
use Box\Spout\Common\Manager\OptionsManagerAbstract;
use Box\Spout\Reader\Common\Entity\Options;
/**
* Class OptionsManager
* CSV Reader options manager
*/
class OptionsManager extends OptionsManagerAbstract
{
/**
* {@inheritdoc}
*/
protected function getSupportedOptions()
{
return [
Options::SHOULD_FORMAT_DATES,
Options::SHOULD_PRESERVE_EMPTY_ROWS,
Options::FIELD_DELIMITER,
Options::FIELD_ENCLOSURE,
Options::ENCODING,
];
}
/**
* {@inheritdoc}
*/
protected function setDefaultOptions()
{
$this->setOption(Options::SHOULD_FORMAT_DATES, false);
$this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
$this->setOption(Options::FIELD_DELIMITER, ',');
$this->setOption(Options::FIELD_ENCLOSURE, '"');
$this->setOption(Options::ENCODING, EncodingHelper::ENCODING_UTF8);
}
}
+15 -36
View File
@@ -2,16 +2,16 @@
namespace Box\Spout\Reader\CSV;
use Box\Spout\Reader\AbstractReader;
use Box\Spout\Common\Exception\IOException;
use Box\Spout\Reader\Common\Entity\Options;
use Box\Spout\Reader\CSV\Creator\InternalEntityFactory;
use Box\Spout\Reader\ReaderAbstract;
/**
* Class Reader
* This class provides support to read data from a CSV file.
*
* @package Box\Spout\Reader\CSV
*/
class Reader extends AbstractReader
class Reader extends ReaderAbstract
{
/** @var resource Pointer to the file to be written */
protected $filePointer;
@@ -22,19 +22,6 @@ class Reader extends AbstractReader
/** @var string Original value for the "auto_detect_line_endings" INI value */
protected $originalAutoDetectLineEndings;
/**
* Returns the reader's current options
*
* @return ReaderOptions
*/
protected function getOptions()
{
if (!isset($this->options)) {
$this->options = new ReaderOptions();
}
return $this->options;
}
/**
* Sets the field delimiter for the CSV.
* Needs to be called before opening the reader.
@@ -44,7 +31,8 @@ class Reader extends AbstractReader
*/
public function setFieldDelimiter($fieldDelimiter)
{
$this->getOptions()->setFieldDelimiter($fieldDelimiter);
$this->optionsManager->setOption(Options::FIELD_DELIMITER, $fieldDelimiter);
return $this;
}
@@ -57,7 +45,8 @@ class Reader extends AbstractReader
*/
public function setFieldEnclosure($fieldEnclosure)
{
$this->getOptions()->setFieldEnclosure($fieldEnclosure);
$this->optionsManager->setOption(Options::FIELD_ENCLOSURE, $fieldEnclosure);
return $this;
}
@@ -70,20 +59,8 @@ class Reader extends AbstractReader
*/
public function setEncoding($encoding)
{
$this->getOptions()->setEncoding($encoding);
return $this;
}
$this->optionsManager->setOption(Options::ENCODING, $encoding);
/**
* Sets the EOL for the CSV.
* Needs to be called before opening the reader.
*
* @param string $endOfLineCharacter used to properly get lines from the CSV file.
* @return Reader
*/
public function setEndOfLineCharacter($endOfLineCharacter)
{
$this->getOptions()->setEndOfLineCharacter($endOfLineCharacter);
return $this;
}
@@ -102,8 +79,8 @@ class Reader extends AbstractReader
* If setEncoding() was not called, it assumes that the file is encoded in UTF-8.
*
* @param string $filePath Path of the CSV file to be read
* @return void
* @throws \Box\Spout\Common\Exception\IOException
* @return void
*/
protected function openReader($filePath)
{
@@ -115,9 +92,12 @@ class Reader extends AbstractReader
throw new IOException("Could not open file $filePath for reading.");
}
$this->sheetIterator = new SheetIterator(
/** @var InternalEntityFactory $entityFactory */
$entityFactory = $this->entityFactory;
$this->sheetIterator = $entityFactory->createSheetIterator(
$this->filePointer,
$this->getOptions(),
$this->optionsManager,
$this->globalFunctionsHelper
);
}
@@ -132,7 +112,6 @@ class Reader extends AbstractReader
return $this->sheetIterator;
}
/**
* Closes the reader. To be used after reading the file.
*
-110
View File
@@ -1,110 +0,0 @@
<?php
namespace Box\Spout\Reader\CSV;
use Box\Spout\Common\Helper\EncodingHelper;
/**
* Class ReaderOptions
* This class is used to customize the reader's behavior
*
* @package Box\Spout\Reader\CSV
*/
class ReaderOptions extends \Box\Spout\Reader\Common\ReaderOptions
{
/** @var string Defines the character used to delimit fields (one character only) */
protected $fieldDelimiter = ',';
/** @var string Defines the character used to enclose fields (one character only) */
protected $fieldEnclosure = '"';
/** @var string Encoding of the CSV file to be read */
protected $encoding = EncodingHelper::ENCODING_UTF8;
/** @var string Defines the End of line */
protected $endOfLineCharacter = "\n";
/**
* @return string
*/
public function getFieldDelimiter()
{
return $this->fieldDelimiter;
}
/**
* Sets the field delimiter for the CSV.
* Needs to be called before opening the reader.
*
* @param string $fieldDelimiter Character that delimits fields
* @return ReaderOptions
*/
public function setFieldDelimiter($fieldDelimiter)
{
$this->fieldDelimiter = $fieldDelimiter;
return $this;
}
/**
* @return string
*/
public function getFieldEnclosure()
{
return $this->fieldEnclosure;
}
/**
* Sets the field enclosure for the CSV.
* Needs to be called before opening the reader.
*
* @param string $fieldEnclosure Character that enclose fields
* @return ReaderOptions
*/
public function setFieldEnclosure($fieldEnclosure)
{
$this->fieldEnclosure = $fieldEnclosure;
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
return $this->encoding;
}
/**
* Sets the encoding of the CSV file to be read.
* Needs to be called before opening the reader.
*
* @param string $encoding Encoding of the CSV file to be read
* @return ReaderOptions
*/
public function setEncoding($encoding)
{
$this->encoding = $encoding;
return $this;
}
/**
* @return string EOL for the CSV
*/
public function getEndOfLineCharacter()
{
return $this->endOfLineCharacter;
}
/**
* Sets the EOL for the CSV.
* Needs to be called before opening the reader.
*
* @param string $endOfLineCharacter used to properly get lines from the CSV file.
* @return ReaderOptions
*/
public function setEndOfLineCharacter($endOfLineCharacter)
{
$this->endOfLineCharacter = $endOfLineCharacter;
return $this;
}
}
+44 -52
View File
@@ -2,14 +2,17 @@
namespace Box\Spout\Reader\CSV;
use Box\Spout\Reader\IteratorInterface;
use Box\Spout\Common\Entity\Row;
use Box\Spout\Common\Helper\EncodingHelper;
use Box\Spout\Common\Helper\GlobalFunctionsHelper;
use Box\Spout\Common\Manager\OptionsManagerInterface;
use Box\Spout\Reader\Common\Entity\Options;
use Box\Spout\Reader\CSV\Creator\InternalEntityFactory;
use Box\Spout\Reader\IteratorInterface;
/**
* Class RowIterator
* Iterate over CSV rows.
*
* @package Box\Spout\Reader\CSV
*/
class RowIterator implements IteratorInterface
{
@@ -24,8 +27,8 @@ class RowIterator implements IteratorInterface
/** @var int Number of read rows */
protected $numReadRows = 0;
/** @var array|null Buffer used to store the row data, while checking if there are more rows to read */
protected $rowDataBuffer = null;
/** @var Row|null Buffer used to store the current row, while checking if there are more rows to read */
protected $rowBuffer;
/** @var bool Indicates whether all rows have been read */
protected $hasReachedEndOfFile = false;
@@ -39,42 +42,45 @@ class RowIterator implements IteratorInterface
/** @var string Encoding of the CSV file to be read */
protected $encoding;
/** @var string End of line delimiter, given by the user as input. */
protected $inputEOLDelimiter;
/** @var bool Whether empty rows should be returned or skipped */
protected $shouldPreserveEmptyRows;
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper;
/** @var \Box\Spout\Common\Helper\EncodingHelper Helper to work with different encodings */
protected $encodingHelper;
/** @var string End of line delimiter, encoded using the same encoding as the CSV */
protected $encodedEOLDelimiter;
/** @var \Box\Spout\Reader\CSV\Creator\InternalEntityFactory Factory to create entities */
protected $entityFactory;
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper;
/**
* @param resource $filePointer Pointer to the CSV file to read
* @param \Box\Spout\Reader\CSV\ReaderOptions $options
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
* @param OptionsManagerInterface $optionsManager
* @param EncodingHelper $encodingHelper
* @param InternalEntityFactory $entityFactory
* @param GlobalFunctionsHelper $globalFunctionsHelper
*/
public function __construct($filePointer, $options, $globalFunctionsHelper)
{
public function __construct(
$filePointer,
OptionsManagerInterface $optionsManager,
EncodingHelper $encodingHelper,
InternalEntityFactory $entityFactory,
GlobalFunctionsHelper $globalFunctionsHelper
) {
$this->filePointer = $filePointer;
$this->fieldDelimiter = $options->getFieldDelimiter();
$this->fieldEnclosure = $options->getFieldEnclosure();
$this->encoding = $options->getEncoding();
$this->inputEOLDelimiter = $options->getEndOfLineCharacter();
$this->shouldPreserveEmptyRows = $options->shouldPreserveEmptyRows();
$this->fieldDelimiter = $optionsManager->getOption(Options::FIELD_DELIMITER);
$this->fieldEnclosure = $optionsManager->getOption(Options::FIELD_ENCLOSURE);
$this->encoding = $optionsManager->getOption(Options::ENCODING);
$this->shouldPreserveEmptyRows = $optionsManager->getOption(Options::SHOULD_PRESERVE_EMPTY_ROWS);
$this->encodingHelper = $encodingHelper;
$this->entityFactory = $entityFactory;
$this->globalFunctionsHelper = $globalFunctionsHelper;
$this->encodingHelper = new EncodingHelper($globalFunctionsHelper);
}
/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @see http://php.net/manual/en/iterator.rewind.php
*
* @return void
*/
@@ -83,7 +89,7 @@ class RowIterator implements IteratorInterface
$this->rewindAndSkipBom();
$this->numReadRows = 0;
$this->rowDataBuffer = null;
$this->rowBuffer = null;
$this->next();
}
@@ -104,7 +110,7 @@ class RowIterator implements IteratorInterface
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @see http://php.net/manual/en/iterator.valid.php
*
* @return bool
*/
@@ -115,10 +121,10 @@ class RowIterator implements IteratorInterface
/**
* Move forward to next element. Reads data for the next unprocessed row.
* @link http://php.net/manual/en/iterator.next.php
* @see http://php.net/manual/en/iterator.next.php
*
* @return void
* @throws \Box\Spout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
* @return void
*/
public function next()
{
@@ -130,8 +136,8 @@ class RowIterator implements IteratorInterface
}
/**
* @return void
* @throws \Box\Spout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
* @return void
*/
protected function readDataForNextRow()
{
@@ -141,7 +147,8 @@ class RowIterator implements IteratorInterface
if ($rowData !== false) {
// str_replace will replace NULL values by empty strings
$this->rowDataBuffer = str_replace(null, null, $rowData);
$rowDataBufferAsArray = str_replace(null, null, $rowData);
$this->rowBuffer = $this->entityFactory->createRowFromArray($rowDataBufferAsArray);
$this->numReadRows++;
} else {
// If we reach this point, it means end of file was reached.
@@ -171,8 +178,8 @@ class RowIterator implements IteratorInterface
* As fgetcsv() does not manage correctly encoding for non UTF-8 data,
* we remove manually whitespace with ltrim or rtrim (depending on the order of the bytes)
*
* @return array|false The row for the current file pointer, encoded in UTF-8 or FALSE if nothing to read
* @throws \Box\Spout\Common\Exception\EncodingConversionException If unable to convert data to UTF-8
* @return array|false The row for the current file pointer, encoded in UTF-8 or FALSE if nothing to read
*/
protected function getNextUTF8EncodedRow()
{
@@ -182,7 +189,7 @@ class RowIterator implements IteratorInterface
}
foreach ($encodedRowData as $cellIndex => $cellValue) {
switch($this->encoding) {
switch ($this->encoding) {
case EncodingHelper::ENCODING_UTF16_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
@@ -202,21 +209,6 @@ class RowIterator implements IteratorInterface
return $encodedRowData;
}
/**
* Returns the end of line delimiter, encoded using the same encoding as the CSV.
* The return value is cached.
*
* @return string
*/
protected function getEncodedEOLDelimiter()
{
if (!isset($this->encodedEOLDelimiter)) {
$this->encodedEOLDelimiter = $this->encodingHelper->attemptConversionFromUTF8($this->inputEOLDelimiter, $this->encoding);
}
return $this->encodedEOLDelimiter;
}
/**
* @param array|bool $lineData Array containing the cells value for the line
* @return bool Whether the given line is empty
@@ -228,18 +220,18 @@ class RowIterator implements IteratorInterface
/**
* Return the current element from the buffer
* @link http://php.net/manual/en/iterator.current.php
* @see http://php.net/manual/en/iterator.current.php
*
* @return array|null
* @return Row|null
*/
public function current()
{
return $this->rowDataBuffer;
return $this->rowBuffer;
}
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @see http://php.net/manual/en/iterator.key.php
*
* @return int
*/
+11 -11
View File
@@ -6,8 +6,6 @@ use Box\Spout\Reader\SheetInterface;
/**
* Class Sheet
*
* @package Box\Spout\Reader\CSV
*/
class Sheet implements SheetInterface
{
@@ -15,17 +13,14 @@ class Sheet implements SheetInterface
protected $rowIterator;
/**
* @param resource $filePointer Pointer to the CSV file to read
* @param \Box\Spout\Reader\CSV\ReaderOptions $options
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
* @param RowIterator $rowIterator Corresponding row iterator
*/
public function __construct($filePointer, $options, $globalFunctionsHelper)
public function __construct(RowIterator $rowIterator)
{
$this->rowIterator = new RowIterator($filePointer, $options, $globalFunctionsHelper);
$this->rowIterator = $rowIterator;
}
/**
* @api
* @return \Box\Spout\Reader\CSV\RowIterator
*/
public function getRowIterator()
@@ -34,7 +29,6 @@ class Sheet implements SheetInterface
}
/**
* @api
* @return int Index of the sheet
*/
public function getIndex()
@@ -43,7 +37,6 @@ class Sheet implements SheetInterface
}
/**
* @api
* @return string Name of the sheet - empty string since CSV does not support that
*/
public function getName()
@@ -52,11 +45,18 @@ class Sheet implements SheetInterface
}
/**
* @api
* @return bool Always TRUE as there is only one sheet
*/
public function isActive()
{
return true;
}
/**
* @return bool Always TRUE as the only sheet is always visible
*/
public function isVisible()
{
return true;
}
}
+8 -12
View File
@@ -7,8 +7,6 @@ use Box\Spout\Reader\IteratorInterface;
/**
* Class SheetIterator
* Iterate over CSV unique "sheet".
*
* @package Box\Spout\Reader\CSV
*/
class SheetIterator implements IteratorInterface
{
@@ -19,18 +17,16 @@ class SheetIterator implements IteratorInterface
protected $hasReadUniqueSheet = false;
/**
* @param resource $filePointer
* @param \Box\Spout\Reader\CSV\ReaderOptions $options
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
* @param Sheet $sheet Corresponding unique sheet
*/
public function __construct($filePointer, $options, $globalFunctionsHelper)
public function __construct($sheet)
{
$this->sheet = new Sheet($filePointer, $options, $globalFunctionsHelper);
$this->sheet = $sheet;
}
/**
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @see http://php.net/manual/en/iterator.rewind.php
*
* @return void
*/
@@ -41,7 +37,7 @@ class SheetIterator implements IteratorInterface
/**
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @see http://php.net/manual/en/iterator.valid.php
*
* @return bool
*/
@@ -52,7 +48,7 @@ class SheetIterator implements IteratorInterface
/**
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @see http://php.net/manual/en/iterator.next.php
*
* @return void
*/
@@ -63,7 +59,7 @@ class SheetIterator implements IteratorInterface
/**
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @see http://php.net/manual/en/iterator.current.php
*
* @return \Box\Spout\Reader\CSV\Sheet
*/
@@ -74,7 +70,7 @@ class SheetIterator implements IteratorInterface
/**
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @see http://php.net/manual/en/iterator.key.php
*
* @return int
*/
@@ -0,0 +1,24 @@
<?php
namespace Box\Spout\Reader\Common\Creator;
use Box\Spout\Common\Entity\Cell;
use Box\Spout\Common\Entity\Row;
/**
* Interface EntityFactoryInterface
*/
interface InternalEntityFactoryInterface
{
/**
* @param Cell[] $cells
* @return Row
*/
public function createRow(array $cells = []);
/**
* @param mixed $cellValue
* @return Cell
*/
public function createCell($cellValue);
}
@@ -0,0 +1,68 @@
<?php
namespace Box\Spout\Reader\Common\Creator;
use Box\Spout\Common\Exception\UnsupportedTypeException;
use Box\Spout\Common\Type;
use Box\Spout\Reader\ReaderInterface;
/**
* Class ReaderEntityFactory
* Factory to create external entities
*/
class ReaderEntityFactory
{
/**
* Creates a reader by file extension
*
* @param string $path The path to the spreadsheet file. Supported extensions are .csv, .ods and .xlsx
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException
* @return ReaderInterface
*/
public static function createReaderFromFile(string $path)
{
return ReaderFactory::createFromFile($path);
}
/**
* This creates an instance of a CSV reader
*
* @return \Box\Spout\Reader\CSV\Reader
*/
public static function createCSVReader()
{
try {
return ReaderFactory::createFromType(Type::CSV);
} catch (UnsupportedTypeException $e) {
// should never happen
}
}
/**
* This creates an instance of a XLSX reader
*
* @return \Box\Spout\Reader\XLSX\Reader
*/
public static function createXLSXReader()
{
try {
return ReaderFactory::createFromType(Type::XLSX);
} catch (UnsupportedTypeException $e) {
// should never happen
}
}
/**
* This creates an instance of a ODS reader
*
* @return \Box\Spout\Reader\ODS\Reader
*/
public static function createODSReader()
{
try {
return ReaderFactory::createFromType(Type::ODS);
} catch (UnsupportedTypeException $e) {
// should never happen
}
}
}
@@ -0,0 +1,103 @@
<?php
namespace Box\Spout\Reader\Common\Creator;
use Box\Spout\Common\Creator\HelperFactory;
use Box\Spout\Common\Exception\UnsupportedTypeException;
use Box\Spout\Common\Type;
use Box\Spout\Reader\CSV\Creator\InternalEntityFactory as CSVInternalEntityFactory;
use Box\Spout\Reader\CSV\Manager\OptionsManager as CSVOptionsManager;
use Box\Spout\Reader\CSV\Reader as CSVReader;
use Box\Spout\Reader\ODS\Creator\HelperFactory as ODSHelperFactory;
use Box\Spout\Reader\ODS\Creator\InternalEntityFactory as ODSInternalEntityFactory;
use Box\Spout\Reader\ODS\Creator\ManagerFactory as ODSManagerFactory;
use Box\Spout\Reader\ODS\Manager\OptionsManager as ODSOptionsManager;
use Box\Spout\Reader\ODS\Reader as ODSReader;
use Box\Spout\Reader\ReaderInterface;
use Box\Spout\Reader\XLSX\Creator\HelperFactory as XLSXHelperFactory;
use Box\Spout\Reader\XLSX\Creator\InternalEntityFactory as XLSXInternalEntityFactory;
use Box\Spout\Reader\XLSX\Creator\ManagerFactory as XLSXManagerFactory;
use Box\Spout\Reader\XLSX\Manager\OptionsManager as XLSXOptionsManager;
use Box\Spout\Reader\XLSX\Manager\SharedStringsCaching\CachingStrategyFactory;
use Box\Spout\Reader\XLSX\Reader as XLSXReader;
/**
* Class ReaderFactory
* This factory is used to create readers, based on the type of the file to be read.
* It supports CSV, XLSX and ODS formats.
*/
class ReaderFactory
{
/**
* Creates a reader by file extension
*
* @param string $path The path to the spreadsheet file. Supported extensions are .csv,.ods and .xlsx
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException
* @return ReaderInterface
*/
public static function createFromFile(string $path)
{
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
return self::createFromType($extension);
}
/**
* This creates an instance of the appropriate reader, given the type of the file to be read
*
* @param string $readerType Type of the reader to instantiate
* @throws \Box\Spout\Common\Exception\UnsupportedTypeException
* @return ReaderInterface
*/
public static function createFromType($readerType)
{
switch ($readerType) {
case Type::CSV: return self::createCSVReader();
case Type::XLSX: return self::createXLSXReader();
case Type::ODS: return self::createODSReader();
default:
throw new UnsupportedTypeException('No readers supporting the given type: ' . $readerType);
}
}
/**
* @return CSVReader
*/
private static function createCSVReader()
{
$optionsManager = new CSVOptionsManager();
$helperFactory = new HelperFactory();
$entityFactory = new CSVInternalEntityFactory($helperFactory);
$globalFunctionsHelper = $helperFactory->createGlobalFunctionsHelper();
return new CSVReader($optionsManager, $globalFunctionsHelper, $entityFactory);
}
/**
* @return XLSXReader
*/
private static function createXLSXReader()
{
$optionsManager = new XLSXOptionsManager();
$helperFactory = new XLSXHelperFactory();
$managerFactory = new XLSXManagerFactory($helperFactory, new CachingStrategyFactory());
$entityFactory = new XLSXInternalEntityFactory($managerFactory, $helperFactory);
$globalFunctionsHelper = $helperFactory->createGlobalFunctionsHelper();
return new XLSXReader($optionsManager, $globalFunctionsHelper, $entityFactory, $managerFactory);
}
/**
* @return ODSReader
*/
private static function createODSReader()
{
$optionsManager = new ODSOptionsManager();
$helperFactory = new ODSHelperFactory();
$managerFactory = new ODSManagerFactory();
$entityFactory = new ODSInternalEntityFactory($helperFactory, $managerFactory);
$globalFunctionsHelper = $helperFactory->createGlobalFunctionsHelper();
return new ODSReader($optionsManager, $globalFunctionsHelper, $entityFactory);
}
}
@@ -0,0 +1,23 @@
<?php
namespace Box\Spout\Reader\Common\Entity;
/**
* Class Options
* Readers' options holder
*/
abstract class Options
{
// Common options
const SHOULD_FORMAT_DATES = 'shouldFormatDates';
const SHOULD_PRESERVE_EMPTY_ROWS = 'shouldPreserveEmptyRows';
// CSV specific options
const FIELD_DELIMITER = 'fieldDelimiter';
const FIELD_ENCLOSURE = 'fieldEnclosure';
const ENCODING = 'encoding';
// XLSX specific options
const TEMP_FOLDER = 'tempFolder';
const SHOULD_USE_1904_DATES = 'shouldUse1904Dates';
}
@@ -0,0 +1,65 @@
<?php
namespace Box\Spout\Reader\Common\Manager;
use Box\Spout\Common\Entity\Row;
use Box\Spout\Reader\Common\Creator\InternalEntityFactoryInterface;
/**
* Class RowManager
*/
class RowManager
{
/** @var InternalEntityFactoryInterface Factory to create entities */
private $entityFactory;
/**
* @param InternalEntityFactoryInterface $entityFactory Factory to create entities
*/
public function __construct(InternalEntityFactoryInterface $entityFactory)
{
$this->entityFactory = $entityFactory;
}
/**
* Detect whether a row is considered empty.
* An empty row has all of its cells empty.
*
* @param Row $row
* @return bool
*/
public function isEmpty(Row $row)
{
foreach ($row->getCells() as $cell) {
if (!$cell->isEmpty()) {
return false;
}
}
return true;
}
/**
* Fills the missing indexes of a row with empty cells.
*
* @param Row $row
* @return Row
*/
public function fillMissingIndexesWithEmptyCells(Row $row)
{
if ($row->getNumCells() === 0) {
return $row;
}
$rowCells = $row->getCells();
$maxCellIndex = max(array_keys($rowCells));
for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) {
if (!isset($rowCells[$cellIndex])) {
$row->setCellAtIndex($this->entityFactory->createCell(''), $cellIndex);
}
}
return $row;
}
}
-58
View File
@@ -1,58 +0,0 @@
<?php
namespace Box\Spout\Reader\Common;
/**
* Class ReaderOptions
* Readers' common options
*
* @package Box\Spout\Reader\Common
*/
class ReaderOptions
{
/** @var bool Whether date/time values should be returned as PHP objects or be formatted as strings */
protected $shouldFormatDates = false;
/** @var bool Whether empty rows should be returned or skipped */
protected $shouldPreserveEmptyRows = false;
/**
* @return bool Whether date/time values should be returned as PHP objects or be formatted as strings.
*/
public function shouldFormatDates()
{
return $this->shouldFormatDates;
}
/**
* Sets whether date/time values should be returned as PHP objects or be formatted as strings.
*
* @param bool $shouldFormatDates
* @return ReaderOptions
*/
public function setShouldFormatDates($shouldFormatDates)
{
$this->shouldFormatDates = $shouldFormatDates;
return $this;
}
/**
* @return bool Whether empty rows should be returned or skipped.
*/
public function shouldPreserveEmptyRows()
{
return $this->shouldPreserveEmptyRows;
}
/**
* Sets whether empty rows should be returned or skipped.
*
* @param bool $shouldPreserveEmptyRows
* @return ReaderOptions
*/
public function setShouldPreserveEmptyRows($shouldPreserveEmptyRows)
{
$this->shouldPreserveEmptyRows = $shouldPreserveEmptyRows;
return $this;
}
}
+1 -5
View File
@@ -7,8 +7,6 @@ use Box\Spout\Reader\Wrapper\XMLReader;
/**
* Class XMLProcessor
* Helps process XML files
*
* @package Box\Spout\Reader\Common
*/
class XMLProcessor
{
@@ -24,14 +22,12 @@ class XMLProcessor
const PROCESSING_CONTINUE = 1;
const PROCESSING_STOP = 2;
/** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */
protected $xmlReader;
/** @var array Registered callbacks */
private $callbacks = [];
/**
* @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object
*/
@@ -90,8 +86,8 @@ class XMLProcessor
* Resumes the reading of the XML file where it was left off.
* Stops whenever a callback indicates that reading should stop or at the end of the file.
*
* @return void
* @throws \Box\Spout\Reader\Exception\XMLProcessingException
* @return void
*/
public function readUntilStopped()
{
@@ -0,0 +1,34 @@
<?php
namespace Box\Spout\Reader\Exception;
use Throwable;
/**
* Class InvalidValueException
*/
class InvalidValueException extends ReaderException
{
/** @var mixed */
private $invalidValue;
/**
* @param mixed $invalidValue
* @param string $message
* @param int $code
* @param Throwable|null $previous
*/
public function __construct($invalidValue, $message = '', $code = 0, Throwable $previous = null)
{
$this->invalidValue = $invalidValue;
parent::__construct($message, $code, $previous);
}
/**
* @return mixed
*/
public function getInvalidValue()
{
return $this->invalidValue;
}
}
@@ -4,9 +4,6 @@ namespace Box\Spout\Reader\Exception;
/**
* Class IteratorNotRewindableException
*
* @api
* @package Box\Spout\Reader\Exception
*/
class IteratorNotRewindableException extends ReaderException
{
Loaded 100 of 288 files, more files were not shown because too many files have changed in this diff. Show more