Merge branch 'PHPOffice:master' into Table-for-Xlsx
This commit is contained in:
commit
1d99dc8d76
|
|
@ -26,6 +26,20 @@ $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
|
||||||
|
|
||||||
// add code that show the issue here...
|
// add code that show the issue here...
|
||||||
```
|
```
|
||||||
|
If this is an issue with reading a specific spreadsheet file, then it may be appropriate to provide a sample file that demonstrates the problem; but please keep it as small as possible, and sanitize any confidential information before uploading.
|
||||||
|
|
||||||
|
### What features do you think are causing the issue
|
||||||
|
|
||||||
|
- [ ] Reader
|
||||||
|
- [ ] Writer
|
||||||
|
- [ ] Styles
|
||||||
|
- [ ] Data Validations
|
||||||
|
- [ ] Formula Calulations
|
||||||
|
- [ ] Charts
|
||||||
|
- [ ] AutoFilter
|
||||||
|
- [ ] Form Elements
|
||||||
|
|
||||||
|
### Does an issue affect all spreadsheet file formats? If not, which formats are affected?
|
||||||
|
|
||||||
|
|
||||||
### Which versions of PhpSpreadsheet and PHP are affected?
|
### Which versions of PhpSpreadsheet and PHP are affected?
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,21 @@ This is:
|
||||||
```
|
```
|
||||||
- [ ] a bugfix
|
- [ ] a bugfix
|
||||||
- [ ] a new feature
|
- [ ] a new feature
|
||||||
|
- [ ] refactoring
|
||||||
|
- [ ] additional unit tests
|
||||||
```
|
```
|
||||||
|
|
||||||
Checklist:
|
Checklist:
|
||||||
|
|
||||||
- [ ] Changes are covered by unit tests
|
- [ ] Changes are covered by unit tests
|
||||||
|
- [ ] Changes are covered by existing unit tests
|
||||||
|
- [ ] New unit tests have been added
|
||||||
- [ ] Code style is respected
|
- [ ] Code style is respected
|
||||||
- [ ] Commit message explains **why** the change is made (see https://github.com/erlang/otp/wiki/Writing-good-commit-messages)
|
- [ ] Commit message explains **why** the change is made (see https://github.com/erlang/otp/wiki/Writing-good-commit-messages)
|
||||||
- [ ] CHANGELOG.md contains a short summary of the change
|
- [ ] CHANGELOG.md contains a short summary of the change
|
||||||
- [ ] Documentation is updated as necessary
|
- [ ] Documentation is updated as necessary
|
||||||
|
|
||||||
### Why this change is needed?
|
### Why this change is needed?
|
||||||
|
|
||||||
|
Provide an explanation of why this change is needed, with links to any Issues (if appropriate).
|
||||||
|
If this is a bugfix or a new feature, and there are no existing Issues, then please also create an issue that will make it easier to track progress with this PR.
|
||||||
|
|
|
||||||
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Ods Writer support for Freeze Pane [Issue #2013](https://github.com/PHPOffice/PhpSpreadsheet/issues/2013) [PR #2755](https://github.com/PHPOffice/PhpSpreadsheet/pull/2755)
|
||||||
|
- Ods Writer support for setting column width/row height (including the use of AutoSize) [Issue #2346](https://github.com/PHPOffice/PhpSpreadsheet/issues/2346) [PR #2753](https://github.com/PHPOffice/PhpSpreadsheet/pull/2753)
|
||||||
- Introduced CellAddress, CellRange, RowRange and ColumnRange value objects that can be used as an alternative to a string value (e.g. `'C5'`, `'B2:D4'`, `'2:2'` or `'B:C'`) in appropriate contexts.
|
- Introduced CellAddress, CellRange, RowRange and ColumnRange value objects that can be used as an alternative to a string value (e.g. `'C5'`, `'B2:D4'`, `'2:2'` or `'B:C'`) in appropriate contexts.
|
||||||
- Implementation of the FILTER(), SORT(), SORTBY() and UNIQUE() Lookup/Reference (array) functions.
|
- Implementation of the FILTER(), SORT(), SORTBY() and UNIQUE() Lookup/Reference (array) functions.
|
||||||
- Implementation of the ISREF() Information function.
|
- Implementation of the ISREF() Information function.
|
||||||
|
|
@ -18,11 +20,13 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
This functionality is locale-aware, using the server's locale settings to identify the thousands and decimal separators.
|
This functionality is locale-aware, using the server's locale settings to identify the thousands and decimal separators.
|
||||||
|
|
||||||
- Support for two cell anchor drawing of images. [#2532](https://github.com/PHPOffice/PhpSpreadsheet/pull/2532)
|
- Support for two cell anchor drawing of images. [#2532](https://github.com/PHPOffice/PhpSpreadsheet/pull/2532) [#2674](https://github.com/PHPOffice/PhpSpreadsheet/pull/2674)
|
||||||
- Limited support for Xls Reader to handle Conditional Formatting:
|
- Limited support for Xls Reader to handle Conditional Formatting:
|
||||||
|
|
||||||
Ranges and Rules are read, but style is currently limited to font size, weight and color; and to fill style and color.
|
Ranges and Rules are read, but style is currently limited to font size, weight and color; and to fill style and color.
|
||||||
|
|
||||||
|
- Add ability to suppress Mac line ending check for CSV [#2623](https://github.com/PHPOffice/PhpSpreadsheet/pull/2623)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Gnumeric Reader now loads number formatting for cells.
|
- Gnumeric Reader now loads number formatting for cells.
|
||||||
|
|
@ -66,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Make allowance for the AutoFilter dropdown icon in the first row of an Autofilter range when using Autosize columns. [Issue #2413](https://github.com/PHPOffice/PhpSpreadsheet/issues/2413) [PR #2754](https://github.com/PHPOffice/PhpSpreadsheet/pull/2754)
|
||||||
- Support for "chained" ranges (e.g. `A5:C10:C20:F1`) in the Calculation Engine; and also support for using named ranges with the Range operator (e.g. `NamedRange1:NamedRange2`) [Issue #2730](https://github.com/PHPOffice/PhpSpreadsheet/issues/2730) [PR #2746](https://github.com/PHPOffice/PhpSpreadsheet/pull/2746)
|
- Support for "chained" ranges (e.g. `A5:C10:C20:F1`) in the Calculation Engine; and also support for using named ranges with the Range operator (e.g. `NamedRange1:NamedRange2`) [Issue #2730](https://github.com/PHPOffice/PhpSpreadsheet/issues/2730) [PR #2746](https://github.com/PHPOffice/PhpSpreadsheet/pull/2746)
|
||||||
- Update Conditional Formatting ranges and rule conditions when inserting/deleting rows/columns [Issue #2678](https://github.com/PHPOffice/PhpSpreadsheet/issues/2678) [PR #2689](https://github.com/PHPOffice/PhpSpreadsheet/pull/2689)
|
- Update Conditional Formatting ranges and rule conditions when inserting/deleting rows/columns [Issue #2678](https://github.com/PHPOffice/PhpSpreadsheet/issues/2678) [PR #2689](https://github.com/PHPOffice/PhpSpreadsheet/pull/2689)
|
||||||
- Allow `INDIRECT()` to accept row/column ranges as well as cell ranges [PR #2687](https://github.com/PHPOffice/PhpSpreadsheet/pull/2687)
|
- Allow `INDIRECT()` to accept row/column ranges as well as cell ranges [PR #2687](https://github.com/PHPOffice/PhpSpreadsheet/pull/2687)
|
||||||
|
|
@ -73,16 +78,20 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
- Fix bug in Conditional Formatting in the Xls Writer that resulted in a broken file when there were multiple conditional ranges in a worksheet.
|
- Fix bug in Conditional Formatting in the Xls Writer that resulted in a broken file when there were multiple conditional ranges in a worksheet.
|
||||||
- Fix Conditional Formatting in the Xls Writer to work with rules that contain string literals, cell references and formulae.
|
- Fix Conditional Formatting in the Xls Writer to work with rules that contain string literals, cell references and formulae.
|
||||||
- Fix for setting Active Sheet to the first loaded worksheet when bookViews element isn't defined [Issue #2666](https://github.com/PHPOffice/PhpSpreadsheet/issues/2666) [PR #2669](https://github.com/PHPOffice/PhpSpreadsheet/pull/2669)
|
- Fix for setting Active Sheet to the first loaded worksheet when bookViews element isn't defined [Issue #2666](https://github.com/PHPOffice/PhpSpreadsheet/issues/2666) [PR #2669](https://github.com/PHPOffice/PhpSpreadsheet/pull/2669)
|
||||||
- Fixed behaviour of XLSX font style vertical align settings.
|
- Fixed behaviour of XLSX font style vertical align settings [PR #2619](https://github.com/PHPOffice/PhpSpreadsheet/pull/2619)
|
||||||
- Resolved formula translations to handle separators (row and column) for array functions as well as for function argument separators; and cleanly handle nesting levels.
|
- Resolved formula translations to handle separators (row and column) for array functions as well as for function argument separators; and cleanly handle nesting levels.
|
||||||
|
|
||||||
Note that this method is used when translating Excel functions between `en_us` and other locale languages, as well as when converting formulae between different spreadsheet formats (e.g. Ods to Excel).
|
Note that this method is used when translating Excel functions between `en_us` and other locale languages, as well as when converting formulae between different spreadsheet formats (e.g. Ods to Excel).
|
||||||
|
|
||||||
Nor is this a perfect solution, as there may still be issues when function calls have array arguments that themselves contain function calls; but it's still better than the current logic.
|
Nor is this a perfect solution, as there may still be issues when function calls have array arguments that themselves contain function calls; but it's still better than the current logic.
|
||||||
- Fix for escaping double quotes within a formula [Issue #1971](https://github.com/PHPOffice/PhpSpreadsheet/issues/1971) [PR #2651](https://github.com/PHPOffice/PhpSpreadsheet/pull/2651)
|
- Fix for escaping double quotes within a formula [Issue #1971](https://github.com/PHPOffice/PhpSpreadsheet/issues/1971) [PR #2651](https://github.com/PHPOffice/PhpSpreadsheet/pull/2651)
|
||||||
|
- Change open mode for output from `wb+` to `wb` [Issue #2372](https://github.com/PHPOffice/PhpSpreadsheet/issues/2372) [PR #2657](https://github.com/PHPOffice/PhpSpreadsheet/pull/2657)
|
||||||
|
- Use color palette if supplied [Issue #2499](https://github.com/PHPOffice/PhpSpreadsheet/issues/2499) [PR #2595](https://github.com/PHPOffice/PhpSpreadsheet/pull/2595)
|
||||||
|
- Xls reader treat drawing offsets as int rather than float [PR #2648](https://github.com/PHPOffice/PhpSpreadsheet/pull/2648)
|
||||||
|
- Handle booleans in conditional styles properly [PR #2654](https://github.com/PHPOffice/PhpSpreadsheet/pull/2654)
|
||||||
|
- Fix for reading files in the root directory of a ZipFile, which should not be prefixed by relative paths ("./") as dirname($filename) does by default.
|
||||||
- Fix invalid style of cells in empty columns with columnDimensions and rows with rowDimensions in added external sheet. [PR #2739](https://github.com/PHPOffice/PhpSpreadsheet/pull/2739)
|
- Fix invalid style of cells in empty columns with columnDimensions and rows with rowDimensions in added external sheet. [PR #2739](https://github.com/PHPOffice/PhpSpreadsheet/pull/2739)
|
||||||
|
|
||||||
|
|
||||||
## 1.22.0 - 2022-02-18
|
## 1.22.0 - 2022-02-18
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,8 @@ semi-colon (`;`) are used as separators instead of a comma, although
|
||||||
other symbols can be used. Because CSV is a text-only format, it doesn't
|
other symbols can be used. Because CSV is a text-only format, it doesn't
|
||||||
support any data formatting options.
|
support any data formatting options.
|
||||||
|
|
||||||
"CSV" is not a single, well-defined format (although see RFC 4180 for
|
"CSV" is not a single, well-defined format (although see
|
||||||
|
[RFC 4180](https://www.rfc-editor.org/rfc/rfc4180.html) for
|
||||||
one definition that is commonly used). Rather, in practice the term
|
one definition that is commonly used). Rather, in practice the term
|
||||||
"CSV" refers to any file that:
|
"CSV" refers to any file that:
|
||||||
|
|
||||||
|
|
@ -117,5 +118,5 @@ Wide Web Consortium (W3C). However, in 2000, HTML also became an
|
||||||
international standard (ISO/IEC 15445:2000). HTML 4.01 was published in
|
international standard (ISO/IEC 15445:2000). HTML 4.01 was published in
|
||||||
late 1999, with further errata published through 2001. In 2004
|
late 1999, with further errata published through 2001. In 2004
|
||||||
development began on HTML5 in the Web Hypertext Application Technology
|
development began on HTML5 in the Web Hypertext Application Technology
|
||||||
Working Group (WHATWG), which became a joint deliverable with the W3C in
|
Working Group (WHATWG), which became a joint deliverable with the W3C in 2008.
|
||||||
2008.
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,22 @@ practise), it will reject the Xls loader that it would normally use for
|
||||||
a .xls file; and test the file using the other loaders until it finds
|
a .xls file; and test the file using the other loaders until it finds
|
||||||
the appropriate loader, and then use that to read the file.
|
the appropriate loader, and then use that to read the file.
|
||||||
|
|
||||||
|
If you know that this is an `xls` file, but don't know whether it is a
|
||||||
|
genuine BIFF-format Excel or Html markup with an xls extension, you can
|
||||||
|
limit the loader to check only those two possibilities by passing in an
|
||||||
|
array of Readers to test against.
|
||||||
|
|
||||||
|
```php
|
||||||
|
$inputFileName = './sampleData/example1.xls';
|
||||||
|
$testAgainstFormats = [
|
||||||
|
\PhpOffice\PhpSpreadsheet\IOFactory::READER_XLS,
|
||||||
|
\PhpOffice\PhpSpreadsheet\IOFactory::READER_HTML,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Load $inputFileName to a Spreadsheet Object **/
|
||||||
|
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($inputFileName, 0, $testAgainstFormats);
|
||||||
|
```
|
||||||
|
|
||||||
While easy to implement in your code, and you don't need to worry about
|
While easy to implement in your code, and you don't need to worry about
|
||||||
the file type; this isn't the most efficient method to load a file; and
|
the file type; this isn't the most efficient method to load a file; and
|
||||||
it lacks the flexibility to configure the loader in any way before
|
it lacks the flexibility to configure the loader in any way before
|
||||||
|
|
@ -118,6 +134,34 @@ $spreadsheet = $reader->load($inputFileName);
|
||||||
See `samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php`
|
See `samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php`
|
||||||
for a working example of this code.
|
for a working example of this code.
|
||||||
|
|
||||||
|
As with the IOFactory `load()` method, you can also pass an array of formats
|
||||||
|
for the `identify()` method to check against if you know that it will only
|
||||||
|
be in a subset of the possible formats that PhpSpreadsheet supports.
|
||||||
|
|
||||||
|
```php
|
||||||
|
$inputFileName = './sampleData/example1.xls';
|
||||||
|
$testAgainstFormats = [
|
||||||
|
\PhpOffice\PhpSpreadsheet\IOFactory::READER_XLS,
|
||||||
|
\PhpOffice\PhpSpreadsheet\IOFactory::READER_HTML,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Identify the type of $inputFileName **/
|
||||||
|
$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($inputFileName, $testAgainstFormats);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use this to confirm that a file is what it claims to be:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$inputFileName = './sampleData/example1.xls';
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** Verify that $inputFileName really is an Xls file **/
|
||||||
|
$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($inputFileName, [\PhpOffice\PhpSpreadsheet\IOFactory::READER_XLS]);
|
||||||
|
} catch (\PhpOffice\PhpSpreadsheet\Reader\Exception $e) {
|
||||||
|
// File isn't actually an Xls file, even though it has an xls extension
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Spreadsheet Reader Options
|
## Spreadsheet Reader Options
|
||||||
|
|
||||||
Once you have created a reader object for the workbook that you want to
|
Once you have created a reader object for the workbook that you want to
|
||||||
|
|
@ -146,7 +190,7 @@ $spreadsheet = $reader->load($inputFileName);
|
||||||
See `samples/Reader/05_Simple_file_reader_using_the_read_data_only_option.php`
|
See `samples/Reader/05_Simple_file_reader_using_the_read_data_only_option.php`
|
||||||
for a working example of this code.
|
for a working example of this code.
|
||||||
|
|
||||||
It is important to note that Workbooks (and PhpSpreadsheet) store dates
|
It is important to note that most Workbooks (and PhpSpreadsheet) store dates
|
||||||
and times as simple numeric values: they can only be distinguished from
|
and times as simple numeric values: they can only be distinguished from
|
||||||
other numeric values by the format mask that is applied to that cell.
|
other numeric values by the format mask that is applied to that cell.
|
||||||
When setting read data only to true, PhpSpreadsheet doesn't read the
|
When setting read data only to true, PhpSpreadsheet doesn't read the
|
||||||
|
|
@ -162,8 +206,8 @@ Reading Only Data from a Spreadsheet File applies to Readers:
|
||||||
|
|
||||||
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
||||||
----------|:---:|--------|:---:|--------------|:---:|
|
----------|:---:|--------|:---:|--------------|:---:|
|
||||||
Xlsx | YES | Xls | YES | Xml | YES |
|
Xlsx | YES | Xls | YES | Xml | YES |
|
||||||
Ods | YES | SYLK | NO | Gnumeric | YES |
|
Ods | YES | SYLK | NO | Gnumeric | YES |
|
||||||
CSV | NO | HTML | NO
|
CSV | NO | HTML | NO
|
||||||
|
|
||||||
### Reading Only Named WorkSheets from a File
|
### Reading Only Named WorkSheets from a File
|
||||||
|
|
@ -233,8 +277,8 @@ Reading Only Named WorkSheets from a File applies to Readers:
|
||||||
|
|
||||||
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
||||||
----------|:---:|--------|:---:|--------------|:---:|
|
----------|:---:|--------|:---:|--------------|:---:|
|
||||||
Xlsx | YES | Xls | YES | Xml | YES |
|
Xlsx | YES | Xls | YES | Xml | YES |
|
||||||
Ods | YES | SYLK | NO | Gnumeric | YES |
|
Ods | YES | SYLK | NO | Gnumeric | YES |
|
||||||
CSV | NO | HTML | NO
|
CSV | NO | HTML | NO
|
||||||
|
|
||||||
### Reading Only Specific Columns and Rows from a File (Read Filters)
|
### Reading Only Specific Columns and Rows from a File (Read Filters)
|
||||||
|
|
@ -381,7 +425,7 @@ Using Read Filters applies to:
|
||||||
|
|
||||||
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
||||||
----------|:---:|--------|:---:|--------------|:---:|
|
----------|:---:|--------|:---:|--------------|:---:|
|
||||||
Xlsx | YES | Xls | YES | Xml | YES |
|
Xlsx | YES | Xls | YES | Xml | YES |
|
||||||
Ods | YES | SYLK | NO | Gnumeric | YES |
|
Ods | YES | SYLK | NO | Gnumeric | YES |
|
||||||
CSV | YES | HTML | NO | | |
|
CSV | YES | HTML | NO | | |
|
||||||
|
|
||||||
|
|
@ -439,7 +483,7 @@ Combining Multiple Files into a Single Spreadsheet Object applies to:
|
||||||
|
|
||||||
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
||||||
----------|:---:|--------|:---:|--------------|:---:|
|
----------|:---:|--------|:---:|--------------|:---:|
|
||||||
Xlsx | NO | Xls | NO | Xml | NO |
|
Xlsx | NO | Xls | NO | Xml | NO |
|
||||||
Ods | NO | SYLK | YES | Gnumeric | NO |
|
Ods | NO | SYLK | YES | Gnumeric | NO |
|
||||||
CSV | YES | HTML | NO
|
CSV | YES | HTML | NO
|
||||||
|
|
||||||
|
|
@ -516,7 +560,7 @@ Splitting a single loaded file across multiple worksheets applies to:
|
||||||
|
|
||||||
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
||||||
----------|:---:|--------|:---:|--------------|:---:|
|
----------|:---:|--------|:---:|--------------|:---:|
|
||||||
Xlsx | NO | Xls | NO | Xml | NO |
|
Xlsx | NO | Xls | NO | Xml | NO |
|
||||||
Ods | NO | SYLK | NO | Gnumeric | NO |
|
Ods | NO | SYLK | NO | Gnumeric | NO |
|
||||||
CSV | YES | HTML | NO
|
CSV | YES | HTML | NO
|
||||||
|
|
||||||
|
|
@ -556,7 +600,7 @@ Setting CSV delimiter applies to:
|
||||||
|
|
||||||
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
||||||
----------|:---:|--------|:---:|--------------|:---:|
|
----------|:---:|--------|:---:|--------------|:---:|
|
||||||
Xlsx | NO | Xls | NO | Xml | NO |
|
Xlsx | NO | Xls | NO | Xml | NO |
|
||||||
Ods | NO | SYLK | NO | Gnumeric | NO |
|
Ods | NO | SYLK | NO | Gnumeric | NO |
|
||||||
CSV | YES | HTML | NO
|
CSV | YES | HTML | NO
|
||||||
|
|
||||||
|
|
@ -594,7 +638,7 @@ Applies to:
|
||||||
|
|
||||||
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
||||||
----------|:---:|--------|:---:|--------------|:---:|
|
----------|:---:|--------|:---:|--------------|:---:|
|
||||||
Xlsx | NO | Xls | NO | Xml | NO |
|
Xlsx | NO | Xls | NO | Xml | NO |
|
||||||
Ods | NO | SYLK | NO | Gnumeric | NO |
|
Ods | NO | SYLK | NO | Gnumeric | NO |
|
||||||
CSV | YES | HTML | NO
|
CSV | YES | HTML | NO
|
||||||
|
|
||||||
|
|
@ -646,7 +690,7 @@ Loading using a Value Binder applies to:
|
||||||
|
|
||||||
Reader | Y/N |Reader | Y/N |Reader | Y/N
|
Reader | Y/N |Reader | Y/N |Reader | Y/N
|
||||||
----------|:---:|--------|:---:|--------------|:---:
|
----------|:---:|--------|:---:|--------------|:---:
|
||||||
Xlsx | NO | Xls | NO | Xml | NO
|
Xlsx | NO | Xls | NO | Xml | NO
|
||||||
Ods | NO | SYLK | NO | Gnumeric | NO
|
Ods | NO | SYLK | NO | Gnumeric | NO
|
||||||
CSV | YES | HTML | YES
|
CSV | YES | HTML | YES
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1167,13 +1167,15 @@ that you are setting is measured in.
|
||||||
Valid units are `pt` (points), `px` (pixels), `pc` (pica), `in` (inches),
|
Valid units are `pt` (points), `px` (pixels), `pc` (pica), `in` (inches),
|
||||||
`cm` (centimeters) and `mm` (millimeters).
|
`cm` (centimeters) and `mm` (millimeters).
|
||||||
|
|
||||||
|
Setting the column width to `-1` tells MS Excel to display the column using its default width.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(120, 'pt');
|
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(120, 'pt');
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want PhpSpreadsheet to perform an automatic width calculation,
|
If you want PhpSpreadsheet to perform an automatic width calculation,
|
||||||
use the following code. PhpSpreadsheet will approximate the column with
|
use the following code. PhpSpreadsheet will approximate the column width
|
||||||
to the width of the widest column value.
|
to the width of the widest value displayed in that column.
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$spreadsheet->getActiveSheet()->getColumnDimension('B')->setAutoSize(true);
|
$spreadsheet->getActiveSheet()->getColumnDimension('B')->setAutoSize(true);
|
||||||
|
|
@ -1266,6 +1268,18 @@ Valid units are `pt` (points), `px` (pixels), `pc` (pica), `in` (inches),
|
||||||
$spreadsheet->getActiveSheet()->getRowDimension('10')->setRowHeight(100, 'pt');
|
$spreadsheet->getActiveSheet()->getRowDimension('10')->setRowHeight(100, 'pt');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Setting the row height to `-1` tells MS Excel to display the column using its default height, which is based on the character font size.
|
||||||
|
|
||||||
|
If you have wrapped text in a cell, then the `-1` default will only set the row height to display a single line of that wrapped text.
|
||||||
|
If you need to calculate the actual height for the row, then count the lines that should be displayed (count the `\n` and add 1); then adjust for the font.
|
||||||
|
The adjustment for Calibri 11 is approximately 14.5; for Calibri 12 15.9, etc.
|
||||||
|
```php
|
||||||
|
$spreadsheet->getActiveSheet()->getRowDimension(1)->setRowHeight(
|
||||||
|
14.5 * (substr_count($sheet->getCell('A1')->getValue(), "\n") + 1)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Show/hide a row
|
## Show/hide a row
|
||||||
|
|
||||||
To set a worksheet''s row visibility, you can use the following code.
|
To set a worksheet''s row visibility, you can use the following code.
|
||||||
|
|
|
||||||
|
|
@ -1175,11 +1175,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: src/PhpSpreadsheet/Cell/Coordinate.php
|
path: src/PhpSpreadsheet/Cell/Coordinate.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot use array destructuring on array\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/PhpSpreadsheet/Cell/Coordinate.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#4 \\$currentRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:validateRange\\(\\) expects int, string given\\.$#"
|
message: "#^Parameter \\#4 \\$currentRow of static method PhpOffice\\\\PhpSpreadsheet\\\\Cell\\\\Coordinate\\:\\:validateRange\\(\\) expects int, string given\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|
@ -3120,11 +3115,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: src/PhpSpreadsheet/Reader/Xml/Style.php
|
path: src/PhpSpreadsheet/Reader/Xml/Style.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot use array destructuring on array\\|null\\.$#"
|
|
||||||
count: 4
|
|
||||||
path: src/PhpSpreadsheet/ReferenceHelper.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Elseif condition is always true\\.$#"
|
message: "#^Elseif condition is always true\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|
@ -4675,11 +4665,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: src/PhpSpreadsheet/Writer/Ods/Content.php
|
path: src/PhpSpreadsheet/Writer/Ods/Content.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#"
|
|
||||||
count: 4
|
|
||||||
path: src/PhpSpreadsheet/Writer/Ods/Content.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<2, max\\> given\\.$#"
|
message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int\\<2, max\\> given\\.$#"
|
||||||
count: 3
|
count: 3
|
||||||
|
|
@ -4695,11 +4680,6 @@ parameters:
|
||||||
count: 1
|
count: 1
|
||||||
path: src/PhpSpreadsheet/Writer/Ods/Formula.php
|
path: src/PhpSpreadsheet/Writer/Ods/Formula.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Parameter \\#1 \\$content of method XMLWriter\\:\\:text\\(\\) expects string, int given\\.$#"
|
|
||||||
count: 2
|
|
||||||
path: src/PhpSpreadsheet/Writer/Ods/Settings.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
|
message: "#^Cannot call method getHashCode\\(\\) on PhpOffice\\\\PhpSpreadsheet\\\\Style\\\\Font\\|null\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheet\Calculation;
|
||||||
|
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
|
||||||
|
|
||||||
class Functions
|
class Functions
|
||||||
{
|
{
|
||||||
|
|
@ -686,12 +685,13 @@ class Functions
|
||||||
// Uppercase coordinate
|
// Uppercase coordinate
|
||||||
$pCoordinatex = strtoupper($coordinate);
|
$pCoordinatex = strtoupper($coordinate);
|
||||||
// Eliminate leading equal sign
|
// Eliminate leading equal sign
|
||||||
$pCoordinatex = Worksheet::pregReplace('/^=/', '', $pCoordinatex);
|
$pCoordinatex = (string) preg_replace('/^=/', '', $pCoordinatex);
|
||||||
$defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet);
|
$defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet);
|
||||||
if ($defined !== null) {
|
if ($defined !== null) {
|
||||||
$worksheet2 = $defined->getWorkSheet();
|
$worksheet2 = $defined->getWorkSheet();
|
||||||
if (!$defined->isFormula() && $worksheet2 !== null) {
|
if (!$defined->isFormula() && $worksheet2 !== null) {
|
||||||
$coordinate = "'" . $worksheet2->getTitle() . "'!" . Worksheet::pregReplace('/^=/', '', $defined->getValue());
|
$coordinate = "'" . $worksheet2->getTitle() . "'!" .
|
||||||
|
(string) preg_replace('/^=/', '', $defined->getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -700,7 +700,7 @@ class Functions
|
||||||
|
|
||||||
public static function trimTrailingRange(string $coordinate): string
|
public static function trimTrailingRange(string $coordinate): string
|
||||||
{
|
{
|
||||||
return Worksheet::pregReplace('/:[\\w\$]+$/', '', $coordinate);
|
return (string) preg_replace('/:[\\w\$]+$/', '', $coordinate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function trimSheetFromCellReference(string $coordinate): string
|
public static function trimSheetFromCellReference(string $coordinate): string
|
||||||
|
|
|
||||||
|
|
@ -383,7 +383,7 @@ abstract class Coordinate
|
||||||
// Sort the result by column and row
|
// Sort the result by column and row
|
||||||
$sortKeys = [];
|
$sortKeys = [];
|
||||||
foreach ($cellList as $coord) {
|
foreach ($cellList as $coord) {
|
||||||
[$column, $row] = sscanf($coord, '%[A-Z]%d');
|
sscanf($coord, '%[A-Z]%d', $column, $row);
|
||||||
$sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
|
$sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
|
||||||
}
|
}
|
||||||
ksort($sortKeys);
|
ksort($sortKeys);
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ namespace PhpOffice\PhpSpreadsheet\Collection;
|
||||||
|
|
||||||
use Generator;
|
use Generator;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
|
||||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Settings;
|
||||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
use Psr\SimpleCache\CacheInterface;
|
use Psr\SimpleCache\CacheInterface;
|
||||||
|
|
||||||
|
|
@ -151,8 +151,6 @@ class Cells
|
||||||
{
|
{
|
||||||
$sortKeys = [];
|
$sortKeys = [];
|
||||||
foreach ($this->getCoordinates() as $coord) {
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
$column = '';
|
|
||||||
$row = 0;
|
|
||||||
sscanf($coord, '%[A-Z]%d', $column, $row);
|
sscanf($coord, '%[A-Z]%d', $column, $row);
|
||||||
$sortKeys[sprintf('%09d%3s', $row, $column)] = $coord;
|
$sortKeys[sprintf('%09d%3s', $row, $column)] = $coord;
|
||||||
}
|
}
|
||||||
|
|
@ -172,8 +170,6 @@ class Cells
|
||||||
$col = ['A' => '1A'];
|
$col = ['A' => '1A'];
|
||||||
$row = [1];
|
$row = [1];
|
||||||
foreach ($this->getCoordinates() as $coord) {
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
$c = '';
|
|
||||||
$r = 0;
|
|
||||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
$row[$r] = $r;
|
$row[$r] = $r;
|
||||||
$col[$c] = strlen($c) . $c;
|
$col[$c] = strlen($c) . $c;
|
||||||
|
|
@ -206,9 +202,6 @@ class Cells
|
||||||
*/
|
*/
|
||||||
public function getCurrentColumn()
|
public function getCurrentColumn()
|
||||||
{
|
{
|
||||||
$column = '';
|
|
||||||
$row = 0;
|
|
||||||
|
|
||||||
sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
|
sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
|
||||||
|
|
||||||
return $column;
|
return $column;
|
||||||
|
|
@ -221,9 +214,6 @@ class Cells
|
||||||
*/
|
*/
|
||||||
public function getCurrentRow()
|
public function getCurrentRow()
|
||||||
{
|
{
|
||||||
$column = '';
|
|
||||||
$row = 0;
|
|
||||||
|
|
||||||
sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
|
sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
|
||||||
|
|
||||||
return (int) $row;
|
return (int) $row;
|
||||||
|
|
@ -240,24 +230,19 @@ class Cells
|
||||||
public function getHighestColumn($row = null)
|
public function getHighestColumn($row = null)
|
||||||
{
|
{
|
||||||
if ($row === null) {
|
if ($row === null) {
|
||||||
$colRow = $this->getHighestRowAndColumn();
|
return $this->getHighestRowAndColumn()['column'];
|
||||||
|
|
||||||
return $colRow['column'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$columnList = [1];
|
$maxColumn = '1A';
|
||||||
foreach ($this->getCoordinates() as $coord) {
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
$c = '';
|
|
||||||
$r = 0;
|
|
||||||
|
|
||||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
if ($r != $row) {
|
if ($r != $row) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$columnList[] = Coordinate::columnIndexFromString($c);
|
$maxColumn = max($maxColumn, strlen($c) . $c);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Coordinate::stringFromColumnIndex((int) @max($columnList));
|
return substr($maxColumn, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -271,24 +256,19 @@ class Cells
|
||||||
public function getHighestRow($column = null)
|
public function getHighestRow($column = null)
|
||||||
{
|
{
|
||||||
if ($column === null) {
|
if ($column === null) {
|
||||||
$colRow = $this->getHighestRowAndColumn();
|
return $this->getHighestRowAndColumn()['row'];
|
||||||
|
|
||||||
return $colRow['row'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$rowList = [0];
|
$maxRow = 1;
|
||||||
foreach ($this->getCoordinates() as $coord) {
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
$c = '';
|
|
||||||
$r = 0;
|
|
||||||
|
|
||||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
if ($c != $column) {
|
if ($c != $column) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$rowList[] = $r;
|
$maxRow = max($maxRow, $r);
|
||||||
}
|
}
|
||||||
|
|
||||||
return max($rowList);
|
return $maxRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -298,7 +278,9 @@ class Cells
|
||||||
*/
|
*/
|
||||||
private function getUniqueID()
|
private function getUniqueID()
|
||||||
{
|
{
|
||||||
return uniqid('phpspreadsheet.', true) . '.';
|
return Settings::getCache() instanceof Memory
|
||||||
|
? random_bytes(7) . ':'
|
||||||
|
: uniqid('phpspreadsheet.', true) . '.';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -345,9 +327,6 @@ class Cells
|
||||||
public function removeRow($row): void
|
public function removeRow($row): void
|
||||||
{
|
{
|
||||||
foreach ($this->getCoordinates() as $coord) {
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
$c = '';
|
|
||||||
$r = 0;
|
|
||||||
|
|
||||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
if ($r == $row) {
|
if ($r == $row) {
|
||||||
$this->delete($coord);
|
$this->delete($coord);
|
||||||
|
|
@ -363,9 +342,6 @@ class Cells
|
||||||
public function removeColumn($column): void
|
public function removeColumn($column): void
|
||||||
{
|
{
|
||||||
foreach ($this->getCoordinates() as $coord) {
|
foreach ($this->getCoordinates() as $coord) {
|
||||||
$c = '';
|
|
||||||
$r = 0;
|
|
||||||
|
|
||||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||||
if ($c == $column) {
|
if ($c == $column) {
|
||||||
$this->delete($coord);
|
$this->delete($coord);
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ class Properties
|
||||||
// Initialise values
|
// Initialise values
|
||||||
$this->lastModifiedBy = $this->creator;
|
$this->lastModifiedBy = $this->creator;
|
||||||
$this->created = self::intOrFloatTimestamp(null);
|
$this->created = self::intOrFloatTimestamp(null);
|
||||||
$this->modified = self::intOrFloatTimestamp(null);
|
$this->modified = $this->created;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -14,23 +14,39 @@ use PhpOffice\PhpSpreadsheet\Writer\IWriter;
|
||||||
*/
|
*/
|
||||||
abstract class IOFactory
|
abstract class IOFactory
|
||||||
{
|
{
|
||||||
|
public const READER_XLSX = 'Xlsx';
|
||||||
|
public const READER_XLS = 'Xls';
|
||||||
|
public const READER_XML = 'Xml';
|
||||||
|
public const READER_ODS = 'Ods';
|
||||||
|
public const READER_SYLK = 'Slk';
|
||||||
|
public const READER_SLK = 'Slk';
|
||||||
|
public const READER_GNUMERIC = 'Gnumeric';
|
||||||
|
public const READER_HTML = 'Html';
|
||||||
|
public const READER_CSV = 'Csv';
|
||||||
|
|
||||||
|
public const WRITER_XLSX = 'Xlsx';
|
||||||
|
public const WRITER_XLS = 'Xls';
|
||||||
|
public const WRITER_ODS = 'Ods';
|
||||||
|
public const WRITER_CSV = 'Csv';
|
||||||
|
public const WRITER_HTML = 'Html';
|
||||||
|
|
||||||
private static $readers = [
|
private static $readers = [
|
||||||
'Xlsx' => Reader\Xlsx::class,
|
self::READER_XLSX => Reader\Xlsx::class,
|
||||||
'Xls' => Reader\Xls::class,
|
self::READER_XLS => Reader\Xls::class,
|
||||||
'Xml' => Reader\Xml::class,
|
self::READER_XML => Reader\Xml::class,
|
||||||
'Ods' => Reader\Ods::class,
|
self::READER_ODS => Reader\Ods::class,
|
||||||
'Slk' => Reader\Slk::class,
|
self::READER_SLK => Reader\Slk::class,
|
||||||
'Gnumeric' => Reader\Gnumeric::class,
|
self::READER_GNUMERIC => Reader\Gnumeric::class,
|
||||||
'Html' => Reader\Html::class,
|
self::READER_HTML => Reader\Html::class,
|
||||||
'Csv' => Reader\Csv::class,
|
self::READER_CSV => Reader\Csv::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
private static $writers = [
|
private static $writers = [
|
||||||
'Xls' => Writer\Xls::class,
|
self::WRITER_XLS => Writer\Xls::class,
|
||||||
'Xlsx' => Writer\Xlsx::class,
|
self::WRITER_XLSX => Writer\Xlsx::class,
|
||||||
'Ods' => Writer\Ods::class,
|
self::WRITER_ODS => Writer\Ods::class,
|
||||||
'Csv' => Writer\Csv::class,
|
self::WRITER_CSV => Writer\Csv::class,
|
||||||
'Html' => Writer\Html::class,
|
self::WRITER_HTML => Writer\Html::class,
|
||||||
'Tcpdf' => Writer\Pdf\Tcpdf::class,
|
'Tcpdf' => Writer\Pdf\Tcpdf::class,
|
||||||
'Dompdf' => Writer\Pdf\Dompdf::class,
|
'Dompdf' => Writer\Pdf\Dompdf::class,
|
||||||
'Mpdf' => Writer\Pdf\Mpdf::class,
|
'Mpdf' => Writer\Pdf\Mpdf::class,
|
||||||
|
|
@ -70,10 +86,18 @@ abstract class IOFactory
|
||||||
* Loads Spreadsheet from file using automatic Reader\IReader resolution.
|
* Loads Spreadsheet from file using automatic Reader\IReader resolution.
|
||||||
*
|
*
|
||||||
* @param string $filename The name of the spreadsheet file
|
* @param string $filename The name of the spreadsheet file
|
||||||
|
* @param int $flags the optional second parameter flags may be used to identify specific elements
|
||||||
|
* that should be loaded, but which won't be loaded by default, using these values:
|
||||||
|
* IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file
|
||||||
|
* @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
|
||||||
|
* all possible Readers until it finds a match; but this allows you to pass in a
|
||||||
|
* list of Readers so it will only try the subset that you specify here.
|
||||||
|
* Values in this list can be any of the constant values defined in the set
|
||||||
|
* IOFactory::READER_*.
|
||||||
*/
|
*/
|
||||||
public static function load(string $filename, int $flags = 0): Spreadsheet
|
public static function load(string $filename, int $flags = 0, ?array $readers = null): Spreadsheet
|
||||||
{
|
{
|
||||||
$reader = self::createReaderForFile($filename);
|
$reader = self::createReaderForFile($filename, $readers);
|
||||||
|
|
||||||
return $reader->load($filename, $flags);
|
return $reader->load($filename, $flags);
|
||||||
}
|
}
|
||||||
|
|
@ -81,9 +105,9 @@ abstract class IOFactory
|
||||||
/**
|
/**
|
||||||
* Identify file type using automatic IReader resolution.
|
* Identify file type using automatic IReader resolution.
|
||||||
*/
|
*/
|
||||||
public static function identify(string $filename): string
|
public static function identify(string $filename, ?array $readers = null): string
|
||||||
{
|
{
|
||||||
$reader = self::createReaderForFile($filename);
|
$reader = self::createReaderForFile($filename, $readers);
|
||||||
$className = get_class($reader);
|
$className = get_class($reader);
|
||||||
$classType = explode('\\', $className);
|
$classType = explode('\\', $className);
|
||||||
unset($reader);
|
unset($reader);
|
||||||
|
|
@ -93,14 +117,32 @@ abstract class IOFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create Reader\IReader for file using automatic IReader resolution.
|
* Create Reader\IReader for file using automatic IReader resolution.
|
||||||
|
*
|
||||||
|
* @param string[] $readers An array of Readers to use to identify the file type. By default, load() will try
|
||||||
|
* all possible Readers until it finds a match; but this allows you to pass in a
|
||||||
|
* list of Readers so it will only try the subset that you specify here.
|
||||||
|
* Values in this list can be any of the constant values defined in the set
|
||||||
|
* IOFactory::READER_*.
|
||||||
*/
|
*/
|
||||||
public static function createReaderForFile(string $filename): IReader
|
public static function createReaderForFile(string $filename, ?array $readers = null): IReader
|
||||||
{
|
{
|
||||||
File::assertFile($filename);
|
File::assertFile($filename);
|
||||||
|
|
||||||
|
$testReaders = self::$readers;
|
||||||
|
if ($readers !== null) {
|
||||||
|
$readers = array_map('strtoupper', $readers);
|
||||||
|
$testReaders = array_filter(
|
||||||
|
self::$readers,
|
||||||
|
function (string $readerType) use ($readers) {
|
||||||
|
return in_array(strtoupper($readerType), $readers, true);
|
||||||
|
},
|
||||||
|
ARRAY_FILTER_USE_KEY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// First, lucky guess by inspecting file extension
|
// First, lucky guess by inspecting file extension
|
||||||
$guessedReader = self::getReaderTypeFromExtension($filename);
|
$guessedReader = self::getReaderTypeFromExtension($filename);
|
||||||
if ($guessedReader !== null) {
|
if (($guessedReader !== null) && array_key_exists($guessedReader, $testReaders)) {
|
||||||
$reader = self::createReader($guessedReader);
|
$reader = self::createReader($guessedReader);
|
||||||
|
|
||||||
// Let's see if we are lucky
|
// Let's see if we are lucky
|
||||||
|
|
@ -110,11 +152,11 @@ abstract class IOFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we reach here then "lucky guess" didn't give any result
|
// If we reach here then "lucky guess" didn't give any result
|
||||||
// Try walking through all the options in self::$autoResolveClasses
|
// Try walking through all the options in self::$readers (or the selected subset)
|
||||||
foreach (self::$readers as $type => $class) {
|
foreach ($testReaders as $readerType => $class) {
|
||||||
// Ignore our original guess, we know that won't work
|
// Ignore our original guess, we know that won't work
|
||||||
if ($type !== $guessedReader) {
|
if ($readerType !== $guessedReader) {
|
||||||
$reader = self::createReader($type);
|
$reader = self::createReader($readerType);
|
||||||
if ($reader->canRead($filename)) {
|
if ($reader->canRead($filename)) {
|
||||||
return $reader;
|
return $reader;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,10 @@ abstract class BaseReader implements IReader
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads Spreadsheet from file.
|
* Loads Spreadsheet from file.
|
||||||
|
*
|
||||||
|
* @param int $flags the optional second parameter flags may be used to identify specific elements
|
||||||
|
* that should be loaded, but which won't be loaded by default, using these values:
|
||||||
|
* IReader::LOAD_WITH_CHARTS - Include any charts that are defined in the loaded file
|
||||||
*/
|
*/
|
||||||
public function load(string $filename, int $flags = 0): Spreadsheet
|
public function load(string $filename, int $flags = 0): Spreadsheet
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3759,8 +3759,13 @@ class Xls extends BaseReader
|
||||||
} else {
|
} else {
|
||||||
$textRun = $richText->createTextRun($text);
|
$textRun = $richText->createTextRun($text);
|
||||||
if (isset($fmtRuns[$i - 1])) {
|
if (isset($fmtRuns[$i - 1])) {
|
||||||
$fontIndex = $fmtRuns[$i - 1]['fontIndex'];
|
if ($fmtRuns[$i - 1]['fontIndex'] < 4) {
|
||||||
|
$fontIndex = $fmtRuns[$i - 1]['fontIndex'];
|
||||||
|
} else {
|
||||||
|
// this has to do with that index 4 is omitted in all BIFF versions for some stra nge reason
|
||||||
|
// check the OpenOffice documentation of the FONT record
|
||||||
|
$fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1;
|
||||||
|
}
|
||||||
if (array_key_exists($fontIndex, $this->objFonts) === false) {
|
if (array_key_exists($fontIndex, $this->objFonts) === false) {
|
||||||
$fontIndex = count($this->objFonts) - 1;
|
$fontIndex = count($this->objFonts) - 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,9 @@ class Xlsx extends BaseReader
|
||||||
if (strpos($fileName, '//') !== false) {
|
if (strpos($fileName, '//') !== false) {
|
||||||
$fileName = substr($fileName, strpos($fileName, '//') + 1);
|
$fileName = substr($fileName, strpos($fileName, '//') + 1);
|
||||||
}
|
}
|
||||||
|
// Relative paths generated by dirname($filename) when $filename
|
||||||
|
// has no path (i.e.files in root of the zip archive)
|
||||||
|
$fileName = (string) preg_replace('/^\.\//', '', $fileName);
|
||||||
$fileName = File::realpath($fileName);
|
$fileName = File::realpath($fileName);
|
||||||
|
|
||||||
// Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
|
// Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ class ReferenceHelper
|
||||||
*/
|
*/
|
||||||
public static function cellSort($a, $b)
|
public static function cellSort($a, $b)
|
||||||
{
|
{
|
||||||
[$ac, $ar] = sscanf($a, '%[A-Z]%d');
|
sscanf($a, '%[A-Z]%d', $ac, $ar);
|
||||||
[$bc, $br] = sscanf($b, '%[A-Z]%d');
|
sscanf($b, '%[A-Z]%d', $bc, $br);
|
||||||
|
|
||||||
if ($ar === $br) {
|
if ($ar === $br) {
|
||||||
return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
|
return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
|
||||||
|
|
@ -112,8 +112,8 @@ class ReferenceHelper
|
||||||
*/
|
*/
|
||||||
public static function cellReverseSort($a, $b)
|
public static function cellReverseSort($a, $b)
|
||||||
{
|
{
|
||||||
[$ac, $ar] = sscanf($a, '%[A-Z]%d');
|
sscanf($a, '%[A-Z]%d', $ac, $ar);
|
||||||
[$bc, $br] = sscanf($b, '%[A-Z]%d');
|
sscanf($b, '%[A-Z]%d', $bc, $br);
|
||||||
|
|
||||||
if ($ar === $br) {
|
if ($ar === $br) {
|
||||||
return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
|
return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
|
||||||
|
|
|
||||||
|
|
@ -222,11 +222,15 @@ class Font
|
||||||
* @param RichText|string $cellText Text to calculate width
|
* @param RichText|string $cellText Text to calculate width
|
||||||
* @param int $rotation Rotation angle
|
* @param int $rotation Rotation angle
|
||||||
* @param null|FontStyle $defaultFont Font object
|
* @param null|FontStyle $defaultFont Font object
|
||||||
*
|
* @param bool $filterAdjustment Add space for Autofilter or Table dropdown
|
||||||
* @return int Column width
|
|
||||||
*/
|
*/
|
||||||
public static function calculateColumnWidth(FontStyle $font, $cellText = '', $rotation = 0, ?FontStyle $defaultFont = null)
|
public static function calculateColumnWidth(
|
||||||
{
|
FontStyle $font,
|
||||||
|
$cellText = '',
|
||||||
|
$rotation = 0,
|
||||||
|
?FontStyle $defaultFont = null,
|
||||||
|
bool $filterAdjustment = false
|
||||||
|
): int {
|
||||||
// If it is rich text, use plain text
|
// If it is rich text, use plain text
|
||||||
if ($cellText instanceof RichText) {
|
if ($cellText instanceof RichText) {
|
||||||
$cellText = $cellText->getPlainText();
|
$cellText = $cellText->getPlainText();
|
||||||
|
|
@ -237,7 +241,7 @@ class Font
|
||||||
$lineTexts = explode("\n", $cellText);
|
$lineTexts = explode("\n", $cellText);
|
||||||
$lineWidths = [];
|
$lineWidths = [];
|
||||||
foreach ($lineTexts as $lineText) {
|
foreach ($lineTexts as $lineText) {
|
||||||
$lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont);
|
$lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont, $filterAdjustment);
|
||||||
}
|
}
|
||||||
|
|
||||||
return max($lineWidths); // width of longest line in cell
|
return max($lineWidths); // width of longest line in cell
|
||||||
|
|
@ -247,7 +251,13 @@ class Font
|
||||||
$approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX;
|
$approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX;
|
||||||
$columnWidth = 0;
|
$columnWidth = 0;
|
||||||
if (!$approximate) {
|
if (!$approximate) {
|
||||||
$columnWidthAdjust = ceil(self::getTextWidthPixelsExact('n', $font, 0) * 1.07);
|
$columnWidthAdjust = ceil(
|
||||||
|
self::getTextWidthPixelsExact(
|
||||||
|
str_repeat('n', 1 * ($filterAdjustment ? 3 : 1)),
|
||||||
|
$font,
|
||||||
|
0
|
||||||
|
) * 1.07
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Width of text in pixels excl. padding
|
// Width of text in pixels excl. padding
|
||||||
|
|
@ -259,7 +269,11 @@ class Font
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($approximate) {
|
if ($approximate) {
|
||||||
$columnWidthAdjust = self::getTextWidthPixelsApprox('n', $font, 0);
|
$columnWidthAdjust = self::getTextWidthPixelsApprox(
|
||||||
|
str_repeat('n', 1 * ($filterAdjustment ? 3 : 1)),
|
||||||
|
$font,
|
||||||
|
0
|
||||||
|
);
|
||||||
// Width of text in pixels excl. padding, approximation
|
// Width of text in pixels excl. padding, approximation
|
||||||
// and addition because Excel adds some padding, just use approx width of 'n' glyph
|
// and addition because Excel adds some padding, just use approx width of 'n' glyph
|
||||||
$columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
|
$columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use DateTimeZone;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||||
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
|
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||||
|
|
@ -50,9 +51,18 @@ class AutoFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new AutoFilter.
|
* Create a new AutoFilter.
|
||||||
|
*
|
||||||
|
* @param AddressRange|array<int>|string $range
|
||||||
|
* A simple string containing a Cell range like 'A1:E10' is permitted
|
||||||
|
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
|
||||||
|
* or an AddressRange object.
|
||||||
*/
|
*/
|
||||||
public function __construct(string $range = '', ?Worksheet $worksheet = null)
|
public function __construct($range = '', ?Worksheet $worksheet = null)
|
||||||
{
|
{
|
||||||
|
if ($range !== '') {
|
||||||
|
[, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
|
||||||
|
}
|
||||||
|
|
||||||
$this->range = $range;
|
$this->range = $range;
|
||||||
$this->workSheet = $worksheet;
|
$this->workSheet = $worksheet;
|
||||||
}
|
}
|
||||||
|
|
@ -92,12 +102,19 @@ class AutoFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set AutoFilter Cell Range.
|
* Set AutoFilter Cell Range.
|
||||||
|
*
|
||||||
|
* @param AddressRange|array<int>|string $range
|
||||||
|
* A simple string containing a Cell range like 'A1:E10' is permitted
|
||||||
|
* or passing in an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 8]),
|
||||||
|
* or an AddressRange object.
|
||||||
*/
|
*/
|
||||||
public function setRange(string $range): self
|
public function setRange($range = ''): self
|
||||||
{
|
{
|
||||||
$this->evaluated = false;
|
$this->evaluated = false;
|
||||||
// extract coordinate
|
// extract coordinate
|
||||||
[$worksheet, $range] = Worksheet::extractSheetTitle($range, true);
|
if ($range !== '') {
|
||||||
|
[, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
|
||||||
|
}
|
||||||
if (empty($range)) {
|
if (empty($range)) {
|
||||||
// Discard all column rules
|
// Discard all column rules
|
||||||
$this->columns = [];
|
$this->columns = [];
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,10 @@ class ColumnDimension extends Dimension
|
||||||
/**
|
/**
|
||||||
* Get Width.
|
* Get Width.
|
||||||
*
|
*
|
||||||
* Each unit of column width is equal to the width of one character in the default font size.
|
* Each unit of column width is equal to the width of one character in the default font size. A value of -1
|
||||||
* By default, this will be the return value; but this method also accepts a unit of measure argument and will
|
* tells Excel to display this column in its default width.
|
||||||
* return the value converted to the specified UoM using an approximation method.
|
* By default, this will be the return value; but this method also accepts an optional unit of measure argument
|
||||||
|
* and will convert the returned value to the specified UoM..
|
||||||
*/
|
*/
|
||||||
public function getWidth(?string $unitOfMeasure = null): float
|
public function getWidth(?string $unitOfMeasure = null): float
|
||||||
{
|
{
|
||||||
|
|
@ -97,9 +98,11 @@ class ColumnDimension extends Dimension
|
||||||
/**
|
/**
|
||||||
* Set Width.
|
* Set Width.
|
||||||
*
|
*
|
||||||
* Each unit of column width is equal to the width of one character in the default font size.
|
* Each unit of column width is equal to the width of one character in the default font size. A value of -1
|
||||||
* By default, this will be the unit of measure for the passed value; but this method accepts a unit of measure
|
* tells Excel to display this column in its default width.
|
||||||
* argument, and will convert the value from the specified UoM using an approximation method.
|
* By default, this will be the unit of measure for the passed value; but this method also accepts an
|
||||||
|
* optional unit of measure argument, and will convert the value from the specified UoM using an
|
||||||
|
* approximation method.
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,9 @@ class RowDimension extends Dimension
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Row Height.
|
* Get Row Height.
|
||||||
* By default, this will be in points; but this method accepts a unit of measure
|
* By default, this will be in points; but this method also accepts an optional unit of measure
|
||||||
* argument, and will convert the value to the specified UoM.
|
* argument, and will convert the value from points to the specified UoM.
|
||||||
|
* A value of -1 tells Excel to display this column in its default height.
|
||||||
*
|
*
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
|
|
@ -80,8 +81,8 @@ class RowDimension extends Dimension
|
||||||
/**
|
/**
|
||||||
* Set Row Height.
|
* Set Row Height.
|
||||||
*
|
*
|
||||||
* @param float $height in points
|
* @param float $height in points. A value of -1 tells Excel to display this column in its default height.
|
||||||
* By default, this will be the passed argument value; but this method accepts a unit of measure
|
* By default, this will be the passed argument value; but this method also accepts an optional unit of measure
|
||||||
* argument, and will convert the passed argument value to points from the specified UoM
|
* argument, and will convert the passed argument value to points from the specified UoM
|
||||||
*
|
*
|
||||||
* @return $this
|
* @return $this
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheet\Worksheet;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\CellRange;
|
||||||
|
|
||||||
|
class Validations
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Validate a cell address.
|
||||||
|
*
|
||||||
|
* @param null|array<int>|CellAddress|string $cellAddress Coordinate of the cell as a string, eg: 'C5';
|
||||||
|
* or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
|
||||||
|
*/
|
||||||
|
public static function validateCellAddress($cellAddress): string
|
||||||
|
{
|
||||||
|
if (is_string($cellAddress)) {
|
||||||
|
[$worksheet, $address] = Worksheet::extractSheetTitle($cellAddress, true);
|
||||||
|
// if (!empty($worksheet) && $worksheet !== $this->getTitle()) {
|
||||||
|
// throw new Exception('Reference is not for this worksheet');
|
||||||
|
// }
|
||||||
|
|
||||||
|
return empty($worksheet) ? strtoupper($address) : $worksheet . '!' . strtoupper($address);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($cellAddress)) {
|
||||||
|
$cellAddress = CellAddress::fromColumnRowArray($cellAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $cellAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a cell address or cell range.
|
||||||
|
*
|
||||||
|
* @param AddressRange|array<int>|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
|
||||||
|
* or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
|
||||||
|
* or as a CellAddress or AddressRange object.
|
||||||
|
*/
|
||||||
|
public static function validateCellOrCellRange($cellRange): string
|
||||||
|
{
|
||||||
|
if (is_string($cellRange) || is_numeric($cellRange)) {
|
||||||
|
$cellRange = (string) $cellRange;
|
||||||
|
// Convert a single column reference like 'A' to 'A:A'
|
||||||
|
$cellRange = (string) preg_replace('/^([A-Z]+)$/', '${1}:${1}', $cellRange);
|
||||||
|
// Convert a single row reference like '1' to '1:1'
|
||||||
|
$cellRange = (string) preg_replace('/^(\d+)$/', '${1}:${1}', $cellRange);
|
||||||
|
} elseif (is_object($cellRange) && $cellRange instanceof CellAddress) {
|
||||||
|
$cellRange = new CellRange($cellRange, $cellRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::validateCellRange($cellRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a cell range.
|
||||||
|
*
|
||||||
|
* @param AddressRange|array<int>|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
|
||||||
|
* or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
|
||||||
|
* or as an AddressRange object.
|
||||||
|
*/
|
||||||
|
public static function validateCellRange($cellRange): string
|
||||||
|
{
|
||||||
|
if (is_string($cellRange)) {
|
||||||
|
[$worksheet, $addressRange] = Worksheet::extractSheetTitle($cellRange, true);
|
||||||
|
|
||||||
|
// Convert Column ranges like 'A:C' to 'A1:C1048576'
|
||||||
|
$addressRange = (string) preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $addressRange);
|
||||||
|
// Convert Row ranges like '1:3' to 'A1:XFD3'
|
||||||
|
$addressRange = (string) preg_replace('/^(\\d+):(\\d+)$/', 'A${1}:XFD${2}', $addressRange);
|
||||||
|
|
||||||
|
return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($cellRange)) {
|
||||||
|
[$from, $to] = array_chunk($cellRange, 2);
|
||||||
|
$cellRange = new CellRange(CellAddress::fromColumnRowArray($from), CellAddress::fromColumnRowArray($to));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $cellRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function definedNameToCoordinate(string $coordinate, Worksheet $worksheet): string
|
||||||
|
{
|
||||||
|
// Uppercase coordinate
|
||||||
|
$testCoordinate = strtoupper($coordinate);
|
||||||
|
// Eliminate leading equal sign
|
||||||
|
$testCoordinate = (string) preg_replace('/^=/', '', $coordinate);
|
||||||
|
$defined = $worksheet->getParent()->getDefinedName($testCoordinate, $worksheet);
|
||||||
|
if ($defined !== null) {
|
||||||
|
if ($defined->getWorksheet() === $worksheet && !$defined->isFormula()) {
|
||||||
|
$coordinate = (string) preg_replace('/^=/', '', $defined->getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $coordinate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -743,9 +743,19 @@ class Worksheet implements IComparable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$autoFilterRange = $autoFilterFirstRowRange = $this->autoFilter->getRange();
|
||||||
|
if (!empty($autoFilterRange)) {
|
||||||
|
$autoFilterRangeBoundaries = Coordinate::rangeBoundaries($autoFilterRange);
|
||||||
|
$autoFilterFirstRowRange = (string) new CellRange(
|
||||||
|
CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[0][0], $autoFilterRangeBoundaries[0][1]),
|
||||||
|
CellAddress::fromColumnAndRow($autoFilterRangeBoundaries[1][0], $autoFilterRangeBoundaries[0][1])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// loop through all cells in the worksheet
|
// loop through all cells in the worksheet
|
||||||
foreach ($this->getCoordinates(false) as $coordinate) {
|
foreach ($this->getCoordinates(false) as $coordinate) {
|
||||||
$cell = $this->getCellOrNull($coordinate);
|
$cell = $this->getCellOrNull($coordinate);
|
||||||
|
|
||||||
if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
|
if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
|
||||||
//Determine if cell is in merge range
|
//Determine if cell is in merge range
|
||||||
$isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
|
$isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
|
||||||
|
|
@ -762,13 +772,21 @@ class Worksheet implements IComparable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range
|
// Determine width if cell is not part of a merge or does and is a value cell of 1-column wide range
|
||||||
if (!$isMerged || $isMergedButProceed) {
|
if (!$isMerged || $isMergedButProceed) {
|
||||||
|
// Determine if we need to make an adjustment for the first row in an AutoFilter range that
|
||||||
|
// has a column filter dropdown
|
||||||
|
$filterAdjustment = false;
|
||||||
|
if (!empty($autoFilterRange) && $cell->isInRange($autoFilterFirstRowRange)) {
|
||||||
|
$filterAdjustment = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Calculated value
|
// Calculated value
|
||||||
// To formatted string
|
// To formatted string
|
||||||
$cellValue = NumberFormat::toFormattedString(
|
$cellValue = NumberFormat::toFormattedString(
|
||||||
$cell->getCalculatedValue(),
|
$cell->getCalculatedValue(),
|
||||||
$this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
|
$this->getParent()->getCellXfByIndex($cell->getXfIndex())
|
||||||
|
->getNumberFormat()->getFormatCode()
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($cellValue !== null && $cellValue !== '') {
|
if ($cellValue !== null && $cellValue !== '') {
|
||||||
|
|
@ -777,8 +795,10 @@ class Worksheet implements IComparable
|
||||||
(float) Shared\Font::calculateColumnWidth(
|
(float) Shared\Font::calculateColumnWidth(
|
||||||
$this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
|
$this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
|
||||||
$cellValue,
|
$cellValue,
|
||||||
$this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
|
$this->getParent()->getCellXfByIndex($cell->getXfIndex())
|
||||||
$this->getParent()->getDefaultStyle()->getFont()
|
->getAlignment()->getTextRotation(),
|
||||||
|
$this->getParent()->getDefaultStyle()->getFont(),
|
||||||
|
$filterAdjustment
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1057,7 +1077,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function getHighestColumn($row = null)
|
public function getHighestColumn($row = null)
|
||||||
{
|
{
|
||||||
if (empty($row)) {
|
if ($row === null) {
|
||||||
return Coordinate::stringFromColumnIndex($this->cachedHighestColumn);
|
return Coordinate::stringFromColumnIndex($this->cachedHighestColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1087,7 +1107,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function getHighestRow($column = null)
|
public function getHighestRow($column = null)
|
||||||
{
|
{
|
||||||
if ($column == null) {
|
if ($column === null) {
|
||||||
return $this->cachedHighestRow;
|
return $this->cachedHighestRow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1117,96 +1137,6 @@ class Worksheet implements IComparable
|
||||||
return $this->cellCollection->getHighestRowAndColumn();
|
return $this->cellCollection->getHighestRowAndColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a cell address.
|
|
||||||
*
|
|
||||||
* @param null|array<int>|CellAddress|string $cellAddress Coordinate of the cell as a string, eg: 'C5';
|
|
||||||
* or as an array of [$columnIndex, $row] (e.g. [3, 5]), or a CellAddress object.
|
|
||||||
*/
|
|
||||||
protected function validateCellAddress($cellAddress): string
|
|
||||||
{
|
|
||||||
if (is_string($cellAddress)) {
|
|
||||||
[$worksheet, $address] = self::extractSheetTitle($cellAddress, true);
|
|
||||||
// if (!empty($worksheet) && $worksheet !== $this->getTitle()) {
|
|
||||||
// throw new Exception('Reference is not for this worksheet');
|
|
||||||
// }
|
|
||||||
|
|
||||||
return empty($worksheet) ? strtoupper($address) : $worksheet . '!' . strtoupper($address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($cellAddress)) {
|
|
||||||
$cellAddress = CellAddress::fromColumnRowArray($cellAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (string) $cellAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function tryDefinedName(string $coordinate): string
|
|
||||||
{
|
|
||||||
// Uppercase coordinate
|
|
||||||
$coordinate = strtoupper($coordinate);
|
|
||||||
// Eliminate leading equal sign
|
|
||||||
$coordinate = self::pregReplace('/^=/', '', $coordinate);
|
|
||||||
$defined = $this->parent->getDefinedName($coordinate, $this);
|
|
||||||
if ($defined !== null) {
|
|
||||||
if ($defined->getWorksheet() === $this && !$defined->isFormula()) {
|
|
||||||
$coordinate = self::pregReplace('/^=/', '', $defined->getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $coordinate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a cell address or cell range.
|
|
||||||
*
|
|
||||||
* @param AddressRange|array<int>|CellAddress|int|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
|
|
||||||
* or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
|
|
||||||
* or as a CellAddress or AddressRange object.
|
|
||||||
*/
|
|
||||||
protected function validateCellOrCellRange($cellRange): string
|
|
||||||
{
|
|
||||||
if (is_string($cellRange) || is_numeric($cellRange)) {
|
|
||||||
$cellRange = (string) $cellRange;
|
|
||||||
// Convert a single column reference like 'A' to 'A:A'
|
|
||||||
$cellRange = self::pregReplace('/^([A-Z]+)$/', '${1}:${1}', $cellRange);
|
|
||||||
// Convert a single row reference like '1' to '1:1'
|
|
||||||
$cellRange = self::pregReplace('/^(\d+)$/', '${1}:${1}', $cellRange);
|
|
||||||
} elseif (is_object($cellRange) && $cellRange instanceof CellAddress) {
|
|
||||||
$cellRange = new CellRange($cellRange, $cellRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->validateCellRange($cellRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a cell range.
|
|
||||||
*
|
|
||||||
* @param AddressRange|array<int>|string $cellRange Coordinate of the cells as a string, eg: 'C5:F12';
|
|
||||||
* or as an array of [$fromColumnIndex, $fromRow, $toColumnIndex, $toRow] (e.g. [3, 5, 6, 12]),
|
|
||||||
* or as an AddressRange object.
|
|
||||||
*/
|
|
||||||
protected function validateCellRange($cellRange): string
|
|
||||||
{
|
|
||||||
if (is_string($cellRange)) {
|
|
||||||
[$worksheet, $addressRange] = self::extractSheetTitle($cellRange, true);
|
|
||||||
|
|
||||||
// Convert Column ranges like 'A:C' to 'A1:C1048576'
|
|
||||||
$addressRange = self::pregReplace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $addressRange);
|
|
||||||
// Convert Row ranges like '1:3' to 'A1:XFD3'
|
|
||||||
$addressRange = self::pregReplace('/^(\\d+):(\\d+)$/', 'A${1}:XFD${2}', $addressRange);
|
|
||||||
|
|
||||||
return empty($worksheet) ? strtoupper($addressRange) : $worksheet . '!' . strtoupper($addressRange);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_array($cellRange)) {
|
|
||||||
[$from, $to] = array_chunk($cellRange, 2);
|
|
||||||
$cellRange = new CellRange(CellAddress::fromColumnRowArray($from), CellAddress::fromColumnRowArray($to));
|
|
||||||
}
|
|
||||||
|
|
||||||
return (string) $cellRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a cell value.
|
* Set a cell value.
|
||||||
*
|
*
|
||||||
|
|
@ -1218,7 +1148,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function setCellValue($coordinate, $value)
|
public function setCellValue($coordinate, $value)
|
||||||
{
|
{
|
||||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate));
|
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
|
||||||
$this->getCell($cellAddress)->setValue($value);
|
$this->getCell($cellAddress)->setValue($value);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
@ -1256,7 +1186,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function setCellValueExplicit($coordinate, $value, $dataType)
|
public function setCellValueExplicit($coordinate, $value, $dataType)
|
||||||
{
|
{
|
||||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate));
|
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
|
||||||
$this->getCell($cellAddress)->setValueExplicit($value, $dataType);
|
$this->getCell($cellAddress)->setValueExplicit($value, $dataType);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
|
@ -1293,7 +1223,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function getCell($coordinate): Cell
|
public function getCell($coordinate): Cell
|
||||||
{
|
{
|
||||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate));
|
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
|
||||||
|
|
||||||
// Shortcut for increased performance for the vast majority of simple cases
|
// Shortcut for increased performance for the vast majority of simple cases
|
||||||
if ($this->cellCollection->has($cellAddress)) {
|
if ($this->cellCollection->has($cellAddress)) {
|
||||||
|
|
@ -1444,7 +1374,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function cellExists($coordinate): bool
|
public function cellExists($coordinate): bool
|
||||||
{
|
{
|
||||||
$cellAddress = $this->validateCellAddress($coordinate);
|
$cellAddress = Validations::validateCellAddress($coordinate);
|
||||||
/** @var Worksheet $sheet */
|
/** @var Worksheet $sheet */
|
||||||
[$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress);
|
[$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress);
|
||||||
|
|
||||||
|
|
@ -1536,7 +1466,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function getStyle($cellCoordinate): Style
|
public function getStyle($cellCoordinate): Style
|
||||||
{
|
{
|
||||||
$cellCoordinate = $this->validateCellOrCellRange($cellCoordinate);
|
$cellCoordinate = Validations::validateCellOrCellRange($cellCoordinate);
|
||||||
|
|
||||||
// set this sheet as active
|
// set this sheet as active
|
||||||
$this->parent->setActiveSheetIndex($this->parent->getIndex($this));
|
$this->parent->setActiveSheetIndex($this->parent->getIndex($this));
|
||||||
|
|
@ -1774,7 +1704,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function setBreak($coordinate, $break)
|
public function setBreak($coordinate, $break)
|
||||||
{
|
{
|
||||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate));
|
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
|
||||||
|
|
||||||
if ($break === self::BREAK_NONE) {
|
if ($break === self::BREAK_NONE) {
|
||||||
if (isset($this->breaks[$cellAddress])) {
|
if (isset($this->breaks[$cellAddress])) {
|
||||||
|
|
@ -1826,7 +1756,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function mergeCells($range)
|
public function mergeCells($range)
|
||||||
{
|
{
|
||||||
$range = Functions::trimSheetFromCellReference($this->validateCellRange($range));
|
$range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range));
|
||||||
|
|
||||||
if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/', $range, $matches) === 1) {
|
if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/', $range, $matches) === 1) {
|
||||||
$this->mergeCells[$range] = $range;
|
$this->mergeCells[$range] = $range;
|
||||||
|
|
@ -1935,7 +1865,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function unmergeCells($range)
|
public function unmergeCells($range)
|
||||||
{
|
{
|
||||||
$range = Functions::trimSheetFromCellReference($this->validateCellRange($range));
|
$range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range));
|
||||||
|
|
||||||
if (strpos($range, ':') !== false) {
|
if (strpos($range, ':') !== false) {
|
||||||
if (isset($this->mergeCells[$range])) {
|
if (isset($this->mergeCells[$range])) {
|
||||||
|
|
@ -2013,7 +1943,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function protectCells($range, $password, $alreadyHashed = false)
|
public function protectCells($range, $password, $alreadyHashed = false)
|
||||||
{
|
{
|
||||||
$range = Functions::trimSheetFromCellReference($this->validateCellOrCellRange($range));
|
$range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range));
|
||||||
|
|
||||||
if (!$alreadyHashed) {
|
if (!$alreadyHashed) {
|
||||||
$password = Shared\PasswordHasher::hashPassword($password);
|
$password = Shared\PasswordHasher::hashPassword($password);
|
||||||
|
|
@ -2061,7 +1991,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function unprotectCells($range)
|
public function unprotectCells($range)
|
||||||
{
|
{
|
||||||
$range = Functions::trimSheetFromCellReference($this->validateCellOrCellRange($range));
|
$range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range));
|
||||||
|
|
||||||
if (isset($this->protectedCells[$range])) {
|
if (isset($this->protectedCells[$range])) {
|
||||||
unset($this->protectedCells[$range]);
|
unset($this->protectedCells[$range]);
|
||||||
|
|
@ -2132,7 +2062,7 @@ class Worksheet implements IComparable
|
||||||
if (is_object($autoFilterOrRange) && ($autoFilterOrRange instanceof AutoFilter)) {
|
if (is_object($autoFilterOrRange) && ($autoFilterOrRange instanceof AutoFilter)) {
|
||||||
$this->autoFilter = $autoFilterOrRange;
|
$this->autoFilter = $autoFilterOrRange;
|
||||||
} else {
|
} else {
|
||||||
$cellRange = Functions::trimSheetFromCellReference($this->validateCellRange($autoFilterOrRange));
|
$cellRange = Functions::trimSheetFromCellReference(Validations::validateCellRange($autoFilterOrRange));
|
||||||
|
|
||||||
$this->autoFilter->setRange($cellRange);
|
$this->autoFilter->setRange($cellRange);
|
||||||
}
|
}
|
||||||
|
|
@ -2278,13 +2208,13 @@ class Worksheet implements IComparable
|
||||||
public function freezePane($coordinate, $topLeftCell = null)
|
public function freezePane($coordinate, $topLeftCell = null)
|
||||||
{
|
{
|
||||||
$cellAddress = ($coordinate !== null)
|
$cellAddress = ($coordinate !== null)
|
||||||
? Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate))
|
? Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate))
|
||||||
: null;
|
: null;
|
||||||
if ($cellAddress !== null && Coordinate::coordinateIsRange($cellAddress)) {
|
if ($cellAddress !== null && Coordinate::coordinateIsRange($cellAddress)) {
|
||||||
throw new Exception('Freeze pane can not be set on a range of cells.');
|
throw new Exception('Freeze pane can not be set on a range of cells.');
|
||||||
}
|
}
|
||||||
$topLeftCell = ($topLeftCell !== null)
|
$topLeftCell = ($topLeftCell !== null)
|
||||||
? Functions::trimSheetFromCellReference($this->validateCellAddress($topLeftCell))
|
? Functions::trimSheetFromCellReference(Validations::validateCellAddress($topLeftCell))
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if ($cellAddress !== null && $topLeftCell === null) {
|
if ($cellAddress !== null && $topLeftCell === null) {
|
||||||
|
|
@ -2688,7 +2618,7 @@ class Worksheet implements IComparable
|
||||||
*/
|
*/
|
||||||
public function getComment($cellCoordinate)
|
public function getComment($cellCoordinate)
|
||||||
{
|
{
|
||||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($cellCoordinate));
|
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate));
|
||||||
|
|
||||||
if (Coordinate::coordinateIsRange($cellAddress)) {
|
if (Coordinate::coordinateIsRange($cellAddress)) {
|
||||||
throw new Exception('Cell coordinate string can not be a range of cells.');
|
throw new Exception('Cell coordinate string can not be a range of cells.');
|
||||||
|
|
@ -2759,22 +2689,6 @@ class Worksheet implements IComparable
|
||||||
return $this->setSelectedCells($coordinate);
|
return $this->setSelectedCells($coordinate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sigh - Phpstan thinks, correctly, that preg_replace can return null.
|
|
||||||
* But Scrutinizer doesn't. Try to satisfy both.
|
|
||||||
*
|
|
||||||
* @param mixed $str
|
|
||||||
*/
|
|
||||||
private static function ensureString($str): string
|
|
||||||
{
|
|
||||||
return is_string($str) ? $str : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function pregReplace(string $pattern, string $replacement, string $subject): string
|
|
||||||
{
|
|
||||||
return self::ensureString(preg_replace($pattern, $replacement, $subject));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a range of cells.
|
* Select a range of cells.
|
||||||
*
|
*
|
||||||
|
|
@ -2787,9 +2701,9 @@ class Worksheet implements IComparable
|
||||||
public function setSelectedCells($coordinate)
|
public function setSelectedCells($coordinate)
|
||||||
{
|
{
|
||||||
if (is_string($coordinate)) {
|
if (is_string($coordinate)) {
|
||||||
$coordinate = $this->tryDefinedName($coordinate);
|
$coordinate = Validations::definedNameToCoordinate($coordinate, $this);
|
||||||
}
|
}
|
||||||
$coordinate = $this->validateCellOrCellRange($coordinate);
|
$coordinate = Validations::validateCellOrCellRange($coordinate);
|
||||||
|
|
||||||
if (Coordinate::coordinateIsRange($coordinate)) {
|
if (Coordinate::coordinateIsRange($coordinate)) {
|
||||||
[$first] = Coordinate::splitRange($coordinate);
|
[$first] = Coordinate::splitRange($coordinate);
|
||||||
|
|
|
||||||
|
|
@ -132,10 +132,11 @@ class Ods extends BaseWriter
|
||||||
|
|
||||||
$zip->addFile('META-INF/manifest.xml', $this->getWriterPartMetaInf()->write());
|
$zip->addFile('META-INF/manifest.xml', $this->getWriterPartMetaInf()->write());
|
||||||
$zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPartthumbnails()->write());
|
$zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPartthumbnails()->write());
|
||||||
|
// Settings always need to be written before Content; Styles after Content
|
||||||
|
$zip->addFile('settings.xml', $this->getWriterPartsettings()->write());
|
||||||
$zip->addFile('content.xml', $this->getWriterPartcontent()->write());
|
$zip->addFile('content.xml', $this->getWriterPartcontent()->write());
|
||||||
$zip->addFile('meta.xml', $this->getWriterPartmeta()->write());
|
$zip->addFile('meta.xml', $this->getWriterPartmeta()->write());
|
||||||
$zip->addFile('mimetype', $this->getWriterPartmimetype()->write());
|
$zip->addFile('mimetype', $this->getWriterPartmimetype()->write());
|
||||||
$zip->addFile('settings.xml', $this->getWriterPartsettings()->write());
|
|
||||||
$zip->addFile('styles.xml', $this->getWriterPartstyles()->write());
|
$zip->addFile('styles.xml', $this->getWriterPartstyles()->write());
|
||||||
|
|
||||||
// Close file
|
// Close file
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,20 @@
|
||||||
|
|
||||||
namespace PhpOffice\PhpSpreadsheet\Writer\Ods\Cell;
|
namespace PhpOffice\PhpSpreadsheet\Writer\Ods\Cell;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Helper\Dimension;
|
||||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||||
use PhpOffice\PhpSpreadsheet\Style\Style as CellStyle;
|
use PhpOffice\PhpSpreadsheet\Style\Style as CellStyle;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\RowDimension;
|
||||||
|
|
||||||
class Style
|
class Style
|
||||||
{
|
{
|
||||||
public const CELL_STYLE_PREFIX = 'ce';
|
public const CELL_STYLE_PREFIX = 'ce';
|
||||||
|
public const COLUMN_STYLE_PREFIX = 'co';
|
||||||
|
public const ROW_STYLE_PREFIX = 'ro';
|
||||||
|
|
||||||
private $writer;
|
private $writer;
|
||||||
|
|
||||||
|
|
@ -159,6 +164,63 @@ class Style
|
||||||
$this->writer->endElement(); // Close style:text-properties
|
$this->writer->endElement(); // Close style:text-properties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function writeColumnProperties(ColumnDimension $columnDimension): void
|
||||||
|
{
|
||||||
|
$this->writer->startElement('style:table-column-properties');
|
||||||
|
$this->writer->writeAttribute(
|
||||||
|
'style:column-width',
|
||||||
|
round($columnDimension->getWidth(Dimension::UOM_CENTIMETERS), 3) . 'cm'
|
||||||
|
);
|
||||||
|
$this->writer->writeAttribute('fo:break-before', 'auto');
|
||||||
|
|
||||||
|
// End
|
||||||
|
$this->writer->endElement(); // Close style:table-column-properties
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeColumnStyles(ColumnDimension $columnDimension, int $sheetId): void
|
||||||
|
{
|
||||||
|
$this->writer->startElement('style:style');
|
||||||
|
$this->writer->writeAttribute('style:family', 'table-column');
|
||||||
|
$this->writer->writeAttribute(
|
||||||
|
'style:name',
|
||||||
|
sprintf('%s_%d_%d', self::COLUMN_STYLE_PREFIX, $sheetId, $columnDimension->getColumnNumeric())
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->writeColumnProperties($columnDimension);
|
||||||
|
|
||||||
|
// End
|
||||||
|
$this->writer->endElement(); // Close style:style
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function writeRowProperties(RowDimension $rowDimension): void
|
||||||
|
{
|
||||||
|
$this->writer->startElement('style:table-row-properties');
|
||||||
|
$this->writer->writeAttribute(
|
||||||
|
'style:row-height',
|
||||||
|
round($rowDimension->getRowHeight(Dimension::UOM_CENTIMETERS), 3) . 'cm'
|
||||||
|
);
|
||||||
|
$this->writer->writeAttribute('style:use-optimal-row-height', 'true');
|
||||||
|
$this->writer->writeAttribute('fo:break-before', 'auto');
|
||||||
|
|
||||||
|
// End
|
||||||
|
$this->writer->endElement(); // Close style:table-row-properties
|
||||||
|
}
|
||||||
|
|
||||||
|
public function writeRowStyles(RowDimension $rowDimension, int $sheetId): void
|
||||||
|
{
|
||||||
|
$this->writer->startElement('style:style');
|
||||||
|
$this->writer->writeAttribute('style:family', 'table-row');
|
||||||
|
$this->writer->writeAttribute(
|
||||||
|
'style:name',
|
||||||
|
sprintf('%s_%d_%d', self::ROW_STYLE_PREFIX, $sheetId, $rowDimension->getRowIndex())
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->writeRowProperties($rowDimension);
|
||||||
|
|
||||||
|
// End
|
||||||
|
$this->writer->endElement(); // Close style:style
|
||||||
|
}
|
||||||
|
|
||||||
public function write(CellStyle $style): void
|
public function write(CellStyle $style): void
|
||||||
{
|
{
|
||||||
$this->writer->startElement('style:style');
|
$this->writer->startElement('style:style');
|
||||||
|
|
|
||||||
|
|
@ -119,14 +119,21 @@ class Content extends WriterPart
|
||||||
{
|
{
|
||||||
$spreadsheet = $this->getParentWriter()->getSpreadsheet(); /** @var Spreadsheet $spreadsheet */
|
$spreadsheet = $this->getParentWriter()->getSpreadsheet(); /** @var Spreadsheet $spreadsheet */
|
||||||
$sheetCount = $spreadsheet->getSheetCount();
|
$sheetCount = $spreadsheet->getSheetCount();
|
||||||
for ($i = 0; $i < $sheetCount; ++$i) {
|
for ($sheetIndex = 0; $sheetIndex < $sheetCount; ++$sheetIndex) {
|
||||||
$objWriter->startElement('table:table');
|
$objWriter->startElement('table:table');
|
||||||
$objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle());
|
$objWriter->writeAttribute('table:name', $spreadsheet->getSheet($sheetIndex)->getTitle());
|
||||||
$objWriter->writeElement('office:forms');
|
$objWriter->writeElement('office:forms');
|
||||||
$objWriter->startElement('table:table-column');
|
foreach ($spreadsheet->getSheet($sheetIndex)->getColumnDimensions() as $columnDimension) {
|
||||||
$objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
|
$objWriter->startElement('table:table-column');
|
||||||
$objWriter->endElement();
|
$objWriter->writeAttribute(
|
||||||
$this->writeRows($objWriter, $spreadsheet->getSheet($i));
|
'table:style-name',
|
||||||
|
sprintf('%s_%d_%d', Style::COLUMN_STYLE_PREFIX, $sheetIndex, $columnDimension->getColumnNumeric())
|
||||||
|
);
|
||||||
|
$objWriter->writeAttribute('table:default-cell-style-name', 'ce0');
|
||||||
|
// $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
|
||||||
|
$objWriter->endElement();
|
||||||
|
}
|
||||||
|
$this->writeRows($objWriter, $spreadsheet->getSheet($sheetIndex), $sheetIndex);
|
||||||
$objWriter->endElement();
|
$objWriter->endElement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +141,7 @@ class Content extends WriterPart
|
||||||
/**
|
/**
|
||||||
* Write rows of the specified sheet.
|
* Write rows of the specified sheet.
|
||||||
*/
|
*/
|
||||||
private function writeRows(XMLWriter $objWriter, Worksheet $sheet): void
|
private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetIndex): void
|
||||||
{
|
{
|
||||||
$numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX;
|
$numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX;
|
||||||
$span_row = 0;
|
$span_row = 0;
|
||||||
|
|
@ -148,8 +155,14 @@ class Content extends WriterPart
|
||||||
if ($span_row > 1) {
|
if ($span_row > 1) {
|
||||||
$objWriter->writeAttribute('table:number-rows-repeated', $span_row);
|
$objWriter->writeAttribute('table:number-rows-repeated', $span_row);
|
||||||
}
|
}
|
||||||
|
if ($sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0) {
|
||||||
|
$objWriter->writeAttribute(
|
||||||
|
'table:style_name',
|
||||||
|
sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex())
|
||||||
|
);
|
||||||
|
}
|
||||||
$objWriter->startElement('table:table-cell');
|
$objWriter->startElement('table:table-cell');
|
||||||
$objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
|
$objWriter->writeAttribute('table:number-columns-repeated', (string) self::NUMBER_COLS_REPEATED_MAX);
|
||||||
$objWriter->endElement();
|
$objWriter->endElement();
|
||||||
$objWriter->endElement();
|
$objWriter->endElement();
|
||||||
$span_row = 0;
|
$span_row = 0;
|
||||||
|
|
@ -275,6 +288,26 @@ class Content extends WriterPart
|
||||||
private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void
|
private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void
|
||||||
{
|
{
|
||||||
$styleWriter = new Style($writer);
|
$styleWriter = new Style($writer);
|
||||||
|
|
||||||
|
$sheetCount = $spreadsheet->getSheetCount();
|
||||||
|
for ($i = 0; $i < $sheetCount; ++$i) {
|
||||||
|
$worksheet = $spreadsheet->getSheet($i);
|
||||||
|
$worksheet->calculateColumnWidths();
|
||||||
|
foreach ($worksheet->getColumnDimensions() as $columnDimension) {
|
||||||
|
if ($columnDimension->getWidth() !== -1.0) {
|
||||||
|
$styleWriter->writeColumnStyles($columnDimension, $i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $sheetCount; ++$i) {
|
||||||
|
$worksheet = $spreadsheet->getSheet($i);
|
||||||
|
foreach ($worksheet->getRowDimensions() as $rowDimension) {
|
||||||
|
if ($rowDimension->getRowHeight() > 0.0) {
|
||||||
|
$styleWriter->writeRowStyles($rowDimension, $i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($spreadsheet->getCellXfCollection() as $style) {
|
foreach ($spreadsheet->getCellXfCollection() as $style) {
|
||||||
$styleWriter->write($style);
|
$styleWriter->write($style);
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +329,7 @@ class Content extends WriterPart
|
||||||
$columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
|
$columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
|
||||||
$rowSpan = ((int) $end[1]) - ((int) $start[1]) + 1;
|
$rowSpan = ((int) $end[1]) - ((int) $start[1]) + 1;
|
||||||
|
|
||||||
$objWriter->writeAttribute('table:number-columns-spanned', $columnSpan);
|
$objWriter->writeAttribute('table:number-columns-spanned', (string) $columnSpan);
|
||||||
$objWriter->writeAttribute('table:number-rows-spanned', $rowSpan);
|
$objWriter->writeAttribute('table:number-rows-spanned', (string) $rowSpan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
|
namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
|
||||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
|
||||||
class Settings extends WriterPart
|
class Settings extends WriterPart
|
||||||
{
|
{
|
||||||
|
|
@ -45,28 +48,9 @@ class Settings extends WriterPart
|
||||||
$objWriter->text('view1');
|
$objWriter->text('view1');
|
||||||
$objWriter->endElement(); // ViewId
|
$objWriter->endElement(); // ViewId
|
||||||
$objWriter->startElement('config:config-item-map-named');
|
$objWriter->startElement('config:config-item-map-named');
|
||||||
$objWriter->writeAttribute('config:name', 'Tables');
|
|
||||||
foreach ($spreadsheet->getWorksheetIterator() as $ws) {
|
$this->writeAllWorksheetSettings($objWriter, $spreadsheet);
|
||||||
$objWriter->startElement('config:config-item-map-entry');
|
|
||||||
$objWriter->writeAttribute('config:name', $ws->getTitle());
|
|
||||||
$selected = $ws->getSelectedCells();
|
|
||||||
if (preg_match('/^([a-z]+)([0-9]+)/i', $selected, $matches) === 1) {
|
|
||||||
$colSel = Coordinate::columnIndexFromString($matches[1]) - 1;
|
|
||||||
$rowSel = (int) $matches[2] - 1;
|
|
||||||
$objWriter->startElement('config:config-item');
|
|
||||||
$objWriter->writeAttribute('config:name', 'CursorPositionX');
|
|
||||||
$objWriter->writeAttribute('config:type', 'int');
|
|
||||||
$objWriter->text($colSel);
|
|
||||||
$objWriter->endElement();
|
|
||||||
$objWriter->startElement('config:config-item');
|
|
||||||
$objWriter->writeAttribute('config:name', 'CursorPositionY');
|
|
||||||
$objWriter->writeAttribute('config:type', 'int');
|
|
||||||
$objWriter->text($rowSel);
|
|
||||||
$objWriter->endElement();
|
|
||||||
}
|
|
||||||
$objWriter->endElement(); // config:config-item-map-entry
|
|
||||||
}
|
|
||||||
$objWriter->endElement(); // config:config-item-map-named
|
|
||||||
$wstitle = $spreadsheet->getActiveSheet()->getTitle();
|
$wstitle = $spreadsheet->getActiveSheet()->getTitle();
|
||||||
$objWriter->startElement('config:config-item');
|
$objWriter->startElement('config:config-item');
|
||||||
$objWriter->writeAttribute('config:name', 'ActiveTable');
|
$objWriter->writeAttribute('config:name', 'ActiveTable');
|
||||||
|
|
@ -85,4 +69,84 @@ class Settings extends WriterPart
|
||||||
|
|
||||||
return $objWriter->getData();
|
return $objWriter->getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function writeAllWorksheetSettings(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
|
||||||
|
{
|
||||||
|
$objWriter->writeAttribute('config:name', 'Tables');
|
||||||
|
|
||||||
|
foreach ($spreadsheet->getWorksheetIterator() as $worksheet) {
|
||||||
|
$this->writeWorksheetSettings($objWriter, $worksheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
$objWriter->endElement(); // config:config-item-map-entry Tables
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeWorksheetSettings(XMLWriter $objWriter, Worksheet $worksheet): void
|
||||||
|
{
|
||||||
|
$objWriter->startElement('config:config-item-map-entry');
|
||||||
|
$objWriter->writeAttribute('config:name', $worksheet->getTitle());
|
||||||
|
|
||||||
|
$this->writeSelectedCells($objWriter, $worksheet);
|
||||||
|
if ($worksheet->getFreezePane() !== null) {
|
||||||
|
$this->writeFreezePane($objWriter, $worksheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
$objWriter->endElement(); // config:config-item-map-entry Worksheet
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeSelectedCells(XMLWriter $objWriter, Worksheet $worksheet): void
|
||||||
|
{
|
||||||
|
$selected = $worksheet->getSelectedCells();
|
||||||
|
if (preg_match('/^([a-z]+)([0-9]+)/i', $selected, $matches) === 1) {
|
||||||
|
$colSel = Coordinate::columnIndexFromString($matches[1]) - 1;
|
||||||
|
$rowSel = (int) $matches[2] - 1;
|
||||||
|
$objWriter->startElement('config:config-item');
|
||||||
|
$objWriter->writeAttribute('config:name', 'CursorPositionX');
|
||||||
|
$objWriter->writeAttribute('config:type', 'int');
|
||||||
|
$objWriter->text((string) $colSel);
|
||||||
|
$objWriter->endElement();
|
||||||
|
$objWriter->startElement('config:config-item');
|
||||||
|
$objWriter->writeAttribute('config:name', 'CursorPositionY');
|
||||||
|
$objWriter->writeAttribute('config:type', 'int');
|
||||||
|
$objWriter->text((string) $rowSel);
|
||||||
|
$objWriter->endElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeSplitValue(XMLWriter $objWriter, string $splitMode, string $type, string $value): void
|
||||||
|
{
|
||||||
|
$objWriter->startElement('config:config-item');
|
||||||
|
$objWriter->writeAttribute('config:name', $splitMode);
|
||||||
|
$objWriter->writeAttribute('config:type', $type);
|
||||||
|
$objWriter->text($value);
|
||||||
|
$objWriter->endElement();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function writeFreezePane(XMLWriter $objWriter, Worksheet $worksheet): void
|
||||||
|
{
|
||||||
|
$freezePane = CellAddress::fromCellAddress($worksheet->getFreezePane());
|
||||||
|
if ($freezePane->cellAddress() === 'A1') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$columnId = $freezePane->columnId();
|
||||||
|
$columnName = $freezePane->columnName();
|
||||||
|
$row = $freezePane->rowId();
|
||||||
|
|
||||||
|
$this->writeSplitValue($objWriter, 'HorizontalSplitMode', 'short', '2');
|
||||||
|
$this->writeSplitValue($objWriter, 'HorizontalSplitPosition', 'int', (string) ($columnId - 1));
|
||||||
|
$this->writeSplitValue($objWriter, 'PositionLeft', 'short', '0');
|
||||||
|
$this->writeSplitValue($objWriter, 'PositionRight', 'short', (string) ($columnId - 1));
|
||||||
|
|
||||||
|
for ($column = 'A'; $column !== $columnName; ++$column) {
|
||||||
|
$worksheet->getColumnDimension($column)->setAutoSize(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeSplitValue($objWriter, 'VerticalSplitMode', 'short', '2');
|
||||||
|
$this->writeSplitValue($objWriter, 'VerticalSplitPosition', 'int', (string) ($row - 1));
|
||||||
|
$this->writeSplitValue($objWriter, 'PositionTop', 'short', '0');
|
||||||
|
$this->writeSplitValue($objWriter, 'PositionBottom', 'short', (string) ($row - 1));
|
||||||
|
|
||||||
|
$this->writeSplitValue($objWriter, 'ActiveSplitRange', 'short', '3');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace PhpOffice\PhpSpreadsheetTests\Document;
|
namespace PhpOffice\PhpSpreadsheetTests\Document;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
use DateTimeZone;
|
use DateTimeZone;
|
||||||
use PhpOffice\PhpSpreadsheet\Document\Properties;
|
use PhpOffice\PhpSpreadsheet\Document\Properties;
|
||||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||||
|
|
@ -14,20 +15,27 @@ class PropertiesTest extends TestCase
|
||||||
*/
|
*/
|
||||||
private $properties;
|
private $properties;
|
||||||
|
|
||||||
|
/** @var float */
|
||||||
|
private $startTime;
|
||||||
|
|
||||||
protected function setup(): void
|
protected function setup(): void
|
||||||
{
|
{
|
||||||
$this->properties = new Properties();
|
do {
|
||||||
|
// loop to avoid rare situation where timestamp changes
|
||||||
|
$this->startTime = (float) (new DateTime())->format('U');
|
||||||
|
$this->properties = new Properties();
|
||||||
|
$endTime = (float) (new DateTime())->format('U');
|
||||||
|
} while ($this->startTime !== $endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNewInstance(): void
|
public function testNewInstance(): void
|
||||||
{
|
{
|
||||||
$createdTime = $modifiedTime = time();
|
|
||||||
self::assertSame('Unknown Creator', $this->properties->getCreator());
|
self::assertSame('Unknown Creator', $this->properties->getCreator());
|
||||||
self::assertSame('Unknown Creator', $this->properties->getLastModifiedBy());
|
self::assertSame('Unknown Creator', $this->properties->getLastModifiedBy());
|
||||||
self::assertSame('Untitled Spreadsheet', $this->properties->getTitle());
|
self::assertSame('Untitled Spreadsheet', $this->properties->getTitle());
|
||||||
self::assertSame('', $this->properties->getCompany());
|
self::assertSame('', $this->properties->getCompany());
|
||||||
self::assertSame($createdTime, $this->properties->getCreated());
|
self::assertEquals($this->startTime, $this->properties->getCreated());
|
||||||
self::assertSame($modifiedTime, $this->properties->getModified());
|
self::assertEquals($this->startTime, $this->properties->getModified());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetCreator(): void
|
public function testSetCreator(): void
|
||||||
|
|
@ -46,10 +54,10 @@ class PropertiesTest extends TestCase
|
||||||
*/
|
*/
|
||||||
public function testSetCreated($expectedCreationTime, $created): void
|
public function testSetCreated($expectedCreationTime, $created): void
|
||||||
{
|
{
|
||||||
$expectedCreationTime = $expectedCreationTime ?? time();
|
$expectedCreationTime = $expectedCreationTime ?? $this->startTime;
|
||||||
|
|
||||||
$this->properties->setCreated($created);
|
$this->properties->setCreated($created);
|
||||||
self::assertSame($expectedCreationTime, $this->properties->getCreated());
|
self::assertEquals($expectedCreationTime, $this->properties->getCreated());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function providerCreationTime(): array
|
public function providerCreationTime(): array
|
||||||
|
|
@ -78,10 +86,10 @@ class PropertiesTest extends TestCase
|
||||||
*/
|
*/
|
||||||
public function testSetModified($expectedModifiedTime, $modified): void
|
public function testSetModified($expectedModifiedTime, $modified): void
|
||||||
{
|
{
|
||||||
$expectedModifiedTime = $expectedModifiedTime ?? time();
|
$expectedModifiedTime = $expectedModifiedTime ?? $this->startTime;
|
||||||
|
|
||||||
$this->properties->setModified($modified);
|
$this->properties->setModified($modified);
|
||||||
self::assertSame($expectedModifiedTime, $this->properties->getModified());
|
self::assertEquals($expectedModifiedTime, $this->properties->getModified());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function providerModifiedTime(): array
|
public function providerModifiedTime(): array
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,22 @@ class IOFactoryTest extends TestCase
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFormatAsExpected(): void
|
||||||
|
{
|
||||||
|
$fileName = 'samples/templates/30template.xls';
|
||||||
|
|
||||||
|
$actual = IOFactory::identify($fileName, [IOFactory::READER_XLS]);
|
||||||
|
self::assertSame('Xls', $actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFormatNotAsExpectedThrowsException(): void
|
||||||
|
{
|
||||||
|
$fileName = 'samples/templates/30template.xls';
|
||||||
|
|
||||||
|
$this->expectException(ReaderException::class);
|
||||||
|
IOFactory::identify($fileName, [IOFactory::READER_ODS]);
|
||||||
|
}
|
||||||
|
|
||||||
public function testIdentifyNonExistingFileThrowException(): void
|
public function testIdentifyNonExistingFileThrowException(): void
|
||||||
{
|
{
|
||||||
$this->expectException(ReaderException::class);
|
$this->expectException(ReaderException::class);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xls;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Reader\Xls;
|
||||||
|
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
|
||||||
|
|
||||||
|
class RichTextSizeTest extends AbstractFunctional
|
||||||
|
{
|
||||||
|
public function testRichTextRunSize(): void
|
||||||
|
{
|
||||||
|
$filename = 'tests/data/Reader/XLS/RichTextFontSize.xls';
|
||||||
|
$reader = new Xls();
|
||||||
|
$spreadsheet = $reader->load($filename);
|
||||||
|
$sheet = $spreadsheet->getSheetByName('橱柜门板');
|
||||||
|
self::assertNotNull($sheet);
|
||||||
|
$text = $sheet->getCell('L15')->getValue();
|
||||||
|
$elements = $text->getRichTextElements();
|
||||||
|
self::assertEquals(10, $elements[2]->getFont()->getSize());
|
||||||
|
$spreadsheet->disconnectWorksheets();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
|
||||||
|
|
||||||
|
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class XlsxRootZipFilesTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private static $testbook = 'tests/data/Reader/XLSX/rootZipFiles.xlsx';
|
||||||
|
|
||||||
|
public function testXlsxRootZipFiles(): void
|
||||||
|
{
|
||||||
|
$filename = self::$testbook;
|
||||||
|
$reader = new Xlsx();
|
||||||
|
$spreadsheet = $reader->load($filename);
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
$value = $sheet->getCell('A1')->getValue();
|
||||||
|
self::assertSame('TEST CELL', $value->getPlainText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ class PreCalcTest extends AbstractFunctional
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const AUTOSIZE_TYPES = ['Xlsx', 'Xls', 'Html'];
|
private const AUTOSIZE_TYPES = ['Xlsx', 'Xls', 'Html', 'Ods'];
|
||||||
|
|
||||||
private static function verifyA3B2(Calculation $calculation, string $title, ?bool $preCalc, string $type): void
|
private static function verifyA3B2(Calculation $calculation, string $title, ?bool $preCalc, string $type): void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -14,7 +14,6 @@
|
||||||
<table:calculation-settings />
|
<table:calculation-settings />
|
||||||
<table:table table:name="Worksheet">
|
<table:table table:name="Worksheet">
|
||||||
<office:forms />
|
<office:forms />
|
||||||
<table:table-column table:number-columns-repeated="1024" />
|
|
||||||
<table:table-row>
|
<table:table-row>
|
||||||
<table:table-cell table:style-name="ce0" />
|
<table:table-cell table:style-name="ce0" />
|
||||||
<table:table-cell table:number-columns-repeated="1023" />
|
<table:table-cell table:number-columns-repeated="1023" />
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,6 @@
|
||||||
<table:calculation-settings/>
|
<table:calculation-settings/>
|
||||||
<table:table table:name="Worksheet">
|
<table:table table:name="Worksheet">
|
||||||
<office:forms/>
|
<office:forms/>
|
||||||
<table:table-column table:number-columns-repeated="1024"/>
|
|
||||||
<table:table-row>
|
<table:table-row>
|
||||||
<table:table-cell office:value="1" office:value-type="float" table:style-name="ce2">
|
<table:table-cell office:value="1" office:value-type="float" table:style-name="ce2">
|
||||||
<text:p>1</text:p>
|
<text:p>1</text:p>
|
||||||
|
|
@ -110,7 +109,6 @@
|
||||||
</table:table>
|
</table:table>
|
||||||
<table:table table:name="New Worksheet">
|
<table:table table:name="New Worksheet">
|
||||||
<office:forms/>
|
<office:forms/>
|
||||||
<table:table-column table:number-columns-repeated="1024"/>
|
|
||||||
<table:table-row>
|
<table:table-row>
|
||||||
<table:table-cell office:value="2" office:value-type="float" table:style-name="ce0">
|
<table:table-cell office:value="2" office:value-type="float" table:style-name="ce0">
|
||||||
<text:p>2</text:p>
|
<text:p>2</text:p>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue