├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .php-cs-fixer.dist.php
├── LICENSE
├── README.md
├── UPGRADE-3.0.md
├── composer.json
├── composer.lock
├── phpstan.neon
└── src
└── Spout
├── Autoloader
├── Psr4Autoloader.php
└── autoload.php
├── Common
├── Creator
│ └── HelperFactory.php
├── Entity
│ ├── Cell.php
│ ├── Row.php
│ └── Style
│ │ ├── Border.php
│ │ ├── BorderPart.php
│ │ ├── CellAlignment.php
│ │ ├── Color.php
│ │ └── Style.php
├── Exception
│ ├── EncodingConversionException.php
│ ├── IOException.php
│ ├── InvalidArgumentException.php
│ ├── InvalidColorException.php
│ ├── SpoutException.php
│ └── UnsupportedTypeException.php
├── Helper
│ ├── CellTypeHelper.php
│ ├── EncodingHelper.php
│ ├── Escaper
│ │ ├── CSV.php
│ │ ├── EscaperInterface.php
│ │ ├── ODS.php
│ │ └── XLSX.php
│ ├── FileSystemHelper.php
│ ├── FileSystemHelperInterface.php
│ ├── GlobalFunctionsHelper.php
│ └── StringHelper.php
├── Manager
│ ├── OptionsManagerAbstract.php
│ └── OptionsManagerInterface.php
└── Type.php
├── Reader
├── CSV
│ ├── Creator
│ │ └── InternalEntityFactory.php
│ ├── Manager
│ │ └── OptionsManager.php
│ ├── Reader.php
│ ├── RowIterator.php
│ ├── Sheet.php
│ └── SheetIterator.php
├── Common
│ ├── Creator
│ │ ├── InternalEntityFactoryInterface.php
│ │ ├── ReaderEntityFactory.php
│ │ └── ReaderFactory.php
│ ├── Entity
│ │ └── Options.php
│ ├── Manager
│ │ └── RowManager.php
│ └── XMLProcessor.php
├── Exception
│ ├── InvalidValueException.php
│ ├── IteratorNotRewindableException.php
│ ├── NoSheetsFoundException.php
│ ├── ReaderException.php
│ ├── ReaderNotOpenedException.php
│ ├── SharedStringNotFoundException.php
│ └── XMLProcessingException.php
├── IteratorInterface.php
├── ODS
│ ├── Creator
│ │ ├── HelperFactory.php
│ │ ├── InternalEntityFactory.php
│ │ └── ManagerFactory.php
│ ├── Helper
│ │ ├── CellValueFormatter.php
│ │ └── SettingsHelper.php
│ ├── Manager
│ │ └── OptionsManager.php
│ ├── Reader.php
│ ├── RowIterator.php
│ ├── Sheet.php
│ └── SheetIterator.php
├── ReaderAbstract.php
├── ReaderInterface.php
├── SheetInterface.php
├── Wrapper
│ ├── XMLInternalErrorsHelper.php
│ └── XMLReader.php
└── XLSX
│ ├── Creator
│ ├── HelperFactory.php
│ ├── InternalEntityFactory.php
│ └── ManagerFactory.php
│ ├── Helper
│ ├── CellHelper.php
│ ├── CellValueFormatter.php
│ └── DateFormatHelper.php
│ ├── Manager
│ ├── OptionsManager.php
│ ├── SharedStringsCaching
│ │ ├── CachingStrategyFactory.php
│ │ ├── CachingStrategyInterface.php
│ │ ├── FileBasedStrategy.php
│ │ └── InMemoryStrategy.php
│ ├── SharedStringsManager.php
│ ├── SheetManager.php
│ ├── StyleManager.php
│ └── WorkbookRelationshipsManager.php
│ ├── Reader.php
│ ├── RowIterator.php
│ ├── Sheet.php
│ └── SheetIterator.php
└── Writer
├── CSV
├── Manager
│ └── OptionsManager.php
└── Writer.php
├── Common
├── Creator
│ ├── InternalEntityFactory.php
│ ├── ManagerFactoryInterface.php
│ ├── Style
│ │ ├── BorderBuilder.php
│ │ └── StyleBuilder.php
│ ├── WriterEntityFactory.php
│ └── WriterFactory.php
├── Entity
│ ├── Options.php
│ ├── Sheet.php
│ ├── Workbook.php
│ └── Worksheet.php
├── Helper
│ ├── CellHelper.php
│ ├── FileSystemWithRootFolderHelperInterface.php
│ └── ZipHelper.php
└── Manager
│ ├── CellManager.php
│ ├── RegisteredStyle.php
│ ├── RowManager.php
│ ├── SheetManager.php
│ ├── Style
│ ├── PossiblyUpdatedStyle.php
│ ├── StyleManager.php
│ ├── StyleManagerInterface.php
│ ├── StyleMerger.php
│ └── StyleRegistry.php
│ ├── WorkbookManagerAbstract.php
│ ├── WorkbookManagerInterface.php
│ └── WorksheetManagerInterface.php
├── Exception
├── Border
│ ├── InvalidNameException.php
│ ├── InvalidStyleException.php
│ └── InvalidWidthException.php
├── InvalidSheetNameException.php
├── SheetNotFoundException.php
├── WriterAlreadyOpenedException.php
├── WriterException.php
└── WriterNotOpenedException.php
├── ODS
├── Creator
│ ├── HelperFactory.php
│ └── ManagerFactory.php
├── Helper
│ ├── BorderHelper.php
│ └── FileSystemHelper.php
├── Manager
│ ├── OptionsManager.php
│ ├── Style
│ │ ├── StyleManager.php
│ │ └── StyleRegistry.php
│ ├── WorkbookManager.php
│ └── WorksheetManager.php
└── Writer.php
├── WriterAbstract.php
├── WriterInterface.php
├── WriterMultiSheetsAbstract.php
└── XLSX
├── Creator
├── HelperFactory.php
└── ManagerFactory.php
├── Helper
├── BorderHelper.php
└── FileSystemHelper.php
├── Manager
├── OptionsManager.php
├── SharedStringsManager.php
├── Style
│ ├── StyleManager.php
│ └── StyleRegistry.php
├── WorkbookManager.php
└── WorksheetManager.php
└── Writer.php
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: adrilo
2 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | in(__DIR__)
5 | ->name('*.php')
6 | ->exclude('vendor');
7 |
8 | $config = new PhpCsFixer\Config();
9 | return $config
10 | ->setRiskyAllowed(true)
11 | ->setRules([
12 | '@Symfony' => true,
13 | 'align_multiline_comment' => false,
14 | 'array_syntax' => ['syntax' => 'short'],
15 | 'binary_operator_spaces' => ['default' => null],
16 | 'blank_line_before_statement' => ['statements' => ['return']],
17 | 'combine_consecutive_unsets' => true,
18 | 'concat_space' => ['spacing' => 'one'],
19 | 'declare_equal_normalize' => ['space' => 'single'],
20 | 'heredoc_to_nowdoc' => true,
21 | 'increment_style' => ['style' => 'post'],
22 | 'is_null' => true,
23 | 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
24 | 'modernize_types_casting' => true,
25 | 'no_break_comment' => ['comment_text' => 'do nothing'],
26 | 'no_empty_phpdoc' => false,
27 | 'no_null_property_initialization' => true,
28 | 'echo_tag_syntax' => false,
29 | 'no_superfluous_elseif' => true,
30 | 'no_superfluous_phpdoc_tags' => false,
31 | 'no_unneeded_control_parentheses' => ['statements' => ['break', 'clone', 'continue', 'echo_print', 'switch_case', 'yield']],
32 | 'no_unneeded_curly_braces' => true,
33 | 'no_unneeded_final_method' => true,
34 | 'no_useless_else' => false,
35 | 'no_useless_return' => true,
36 | 'ordered_imports' => true,
37 | 'phpdoc_add_missing_param_annotation' => true,
38 | 'phpdoc_align' => false,
39 | 'phpdoc_annotation_without_dot' => false,
40 | 'phpdoc_no_empty_return' => false,
41 | 'phpdoc_order' => true,
42 | 'phpdoc_summary' => false,
43 | 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
44 | 'phpdoc_separation' => false,
45 | 'protected_to_private' => true,
46 | 'psr_autoloading' => true,
47 | 'return_type_declaration' => ['space_before' => 'one'],
48 | 'semicolon_after_instruction' => true,
49 | 'simplified_null_return' => false,
50 | 'single_line_comment_style' => ['comment_types' => ['hash']],
51 | 'strict_comparison' => true,
52 | 'yoda_style' => ['equal' => false, 'identical' => false],
53 | ])
54 | ->setFinder($finder);
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spout
2 |
3 | [](https://packagist.org/packages/box/spout)
4 | [](https://opensource.box.com/badges)
5 | [](https://github.com/box/spout/actions/workflows/ci.yml?query=branch%3Amaster)
6 | [](https://coveralls.io/github/box/spout?branch=master)
7 | [](https://packagist.org/packages/box/spout)
8 |
9 | ## 🪦 Archived project 🪦
10 |
11 | This project has been archived and is no longer maintained. No bug fix and no additional features will be added.
12 | You won't be able to submit new issues or pull requests, and no additional features will be added
13 |
14 | You can still use Spout as is in your projects though :)
15 |
16 | > Thanks to everyone who contributed to this project, from a typo fix to the new cool feature.
17 | > It was great to see the involvement of this community!
18 |
19 |
20 |
21 | ## About
22 |
23 | Spout is a PHP library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way.
24 | Unlike other file readers or writers, it is capable of processing very large files, while keeping the memory usage really low (less than 3MB).
25 |
26 | Join the community and come discuss Spout: [](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
27 |
28 |
29 | ## Documentation
30 |
31 | Full documentation can be found at [https://opensource.box.com/spout/](https://opensource.box.com/spout/).
32 |
33 |
34 | ## Requirements
35 |
36 | * PHP version 7.2 or higher
37 | * PHP extension `php_zip` enabled
38 | * PHP extension `php_xmlreader` enabled
39 |
40 | ## Upgrade guide
41 |
42 | 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).
43 |
44 | ## Running tests
45 |
46 | The `master` branch includes unit, functional and performance tests.
47 | If you just want to check that everything is working as expected, executing the unit and functional tests is enough.
48 |
49 | * `phpunit` - runs unit and functional tests
50 | * `phpunit --group perf-tests` - only runs the performance tests
51 |
52 | For information, the performance tests take about 10 minutes to run (processing 1 million rows files is not a quick thing).
53 |
54 | ## Support
55 |
56 | Spout is no longer actively supported. You can still ask questions, or discuss about it in the chat room:
57 | [](https://gitter.im/box/spout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
58 |
59 | ## Copyright and License
60 |
61 | Copyright 2022 Box, Inc. All rights reserved.
62 |
63 | Licensed under the Apache License, Version 2.0 (the "License");
64 | you may not use this file except in compliance with the License.
65 | You may obtain a copy of the License at
66 |
67 | http://www.apache.org/licenses/LICENSE-2.0
68 |
69 | Unless required by applicable law or agreed to in writing, software
70 | distributed under the License is distributed on an "AS IS" BASIS,
71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
72 | See the License for the specific language governing permissions and
73 | limitations under the License.
74 |
--------------------------------------------------------------------------------
/UPGRADE-3.0.md:
--------------------------------------------------------------------------------
1 | Upgrading from 2.x to 3.0
2 | =========================
3 |
4 | Spout 3.0 introduced several backwards-incompatible changes. The upgrade from Spout 2.x to 3.0 must therefore be done with caution.
5 | This guide is meant to ease this process.
6 |
7 | Most notable changes
8 | --------------------
9 | In 2.x, styles were applied per row; it was therefore impossible to apply different styles to cells in the same row.
10 | With the 3.0 version, this is now possible: each cell can have its own style.
11 |
12 | 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.
13 |
14 | Finally, **_Spout 3.2 only supports PHP 7.2 and above_**, as other PHP versions are no longer supported by the community.
15 |
16 | Reader changes
17 | --------------
18 | Creating a reader should now be done through the Reader `ReaderEntityFactory`, instead of using the `ReaderFactory`.
19 | Also, the `ReaderFactory::create($type)` method was removed and replaced by methods for each reader:
20 | ```php
21 | use Box\Spout\Reader\Common\Creator\ReaderEntityFactory; // namespace is no longer "Box\Spout\Reader"
22 | ...
23 | $reader = ReaderEntityFactory::createXLSXReader(); // replaces ReaderFactory::create(Type::XLSX)
24 | $reader = ReaderEntityFactory::createCSVReader(); // replaces ReaderFactory::create(Type::CSV)
25 | $reader = ReaderEntityFactory::createODSReader(); // replaces ReaderFactory::create(Type::ODS)
26 | ```
27 |
28 | 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:
29 | ```php
30 | ...
31 | foreach ($reader->getSheetIterator() as $sheet) {
32 | foreach ($sheet->getRowIterator() as $row) { // $row is a "Row" object, not an array
33 | $rowAsArray = $row->toArray(); // this is the 2.x equivalent
34 | // OR
35 | $cellsArray = $row->getCells(); // this can be used to get access to cells' details
36 | ...
37 | }
38 | }
39 | ```
40 |
41 | Writer changes
42 | --------------
43 | Writer creation follows the same change as the reader. It should now be done through the Writer `WriterEntityFactory`, instead of using the `WriterFactory`.
44 | Also, the `WriterFactory::create($type)` method was removed and replaced by methods for each writer:
45 |
46 | ```php
47 | use Box\Spout\Writer\Common\Creator\WriterEntityFactory; // namespace is no longer "Box\Spout\Writer"
48 | ...
49 | $writer = WriterEntityFactory::createXLSXWriter(); // replaces WriterFactory::create(Type::XLSX)
50 | $writer = WriterEntityFactory::createCSVWriter(); // replaces WriterFactory::create(Type::CSV)
51 | $writer = WriterEntityFactory::createODSWriter(); // replaces WriterFactory::create(Type::ODS)
52 | ```
53 |
54 | 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:
55 | ```php
56 | // Adding a row from an array of values (2.x equivalent)
57 | $cellValues = ['foo', 12345];
58 | $row1 = WriterEntityFactory::createRowFromArray($cellValues, $rowStyle);
59 |
60 | // Adding a row from an array of Cell
61 | $cell1 = WriterEntityFactory::createCell('foo', $cellStyle1); // this cell has its own style
62 | $cell2 = WriterEntityFactory::createCell(12345, $cellStyle2); // this cell has its own style
63 | $row2 = WriterEntityFactory::createRow([$cell1, $cell2]);
64 |
65 | $writer->addRows([$row1, $row2]);
66 | ```
67 |
68 | Namespace changes for styles
69 | -----------------
70 | The namespaces for styles have changed. Styles are still created by using a `builder` class.
71 |
72 | For the builder, please update your import statements to use the following namespaces:
73 |
74 | Box\Spout\Writer\Common\Creator\Style\StyleBuilder
75 | Box\Spout\Writer\Common\Creator\Style\BorderBuilder
76 |
77 | The `Style` base class and style definitions like `Border`, `BorderPart` and `Color` also have a new namespace.
78 |
79 | If your are using these classes directly via an import statement in your code, please use the following namespaces:
80 |
81 | Box\Spout\Common\Entity\Style\Border
82 | Box\Spout\Common\Entity\Style\BorderPart
83 | Box\Spout\Common\Entity\Style\Color
84 | Box\Spout\Common\Entity\Style\Style
85 |
86 | Handling of empty rows
87 | ----------------------
88 | In 2.x, empty rows were not added to the spreadsheet.
89 | 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.
90 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "box/spout",
3 | "description": "PHP Library to read and write spreadsheet files (CSV, XLSX and ODS), in a fast and scalable way",
4 | "type": "library",
5 | "keywords": ["php","read","write","csv","xlsx","ods","odf","open","office","excel","spreadsheet","scale","memory","stream","ooxml"],
6 | "license": "Apache-2.0",
7 | "homepage": "https://www.github.com/box/spout",
8 | "authors": [
9 | {
10 | "name": "Adrien Loison",
11 | "email": "adrien@box.com"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=7.3.0",
16 | "ext-zip": "*",
17 | "ext-xmlreader": "*",
18 | "ext-dom": "*"
19 | },
20 | "require-dev": {
21 | "phpunit/phpunit": "^9",
22 | "friendsofphp/php-cs-fixer": "^3",
23 | "phpstan/phpstan": "^1"
24 | },
25 | "suggest": {
26 | "ext-iconv": "To handle non UTF-8 CSV files (if \"php-intl\" is not already installed or is too limited)",
27 | "ext-intl": "To handle non UTF-8 CSV files (if \"iconv\" is not already installed)"
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "Box\\Spout\\": "src/Spout"
32 | }
33 | },
34 | "extra": {
35 | "branch-alias": {
36 | "dev-master": "3.1.x-dev"
37 | }
38 | },
39 | "config": {
40 | "platform": {
41 | "php": "7.3"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 4
3 | paths:
4 | - src
5 | - tests
6 | excludePaths:
7 | # Exclude these files that are OK
8 | - src/Spout/Reader/Common/Creator/ReaderEntityFactory.php
9 | - src/Spout/Writer/Common/Creator/WriterEntityFactory.php
10 |
--------------------------------------------------------------------------------
/src/Spout/Autoloader/Psr4Autoloader.php:
--------------------------------------------------------------------------------
1 | prefixes[$prefix]) === false) {
50 | $this->prefixes[$prefix] = [];
51 | }
52 |
53 | // retain the base directory for the namespace prefix
54 | if ($prepend) {
55 | \array_unshift($this->prefixes[$prefix], $baseDir);
56 | } else {
57 | \array_push($this->prefixes[$prefix], $baseDir);
58 | }
59 | }
60 |
61 | /**
62 | * Loads the class file for a given class name.
63 | *
64 | * @param string $class The fully-qualified class name.
65 | * @return mixed The mapped file name on success, or boolean false on
66 | * failure.
67 | */
68 | public function loadClass($class)
69 | {
70 | // the current namespace prefix
71 | $prefix = $class;
72 |
73 | // work backwards through the namespace names of the fully-qualified
74 | // class name to find a mapped file name
75 | while (($pos = \strrpos($prefix, '\\')) !== false) {
76 | // retain the trailing namespace separator in the prefix
77 | $prefix = \substr($class, 0, $pos + 1);
78 |
79 | // the rest is the relative class name
80 | $relativeClass = \substr($class, $pos + 1);
81 |
82 | // try to load a mapped file for the prefix and relative class
83 | $mappedFile = $this->loadMappedFile($prefix, $relativeClass);
84 | if ($mappedFile !== false) {
85 | return $mappedFile;
86 | }
87 |
88 | // remove the trailing namespace separator for the next iteration
89 | // of strrpos()
90 | $prefix = \rtrim($prefix, '\\');
91 | }
92 |
93 | // never found a mapped file
94 | return false;
95 | }
96 |
97 | /**
98 | * Load the mapped file for a namespace prefix and relative class.
99 | *
100 | * @param string $prefix The namespace prefix.
101 | * @param string $relativeClass The relative class name.
102 | * @return mixed Boolean false if no mapped file can be loaded, or the
103 | * name of the mapped file that was loaded.
104 | */
105 | protected function loadMappedFile($prefix, $relativeClass)
106 | {
107 | // are there any base directories for this namespace prefix?
108 | if (isset($this->prefixes[$prefix]) === false) {
109 | return false;
110 | }
111 |
112 | // look through base directories for this namespace prefix
113 | foreach ($this->prefixes[$prefix] as $baseDir) {
114 | // replace the namespace prefix with the base directory,
115 | // replace namespace separators with directory separators
116 | // in the relative class name, append with .php
117 | $file = $baseDir
118 | . \str_replace('\\', '/', $relativeClass)
119 | . '.php';
120 |
121 | // if the mapped file exists, require it
122 | if ($this->requireFile($file)) {
123 | // yes, we're done
124 | return $file;
125 | }
126 | }
127 |
128 | // never found it
129 | return false;
130 | }
131 |
132 | /**
133 | * If a file exists, require it from the file system.
134 | *
135 | * @param string $file The file to require.
136 | * @return bool True if the file exists, false if not.
137 | */
138 | protected function requireFile($file)
139 | {
140 | if (\file_exists($file)) {
141 | require $file;
142 |
143 | return true;
144 | }
145 |
146 | return false;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Spout/Autoloader/autoload.php:
--------------------------------------------------------------------------------
1 | register();
15 | $loader->addNamespace('Box\Spout', $srcBaseDirectory);
16 |
--------------------------------------------------------------------------------
/src/Spout/Common/Creator/HelperFactory.php:
--------------------------------------------------------------------------------
1 | setValue($value);
74 | $this->setStyle($style);
75 | }
76 |
77 | /**
78 | * @param mixed|null $value
79 | */
80 | public function setValue($value)
81 | {
82 | $this->value = $value;
83 | $this->type = $this->detectType($value);
84 | }
85 |
86 | /**
87 | * @return mixed|null
88 | */
89 | public function getValue()
90 | {
91 | return !$this->isError() ? $this->value : null;
92 | }
93 |
94 | /**
95 | * @return mixed
96 | */
97 | public function getValueEvenIfError()
98 | {
99 | return $this->value;
100 | }
101 |
102 | /**
103 | * @param Style|null $style
104 | */
105 | public function setStyle($style)
106 | {
107 | $this->style = $style ?: new Style();
108 | }
109 |
110 | /**
111 | * @return Style
112 | */
113 | public function getStyle()
114 | {
115 | return $this->style;
116 | }
117 |
118 | /**
119 | * @return int|null
120 | */
121 | public function getType()
122 | {
123 | return $this->type;
124 | }
125 |
126 | /**
127 | * @param int $type
128 | */
129 | public function setType($type)
130 | {
131 | $this->type = $type;
132 | }
133 |
134 | /**
135 | * Get the current value type
136 | *
137 | * @param mixed|null $value
138 | * @return int
139 | */
140 | protected function detectType($value)
141 | {
142 | if (CellTypeHelper::isBoolean($value)) {
143 | return self::TYPE_BOOLEAN;
144 | }
145 | if (CellTypeHelper::isEmpty($value)) {
146 | return self::TYPE_EMPTY;
147 | }
148 | if (CellTypeHelper::isNumeric($value)) {
149 | return self::TYPE_NUMERIC;
150 | }
151 | if (CellTypeHelper::isDateTimeOrDateInterval($value)) {
152 | return self::TYPE_DATE;
153 | }
154 | if (CellTypeHelper::isNonEmptyString($value)) {
155 | return self::TYPE_STRING;
156 | }
157 |
158 | return self::TYPE_ERROR;
159 | }
160 |
161 | /**
162 | * @return bool
163 | */
164 | public function isBoolean()
165 | {
166 | return $this->type === self::TYPE_BOOLEAN;
167 | }
168 |
169 | /**
170 | * @return bool
171 | */
172 | public function isEmpty()
173 | {
174 | return $this->type === self::TYPE_EMPTY;
175 | }
176 |
177 | /**
178 | * @return bool
179 | */
180 | public function isNumeric()
181 | {
182 | return $this->type === self::TYPE_NUMERIC;
183 | }
184 |
185 | /**
186 | * @return bool
187 | */
188 | public function isString()
189 | {
190 | return $this->type === self::TYPE_STRING;
191 | }
192 |
193 | /**
194 | * @return bool
195 | */
196 | public function isDate()
197 | {
198 | return $this->type === self::TYPE_DATE;
199 | }
200 |
201 | /**
202 | * @return bool
203 | */
204 | public function isError()
205 | {
206 | return $this->type === self::TYPE_ERROR;
207 | }
208 |
209 | /**
210 | * @return string
211 | */
212 | public function __toString()
213 | {
214 | return (string) $this->getValue();
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/Spout/Common/Entity/Row.php:
--------------------------------------------------------------------------------
1 | setCells($cells)
30 | ->setStyle($style);
31 | }
32 |
33 | /**
34 | * @return Cell[] $cells
35 | */
36 | public function getCells()
37 | {
38 | return $this->cells;
39 | }
40 |
41 | /**
42 | * @param Cell[] $cells
43 | * @return Row
44 | */
45 | public function setCells(array $cells)
46 | {
47 | $this->cells = [];
48 | foreach ($cells as $cell) {
49 | $this->addCell($cell);
50 | }
51 |
52 | return $this;
53 | }
54 |
55 | /**
56 | * @param Cell $cell
57 | * @param int $cellIndex
58 | * @return Row
59 | */
60 | public function setCellAtIndex(Cell $cell, $cellIndex)
61 | {
62 | $this->cells[$cellIndex] = $cell;
63 |
64 | return $this;
65 | }
66 |
67 | /**
68 | * @param int $cellIndex
69 | * @return Cell|null
70 | */
71 | public function getCellAtIndex($cellIndex)
72 | {
73 | return $this->cells[$cellIndex] ?? null;
74 | }
75 |
76 | /**
77 | * @param Cell $cell
78 | * @return Row
79 | */
80 | public function addCell(Cell $cell)
81 | {
82 | $this->cells[] = $cell;
83 |
84 | return $this;
85 | }
86 |
87 | /**
88 | * @return int
89 | */
90 | public function getNumCells()
91 | {
92 | // When using "setCellAtIndex", it's possible to
93 | // have "$this->cells" contain holes.
94 | if (empty($this->cells)) {
95 | return 0;
96 | }
97 |
98 | return \max(\array_keys($this->cells)) + 1;
99 | }
100 |
101 | /**
102 | * @return Style
103 | */
104 | public function getStyle()
105 | {
106 | return $this->style;
107 | }
108 |
109 | /**
110 | * @param Style|null $style
111 | * @return Row
112 | */
113 | public function setStyle($style)
114 | {
115 | $this->style = $style ?: new Style();
116 |
117 | return $this;
118 | }
119 |
120 | /**
121 | * @return array The row values, as array
122 | */
123 | public function toArray()
124 | {
125 | return \array_map(function (Cell $cell) {
126 | return $cell->getValue();
127 | }, $this->cells);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/Spout/Common/Entity/Style/Border.php:
--------------------------------------------------------------------------------
1 | setParts($borderParts);
34 | }
35 |
36 | /**
37 | * @param string $name The name of the border part
38 | * @return BorderPart|null
39 | */
40 | public function getPart($name)
41 | {
42 | return $this->hasPart($name) ? $this->parts[$name] : null;
43 | }
44 |
45 | /**
46 | * @param string $name The name of the border part
47 | * @return bool
48 | */
49 | public function hasPart($name)
50 | {
51 | return isset($this->parts[$name]);
52 | }
53 |
54 | /**
55 | * @return array
56 | */
57 | public function getParts()
58 | {
59 | return $this->parts;
60 | }
61 |
62 | /**
63 | * Set BorderParts
64 | * @param array $parts
65 | * @return void
66 | */
67 | public function setParts($parts)
68 | {
69 | unset($this->parts);
70 | foreach ($parts as $part) {
71 | $this->addPart($part);
72 | }
73 | }
74 |
75 | /**
76 | * @param BorderPart $borderPart
77 | * @return Border
78 | */
79 | public function addPart(BorderPart $borderPart)
80 | {
81 | $this->parts[$borderPart->getName()] = $borderPart;
82 |
83 | return $this;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Spout/Common/Entity/Style/BorderPart.php:
--------------------------------------------------------------------------------
1 | setName($name);
76 | $this->setColor($color);
77 | $this->setWidth($width);
78 | $this->setStyle($style);
79 | }
80 |
81 | /**
82 | * @return string
83 | */
84 | public function getName()
85 | {
86 | return $this->name;
87 | }
88 |
89 | /**
90 | * @param string $name The name of the border part @see BorderPart::$allowedNames
91 | * @throws InvalidNameException
92 | * @return void
93 | */
94 | public function setName($name)
95 | {
96 | if (!\in_array($name, self::$allowedNames)) {
97 | throw new InvalidNameException($name);
98 | }
99 | $this->name = $name;
100 | }
101 |
102 | /**
103 | * @return string
104 | */
105 | public function getStyle()
106 | {
107 | return $this->style;
108 | }
109 |
110 | /**
111 | * @param string $style The style of the border part @see BorderPart::$allowedStyles
112 | * @throws InvalidStyleException
113 | * @return void
114 | */
115 | public function setStyle($style)
116 | {
117 | if (!\in_array($style, self::$allowedStyles)) {
118 | throw new InvalidStyleException($style);
119 | }
120 | $this->style = $style;
121 | }
122 |
123 | /**
124 | * @return string
125 | */
126 | public function getColor()
127 | {
128 | return $this->color;
129 | }
130 |
131 | /**
132 | * @param string $color The color of the border part @see Color::rgb()
133 | * @return void
134 | */
135 | public function setColor($color)
136 | {
137 | $this->color = $color;
138 | }
139 |
140 | /**
141 | * @return string
142 | */
143 | public function getWidth()
144 | {
145 | return $this->width;
146 | }
147 |
148 | /**
149 | * @param string $width The width of the border part @see BorderPart::$allowedWidths
150 | * @throws InvalidWidthException
151 | * @return void
152 | */
153 | public function setWidth($width)
154 | {
155 | if (!\in_array($width, self::$allowedWidths)) {
156 | throw new InvalidWidthException($width);
157 | }
158 | $this->width = $width;
159 | }
160 |
161 | /**
162 | * @return array
163 | */
164 | public static function getAllowedStyles()
165 | {
166 | return self::$allowedStyles;
167 | }
168 |
169 | /**
170 | * @return array
171 | */
172 | public static function getAllowedNames()
173 | {
174 | return self::$allowedNames;
175 | }
176 |
177 | /**
178 | * @return array
179 | */
180 | public static function getAllowedWidths()
181 | {
182 | return self::$allowedWidths;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/Spout/Common/Entity/Style/CellAlignment.php:
--------------------------------------------------------------------------------
1 | 1,
18 | self::RIGHT => 1,
19 | self::CENTER => 1,
20 | self::JUSTIFY => 1,
21 | ];
22 |
23 | /**
24 | * @param string $cellAlignment
25 | *
26 | * @return bool Whether the given cell alignment is valid
27 | */
28 | public static function isValid($cellAlignment)
29 | {
30 | return isset(self::$VALID_ALIGNMENTS[$cellAlignment]);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Spout/Common/Entity/Style/Color.php:
--------------------------------------------------------------------------------
1 | 255) {
58 | throw new InvalidColorException("The RGB components must be between 0 and 255. Received: $colorComponent");
59 | }
60 | }
61 |
62 | /**
63 | * Converts the color component to its corresponding hexadecimal value
64 | *
65 | * @param int $colorComponent Color component, 0 - 255
66 | * @return string Corresponding hexadecimal value, with a leading 0 if needed. E.g "0f", "2d"
67 | */
68 | protected static function convertColorComponentToHex($colorComponent)
69 | {
70 | return \str_pad(\dechex($colorComponent), 2, '0', STR_PAD_LEFT);
71 | }
72 |
73 | /**
74 | * Returns the ARGB color of the given RGB color,
75 | * assuming that alpha value is always 1.
76 | *
77 | * @param string $rgbColor RGB color like "FF08B2"
78 | * @return string ARGB color
79 | */
80 | public static function toARGB($rgbColor)
81 | {
82 | return 'FF' . $rgbColor;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Spout/Common/Exception/EncodingConversionException.php:
--------------------------------------------------------------------------------
1 | ', '&') as well as
20 | // single/double quotes (for XML attributes) need to be encoded.
21 | if (\defined('ENT_DISALLOWED')) {
22 | // 'ENT_DISALLOWED' ensures that invalid characters in the given document type are replaced.
23 | // Otherwise control characters like a vertical tab "\v" will make the XML document unreadable by the XML processor
24 | // @link https://github.com/box/spout/issues/329
25 | $replacedString = \htmlspecialchars($string, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8');
26 | } else {
27 | // We are on hhvm or any other engine that does not support ENT_DISALLOWED.
28 | $escapedString = \htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
29 |
30 | // control characters values are from 0 to 1F (hex values) in the ASCII table
31 | // some characters should not be escaped though: "\t", "\r" and "\n".
32 | $regexPattern = '[\x00-\x08' .
33 | // skipping "\t" (0x9) and "\n" (0xA)
34 | '\x0B-\x0C' .
35 | // skipping "\r" (0xD)
36 | '\x0E-\x1F]';
37 | $replacedString = \preg_replace("/$regexPattern/", '�', $escapedString);
38 | }
39 |
40 | return $replacedString;
41 | }
42 |
43 | /**
44 | * Unescapes the given string to make it compatible with XLSX
45 | *
46 | * @param string $string The string to unescape
47 | * @return string The unescaped string
48 | */
49 | public function unescape($string)
50 | {
51 | // ==============
52 | // = WARNING =
53 | // ==============
54 | // It is assumed that the given string has already had its XML entities decoded.
55 | // This is true if the string is coming from a DOMNode (as DOMNode already decode XML entities on creation).
56 | // Therefore there is no need to call "htmlspecialchars_decode()".
57 | return $string;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Spout/Common/Helper/FileSystemHelperInterface.php:
--------------------------------------------------------------------------------
1 | hasMbstringSupport = \extension_loaded('mbstring');
28 | $this->isRunningPhp7OrOlder = \version_compare(PHP_VERSION, '8.0.0') < 0;
29 | $this->localeInfo = \localeconv();
30 | }
31 |
32 | /**
33 | * Returns the length of the given string.
34 | * It uses the multi-bytes function is available.
35 | * @see strlen
36 | * @see mb_strlen
37 | *
38 | * @param string $string
39 | * @return int
40 | */
41 | public function getStringLength($string)
42 | {
43 | return $this->hasMbstringSupport ? \mb_strlen($string) : \strlen($string);
44 | }
45 |
46 | /**
47 | * Returns the position of the first occurrence of the given character/substring within the given string.
48 | * It uses the multi-bytes function is available.
49 | * @see strpos
50 | * @see mb_strpos
51 | *
52 | * @param string $char Needle
53 | * @param string $string Haystack
54 | * @return int Char/substring's first occurrence position within the string if found (starts at 0) or -1 if not found
55 | */
56 | public function getCharFirstOccurrencePosition($char, $string)
57 | {
58 | $position = $this->hasMbstringSupport ? \mb_strpos($string, $char) : \strpos($string, $char);
59 |
60 | return ($position !== false) ? $position : -1;
61 | }
62 |
63 | /**
64 | * Returns the position of the last occurrence of the given character/substring within the given string.
65 | * It uses the multi-bytes function is available.
66 | * @see strrpos
67 | * @see mb_strrpos
68 | *
69 | * @param string $char Needle
70 | * @param string $string Haystack
71 | * @return int Char/substring's last occurrence position within the string if found (starts at 0) or -1 if not found
72 | */
73 | public function getCharLastOccurrencePosition($char, $string)
74 | {
75 | $position = $this->hasMbstringSupport ? \mb_strrpos($string, $char) : \strrpos($string, $char);
76 |
77 | return ($position !== false) ? $position : -1;
78 | }
79 |
80 | /**
81 | * Formats a numeric value (int or float) in a way that's compatible with the expected spreadsheet format.
82 | *
83 | * Formatting of float values is locale dependent in PHP < 8.
84 | * Thousands separators and decimal points vary from locale to locale (en_US: 12.34 vs pl_PL: 12,34).
85 | * However, float values must be formatted with no thousands separator and a "." as decimal point
86 | * to work properly. This method can be used to convert the value to the correct format before storing it.
87 | *
88 | * @see https://wiki.php.net/rfc/locale_independent_float_to_string for the changed behavior in PHP8.
89 | *
90 | * @param int|float $numericValue
91 | * @return int|float|string
92 | */
93 | public function formatNumericValue($numericValue)
94 | {
95 | if ($this->isRunningPhp7OrOlder && is_float($numericValue)) {
96 | return str_replace(
97 | [$this->localeInfo['thousands_sep'], $this->localeInfo['decimal_point']],
98 | ['', '.'],
99 | $numericValue
100 | );
101 | }
102 |
103 | return $numericValue;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/Spout/Common/Manager/OptionsManagerAbstract.php:
--------------------------------------------------------------------------------
1 | OPTION_VALUE] */
16 | private $options = [];
17 |
18 | /**
19 | * OptionsManagerAbstract constructor.
20 | */
21 | public function __construct()
22 | {
23 | $this->supportedOptions = $this->getSupportedOptions();
24 | $this->setDefaultOptions();
25 | }
26 |
27 | /**
28 | * @return array List of supported options
29 | */
30 | abstract protected function getSupportedOptions();
31 |
32 | /**
33 | * Sets the default options.
34 | * To be overriden by child classes
35 | *
36 | * @return void
37 | */
38 | abstract protected function setDefaultOptions();
39 |
40 | /**
41 | * Sets the given option, if this option is supported.
42 | *
43 | * @param string $optionName
44 | * @param mixed $optionValue
45 | * @return void
46 | */
47 | public function setOption($optionName, $optionValue)
48 | {
49 | if (\in_array($optionName, $this->supportedOptions)) {
50 | $this->options[$optionName] = $optionValue;
51 | }
52 | }
53 |
54 | /**
55 | * @param string $optionName
56 | * @return mixed|null The set option or NULL if no option with given name found
57 | */
58 | public function getOption($optionName)
59 | {
60 | $optionValue = null;
61 |
62 | if (isset($this->options[$optionName])) {
63 | $optionValue = $this->options[$optionName];
64 | }
65 |
66 | return $optionValue;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Spout/Common/Manager/OptionsManagerInterface.php:
--------------------------------------------------------------------------------
1 | helperFactory = $helperFactory;
30 | }
31 |
32 | /**
33 | * @param resource $filePointer Pointer to the CSV file to read
34 | * @param OptionsManagerInterface $optionsManager
35 | * @param GlobalFunctionsHelper $globalFunctionsHelper
36 | * @return SheetIterator
37 | */
38 | public function createSheetIterator($filePointer, $optionsManager, $globalFunctionsHelper)
39 | {
40 | $rowIterator = $this->createRowIterator($filePointer, $optionsManager, $globalFunctionsHelper);
41 | $sheet = $this->createSheet($rowIterator);
42 |
43 | return new SheetIterator($sheet);
44 | }
45 |
46 | /**
47 | * @param RowIterator $rowIterator
48 | * @return Sheet
49 | */
50 | private function createSheet($rowIterator)
51 | {
52 | return new Sheet($rowIterator);
53 | }
54 |
55 | /**
56 | * @param resource $filePointer Pointer to the CSV file to read
57 | * @param OptionsManagerInterface $optionsManager
58 | * @param GlobalFunctionsHelper $globalFunctionsHelper
59 | * @return RowIterator
60 | */
61 | private function createRowIterator($filePointer, $optionsManager, $globalFunctionsHelper)
62 | {
63 | $encodingHelper = $this->helperFactory->createEncodingHelper($globalFunctionsHelper);
64 |
65 | return new RowIterator($filePointer, $optionsManager, $encodingHelper, $this, $globalFunctionsHelper);
66 | }
67 |
68 | /**
69 | * @param Cell[] $cells
70 | * @return Row
71 | */
72 | public function createRow(array $cells = [])
73 | {
74 | return new Row($cells, null);
75 | }
76 |
77 | /**
78 | * @param mixed $cellValue
79 | * @return Cell
80 | */
81 | public function createCell($cellValue)
82 | {
83 | return new Cell($cellValue);
84 | }
85 |
86 | /**
87 | * @param array $cellValues
88 | * @return Row
89 | */
90 | public function createRowFromArray(array $cellValues = [])
91 | {
92 | $cells = \array_map(function ($cellValue) {
93 | return $this->createCell($cellValue);
94 | }, $cellValues);
95 |
96 | return $this->createRow($cells);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Spout/Reader/CSV/Manager/OptionsManager.php:
--------------------------------------------------------------------------------
1 | setOption(Options::SHOULD_FORMAT_DATES, false);
35 | $this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
36 | $this->setOption(Options::FIELD_DELIMITER, ',');
37 | $this->setOption(Options::FIELD_ENCLOSURE, '"');
38 | $this->setOption(Options::ENCODING, EncodingHelper::ENCODING_UTF8);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Spout/Reader/CSV/Reader.php:
--------------------------------------------------------------------------------
1 | = 8.1 */
29 | private $isRunningAtLeastPhp81;
30 |
31 | /**
32 | * @param OptionsManagerInterface $optionsManager
33 | * @param GlobalFunctionsHelper $globalFunctionsHelper
34 | * @param InternalEntityFactoryInterface $entityFactory
35 | */
36 | public function __construct(
37 | OptionsManagerInterface $optionsManager,
38 | GlobalFunctionsHelper $globalFunctionsHelper,
39 | InternalEntityFactoryInterface $entityFactory
40 | ) {
41 | parent::__construct($optionsManager, $globalFunctionsHelper, $entityFactory);
42 | $this->isRunningAtLeastPhp81 = \version_compare(PHP_VERSION, '8.1.0') >= 0;
43 | }
44 |
45 | /**
46 | * Sets the field delimiter for the CSV.
47 | * Needs to be called before opening the reader.
48 | *
49 | * @param string $fieldDelimiter Character that delimits fields
50 | * @return Reader
51 | */
52 | public function setFieldDelimiter($fieldDelimiter)
53 | {
54 | $this->optionsManager->setOption(Options::FIELD_DELIMITER, $fieldDelimiter);
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * Sets the field enclosure for the CSV.
61 | * Needs to be called before opening the reader.
62 | *
63 | * @param string $fieldEnclosure Character that enclose fields
64 | * @return Reader
65 | */
66 | public function setFieldEnclosure($fieldEnclosure)
67 | {
68 | $this->optionsManager->setOption(Options::FIELD_ENCLOSURE, $fieldEnclosure);
69 |
70 | return $this;
71 | }
72 |
73 | /**
74 | * Sets the encoding of the CSV file to be read.
75 | * Needs to be called before opening the reader.
76 | *
77 | * @param string $encoding Encoding of the CSV file to be read
78 | * @return Reader
79 | */
80 | public function setEncoding($encoding)
81 | {
82 | $this->optionsManager->setOption(Options::ENCODING, $encoding);
83 |
84 | return $this;
85 | }
86 |
87 | /**
88 | * Returns whether stream wrappers are supported
89 | *
90 | * @return bool
91 | */
92 | protected function doesSupportStreamWrapper()
93 | {
94 | return true;
95 | }
96 |
97 | /**
98 | * Opens the file at the given path to make it ready to be read.
99 | * If setEncoding() was not called, it assumes that the file is encoded in UTF-8.
100 | *
101 | * @param string $filePath Path of the CSV file to be read
102 | * @throws \Box\Spout\Common\Exception\IOException
103 | * @return void
104 | */
105 | protected function openReader($filePath)
106 | {
107 | // "auto_detect_line_endings" is deprecated in PHP 8.1
108 | if (!$this->isRunningAtLeastPhp81) {
109 | $this->originalAutoDetectLineEndings = \ini_get('auto_detect_line_endings');
110 | \ini_set('auto_detect_line_endings', '1');
111 | }
112 |
113 | $this->filePointer = $this->globalFunctionsHelper->fopen($filePath, 'r');
114 | if (!$this->filePointer) {
115 | throw new IOException("Could not open file $filePath for reading.");
116 | }
117 |
118 | /** @var InternalEntityFactory $entityFactory */
119 | $entityFactory = $this->entityFactory;
120 |
121 | $this->sheetIterator = $entityFactory->createSheetIterator(
122 | $this->filePointer,
123 | $this->optionsManager,
124 | $this->globalFunctionsHelper
125 | );
126 | }
127 |
128 | /**
129 | * Returns an iterator to iterate over sheets.
130 | *
131 | * @return SheetIterator To iterate over sheets
132 | */
133 | protected function getConcreteSheetIterator()
134 | {
135 | return $this->sheetIterator;
136 | }
137 |
138 | /**
139 | * Closes the reader. To be used after reading the file.
140 | *
141 | * @return void
142 | */
143 | protected function closeReader()
144 | {
145 | if (is_resource($this->filePointer)) {
146 | $this->globalFunctionsHelper->fclose($this->filePointer);
147 | }
148 |
149 | // "auto_detect_line_endings" is deprecated in PHP 8.1
150 | if (!$this->isRunningAtLeastPhp81) {
151 | \ini_set('auto_detect_line_endings', $this->originalAutoDetectLineEndings);
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/Spout/Reader/CSV/Sheet.php:
--------------------------------------------------------------------------------
1 | rowIterator = $rowIterator;
21 | }
22 |
23 | /**
24 | * @return \Box\Spout\Reader\CSV\RowIterator
25 | */
26 | public function getRowIterator()
27 | {
28 | return $this->rowIterator;
29 | }
30 |
31 | /**
32 | * @return int Index of the sheet
33 | */
34 | public function getIndex()
35 | {
36 | return 0;
37 | }
38 |
39 | /**
40 | * @return string Name of the sheet - empty string since CSV does not support that
41 | */
42 | public function getName()
43 | {
44 | return '';
45 | }
46 |
47 | /**
48 | * @return bool Always TRUE as there is only one sheet
49 | */
50 | public function isActive()
51 | {
52 | return true;
53 | }
54 |
55 | /**
56 | * @return bool Always TRUE as the only sheet is always visible
57 | */
58 | public function isVisible()
59 | {
60 | return true;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Spout/Reader/CSV/SheetIterator.php:
--------------------------------------------------------------------------------
1 | sheet = $sheet;
25 | }
26 |
27 | /**
28 | * Rewind the Iterator to the first element
29 | * @see http://php.net/manual/en/iterator.rewind.php
30 | *
31 | * @return void
32 | */
33 | public function rewind() : void
34 | {
35 | $this->hasReadUniqueSheet = false;
36 | }
37 |
38 | /**
39 | * Checks if current position is valid
40 | * @see http://php.net/manual/en/iterator.valid.php
41 | *
42 | * @return bool
43 | */
44 | public function valid() : bool
45 | {
46 | return (!$this->hasReadUniqueSheet);
47 | }
48 |
49 | /**
50 | * Move forward to next element
51 | * @see http://php.net/manual/en/iterator.next.php
52 | *
53 | * @return void
54 | */
55 | public function next() : void
56 | {
57 | $this->hasReadUniqueSheet = true;
58 | }
59 |
60 | /**
61 | * Return the current element
62 | * @see http://php.net/manual/en/iterator.current.php
63 | *
64 | * @return Sheet
65 | */
66 | public function current() : Sheet
67 | {
68 | return $this->sheet;
69 | }
70 |
71 | /**
72 | * Return the key of the current element
73 | * @see http://php.net/manual/en/iterator.key.php
74 | *
75 | * @return int
76 | */
77 | public function key() : int
78 | {
79 | return 1;
80 | }
81 |
82 | /**
83 | * Cleans up what was created to iterate over the object.
84 | *
85 | * @return void
86 | */
87 | public function end() : void
88 | {
89 | // do nothing
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Spout/Reader/Common/Creator/InternalEntityFactoryInterface.php:
--------------------------------------------------------------------------------
1 | createGlobalFunctionsHelper();
72 |
73 | return new CSVReader($optionsManager, $globalFunctionsHelper, $entityFactory);
74 | }
75 |
76 | /**
77 | * @return XLSXReader
78 | */
79 | private static function createXLSXReader()
80 | {
81 | $optionsManager = new XLSXOptionsManager();
82 | $helperFactory = new XLSXHelperFactory();
83 | $managerFactory = new XLSXManagerFactory($helperFactory, new CachingStrategyFactory());
84 | $entityFactory = new XLSXInternalEntityFactory($managerFactory, $helperFactory);
85 | $globalFunctionsHelper = $helperFactory->createGlobalFunctionsHelper();
86 |
87 | return new XLSXReader($optionsManager, $globalFunctionsHelper, $entityFactory, $managerFactory);
88 | }
89 |
90 | /**
91 | * @return ODSReader
92 | */
93 | private static function createODSReader()
94 | {
95 | $optionsManager = new ODSOptionsManager();
96 | $helperFactory = new ODSHelperFactory();
97 | $managerFactory = new ODSManagerFactory();
98 | $entityFactory = new ODSInternalEntityFactory($helperFactory, $managerFactory);
99 | $globalFunctionsHelper = $helperFactory->createGlobalFunctionsHelper();
100 |
101 | return new ODSReader($optionsManager, $globalFunctionsHelper, $entityFactory);
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Spout/Reader/Common/Entity/Options.php:
--------------------------------------------------------------------------------
1 | entityFactory = $entityFactory;
22 | }
23 |
24 | /**
25 | * Detect whether a row is considered empty.
26 | * An empty row has all of its cells empty.
27 | *
28 | * @param Row $row
29 | * @return bool
30 | */
31 | public function isEmpty(Row $row)
32 | {
33 | foreach ($row->getCells() as $cell) {
34 | if (!$cell->isEmpty()) {
35 | return false;
36 | }
37 | }
38 |
39 | return true;
40 | }
41 |
42 | /**
43 | * Fills the missing indexes of a row with empty cells.
44 | *
45 | * @param Row $row
46 | * @return Row
47 | */
48 | public function fillMissingIndexesWithEmptyCells(Row $row)
49 | {
50 | $numCells = $row->getNumCells();
51 |
52 | if ($numCells === 0) {
53 | return $row;
54 | }
55 |
56 | $rowCells = $row->getCells();
57 | $maxCellIndex = $numCells;
58 |
59 | // If the row has empty cells, calling "setCellAtIndex" will add the cell
60 | // but in the wrong place (the new cell is added at the end of the array).
61 | // Therefore, we need to sort the array using keys to have proper order.
62 | // @see https://github.com/box/spout/issues/740
63 | $needsSorting = false;
64 |
65 | for ($cellIndex = 0; $cellIndex < $maxCellIndex; $cellIndex++) {
66 | if (!isset($rowCells[$cellIndex])) {
67 | $row->setCellAtIndex($this->entityFactory->createCell(''), $cellIndex);
68 | $needsSorting = true;
69 | }
70 | }
71 |
72 | if ($needsSorting) {
73 | $rowCells = $row->getCells();
74 | ksort($rowCells);
75 | $row->setCells($rowCells);
76 | }
77 |
78 | return $row;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Spout/Reader/Exception/InvalidValueException.php:
--------------------------------------------------------------------------------
1 | invalidValue = $invalidValue;
24 | parent::__construct($message, $code, $previous);
25 | }
26 |
27 | /**
28 | * @return mixed
29 | */
30 | public function getInvalidValue()
31 | {
32 | return $this->invalidValue;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Spout/Reader/Exception/IteratorNotRewindableException.php:
--------------------------------------------------------------------------------
1 | createStringsEscaper();
21 |
22 | return new CellValueFormatter($shouldFormatDates, $escaper);
23 | }
24 |
25 | /**
26 | * @param InternalEntityFactory $entityFactory
27 | * @return SettingsHelper
28 | */
29 | public function createSettingsHelper($entityFactory)
30 | {
31 | return new SettingsHelper($entityFactory);
32 | }
33 |
34 | /**
35 | * @return \Box\Spout\Common\Helper\Escaper\ODS
36 | */
37 | public function createStringsEscaper()
38 | {
39 | /* @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
40 | return new \Box\Spout\Common\Helper\Escaper\ODS();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Spout/Reader/ODS/Creator/InternalEntityFactory.php:
--------------------------------------------------------------------------------
1 | helperFactory = $helperFactory;
34 | $this->managerFactory = $managerFactory;
35 | }
36 |
37 | /**
38 | * @param string $filePath Path of the file to be read
39 | * @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager Reader's options manager
40 | * @return SheetIterator
41 | */
42 | public function createSheetIterator($filePath, $optionsManager)
43 | {
44 | $escaper = $this->helperFactory->createStringsEscaper();
45 | $settingsHelper = $this->helperFactory->createSettingsHelper($this);
46 |
47 | return new SheetIterator($filePath, $optionsManager, $escaper, $settingsHelper, $this);
48 | }
49 |
50 | /**
51 | * @param XMLReader $xmlReader XML Reader
52 | * @param int $sheetIndex Index of the sheet, based on order in the workbook (zero-based)
53 | * @param string $sheetName Name of the sheet
54 | * @param bool $isSheetActive Whether the sheet was defined as active
55 | * @param bool $isSheetVisible Whether the sheet is visible
56 | * @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager Reader's options manager
57 | * @return Sheet
58 | */
59 | public function createSheet($xmlReader, $sheetIndex, $sheetName, $isSheetActive, $isSheetVisible, $optionsManager)
60 | {
61 | $rowIterator = $this->createRowIterator($xmlReader, $optionsManager);
62 |
63 | return new Sheet($rowIterator, $sheetIndex, $sheetName, $isSheetActive, $isSheetVisible);
64 | }
65 |
66 | /**
67 | * @param XMLReader $xmlReader XML Reader
68 | * @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager Reader's options manager
69 | * @return RowIterator
70 | */
71 | private function createRowIterator($xmlReader, $optionsManager)
72 | {
73 | $shouldFormatDates = $optionsManager->getOption(Options::SHOULD_FORMAT_DATES);
74 | $cellValueFormatter = $this->helperFactory->createCellValueFormatter($shouldFormatDates);
75 | $xmlProcessor = $this->createXMLProcessor($xmlReader);
76 | $rowManager = $this->managerFactory->createRowManager($this);
77 |
78 | return new RowIterator($xmlReader, $optionsManager, $cellValueFormatter, $xmlProcessor, $rowManager, $this);
79 | }
80 |
81 | /**
82 | * @param Cell[] $cells
83 | * @return Row
84 | */
85 | public function createRow(array $cells = [])
86 | {
87 | return new Row($cells, null);
88 | }
89 |
90 | /**
91 | * @param mixed $cellValue
92 | * @return Cell
93 | */
94 | public function createCell($cellValue)
95 | {
96 | return new Cell($cellValue);
97 | }
98 |
99 | /**
100 | * @return XMLReader
101 | */
102 | public function createXMLReader()
103 | {
104 | return new XMLReader();
105 | }
106 |
107 | /**
108 | * @param XMLReader $xmlReader
109 | * @return XMLProcessor
110 | */
111 | private function createXMLProcessor($xmlReader)
112 | {
113 | return new XMLProcessor($xmlReader);
114 | }
115 |
116 | /**
117 | * @return \ZipArchive
118 | */
119 | public function createZipArchive()
120 | {
121 | return new \ZipArchive();
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Spout/Reader/ODS/Creator/ManagerFactory.php:
--------------------------------------------------------------------------------
1 | entityFactory = $entityFactory;
30 | }
31 |
32 | /**
33 | * @param string $filePath Path of the file to be read
34 | * @return string|null Name of the sheet that was defined as active or NULL if none found
35 | */
36 | public function getActiveSheetName($filePath)
37 | {
38 | $xmlReader = $this->entityFactory->createXMLReader();
39 | if ($xmlReader->openFileInZip($filePath, self::SETTINGS_XML_FILE_PATH) === false) {
40 | return null;
41 | }
42 |
43 | $activeSheetName = null;
44 |
45 | try {
46 | while ($xmlReader->readUntilNodeFound(self::XML_NODE_CONFIG_ITEM)) {
47 | if ($xmlReader->getAttribute(self::XML_ATTRIBUTE_CONFIG_NAME) === self::XML_ATTRIBUTE_VALUE_ACTIVE_TABLE) {
48 | $activeSheetName = $xmlReader->readString();
49 | break;
50 | }
51 | }
52 | } catch (XMLProcessingException $exception) {
53 | // do nothing
54 | }
55 |
56 | $xmlReader->close();
57 |
58 | return $activeSheetName;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Spout/Reader/ODS/Manager/OptionsManager.php:
--------------------------------------------------------------------------------
1 | setOption(Options::SHOULD_FORMAT_DATES, false);
31 | $this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Spout/Reader/ODS/Reader.php:
--------------------------------------------------------------------------------
1 | entityFactory;
43 |
44 | $this->zip = $entityFactory->createZipArchive();
45 |
46 | if ($this->zip->open($filePath) === true) {
47 | /** @var InternalEntityFactory $entityFactory */
48 | $entityFactory = $this->entityFactory;
49 | $this->sheetIterator = $entityFactory->createSheetIterator($filePath, $this->optionsManager);
50 | } else {
51 | throw new IOException("Could not open $filePath for reading.");
52 | }
53 | }
54 |
55 | /**
56 | * Returns an iterator to iterate over sheets.
57 | *
58 | * @return SheetIterator To iterate over sheets
59 | */
60 | protected function getConcreteSheetIterator()
61 | {
62 | return $this->sheetIterator;
63 | }
64 |
65 | /**
66 | * Closes the reader. To be used after reading the file.
67 | *
68 | * @return void
69 | */
70 | protected function closeReader()
71 | {
72 | if ($this->zip !== null) {
73 | $this->zip->close();
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Spout/Reader/ODS/Sheet.php:
--------------------------------------------------------------------------------
1 | rowIterator = $rowIterator;
41 | $this->index = $sheetIndex;
42 | $this->name = $sheetName;
43 | $this->isActive = $isSheetActive;
44 | $this->isVisible = $isSheetVisible;
45 | }
46 |
47 | /**
48 | * @return \Box\Spout\Reader\ODS\RowIterator
49 | */
50 | public function getRowIterator()
51 | {
52 | return $this->rowIterator;
53 | }
54 |
55 | /**
56 | * @return int Index of the sheet, based on order in the workbook (zero-based)
57 | */
58 | public function getIndex()
59 | {
60 | return $this->index;
61 | }
62 |
63 | /**
64 | * @return string Name of the sheet
65 | */
66 | public function getName()
67 | {
68 | return $this->name;
69 | }
70 |
71 | /**
72 | * @return bool Whether the sheet was defined as active
73 | */
74 | public function isActive()
75 | {
76 | return $this->isActive;
77 | }
78 |
79 | /**
80 | * @return bool Whether the sheet is visible
81 | */
82 | public function isVisible()
83 | {
84 | return $this->isVisible;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Spout/Reader/ReaderInterface.php:
--------------------------------------------------------------------------------
1 | initialUseInternalErrorsValue = \libxml_use_internal_errors(true);
25 | }
26 |
27 | /**
28 | * Throws an XMLProcessingException if an error occured.
29 | * It also always resets the "libxml_use_internal_errors" setting back to its initial value.
30 | *
31 | * @throws \Box\Spout\Reader\Exception\XMLProcessingException
32 | * @return void
33 | */
34 | protected function resetXMLInternalErrorsSettingAndThrowIfXMLErrorOccured()
35 | {
36 | if ($this->hasXMLErrorOccured()) {
37 | $this->resetXMLInternalErrorsSetting();
38 | throw new XMLProcessingException($this->getLastXMLErrorMessage());
39 | }
40 |
41 | $this->resetXMLInternalErrorsSetting();
42 | }
43 |
44 | /**
45 | * Returns whether the a XML error has occured since the last time errors were cleared.
46 | *
47 | * @return bool TRUE if an error occured, FALSE otherwise
48 | */
49 | private function hasXMLErrorOccured()
50 | {
51 | return (\libxml_get_last_error() !== false);
52 | }
53 |
54 | /**
55 | * Returns the error message for the last XML error that occured.
56 | * @see libxml_get_last_error
57 | *
58 | * @return string|null Last XML error message or null if no error
59 | */
60 | private function getLastXMLErrorMessage()
61 | {
62 | $errorMessage = null;
63 | $error = \libxml_get_last_error();
64 |
65 | if ($error !== false) {
66 | $errorMessage = \trim($error->message);
67 | }
68 |
69 | return $errorMessage;
70 | }
71 |
72 | /**
73 | * @return void
74 | */
75 | protected function resetXMLInternalErrorsSetting()
76 | {
77 | \libxml_use_internal_errors($this->initialUseInternalErrorsValue);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Spout/Reader/XLSX/Creator/HelperFactory.php:
--------------------------------------------------------------------------------
1 | createStringsEscaper();
26 |
27 | return new CellValueFormatter($sharedStringsManager, $styleManager, $shouldFormatDates, $shouldUse1904Dates, $escaper);
28 | }
29 |
30 | /**
31 | * @return Escaper\XLSX
32 | */
33 | public function createStringsEscaper()
34 | {
35 | /* @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
36 | return new Escaper\XLSX();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Spout/Reader/XLSX/Creator/ManagerFactory.php:
--------------------------------------------------------------------------------
1 | helperFactory = $helperFactory;
34 | $this->cachingStrategyFactory = $cachingStrategyFactory;
35 | }
36 |
37 | /**
38 | * @param string $filePath Path of the XLSX file being read
39 | * @param string $tempFolder Temporary folder where the temporary files to store shared strings will be stored
40 | * @param InternalEntityFactory $entityFactory Factory to create entities
41 | * @return SharedStringsManager
42 | */
43 | public function createSharedStringsManager($filePath, $tempFolder, $entityFactory)
44 | {
45 | $workbookRelationshipsManager = $this->createWorkbookRelationshipsManager($filePath, $entityFactory);
46 |
47 | return new SharedStringsManager(
48 | $filePath,
49 | $tempFolder,
50 | $workbookRelationshipsManager,
51 | $entityFactory,
52 | $this->helperFactory,
53 | $this->cachingStrategyFactory
54 | );
55 | }
56 |
57 | /**
58 | * @param string $filePath Path of the XLSX file being read
59 | * @param InternalEntityFactory $entityFactory Factory to create entities
60 | * @return WorkbookRelationshipsManager
61 | */
62 | private function createWorkbookRelationshipsManager($filePath, $entityFactory)
63 | {
64 | if (!isset($this->cachedWorkbookRelationshipsManager)) {
65 | $this->cachedWorkbookRelationshipsManager = new WorkbookRelationshipsManager($filePath, $entityFactory);
66 | }
67 |
68 | return $this->cachedWorkbookRelationshipsManager;
69 | }
70 |
71 | /**
72 | * @param string $filePath Path of the XLSX file being read
73 | * @param \Box\Spout\Common\Manager\OptionsManagerInterface $optionsManager Reader's options manager
74 | * @param \Box\Spout\Reader\XLSX\Manager\SharedStringsManager $sharedStringsManager Manages shared strings
75 | * @param InternalEntityFactory $entityFactory Factory to create entities
76 | * @return SheetManager
77 | */
78 | public function createSheetManager($filePath, $optionsManager, $sharedStringsManager, $entityFactory)
79 | {
80 | $escaper = $this->helperFactory->createStringsEscaper();
81 |
82 | return new SheetManager($filePath, $optionsManager, $sharedStringsManager, $escaper, $entityFactory);
83 | }
84 |
85 | /**
86 | * @param string $filePath Path of the XLSX file being read
87 | * @param InternalEntityFactory $entityFactory Factory to create entities
88 | * @return StyleManager
89 | */
90 | public function createStyleManager($filePath, $entityFactory)
91 | {
92 | $workbookRelationshipsManager = $this->createWorkbookRelationshipsManager($filePath, $entityFactory);
93 |
94 | return new StyleManager($filePath, $workbookRelationshipsManager, $entityFactory);
95 | }
96 |
97 | /**
98 | * @param InternalEntityFactory $entityFactory Factory to create entities
99 | * @return RowManager
100 | */
101 | public function createRowManager($entityFactory)
102 | {
103 | return new RowManager($entityFactory);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/Spout/Reader/XLSX/Helper/CellHelper.php:
--------------------------------------------------------------------------------
1 | 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6,
16 | 'H' => 7, 'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13,
17 | 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20,
18 | 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25,
19 | ];
20 |
21 | /**
22 | * Returns the base 10 column index associated to the cell index (base 26).
23 | * Excel uses A to Z letters for column indexing, where A is the 1st column,
24 | * Z is the 26th and AA is the 27th.
25 | * The mapping is zero based, so that A1 maps to 0, B2 maps to 1, Z13 to 25 and AA4 to 26.
26 | *
27 | * @param string $cellIndex The Excel cell index ('A1', 'BC13', ...)
28 | * @throws \Box\Spout\Common\Exception\InvalidArgumentException When the given cell index is invalid
29 | * @return int
30 | */
31 | public static function getColumnIndexFromCellIndex($cellIndex)
32 | {
33 | if (!self::isValidCellIndex($cellIndex)) {
34 | throw new InvalidArgumentException('Cannot get column index from an invalid cell index.');
35 | }
36 |
37 | $columnIndex = 0;
38 |
39 | // Remove row information
40 | $columnLetters = \preg_replace('/\d/', '', $cellIndex);
41 |
42 | // strlen() is super slow too... Using isset() is way faster and not too unreadable,
43 | // since we checked before that there are between 1 and 3 letters.
44 | $columnLength = isset($columnLetters[1]) ? (isset($columnLetters[2]) ? 3 : 2) : 1;
45 |
46 | // Looping over the different letters of the column is slower than this method.
47 | // Also, not using the pow() function because it's slooooow...
48 | switch ($columnLength) {
49 | case 1:
50 | $columnIndex = (self::$columnLetterToIndexMapping[$columnLetters]);
51 | break;
52 | case 2:
53 | $firstLetterIndex = (self::$columnLetterToIndexMapping[$columnLetters[0]] + 1) * 26;
54 | $secondLetterIndex = self::$columnLetterToIndexMapping[$columnLetters[1]];
55 | $columnIndex = $firstLetterIndex + $secondLetterIndex;
56 | break;
57 | case 3:
58 | $firstLetterIndex = (self::$columnLetterToIndexMapping[$columnLetters[0]] + 1) * 676;
59 | $secondLetterIndex = (self::$columnLetterToIndexMapping[$columnLetters[1]] + 1) * 26;
60 | $thirdLetterIndex = self::$columnLetterToIndexMapping[$columnLetters[2]];
61 | $columnIndex = $firstLetterIndex + $secondLetterIndex + $thirdLetterIndex;
62 | break;
63 | }
64 |
65 | return $columnIndex;
66 | }
67 |
68 | /**
69 | * Returns whether a cell index is valid, in an Excel world.
70 | * To be valid, the cell index should start with capital letters and be followed by numbers.
71 | * There can only be 3 letters, as there can only be 16,384 rows, which is equivalent to 'XFE'.
72 | *
73 | * @param string $cellIndex The Excel cell index ('A1', 'BC13', ...)
74 | * @return bool
75 | */
76 | protected static function isValidCellIndex($cellIndex)
77 | {
78 | return (\preg_match('/^[A-Z]{1,3}\d+$/', $cellIndex) === 1);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Spout/Reader/XLSX/Manager/OptionsManager.php:
--------------------------------------------------------------------------------
1 | setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
33 | $this->setOption(Options::SHOULD_FORMAT_DATES, false);
34 | $this->setOption(Options::SHOULD_PRESERVE_EMPTY_ROWS, false);
35 | $this->setOption(Options::SHOULD_USE_1904_DATES, false);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Spout/Reader/XLSX/Manager/SharedStringsCaching/CachingStrategyInterface.php:
--------------------------------------------------------------------------------
1 | inMemoryCache = new \SplFixedArray($sharedStringsUniqueCount);
27 | $this->isCacheClosed = false;
28 | }
29 |
30 | /**
31 | * Adds the given string to the cache.
32 | *
33 | * @param string $sharedString The string to be added to the cache
34 | * @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
35 | * @return void
36 | */
37 | public function addStringForIndex($sharedString, $sharedStringIndex)
38 | {
39 | if (!$this->isCacheClosed) {
40 | $this->inMemoryCache->offsetSet($sharedStringIndex, $sharedString);
41 | }
42 | }
43 |
44 | /**
45 | * Closes the cache after the last shared string was added.
46 | * This prevents any additional string from being added to the cache.
47 | *
48 | * @return void
49 | */
50 | public function closeCache()
51 | {
52 | $this->isCacheClosed = true;
53 | }
54 |
55 | /**
56 | * Returns the string located at the given index from the cache.
57 | *
58 | * @param int $sharedStringIndex Index of the shared string in the sharedStrings.xml file
59 | * @throws \Box\Spout\Reader\Exception\SharedStringNotFoundException If no shared string found for the given index
60 | * @return string The shared string at the given index
61 | */
62 | public function getStringAtIndex($sharedStringIndex)
63 | {
64 | try {
65 | return $this->inMemoryCache->offsetGet($sharedStringIndex);
66 | } catch (\RuntimeException $e) {
67 | throw new SharedStringNotFoundException("Shared string not found for index: $sharedStringIndex");
68 | }
69 | }
70 |
71 | /**
72 | * Destroys the cache, freeing memory and removing any created artifacts
73 | *
74 | * @return void
75 | */
76 | public function clearCache()
77 | {
78 | unset($this->inMemoryCache);
79 | $this->isCacheClosed = false;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/Spout/Reader/XLSX/Reader.php:
--------------------------------------------------------------------------------
1 | managerFactory = $managerFactory;
46 | }
47 |
48 | /**
49 | * @param string $tempFolder Temporary folder where the temporary files will be created
50 | * @return Reader
51 | */
52 | public function setTempFolder($tempFolder)
53 | {
54 | $this->optionsManager->setOption(Options::TEMP_FOLDER, $tempFolder);
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * Returns whether stream wrappers are supported
61 | *
62 | * @return bool
63 | */
64 | protected function doesSupportStreamWrapper()
65 | {
66 | return false;
67 | }
68 |
69 | /**
70 | * Opens the file at the given file path to make it ready to be read.
71 | * It also parses the sharedStrings.xml file to get all the shared strings available in memory
72 | * and fetches all the available sheets.
73 | *
74 | * @param string $filePath Path of the file to be read
75 | * @throws \Box\Spout\Common\Exception\IOException If the file at the given path or its content cannot be read
76 | * @throws \Box\Spout\Reader\Exception\NoSheetsFoundException If there are no sheets in the file
77 | * @return void
78 | */
79 | protected function openReader($filePath)
80 | {
81 | /** @var InternalEntityFactory $entityFactory */
82 | $entityFactory = $this->entityFactory;
83 |
84 | $this->zip = $entityFactory->createZipArchive();
85 |
86 | if ($this->zip->open($filePath) === true) {
87 | $tempFolder = $this->optionsManager->getOption(Options::TEMP_FOLDER);
88 | $this->sharedStringsManager = $this->managerFactory->createSharedStringsManager($filePath, $tempFolder, $entityFactory);
89 |
90 | if ($this->sharedStringsManager->hasSharedStrings()) {
91 | // Extracts all the strings from the sheets for easy access in the future
92 | $this->sharedStringsManager->extractSharedStrings();
93 | }
94 |
95 | $this->sheetIterator = $entityFactory->createSheetIterator(
96 | $filePath,
97 | $this->optionsManager,
98 | $this->sharedStringsManager
99 | );
100 | } else {
101 | throw new IOException("Could not open $filePath for reading.");
102 | }
103 | }
104 |
105 | /**
106 | * Returns an iterator to iterate over sheets.
107 | *
108 | * @return SheetIterator To iterate over sheets
109 | */
110 | protected function getConcreteSheetIterator()
111 | {
112 | return $this->sheetIterator;
113 | }
114 |
115 | /**
116 | * Closes the reader. To be used after reading the file.
117 | *
118 | * @return void
119 | */
120 | protected function closeReader()
121 | {
122 | if ($this->zip !== null) {
123 | $this->zip->close();
124 | }
125 |
126 | if ($this->sharedStringsManager !== null) {
127 | $this->sharedStringsManager->cleanup();
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/Spout/Reader/XLSX/Sheet.php:
--------------------------------------------------------------------------------
1 | rowIterator = $rowIterator;
38 | $this->index = $sheetIndex;
39 | $this->name = $sheetName;
40 | $this->isActive = $isSheetActive;
41 | $this->isVisible = $isSheetVisible;
42 | }
43 |
44 | /**
45 | * @return \Box\Spout\Reader\XLSX\RowIterator
46 | */
47 | public function getRowIterator()
48 | {
49 | return $this->rowIterator;
50 | }
51 |
52 | /**
53 | * @return int Index of the sheet, based on order in the workbook (zero-based)
54 | */
55 | public function getIndex()
56 | {
57 | return $this->index;
58 | }
59 |
60 | /**
61 | * @return string Name of the sheet
62 | */
63 | public function getName()
64 | {
65 | return $this->name;
66 | }
67 |
68 | /**
69 | * @return bool Whether the sheet was defined as active
70 | */
71 | public function isActive()
72 | {
73 | return $this->isActive;
74 | }
75 |
76 | /**
77 | * @return bool Whether the sheet is visible
78 | */
79 | public function isVisible()
80 | {
81 | return $this->isVisible;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Spout/Reader/XLSX/SheetIterator.php:
--------------------------------------------------------------------------------
1 | sheets = $sheetManager->getSheets();
29 |
30 | if (\count($this->sheets) === 0) {
31 | throw new NoSheetsFoundException('The file must contain at least one sheet.');
32 | }
33 | }
34 |
35 | /**
36 | * Rewind the Iterator to the first element
37 | * @see http://php.net/manual/en/iterator.rewind.php
38 | *
39 | * @return void
40 | */
41 | public function rewind()
42 | {
43 | $this->currentSheetIndex = 0;
44 | }
45 |
46 | /**
47 | * Checks if current position is valid
48 | * @see http://php.net/manual/en/iterator.valid.php
49 | *
50 | * @return bool
51 | */
52 | public function valid()
53 | {
54 | return ($this->currentSheetIndex < \count($this->sheets));
55 | }
56 |
57 | /**
58 | * Move forward to next element
59 | * @see http://php.net/manual/en/iterator.next.php
60 | *
61 | * @return void
62 | */
63 | public function next()
64 | {
65 | // Using isset here because it is way faster than array_key_exists...
66 | if (isset($this->sheets[$this->currentSheetIndex])) {
67 | $currentSheet = $this->sheets[$this->currentSheetIndex];
68 | $currentSheet->getRowIterator()->end();
69 |
70 | $this->currentSheetIndex++;
71 | }
72 | }
73 |
74 | /**
75 | * Return the current element
76 | * @see http://php.net/manual/en/iterator.current.php
77 | *
78 | * @return \Box\Spout\Reader\XLSX\Sheet
79 | */
80 | public function current()
81 | {
82 | return $this->sheets[$this->currentSheetIndex];
83 | }
84 |
85 | /**
86 | * Return the key of the current element
87 | * @see http://php.net/manual/en/iterator.key.php
88 | *
89 | * @return int
90 | */
91 | public function key()
92 | {
93 | return $this->currentSheetIndex + 1;
94 | }
95 |
96 | /**
97 | * Cleans up what was created to iterate over the object.
98 | *
99 | * @return void
100 | */
101 | public function end()
102 | {
103 | // make sure we are not leaking memory in case the iteration stopped before the end
104 | foreach ($this->sheets as $sheet) {
105 | $sheet->getRowIterator()->end();
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/Spout/Writer/CSV/Manager/OptionsManager.php:
--------------------------------------------------------------------------------
1 | setOption(Options::FIELD_DELIMITER, ',');
32 | $this->setOption(Options::FIELD_ENCLOSURE, '"');
33 | $this->setOption(Options::SHOULD_ADD_BOM, true);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Spout/Writer/CSV/Writer.php:
--------------------------------------------------------------------------------
1 | optionsManager->setOption(Options::FIELD_DELIMITER, $fieldDelimiter);
35 |
36 | return $this;
37 | }
38 |
39 | /**
40 | * Sets the field enclosure for the CSV
41 | *
42 | * @param string $fieldEnclosure Character that enclose fields
43 | * @return Writer
44 | */
45 | public function setFieldEnclosure($fieldEnclosure)
46 | {
47 | $this->optionsManager->setOption(Options::FIELD_ENCLOSURE, $fieldEnclosure);
48 |
49 | return $this;
50 | }
51 |
52 | /**
53 | * Set if a BOM has to be added to the file
54 | *
55 | * @param bool $shouldAddBOM
56 | * @return Writer
57 | */
58 | public function setShouldAddBOM($shouldAddBOM)
59 | {
60 | $this->optionsManager->setOption(Options::SHOULD_ADD_BOM, (bool) $shouldAddBOM);
61 |
62 | return $this;
63 | }
64 |
65 | /**
66 | * Opens the CSV streamer and makes it ready to accept data.
67 | *
68 | * @return void
69 | */
70 | protected function openWriter()
71 | {
72 | if ($this->optionsManager->getOption(Options::SHOULD_ADD_BOM)) {
73 | // Adds UTF-8 BOM for Unicode compatibility
74 | $this->globalFunctionsHelper->fputs($this->filePointer, EncodingHelper::BOM_UTF8);
75 | }
76 | }
77 |
78 | /**
79 | * Adds a row to the currently opened writer.
80 | *
81 | * @param Row $row The row containing cells and styles
82 | * @throws IOException If unable to write data
83 | * @return void
84 | */
85 | protected function addRowToWriter(Row $row)
86 | {
87 | $fieldDelimiter = $this->optionsManager->getOption(Options::FIELD_DELIMITER);
88 | $fieldEnclosure = $this->optionsManager->getOption(Options::FIELD_ENCLOSURE);
89 |
90 | $wasWriteSuccessful = $this->globalFunctionsHelper->fputcsv($this->filePointer, $row->getCells(), $fieldDelimiter, $fieldEnclosure);
91 | if ($wasWriteSuccessful === false) {
92 | throw new IOException('Unable to write data');
93 | }
94 |
95 | $this->lastWrittenRowIndex++;
96 | if ($this->lastWrittenRowIndex % self::FLUSH_THRESHOLD === 0) {
97 | $this->globalFunctionsHelper->fflush($this->filePointer);
98 | }
99 | }
100 |
101 | /**
102 | * Closes the CSV streamer, preventing any additional writing.
103 | * If set, sets the headers and redirects output to the browser.
104 | *
105 | * @return void
106 | */
107 | protected function closeWriter()
108 | {
109 | $this->lastWrittenRowIndex = 0;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Creator/InternalEntityFactory.php:
--------------------------------------------------------------------------------
1 | border = new Border();
22 | }
23 |
24 | /**
25 | * @param string $color Border A RGB color code
26 | * @param string $width Border width @see BorderPart::allowedWidths
27 | * @param string $style Border style @see BorderPart::allowedStyles
28 | * @return BorderBuilder
29 | */
30 | public function setBorderTop($color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
31 | {
32 | $this->border->addPart(new BorderPart(Border::TOP, $color, $width, $style));
33 |
34 | return $this;
35 | }
36 |
37 | /**
38 | * @param string $color Border A RGB color code
39 | * @param string $width Border width @see BorderPart::allowedWidths
40 | * @param string $style Border style @see BorderPart::allowedStyles
41 | * @return BorderBuilder
42 | */
43 | public function setBorderRight($color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
44 | {
45 | $this->border->addPart(new BorderPart(Border::RIGHT, $color, $width, $style));
46 |
47 | return $this;
48 | }
49 |
50 | /**
51 | * @param string $color Border A RGB color code
52 | * @param string $width Border width @see BorderPart::allowedWidths
53 | * @param string $style Border style @see BorderPart::allowedStyles
54 | * @return BorderBuilder
55 | */
56 | public function setBorderBottom($color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
57 | {
58 | $this->border->addPart(new BorderPart(Border::BOTTOM, $color, $width, $style));
59 |
60 | return $this;
61 | }
62 |
63 | /**
64 | * @param string $color Border A RGB color code
65 | * @param string $width Border width @see BorderPart::allowedWidths
66 | * @param string $style Border style @see BorderPart::allowedStyles
67 | * @return BorderBuilder
68 | */
69 | public function setBorderLeft($color = Color::BLACK, $width = Border::WIDTH_MEDIUM, $style = Border::STYLE_SOLID)
70 | {
71 | $this->border->addPart(new BorderPart(Border::LEFT, $color, $width, $style));
72 |
73 | return $this;
74 | }
75 |
76 | /**
77 | * @return Border
78 | */
79 | public function build()
80 | {
81 | return $this->border;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Creator/Style/StyleBuilder.php:
--------------------------------------------------------------------------------
1 | style = new Style();
25 | }
26 |
27 | /**
28 | * Makes the font bold.
29 | *
30 | * @return StyleBuilder
31 | */
32 | public function setFontBold()
33 | {
34 | $this->style->setFontBold();
35 |
36 | return $this;
37 | }
38 |
39 | /**
40 | * Makes the font italic.
41 | *
42 | * @return StyleBuilder
43 | */
44 | public function setFontItalic()
45 | {
46 | $this->style->setFontItalic();
47 |
48 | return $this;
49 | }
50 |
51 | /**
52 | * Makes the font underlined.
53 | *
54 | * @return StyleBuilder
55 | */
56 | public function setFontUnderline()
57 | {
58 | $this->style->setFontUnderline();
59 |
60 | return $this;
61 | }
62 |
63 | /**
64 | * Makes the font struck through.
65 | *
66 | * @return StyleBuilder
67 | */
68 | public function setFontStrikethrough()
69 | {
70 | $this->style->setFontStrikethrough();
71 |
72 | return $this;
73 | }
74 |
75 | /**
76 | * Sets the font size.
77 | *
78 | * @param int $fontSize Font size, in pixels
79 | * @return StyleBuilder
80 | */
81 | public function setFontSize($fontSize)
82 | {
83 | $this->style->setFontSize($fontSize);
84 |
85 | return $this;
86 | }
87 |
88 | /**
89 | * Sets the font color.
90 | *
91 | * @param string $fontColor ARGB color (@see Color)
92 | * @return StyleBuilder
93 | */
94 | public function setFontColor($fontColor)
95 | {
96 | $this->style->setFontColor($fontColor);
97 |
98 | return $this;
99 | }
100 |
101 | /**
102 | * Sets the font name.
103 | *
104 | * @param string $fontName Name of the font to use
105 | * @return StyleBuilder
106 | */
107 | public function setFontName($fontName)
108 | {
109 | $this->style->setFontName($fontName);
110 |
111 | return $this;
112 | }
113 |
114 | /**
115 | * Makes the text wrap in the cell if requested
116 | *
117 | * @param bool $shouldWrap Should the text be wrapped
118 | * @return StyleBuilder
119 | */
120 | public function setShouldWrapText($shouldWrap = true)
121 | {
122 | $this->style->setShouldWrapText($shouldWrap);
123 |
124 | return $this;
125 | }
126 |
127 | /**
128 | * Sets the cell alignment.
129 | *
130 | * @param string $cellAlignment The cell alignment
131 | *
132 | * @throws InvalidArgumentException If the given cell alignment is not valid
133 | * @return StyleBuilder
134 | */
135 | public function setCellAlignment($cellAlignment)
136 | {
137 | if (!CellAlignment::isValid($cellAlignment)) {
138 | throw new InvalidArgumentException('Invalid cell alignment value');
139 | }
140 |
141 | $this->style->setCellAlignment($cellAlignment);
142 |
143 | return $this;
144 | }
145 |
146 | /**
147 | * Set a border
148 | *
149 | * @param Border $border
150 | * @return $this
151 | */
152 | public function setBorder(Border $border)
153 | {
154 | $this->style->setBorder($border);
155 |
156 | return $this;
157 | }
158 |
159 | /**
160 | * Sets a background color
161 | *
162 | * @param string $color ARGB color (@see Color)
163 | * @return StyleBuilder
164 | */
165 | public function setBackgroundColor($color)
166 | {
167 | $this->style->setBackgroundColor($color);
168 |
169 | return $this;
170 | }
171 |
172 | /**
173 | * Sets a format
174 | *
175 | * @param string $format Format
176 | * @return StyleBuilder
177 | * @api
178 | */
179 | public function setFormat($format)
180 | {
181 | $this->style->setFormat($format);
182 |
183 | return $this;
184 | }
185 |
186 | /**
187 | * Returns the configured style. The style is cached and can be reused.
188 | *
189 | * @return Style
190 | */
191 | public function build()
192 | {
193 | return $this->style;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Creator/WriterEntityFactory.php:
--------------------------------------------------------------------------------
1 | index = $sheetIndex;
38 | $this->associatedWorkbookId = $associatedWorkbookId;
39 |
40 | $this->sheetManager = $sheetManager;
41 | $this->sheetManager->markWorkbookIdAsUsed($associatedWorkbookId);
42 |
43 | $this->setName(self::DEFAULT_SHEET_NAME_PREFIX . ($sheetIndex + 1));
44 | $this->setIsVisible(true);
45 | }
46 |
47 | /**
48 | * @return int Index of the sheet, based on order in the workbook (zero-based)
49 | */
50 | public function getIndex()
51 | {
52 | return $this->index;
53 | }
54 |
55 | /**
56 | * @return string
57 | */
58 | public function getAssociatedWorkbookId()
59 | {
60 | return $this->associatedWorkbookId;
61 | }
62 |
63 | /**
64 | * @return string Name of the sheet
65 | */
66 | public function getName()
67 | {
68 | return $this->name;
69 | }
70 |
71 | /**
72 | * Sets the name of the sheet. Note that Excel has some restrictions on the name:
73 | * - it should not be blank
74 | * - it should not exceed 31 characters
75 | * - it should not contain these characters: \ / ? * : [ or ]
76 | * - it should be unique
77 | *
78 | * @param string $name Name of the sheet
79 | * @throws \Box\Spout\Writer\Exception\InvalidSheetNameException If the sheet's name is invalid.
80 | * @return Sheet
81 | */
82 | public function setName($name)
83 | {
84 | $this->sheetManager->throwIfNameIsInvalid($name, $this);
85 |
86 | $this->name = $name;
87 |
88 | $this->sheetManager->markSheetNameAsUsed($this);
89 |
90 | return $this;
91 | }
92 |
93 | /**
94 | * @return bool isVisible Visibility of the sheet
95 | */
96 | public function isVisible()
97 | {
98 | return $this->isVisible;
99 | }
100 |
101 | /**
102 | * @param bool $isVisible Visibility of the sheet
103 | * @return Sheet
104 | */
105 | public function setIsVisible($isVisible)
106 | {
107 | $this->isVisible = $isVisible;
108 |
109 | return $this;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Entity/Workbook.php:
--------------------------------------------------------------------------------
1 | internalId = \uniqid();
23 | }
24 |
25 | /**
26 | * @return Worksheet[]
27 | */
28 | public function getWorksheets()
29 | {
30 | return $this->worksheets;
31 | }
32 |
33 | /**
34 | * @param Worksheet[] $worksheets
35 | */
36 | public function setWorksheets($worksheets)
37 | {
38 | $this->worksheets = $worksheets;
39 | }
40 |
41 | /**
42 | * @return string
43 | */
44 | public function getInternalId()
45 | {
46 | return $this->internalId;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Entity/Worksheet.php:
--------------------------------------------------------------------------------
1 | filePath = $worksheetFilePath;
35 | $this->filePointer = null;
36 | $this->externalSheet = $externalSheet;
37 | $this->maxNumColumns = 0;
38 | $this->lastWrittenRowIndex = 0;
39 | }
40 |
41 | /**
42 | * @return string
43 | */
44 | public function getFilePath()
45 | {
46 | return $this->filePath;
47 | }
48 |
49 | /**
50 | * @return resource
51 | */
52 | public function getFilePointer()
53 | {
54 | return $this->filePointer;
55 | }
56 |
57 | /**
58 | * @param resource $filePointer
59 | */
60 | public function setFilePointer($filePointer)
61 | {
62 | $this->filePointer = $filePointer;
63 | }
64 |
65 | /**
66 | * @return Sheet
67 | */
68 | public function getExternalSheet()
69 | {
70 | return $this->externalSheet;
71 | }
72 |
73 | /**
74 | * @return int
75 | */
76 | public function getMaxNumColumns()
77 | {
78 | return $this->maxNumColumns;
79 | }
80 |
81 | /**
82 | * @param int $maxNumColumns
83 | */
84 | public function setMaxNumColumns($maxNumColumns)
85 | {
86 | $this->maxNumColumns = $maxNumColumns;
87 | }
88 |
89 | /**
90 | * @return int
91 | */
92 | public function getLastWrittenRowIndex()
93 | {
94 | return $this->lastWrittenRowIndex;
95 | }
96 |
97 | /**
98 | * @param int $lastWrittenRowIndex
99 | */
100 | public function setLastWrittenRowIndex($lastWrittenRowIndex)
101 | {
102 | $this->lastWrittenRowIndex = $lastWrittenRowIndex;
103 | }
104 |
105 | /**
106 | * @return int The ID of the worksheet
107 | */
108 | public function getId()
109 | {
110 | // sheet index is zero-based, while ID is 1-based
111 | return $this->externalSheet->getIndex() + 1;
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Helper/CellHelper.php:
--------------------------------------------------------------------------------
1 | column letters */
12 | private static $columnIndexToColumnLettersCache = [];
13 |
14 | /**
15 | * Returns the column letters (base 26) associated to the base 10 column index.
16 | * Excel uses A to Z letters for column indexing, where A is the 1st column,
17 | * Z is the 26th and AA is the 27th.
18 | * The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26.
19 | *
20 | * @param int $columnIndexZeroBased The Excel column index (0, 42, ...)
21 | *
22 | * @return string The associated cell index ('A', 'BC', ...)
23 | */
24 | public static function getColumnLettersFromColumnIndex($columnIndexZeroBased)
25 | {
26 | $originalColumnIndex = $columnIndexZeroBased;
27 |
28 | // Using isset here because it is way faster than array_key_exists...
29 | if (!isset(self::$columnIndexToColumnLettersCache[$originalColumnIndex])) {
30 | $columnLetters = '';
31 | $capitalAAsciiValue = \ord('A');
32 |
33 | do {
34 | $modulus = $columnIndexZeroBased % 26;
35 | $columnLetters = \chr($capitalAAsciiValue + $modulus) . $columnLetters;
36 |
37 | // substracting 1 because it's zero-based
38 | $columnIndexZeroBased = (int) ($columnIndexZeroBased / 26) - 1;
39 | } while ($columnIndexZeroBased >= 0);
40 |
41 | self::$columnIndexToColumnLettersCache[$originalColumnIndex] = $columnLetters;
42 | }
43 |
44 | return self::$columnIndexToColumnLettersCache[$originalColumnIndex];
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Helper/FileSystemWithRootFolderHelperInterface.php:
--------------------------------------------------------------------------------
1 | styleMerger = $styleMerger;
22 | }
23 |
24 | /**
25 | * Merges a Style into a cell's Style.
26 | *
27 | * @param Cell $cell
28 | * @param Style $style
29 | * @return void
30 | */
31 | public function applyStyle(Cell $cell, Style $style)
32 | {
33 | $mergedStyle = $this->styleMerger->merge($cell->getStyle(), $style);
34 | $cell->setStyle($mergedStyle);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Manager/RegisteredStyle.php:
--------------------------------------------------------------------------------
1 | style = $style;
26 | $this->isMatchingRowStyle = $isMatchingRowStyle;
27 | }
28 |
29 | public function getStyle() : Style
30 | {
31 | return $this->style;
32 | }
33 |
34 | public function isMatchingRowStyle() : bool
35 | {
36 | return $this->isMatchingRowStyle;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Manager/RowManager.php:
--------------------------------------------------------------------------------
1 | getCells() as $cell) {
19 | if (!$cell->isEmpty()) {
20 | return false;
21 | }
22 | }
23 |
24 | return true;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Manager/Style/PossiblyUpdatedStyle.php:
--------------------------------------------------------------------------------
1 | style = $style;
20 | $this->isUpdated = $isUpdated;
21 | }
22 |
23 | public function getStyle() : Style
24 | {
25 | return $this->style;
26 | }
27 |
28 | public function isUpdated() : bool
29 | {
30 | return $this->isUpdated;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Manager/Style/StyleManager.php:
--------------------------------------------------------------------------------
1 | styleRegistry = $styleRegistry;
23 | }
24 |
25 | /**
26 | * Returns the default style
27 | *
28 | * @return Style Default style
29 | */
30 | protected function getDefaultStyle()
31 | {
32 | // By construction, the default style has ID 0
33 | return $this->styleRegistry->getRegisteredStyles()[0];
34 | }
35 |
36 | /**
37 | * Registers the given style as a used style.
38 | * Duplicate styles won't be registered more than once.
39 | *
40 | * @param Style $style The style to be registered
41 | * @return Style The registered style, updated with an internal ID.
42 | */
43 | public function registerStyle($style)
44 | {
45 | return $this->styleRegistry->registerStyle($style);
46 | }
47 |
48 | /**
49 | * Apply additional styles if the given row needs it.
50 | * Typically, set "wrap text" if a cell contains a new line.
51 | *
52 | * @param Cell $cell
53 | * @return PossiblyUpdatedStyle The eventually updated style
54 | */
55 | public function applyExtraStylesIfNeeded(Cell $cell) : PossiblyUpdatedStyle
56 | {
57 | return $this->applyWrapTextIfCellContainsNewLine($cell);
58 | }
59 |
60 | /**
61 | * Set the "wrap text" option if a cell of the given row contains a new line.
62 | *
63 | * @NOTE: There is a bug on the Mac version of Excel (2011 and below) where new lines
64 | * are ignored even when the "wrap text" option is set. This only occurs with
65 | * inline strings (shared strings do work fine).
66 | * A workaround would be to encode "\n" as "_x000D_" but it does not work
67 | * on the Windows version of Excel...
68 | *
69 | * @param Cell $cell The cell the style should be applied to
70 | * @return PossiblyUpdatedStyle The eventually updated style
71 | */
72 | protected function applyWrapTextIfCellContainsNewLine(Cell $cell) : PossiblyUpdatedStyle
73 | {
74 | $cellStyle = $cell->getStyle();
75 |
76 | // if the "wrap text" option is already set, no-op
77 | if (!$cellStyle->hasSetWrapText() && $cell->isString() && \strpos($cell->getValue(), "\n") !== false) {
78 | $cellStyle->setShouldWrapText();
79 |
80 | return new PossiblyUpdatedStyle($cellStyle, true);
81 | }
82 |
83 | return new PossiblyUpdatedStyle($cellStyle, false);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Manager/Style/StyleManagerInterface.php:
--------------------------------------------------------------------------------
1 | mergeFontStyles($mergedStyle, $style, $baseStyle);
30 | $this->mergeOtherFontProperties($mergedStyle, $style, $baseStyle);
31 | $this->mergeCellProperties($mergedStyle, $style, $baseStyle);
32 |
33 | return $mergedStyle;
34 | }
35 |
36 | /**
37 | * @param Style $styleToUpdate (passed as reference)
38 | * @param Style $style
39 | * @param Style $baseStyle
40 | * @return void
41 | */
42 | private function mergeFontStyles(Style $styleToUpdate, Style $style, Style $baseStyle)
43 | {
44 | if (!$style->hasSetFontBold() && $baseStyle->isFontBold()) {
45 | $styleToUpdate->setFontBold();
46 | }
47 | if (!$style->hasSetFontItalic() && $baseStyle->isFontItalic()) {
48 | $styleToUpdate->setFontItalic();
49 | }
50 | if (!$style->hasSetFontUnderline() && $baseStyle->isFontUnderline()) {
51 | $styleToUpdate->setFontUnderline();
52 | }
53 | if (!$style->hasSetFontStrikethrough() && $baseStyle->isFontStrikethrough()) {
54 | $styleToUpdate->setFontStrikethrough();
55 | }
56 | }
57 |
58 | /**
59 | * @param Style $styleToUpdate Style to update (passed as reference)
60 | * @param Style $style
61 | * @param Style $baseStyle
62 | * @return void
63 | */
64 | private function mergeOtherFontProperties(Style $styleToUpdate, Style $style, Style $baseStyle)
65 | {
66 | if (!$style->hasSetFontSize() && $baseStyle->getFontSize() !== Style::DEFAULT_FONT_SIZE) {
67 | $styleToUpdate->setFontSize($baseStyle->getFontSize());
68 | }
69 | if (!$style->hasSetFontColor() && $baseStyle->getFontColor() !== Style::DEFAULT_FONT_COLOR) {
70 | $styleToUpdate->setFontColor($baseStyle->getFontColor());
71 | }
72 | if (!$style->hasSetFontName() && $baseStyle->getFontName() !== Style::DEFAULT_FONT_NAME) {
73 | $styleToUpdate->setFontName($baseStyle->getFontName());
74 | }
75 | }
76 |
77 | /**
78 | * @param Style $styleToUpdate Style to update (passed as reference)
79 | * @param Style $style
80 | * @param Style $baseStyle
81 | * @return void
82 | */
83 | private function mergeCellProperties(Style $styleToUpdate, Style $style, Style $baseStyle)
84 | {
85 | if (!$style->hasSetWrapText() && $baseStyle->shouldWrapText()) {
86 | $styleToUpdate->setShouldWrapText();
87 | }
88 | if (!$style->hasSetCellAlignment() && $baseStyle->shouldApplyCellAlignment()) {
89 | $styleToUpdate->setCellAlignment($baseStyle->getCellAlignment());
90 | }
91 | if ($style->getBorder() === null && $baseStyle->shouldApplyBorder()) {
92 | $styleToUpdate->setBorder($baseStyle->getBorder());
93 | }
94 | if ($style->getFormat() === null && $baseStyle->shouldApplyFormat()) {
95 | $styleToUpdate->setFormat($baseStyle->getFormat());
96 | }
97 | if (!$style->shouldApplyBackgroundColor() && $baseStyle->shouldApplyBackgroundColor()) {
98 | $styleToUpdate->setBackgroundColor($baseStyle->getBackgroundColor());
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Manager/Style/StyleRegistry.php:
--------------------------------------------------------------------------------
1 | [STYLE_ID] mapping table, keeping track of the registered styles */
14 | protected $serializedStyleToStyleIdMappingTable = [];
15 |
16 | /** @var array [STYLE_ID] => [STYLE] mapping table, keeping track of the registered styles */
17 | protected $styleIdToStyleMappingTable = [];
18 |
19 | /**
20 | * @param Style $defaultStyle
21 | */
22 | public function __construct(Style $defaultStyle)
23 | {
24 | // This ensures that the default style is the first one to be registered
25 | $this->registerStyle($defaultStyle);
26 | }
27 |
28 | /**
29 | * Registers the given style as a used style.
30 | * Duplicate styles won't be registered more than once.
31 | *
32 | * @param Style $style The style to be registered
33 | * @return Style The registered style, updated with an internal ID.
34 | */
35 | public function registerStyle(Style $style)
36 | {
37 | $serializedStyle = $this->serialize($style);
38 |
39 | if (!$this->hasSerializedStyleAlreadyBeenRegistered($serializedStyle)) {
40 | $nextStyleId = \count($this->serializedStyleToStyleIdMappingTable);
41 | $style->markAsRegistered($nextStyleId);
42 |
43 | $this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
44 | $this->styleIdToStyleMappingTable[$nextStyleId] = $style;
45 | }
46 |
47 | return $this->getStyleFromSerializedStyle($serializedStyle);
48 | }
49 |
50 | /**
51 | * Returns whether the serialized style has already been registered.
52 | *
53 | * @param string $serializedStyle The serialized style
54 | * @return bool
55 | */
56 | protected function hasSerializedStyleAlreadyBeenRegistered(string $serializedStyle)
57 | {
58 | // Using isset here because it is way faster than array_key_exists...
59 | return isset($this->serializedStyleToStyleIdMappingTable[$serializedStyle]);
60 | }
61 |
62 | /**
63 | * Returns the registered style associated to the given serialization.
64 | *
65 | * @param string $serializedStyle The serialized style from which the actual style should be fetched from
66 | * @return Style
67 | */
68 | protected function getStyleFromSerializedStyle($serializedStyle)
69 | {
70 | $styleId = $this->serializedStyleToStyleIdMappingTable[$serializedStyle];
71 |
72 | return $this->styleIdToStyleMappingTable[$styleId];
73 | }
74 |
75 | /**
76 | * @return Style[] List of registered styles
77 | */
78 | public function getRegisteredStyles()
79 | {
80 | return \array_values($this->styleIdToStyleMappingTable);
81 | }
82 |
83 | /**
84 | * @param int $styleId
85 | * @return Style
86 | */
87 | public function getStyleFromStyleId($styleId)
88 | {
89 | return $this->styleIdToStyleMappingTable[$styleId];
90 | }
91 |
92 | /**
93 | * Serializes the style for future comparison with other styles.
94 | * The ID is excluded from the comparison, as we only care about
95 | * actual style properties.
96 | *
97 | * @param Style $style
98 | * @return string The serialized style
99 | */
100 | public function serialize(Style $style)
101 | {
102 | // In order to be able to properly compare style, set static ID value and reset registration
103 | $currentId = $style->getId();
104 | $style->unmarkAsRegistered();
105 |
106 | $serializedStyle = \serialize($style);
107 |
108 | $style->markAsRegistered($currentId);
109 |
110 | return $serializedStyle;
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/Spout/Writer/Common/Manager/WorkbookManagerInterface.php:
--------------------------------------------------------------------------------
1 | getOption(Options::TEMP_FOLDER);
27 | $zipHelper = $this->createZipHelper($entityFactory);
28 |
29 | return new FileSystemHelper($tempFolder, $zipHelper);
30 | }
31 |
32 | /**
33 | * @param InternalEntityFactory $entityFactory
34 | * @return ZipHelper
35 | */
36 | private function createZipHelper($entityFactory)
37 | {
38 | return new ZipHelper($entityFactory);
39 | }
40 |
41 | /**
42 | * @return Escaper\ODS
43 | */
44 | public function createStringsEscaper()
45 | {
46 | return new Escaper\ODS();
47 | }
48 |
49 | /**
50 | * @return StringHelper
51 | */
52 | public function createStringHelper()
53 | {
54 | return new StringHelper();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Spout/Writer/ODS/Creator/ManagerFactory.php:
--------------------------------------------------------------------------------
1 | entityFactory = $entityFactory;
35 | $this->helperFactory = $helperFactory;
36 | }
37 |
38 | /**
39 | * @param OptionsManagerInterface $optionsManager
40 | * @return WorkbookManager
41 | */
42 | public function createWorkbookManager(OptionsManagerInterface $optionsManager)
43 | {
44 | $workbook = $this->entityFactory->createWorkbook();
45 |
46 | $fileSystemHelper = $this->helperFactory->createSpecificFileSystemHelper($optionsManager, $this->entityFactory);
47 | $fileSystemHelper->createBaseFilesAndFolders();
48 |
49 | $styleMerger = $this->createStyleMerger();
50 | $styleManager = $this->createStyleManager($optionsManager);
51 | $worksheetManager = $this->createWorksheetManager($styleManager, $styleMerger);
52 |
53 | return new WorkbookManager(
54 | $workbook,
55 | $optionsManager,
56 | $worksheetManager,
57 | $styleManager,
58 | $styleMerger,
59 | $fileSystemHelper,
60 | $this->entityFactory,
61 | $this
62 | );
63 | }
64 |
65 | /**
66 | * @param StyleManager $styleManager
67 | * @param StyleMerger $styleMerger
68 | * @return WorksheetManager
69 | */
70 | private function createWorksheetManager(StyleManager $styleManager, StyleMerger $styleMerger)
71 | {
72 | $stringsEscaper = $this->helperFactory->createStringsEscaper();
73 | $stringsHelper = $this->helperFactory->createStringHelper();
74 |
75 | return new WorksheetManager($styleManager, $styleMerger, $stringsEscaper, $stringsHelper);
76 | }
77 |
78 | /**
79 | * @return SheetManager
80 | */
81 | public function createSheetManager()
82 | {
83 | $stringHelper = $this->helperFactory->createStringHelper();
84 |
85 | return new SheetManager($stringHelper);
86 | }
87 |
88 | /**
89 | * @param OptionsManagerInterface $optionsManager
90 | * @return StyleManager
91 | */
92 | private function createStyleManager(OptionsManagerInterface $optionsManager)
93 | {
94 | $styleRegistry = $this->createStyleRegistry($optionsManager);
95 |
96 | return new StyleManager($styleRegistry);
97 | }
98 |
99 | /**
100 | * @param OptionsManagerInterface $optionsManager
101 | * @return StyleRegistry
102 | */
103 | private function createStyleRegistry(OptionsManagerInterface $optionsManager)
104 | {
105 | $defaultRowStyle = $optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
106 |
107 | return new StyleRegistry($defaultRowStyle);
108 | }
109 |
110 | /**
111 | * @return StyleMerger
112 | */
113 | private function createStyleMerger()
114 | {
115 | return new StyleMerger();
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Spout/Writer/ODS/Helper/BorderHelper.php:
--------------------------------------------------------------------------------
1 |
21 | */
22 | class BorderHelper
23 | {
24 | /**
25 | * Width mappings
26 | *
27 | * @var array
28 | */
29 | protected static $widthMap = [
30 | Border::WIDTH_THIN => '0.75pt',
31 | Border::WIDTH_MEDIUM => '1.75pt',
32 | Border::WIDTH_THICK => '2.5pt',
33 | ];
34 |
35 | /**
36 | * Style mapping
37 | *
38 | * @var array
39 | */
40 | protected static $styleMap = [
41 | Border::STYLE_SOLID => 'solid',
42 | Border::STYLE_DASHED => 'dashed',
43 | Border::STYLE_DOTTED => 'dotted',
44 | Border::STYLE_DOUBLE => 'double',
45 | ];
46 |
47 | /**
48 | * @param BorderPart $borderPart
49 | * @return string
50 | */
51 | public static function serializeBorderPart(BorderPart $borderPart)
52 | {
53 | $definition = 'fo:border-%s="%s"';
54 |
55 | if ($borderPart->getStyle() === Border::STYLE_NONE) {
56 | $borderPartDefinition = \sprintf($definition, $borderPart->getName(), 'none');
57 | } else {
58 | $attributes = [
59 | self::$widthMap[$borderPart->getWidth()],
60 | self::$styleMap[$borderPart->getStyle()],
61 | '#' . $borderPart->getColor(),
62 | ];
63 | $borderPartDefinition = \sprintf($definition, $borderPart->getName(), \implode(' ', $attributes));
64 | }
65 |
66 | return $borderPartDefinition;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Spout/Writer/ODS/Manager/OptionsManager.php:
--------------------------------------------------------------------------------
1 | styleBuilder = $styleBuilder;
25 | parent::__construct();
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | protected function getSupportedOptions()
32 | {
33 | return [
34 | Options::TEMP_FOLDER,
35 | Options::DEFAULT_ROW_STYLE,
36 | Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY,
37 | ];
38 | }
39 |
40 | /**
41 | * {@inheritdoc}
42 | */
43 | protected function setDefaultOptions()
44 | {
45 | $this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
46 | $this->setOption(Options::DEFAULT_ROW_STYLE, $this->styleBuilder->build());
47 | $this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Spout/Writer/ODS/Manager/Style/StyleRegistry.php:
--------------------------------------------------------------------------------
1 | [] Map whose keys contain all the fonts used */
14 | protected $usedFontsSet = [];
15 |
16 | /**
17 | * Registers the given style as a used style.
18 | * Duplicate styles won't be registered more than once.
19 | *
20 | * @param Style $style The style to be registered
21 | * @return Style The registered style, updated with an internal ID.
22 | */
23 | public function registerStyle(Style $style)
24 | {
25 | if ($style->isRegistered()) {
26 | return $style;
27 | }
28 |
29 | $registeredStyle = parent::registerStyle($style);
30 | $this->usedFontsSet[$style->getFontName()] = true;
31 |
32 | return $registeredStyle;
33 | }
34 |
35 | /**
36 | * @return string[] List of used fonts name
37 | */
38 | public function getUsedFonts()
39 | {
40 | return \array_keys($this->usedFontsSet);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Spout/Writer/ODS/Manager/WorkbookManager.php:
--------------------------------------------------------------------------------
1 | fileSystemHelper->getSheetsContentTempFolder();
46 |
47 | return $sheetsContentTempFolder . '/sheet' . $sheet->getIndex() . '.xml';
48 | }
49 |
50 | /**
51 | * Writes all the necessary files to disk and zip them together to create the final file.
52 | *
53 | * @param resource $finalFilePointer Pointer to the spreadsheet that will be created
54 | * @return void
55 | */
56 | protected function writeAllFilesToDiskAndZipThem($finalFilePointer)
57 | {
58 | $worksheets = $this->getWorksheets();
59 | $numWorksheets = \count($worksheets);
60 |
61 | $this->fileSystemHelper
62 | ->createContentFile($this->worksheetManager, $this->styleManager, $worksheets)
63 | ->deleteWorksheetTempFolder()
64 | ->createStylesFile($this->styleManager, $numWorksheets)
65 | ->zipRootFolderAndCopyToStream($finalFilePointer);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Spout/Writer/ODS/Writer.php:
--------------------------------------------------------------------------------
1 | throwIfWriterAlreadyOpened('Writer must be configured before opening it.');
28 |
29 | $this->optionsManager->setOption(Options::TEMP_FOLDER, $tempFolder);
30 |
31 | return $this;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Spout/Writer/WriterInterface.php:
--------------------------------------------------------------------------------
1 | getOption(Options::TEMP_FOLDER);
27 | $zipHelper = $this->createZipHelper($entityFactory);
28 | $escaper = $this->createStringsEscaper();
29 |
30 | return new FileSystemHelper($tempFolder, $zipHelper, $escaper);
31 | }
32 |
33 | /**
34 | * @param InternalEntityFactory $entityFactory
35 | * @return ZipHelper
36 | */
37 | private function createZipHelper(InternalEntityFactory $entityFactory)
38 | {
39 | return new ZipHelper($entityFactory);
40 | }
41 |
42 | /**
43 | * @return Escaper\XLSX
44 | */
45 | public function createStringsEscaper()
46 | {
47 | return new Escaper\XLSX();
48 | }
49 |
50 | /**
51 | * @return StringHelper
52 | */
53 | public function createStringHelper()
54 | {
55 | return new StringHelper();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Spout/Writer/XLSX/Creator/ManagerFactory.php:
--------------------------------------------------------------------------------
1 | entityFactory = $entityFactory;
37 | $this->helperFactory = $helperFactory;
38 | }
39 |
40 | /**
41 | * @param OptionsManagerInterface $optionsManager
42 | * @return WorkbookManager
43 | */
44 | public function createWorkbookManager(OptionsManagerInterface $optionsManager)
45 | {
46 | $workbook = $this->entityFactory->createWorkbook();
47 |
48 | $fileSystemHelper = $this->helperFactory->createSpecificFileSystemHelper($optionsManager, $this->entityFactory);
49 | $fileSystemHelper->createBaseFilesAndFolders();
50 |
51 | $xlFolder = $fileSystemHelper->getXlFolder();
52 | $sharedStringsManager = $this->createSharedStringsManager($xlFolder);
53 |
54 | $styleMerger = $this->createStyleMerger();
55 | $styleManager = $this->createStyleManager($optionsManager);
56 | $worksheetManager = $this->createWorksheetManager($optionsManager, $styleManager, $styleMerger, $sharedStringsManager);
57 |
58 | return new WorkbookManager(
59 | $workbook,
60 | $optionsManager,
61 | $worksheetManager,
62 | $styleManager,
63 | $styleMerger,
64 | $fileSystemHelper,
65 | $this->entityFactory,
66 | $this
67 | );
68 | }
69 |
70 | /**
71 | * @param OptionsManagerInterface $optionsManager
72 | * @param StyleManager $styleManager
73 | * @param StyleMerger $styleMerger
74 | * @param SharedStringsManager $sharedStringsManager
75 | * @return WorksheetManager
76 | */
77 | private function createWorksheetManager(
78 | OptionsManagerInterface $optionsManager,
79 | StyleManager $styleManager,
80 | StyleMerger $styleMerger,
81 | SharedStringsManager $sharedStringsManager
82 | ) {
83 | $rowManager = $this->createRowManager();
84 | $stringsEscaper = $this->helperFactory->createStringsEscaper();
85 | $stringsHelper = $this->helperFactory->createStringHelper();
86 |
87 | return new WorksheetManager(
88 | $optionsManager,
89 | $rowManager,
90 | $styleManager,
91 | $styleMerger,
92 | $sharedStringsManager,
93 | $stringsEscaper,
94 | $stringsHelper
95 | );
96 | }
97 |
98 | /**
99 | * @return SheetManager
100 | */
101 | public function createSheetManager()
102 | {
103 | $stringHelper = $this->helperFactory->createStringHelper();
104 |
105 | return new SheetManager($stringHelper);
106 | }
107 |
108 | /**
109 | * @return RowManager
110 | */
111 | public function createRowManager()
112 | {
113 | return new RowManager();
114 | }
115 |
116 | /**
117 | * @param OptionsManagerInterface $optionsManager
118 | * @return StyleManager
119 | */
120 | private function createStyleManager(OptionsManagerInterface $optionsManager)
121 | {
122 | $styleRegistry = $this->createStyleRegistry($optionsManager);
123 |
124 | return new StyleManager($styleRegistry);
125 | }
126 |
127 | /**
128 | * @param OptionsManagerInterface $optionsManager
129 | * @return StyleRegistry
130 | */
131 | private function createStyleRegistry(OptionsManagerInterface $optionsManager)
132 | {
133 | $defaultRowStyle = $optionsManager->getOption(Options::DEFAULT_ROW_STYLE);
134 |
135 | return new StyleRegistry($defaultRowStyle);
136 | }
137 |
138 | /**
139 | * @return StyleMerger
140 | */
141 | private function createStyleMerger()
142 | {
143 | return new StyleMerger();
144 | }
145 |
146 | /**
147 | * @param string $xlFolder Path to the "xl" folder
148 | * @return SharedStringsManager
149 | */
150 | private function createSharedStringsManager($xlFolder)
151 | {
152 | $stringEscaper = $this->helperFactory->createStringsEscaper();
153 |
154 | return new SharedStringsManager($xlFolder, $stringEscaper);
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/Spout/Writer/XLSX/Helper/BorderHelper.php:
--------------------------------------------------------------------------------
1 | [
12 | Border::WIDTH_THIN => 'thin',
13 | Border::WIDTH_MEDIUM => 'medium',
14 | Border::WIDTH_THICK => 'thick',
15 | ],
16 | Border::STYLE_DOTTED => [
17 | Border::WIDTH_THIN => 'dotted',
18 | Border::WIDTH_MEDIUM => 'dotted',
19 | Border::WIDTH_THICK => 'dotted',
20 | ],
21 | Border::STYLE_DASHED => [
22 | Border::WIDTH_THIN => 'dashed',
23 | Border::WIDTH_MEDIUM => 'mediumDashed',
24 | Border::WIDTH_THICK => 'mediumDashed',
25 | ],
26 | Border::STYLE_DOUBLE => [
27 | Border::WIDTH_THIN => 'double',
28 | Border::WIDTH_MEDIUM => 'double',
29 | Border::WIDTH_THICK => 'double',
30 | ],
31 | Border::STYLE_NONE => [
32 | Border::WIDTH_THIN => 'none',
33 | Border::WIDTH_MEDIUM => 'none',
34 | Border::WIDTH_THICK => 'none',
35 | ],
36 | ];
37 |
38 | /**
39 | * @param BorderPart $borderPart
40 | * @return string
41 | */
42 | public static function serializeBorderPart(BorderPart $borderPart)
43 | {
44 | $borderStyle = self::getBorderStyle($borderPart);
45 |
46 | $colorEl = $borderPart->getColor() ? \sprintf('', $borderPart->getColor()) : '';
47 | $partEl = \sprintf(
48 | '<%s style="%s">%s%s>',
49 | $borderPart->getName(),
50 | $borderStyle,
51 | $colorEl,
52 | $borderPart->getName()
53 | );
54 |
55 | return $partEl . PHP_EOL;
56 | }
57 |
58 | /**
59 | * Get the style definition from the style map
60 | *
61 | * @param BorderPart $borderPart
62 | * @return string
63 | */
64 | protected static function getBorderStyle(BorderPart $borderPart)
65 | {
66 | return self::$xlsxStyleMap[$borderPart->getStyle()][$borderPart->getWidth()];
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Spout/Writer/XLSX/Manager/OptionsManager.php:
--------------------------------------------------------------------------------
1 | styleBuilder = $styleBuilder;
29 | parent::__construct();
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | protected function getSupportedOptions()
36 | {
37 | return [
38 | Options::TEMP_FOLDER,
39 | Options::DEFAULT_ROW_STYLE,
40 | Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY,
41 | Options::SHOULD_USE_INLINE_STRINGS,
42 | ];
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | protected function setDefaultOptions()
49 | {
50 | $defaultRowStyle = $this->styleBuilder
51 | ->setFontSize(self::DEFAULT_FONT_SIZE)
52 | ->setFontName(self::DEFAULT_FONT_NAME)
53 | ->build();
54 |
55 | $this->setOption(Options::TEMP_FOLDER, \sys_get_temp_dir());
56 | $this->setOption(Options::DEFAULT_ROW_STYLE, $defaultRowStyle);
57 | $this->setOption(Options::SHOULD_CREATE_NEW_SHEETS_AUTOMATICALLY, true);
58 | $this->setOption(Options::SHOULD_USE_INLINE_STRINGS, true);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Spout/Writer/XLSX/Manager/SharedStringsManager.php:
--------------------------------------------------------------------------------
1 |
18 | sharedStringsFilePointer = \fopen($sharedStringsFilePath, 'w');
44 |
45 | $this->throwIfSharedStringsFilePointerIsNotAvailable();
46 |
47 | // the headers is split into different parts so that we can fseek and put in the correct count and uniqueCount later
48 | $header = self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER . ' ' . self::DEFAULT_STRINGS_COUNT_PART . '>';
49 | \fwrite($this->sharedStringsFilePointer, $header);
50 |
51 | $this->stringsEscaper = $stringsEscaper;
52 | }
53 |
54 | /**
55 | * Checks if the book has been created. Throws an exception if not created yet.
56 | *
57 | * @throws \Box\Spout\Common\Exception\IOException If the sheet data file cannot be opened for writing
58 | * @return void
59 | */
60 | protected function throwIfSharedStringsFilePointerIsNotAvailable()
61 | {
62 | if (!is_resource($this->sharedStringsFilePointer)) {
63 | throw new IOException('Unable to open shared strings file for writing.');
64 | }
65 | }
66 |
67 | /**
68 | * Writes the given string into the sharedStrings.xml file.
69 | * Starting and ending whitespaces are preserved.
70 | *
71 | * @param string $string
72 | * @return int ID of the written shared string
73 | */
74 | public function writeString($string)
75 | {
76 | \fwrite($this->sharedStringsFilePointer, '' . $this->stringsEscaper->escape($string) . '');
77 | $this->numSharedStrings++;
78 |
79 | // Shared string ID is zero-based
80 | return ($this->numSharedStrings - 1);
81 | }
82 |
83 | /**
84 | * Finishes writing the data in the sharedStrings.xml file and closes the file.
85 | *
86 | * @return void
87 | */
88 | public function close()
89 | {
90 | if (!\is_resource($this->sharedStringsFilePointer)) {
91 | return;
92 | }
93 |
94 | \fwrite($this->sharedStringsFilePointer, '');
95 |
96 | // Replace the default strings count with the actual number of shared strings in the file header
97 | $firstPartHeaderLength = \strlen(self::SHARED_STRINGS_XML_FILE_FIRST_PART_HEADER);
98 | $defaultStringsCountPartLength = \strlen(self::DEFAULT_STRINGS_COUNT_PART);
99 |
100 | // Adding 1 to take into account the space between the last xml attribute and "count"
101 | \fseek($this->sharedStringsFilePointer, $firstPartHeaderLength + 1);
102 | \fwrite($this->sharedStringsFilePointer, \sprintf("%-{$defaultStringsCountPartLength}s", 'count="' . $this->numSharedStrings . '" uniqueCount="' . $this->numSharedStrings . '"'));
103 |
104 | \fclose($this->sharedStringsFilePointer);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Spout/Writer/XLSX/Manager/WorkbookManager.php:
--------------------------------------------------------------------------------
1 | fileSystemHelper->getXlWorksheetsFolder();
46 |
47 | return $worksheetFilesFolder . '/' . \strtolower($sheet->getName()) . '.xml';
48 | }
49 |
50 | /**
51 | * Closes custom objects that are still opened
52 | *
53 | * @return void
54 | */
55 | protected function closeRemainingObjects()
56 | {
57 | $this->worksheetManager->getSharedStringsManager()->close();
58 | }
59 |
60 | /**
61 | * Writes all the necessary files to disk and zip them together to create the final file.
62 | *
63 | * @param resource $finalFilePointer Pointer to the spreadsheet that will be created
64 | * @return void
65 | */
66 | protected function writeAllFilesToDiskAndZipThem($finalFilePointer)
67 | {
68 | $worksheets = $this->getWorksheets();
69 |
70 | $this->fileSystemHelper
71 | ->createContentTypesFile($worksheets)
72 | ->createWorkbookFile($worksheets)
73 | ->createWorkbookRelsFile($worksheets)
74 | ->createStylesFile($this->styleManager)
75 | ->zipRootFolderAndCopyToStream($finalFilePointer);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Spout/Writer/XLSX/Writer.php:
--------------------------------------------------------------------------------
1 | throwIfWriterAlreadyOpened('Writer must be configured before opening it.');
28 |
29 | $this->optionsManager->setOption(Options::TEMP_FOLDER, $tempFolder);
30 |
31 | return $this;
32 | }
33 |
34 | /**
35 | * Use inline string to be more memory efficient. If set to false, it will use shared strings.
36 | * This must be set before opening the writer.
37 | *
38 | * @param bool $shouldUseInlineStrings Whether inline or shared strings should be used
39 | * @throws \Box\Spout\Writer\Exception\WriterAlreadyOpenedException If the writer was already opened
40 | * @return Writer
41 | */
42 | public function setShouldUseInlineStrings($shouldUseInlineStrings)
43 | {
44 | $this->throwIfWriterAlreadyOpened('Writer must be configured before opening it.');
45 |
46 | $this->optionsManager->setOption(Options::SHOULD_USE_INLINE_STRINGS, $shouldUseInlineStrings);
47 |
48 | return $this;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------