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...
|
||||
```
|
||||
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?
|
||||
|
|
|
|||
|
|
@ -3,14 +3,21 @@ This is:
|
|||
```
|
||||
- [ ] a bugfix
|
||||
- [ ] a new feature
|
||||
- [ ] refactoring
|
||||
- [ ] additional unit tests
|
||||
```
|
||||
|
||||
Checklist:
|
||||
|
||||
- [ ] Changes are covered by unit tests
|
||||
- [ ] Changes are covered by existing unit tests
|
||||
- [ ] New unit tests have been added
|
||||
- [ ] Code style is respected
|
||||
- [ ] 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
|
||||
- [ ] Documentation is updated as necessary
|
||||
|
||||
### 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
|
||||
|
||||
- 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.
|
||||
- Implementation of the FILTER(), SORT(), SORTBY() and UNIQUE() Lookup/Reference (array) functions.
|
||||
- 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.
|
||||
|
||||
- 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:
|
||||
|
||||
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
|
||||
|
||||
- Gnumeric Reader now loads number formatting for cells.
|
||||
|
|
@ -66,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
|
|||
|
||||
### 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)
|
||||
- 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)
|
||||
|
|
@ -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 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)
|
||||
- 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.
|
||||
|
||||
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.
|
||||
- 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)
|
||||
|
||||
|
||||
## 1.22.0 - 2022-02-18
|
||||
|
||||
### 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
|
||||
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
|
||||
"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
|
||||
late 1999, with further errata published through 2001. In 2004
|
||||
development began on HTML5 in the Web Hypertext Application Technology
|
||||
Working Group (WHATWG), which became a joint deliverable with the W3C in
|
||||
2008.
|
||||
Working Group (WHATWG), which became a joint deliverable with the W3C in 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
|
||||
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
|
||||
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
|
||||
|
|
@ -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`
|
||||
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
|
||||
|
||||
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`
|
||||
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
|
||||
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
|
||||
|
|
@ -162,8 +206,8 @@ Reading Only Data from a Spreadsheet File applies to Readers:
|
|||
|
||||
Reader | Y/N |Reader | Y/N |Reader | Y/N |
|
||||
----------|:---:|--------|:---:|--------------|:---:|
|
||||
Xlsx | YES | Xls | YES | Xml | YES |
|
||||
Ods | YES | SYLK | NO | Gnumeric | YES |
|
||||
Xlsx | YES | Xls | YES | Xml | YES |
|
||||
Ods | YES | SYLK | NO | Gnumeric | YES |
|
||||
CSV | NO | HTML | NO
|
||||
|
||||
### 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 |
|
||||
----------|:---:|--------|:---:|--------------|:---:|
|
||||
Xlsx | YES | Xls | YES | Xml | YES |
|
||||
Ods | YES | SYLK | NO | Gnumeric | YES |
|
||||
Xlsx | YES | Xls | YES | Xml | YES |
|
||||
Ods | YES | SYLK | NO | Gnumeric | YES |
|
||||
CSV | NO | HTML | NO
|
||||
|
||||
### 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 |
|
||||
----------|:---:|--------|:---:|--------------|:---:|
|
||||
Xlsx | YES | Xls | YES | Xml | YES |
|
||||
Xlsx | YES | Xls | YES | Xml | YES |
|
||||
Ods | YES | SYLK | NO | Gnumeric | YES |
|
||||
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 |
|
||||
----------|:---:|--------|:---:|--------------|:---:|
|
||||
Xlsx | NO | Xls | NO | Xml | NO |
|
||||
Xlsx | NO | Xls | NO | Xml | NO |
|
||||
Ods | NO | SYLK | YES | Gnumeric | 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 |
|
||||
----------|:---:|--------|:---:|--------------|:---:|
|
||||
Xlsx | NO | Xls | NO | Xml | NO |
|
||||
Xlsx | NO | Xls | NO | Xml | NO |
|
||||
Ods | NO | SYLK | NO | Gnumeric | NO |
|
||||
CSV | YES | HTML | NO
|
||||
|
||||
|
|
@ -556,7 +600,7 @@ Setting CSV delimiter applies to:
|
|||
|
||||
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 |
|
||||
CSV | YES | HTML | NO
|
||||
|
||||
|
|
@ -594,7 +638,7 @@ Applies to:
|
|||
|
||||
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 |
|
||||
CSV | YES | HTML | NO
|
||||
|
||||
|
|
@ -646,7 +690,7 @@ Loading using a Value Binder applies to:
|
|||
|
||||
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
|
||||
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),
|
||||
`cm` (centimeters) and `mm` (millimeters).
|
||||
|
||||
Setting the column width to `-1` tells MS Excel to display the column using its default width.
|
||||
|
||||
```php
|
||||
$spreadsheet->getActiveSheet()->getColumnDimension('D')->setWidth(120, 'pt');
|
||||
```
|
||||
|
||||
If you want PhpSpreadsheet to perform an automatic width calculation,
|
||||
use the following code. PhpSpreadsheet will approximate the column with
|
||||
to the width of the widest column value.
|
||||
use the following code. PhpSpreadsheet will approximate the column width
|
||||
to the width of the widest value displayed in that column.
|
||||
|
||||
```php
|
||||
$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');
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
To set a worksheet''s row visibility, you can use the following code.
|
||||
|
|
|
|||
|
|
@ -1175,11 +1175,6 @@ parameters:
|
|||
count: 1
|
||||
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\\.$#"
|
||||
count: 1
|
||||
|
|
@ -3120,11 +3115,6 @@ parameters:
|
|||
count: 1
|
||||
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\\.$#"
|
||||
count: 1
|
||||
|
|
@ -4675,11 +4665,6 @@ parameters:
|
|||
count: 1
|
||||
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\\.$#"
|
||||
count: 3
|
||||
|
|
@ -4695,11 +4680,6 @@ parameters:
|
|||
count: 1
|
||||
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\\.$#"
|
||||
count: 1
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ namespace PhpOffice\PhpSpreadsheet\Calculation;
|
|||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class Functions
|
||||
{
|
||||
|
|
@ -686,12 +685,13 @@ class Functions
|
|||
// Uppercase coordinate
|
||||
$pCoordinatex = strtoupper($coordinate);
|
||||
// Eliminate leading equal sign
|
||||
$pCoordinatex = Worksheet::pregReplace('/^=/', '', $pCoordinatex);
|
||||
$pCoordinatex = (string) preg_replace('/^=/', '', $pCoordinatex);
|
||||
$defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet);
|
||||
if ($defined !== null) {
|
||||
$worksheet2 = $defined->getWorkSheet();
|
||||
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
|
||||
{
|
||||
return Worksheet::pregReplace('/:[\\w\$]+$/', '', $coordinate);
|
||||
return (string) preg_replace('/:[\\w\$]+$/', '', $coordinate);
|
||||
}
|
||||
|
||||
public static function trimSheetFromCellReference(string $coordinate): string
|
||||
|
|
|
|||
|
|
@ -383,7 +383,7 @@ abstract class Coordinate
|
|||
// Sort the result by column and row
|
||||
$sortKeys = [];
|
||||
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;
|
||||
}
|
||||
ksort($sortKeys);
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ namespace PhpOffice\PhpSpreadsheet\Collection;
|
|||
|
||||
use Generator;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Cell;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Settings;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
|
|
@ -151,8 +151,6 @@ class Cells
|
|||
{
|
||||
$sortKeys = [];
|
||||
foreach ($this->getCoordinates() as $coord) {
|
||||
$column = '';
|
||||
$row = 0;
|
||||
sscanf($coord, '%[A-Z]%d', $column, $row);
|
||||
$sortKeys[sprintf('%09d%3s', $row, $column)] = $coord;
|
||||
}
|
||||
|
|
@ -172,8 +170,6 @@ class Cells
|
|||
$col = ['A' => '1A'];
|
||||
$row = [1];
|
||||
foreach ($this->getCoordinates() as $coord) {
|
||||
$c = '';
|
||||
$r = 0;
|
||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||
$row[$r] = $r;
|
||||
$col[$c] = strlen($c) . $c;
|
||||
|
|
@ -206,9 +202,6 @@ class Cells
|
|||
*/
|
||||
public function getCurrentColumn()
|
||||
{
|
||||
$column = '';
|
||||
$row = 0;
|
||||
|
||||
sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
|
||||
|
||||
return $column;
|
||||
|
|
@ -221,9 +214,6 @@ class Cells
|
|||
*/
|
||||
public function getCurrentRow()
|
||||
{
|
||||
$column = '';
|
||||
$row = 0;
|
||||
|
||||
sscanf($this->currentCoordinate ?? '', '%[A-Z]%d', $column, $row);
|
||||
|
||||
return (int) $row;
|
||||
|
|
@ -240,24 +230,19 @@ class Cells
|
|||
public function getHighestColumn($row = null)
|
||||
{
|
||||
if ($row === null) {
|
||||
$colRow = $this->getHighestRowAndColumn();
|
||||
|
||||
return $colRow['column'];
|
||||
return $this->getHighestRowAndColumn()['column'];
|
||||
}
|
||||
|
||||
$columnList = [1];
|
||||
$maxColumn = '1A';
|
||||
foreach ($this->getCoordinates() as $coord) {
|
||||
$c = '';
|
||||
$r = 0;
|
||||
|
||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||
if ($r != $row) {
|
||||
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)
|
||||
{
|
||||
if ($column === null) {
|
||||
$colRow = $this->getHighestRowAndColumn();
|
||||
|
||||
return $colRow['row'];
|
||||
return $this->getHighestRowAndColumn()['row'];
|
||||
}
|
||||
|
||||
$rowList = [0];
|
||||
$maxRow = 1;
|
||||
foreach ($this->getCoordinates() as $coord) {
|
||||
$c = '';
|
||||
$r = 0;
|
||||
|
||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||
if ($c != $column) {
|
||||
continue;
|
||||
}
|
||||
$rowList[] = $r;
|
||||
$maxRow = max($maxRow, $r);
|
||||
}
|
||||
|
||||
return max($rowList);
|
||||
return $maxRow;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -298,7 +278,9 @@ class Cells
|
|||
*/
|
||||
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
|
||||
{
|
||||
foreach ($this->getCoordinates() as $coord) {
|
||||
$c = '';
|
||||
$r = 0;
|
||||
|
||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||
if ($r == $row) {
|
||||
$this->delete($coord);
|
||||
|
|
@ -363,9 +342,6 @@ class Cells
|
|||
public function removeColumn($column): void
|
||||
{
|
||||
foreach ($this->getCoordinates() as $coord) {
|
||||
$c = '';
|
||||
$r = 0;
|
||||
|
||||
sscanf($coord, '%[A-Z]%d', $c, $r);
|
||||
if ($c == $column) {
|
||||
$this->delete($coord);
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ class Properties
|
|||
// Initialise values
|
||||
$this->lastModifiedBy = $this->creator;
|
||||
$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
|
||||
{
|
||||
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 = [
|
||||
'Xlsx' => Reader\Xlsx::class,
|
||||
'Xls' => Reader\Xls::class,
|
||||
'Xml' => Reader\Xml::class,
|
||||
'Ods' => Reader\Ods::class,
|
||||
'Slk' => Reader\Slk::class,
|
||||
'Gnumeric' => Reader\Gnumeric::class,
|
||||
'Html' => Reader\Html::class,
|
||||
'Csv' => Reader\Csv::class,
|
||||
self::READER_XLSX => Reader\Xlsx::class,
|
||||
self::READER_XLS => Reader\Xls::class,
|
||||
self::READER_XML => Reader\Xml::class,
|
||||
self::READER_ODS => Reader\Ods::class,
|
||||
self::READER_SLK => Reader\Slk::class,
|
||||
self::READER_GNUMERIC => Reader\Gnumeric::class,
|
||||
self::READER_HTML => Reader\Html::class,
|
||||
self::READER_CSV => Reader\Csv::class,
|
||||
];
|
||||
|
||||
private static $writers = [
|
||||
'Xls' => Writer\Xls::class,
|
||||
'Xlsx' => Writer\Xlsx::class,
|
||||
'Ods' => Writer\Ods::class,
|
||||
'Csv' => Writer\Csv::class,
|
||||
'Html' => Writer\Html::class,
|
||||
self::WRITER_XLS => Writer\Xls::class,
|
||||
self::WRITER_XLSX => Writer\Xlsx::class,
|
||||
self::WRITER_ODS => Writer\Ods::class,
|
||||
self::WRITER_CSV => Writer\Csv::class,
|
||||
self::WRITER_HTML => Writer\Html::class,
|
||||
'Tcpdf' => Writer\Pdf\Tcpdf::class,
|
||||
'Dompdf' => Writer\Pdf\Dompdf::class,
|
||||
'Mpdf' => Writer\Pdf\Mpdf::class,
|
||||
|
|
@ -70,10 +86,18 @@ abstract class IOFactory
|
|||
* Loads Spreadsheet from file using automatic Reader\IReader resolution.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
|
@ -81,9 +105,9 @@ abstract class IOFactory
|
|||
/**
|
||||
* 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);
|
||||
$classType = explode('\\', $className);
|
||||
unset($reader);
|
||||
|
|
@ -93,14 +117,32 @@ abstract class IOFactory
|
|||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
$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
|
||||
$guessedReader = self::getReaderTypeFromExtension($filename);
|
||||
if ($guessedReader !== null) {
|
||||
if (($guessedReader !== null) && array_key_exists($guessedReader, $testReaders)) {
|
||||
$reader = self::createReader($guessedReader);
|
||||
|
||||
// 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
|
||||
// Try walking through all the options in self::$autoResolveClasses
|
||||
foreach (self::$readers as $type => $class) {
|
||||
// Try walking through all the options in self::$readers (or the selected subset)
|
||||
foreach ($testReaders as $readerType => $class) {
|
||||
// Ignore our original guess, we know that won't work
|
||||
if ($type !== $guessedReader) {
|
||||
$reader = self::createReader($type);
|
||||
if ($readerType !== $guessedReader) {
|
||||
$reader = self::createReader($readerType);
|
||||
if ($reader->canRead($filename)) {
|
||||
return $reader;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,6 +153,10 @@ abstract class BaseReader implements IReader
|
|||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3759,8 +3759,13 @@ class Xls extends BaseReader
|
|||
} else {
|
||||
$textRun = $richText->createTextRun($text);
|
||||
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) {
|
||||
$fontIndex = count($this->objFonts) - 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,6 +368,9 @@ class Xlsx extends BaseReader
|
|||
if (strpos($fileName, '//') !== false) {
|
||||
$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);
|
||||
|
||||
// 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)
|
||||
{
|
||||
[$ac, $ar] = sscanf($a, '%[A-Z]%d');
|
||||
[$bc, $br] = sscanf($b, '%[A-Z]%d');
|
||||
sscanf($a, '%[A-Z]%d', $ac, $ar);
|
||||
sscanf($b, '%[A-Z]%d', $bc, $br);
|
||||
|
||||
if ($ar === $br) {
|
||||
return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
|
||||
|
|
@ -112,8 +112,8 @@ class ReferenceHelper
|
|||
*/
|
||||
public static function cellReverseSort($a, $b)
|
||||
{
|
||||
[$ac, $ar] = sscanf($a, '%[A-Z]%d');
|
||||
[$bc, $br] = sscanf($b, '%[A-Z]%d');
|
||||
sscanf($a, '%[A-Z]%d', $ac, $ar);
|
||||
sscanf($b, '%[A-Z]%d', $bc, $br);
|
||||
|
||||
if ($ar === $br) {
|
||||
return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
|
||||
|
|
|
|||
|
|
@ -222,11 +222,15 @@ class Font
|
|||
* @param RichText|string $cellText Text to calculate width
|
||||
* @param int $rotation Rotation angle
|
||||
* @param null|FontStyle $defaultFont Font object
|
||||
*
|
||||
* @return int Column width
|
||||
* @param bool $filterAdjustment Add space for Autofilter or Table dropdown
|
||||
*/
|
||||
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 ($cellText instanceof RichText) {
|
||||
$cellText = $cellText->getPlainText();
|
||||
|
|
@ -237,7 +241,7 @@ class Font
|
|||
$lineTexts = explode("\n", $cellText);
|
||||
$lineWidths = [];
|
||||
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
|
||||
|
|
@ -247,7 +251,13 @@ class Font
|
|||
$approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX;
|
||||
$columnWidth = 0;
|
||||
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 {
|
||||
// Width of text in pixels excl. padding
|
||||
|
|
@ -259,7 +269,11 @@ class Font
|
|||
}
|
||||
|
||||
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
|
||||
// and addition because Excel adds some padding, just use approx width of 'n' glyph
|
||||
$columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use DateTimeZone;
|
|||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Internal\WildcardMatch;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\AddressRange;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as PhpSpreadsheetException;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
|
@ -50,9 +51,18 @@ class 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->workSheet = $worksheet;
|
||||
}
|
||||
|
|
@ -92,12 +102,19 @@ class AutoFilter
|
|||
|
||||
/**
|
||||
* 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;
|
||||
// extract coordinate
|
||||
[$worksheet, $range] = Worksheet::extractSheetTitle($range, true);
|
||||
if ($range !== '') {
|
||||
[, $range] = Worksheet::extractSheetTitle(Validations::validateCellRange($range), true);
|
||||
}
|
||||
if (empty($range)) {
|
||||
// Discard all column rules
|
||||
$this->columns = [];
|
||||
|
|
|
|||
|
|
@ -83,9 +83,10 @@ class ColumnDimension extends Dimension
|
|||
/**
|
||||
* Get Width.
|
||||
*
|
||||
* Each unit of column width is equal to the width of one character in the default font size.
|
||||
* By default, this will be the return value; but this method also accepts a unit of measure argument and will
|
||||
* return the value converted to the specified UoM using an approximation method.
|
||||
* Each unit of column width is equal to the width of one character in the default font size. A value of -1
|
||||
* tells Excel to display this column in its default width.
|
||||
* 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
|
||||
{
|
||||
|
|
@ -97,9 +98,11 @@ class ColumnDimension extends Dimension
|
|||
/**
|
||||
* Set Width.
|
||||
*
|
||||
* Each unit of column width is equal to the width of one character in the default font size.
|
||||
* By default, this will be the unit of measure for the passed value; but this method accepts a unit of measure
|
||||
* argument, and will convert the value from the specified UoM using an approximation method.
|
||||
* Each unit of column width is equal to the width of one character in the default font size. A value of -1
|
||||
* tells Excel to display this column in its default width.
|
||||
* 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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -65,8 +65,9 @@ class RowDimension extends Dimension
|
|||
|
||||
/**
|
||||
* Get Row Height.
|
||||
* By default, this will be in points; but this method accepts a unit of measure
|
||||
* argument, and will convert the value to the specified UoM.
|
||||
* By default, this will be in points; but this method also accepts an optional unit of measure
|
||||
* 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
|
||||
*/
|
||||
|
|
@ -80,8 +81,8 @@ class RowDimension extends Dimension
|
|||
/**
|
||||
* Set Row Height.
|
||||
*
|
||||
* @param float $height in points
|
||||
* By default, this will be the passed argument value; but this method accepts a unit of measure
|
||||
* @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 also accepts an optional unit of measure
|
||||
* argument, and will convert the passed argument value to points from the specified UoM
|
||||
*
|
||||
* @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
|
||||
foreach ($this->getCoordinates(false) as $coordinate) {
|
||||
$cell = $this->getCellOrNull($coordinate);
|
||||
|
||||
if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
|
||||
//Determine if cell is in merge range
|
||||
$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) {
|
||||
// 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
|
||||
// To formatted string
|
||||
$cellValue = NumberFormat::toFormattedString(
|
||||
$cell->getCalculatedValue(),
|
||||
$this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
|
||||
$this->getParent()->getCellXfByIndex($cell->getXfIndex())
|
||||
->getNumberFormat()->getFormatCode()
|
||||
);
|
||||
|
||||
if ($cellValue !== null && $cellValue !== '') {
|
||||
|
|
@ -777,8 +795,10 @@ class Worksheet implements IComparable
|
|||
(float) Shared\Font::calculateColumnWidth(
|
||||
$this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
|
||||
$cellValue,
|
||||
$this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
|
||||
$this->getParent()->getDefaultStyle()->getFont()
|
||||
$this->getParent()->getCellXfByIndex($cell->getXfIndex())
|
||||
->getAlignment()->getTextRotation(),
|
||||
$this->getParent()->getDefaultStyle()->getFont(),
|
||||
$filterAdjustment
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
@ -1057,7 +1077,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function getHighestColumn($row = null)
|
||||
{
|
||||
if (empty($row)) {
|
||||
if ($row === null) {
|
||||
return Coordinate::stringFromColumnIndex($this->cachedHighestColumn);
|
||||
}
|
||||
|
||||
|
|
@ -1087,7 +1107,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function getHighestRow($column = null)
|
||||
{
|
||||
if ($column == null) {
|
||||
if ($column === null) {
|
||||
return $this->cachedHighestRow;
|
||||
}
|
||||
|
||||
|
|
@ -1117,96 +1137,6 @@ class Worksheet implements IComparable
|
|||
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.
|
||||
*
|
||||
|
|
@ -1218,7 +1148,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function setCellValue($coordinate, $value)
|
||||
{
|
||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate));
|
||||
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
|
||||
$this->getCell($cellAddress)->setValue($value);
|
||||
|
||||
return $this;
|
||||
|
|
@ -1256,7 +1186,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function setCellValueExplicit($coordinate, $value, $dataType)
|
||||
{
|
||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate));
|
||||
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
|
||||
$this->getCell($cellAddress)->setValueExplicit($value, $dataType);
|
||||
|
||||
return $this;
|
||||
|
|
@ -1293,7 +1223,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
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
|
||||
if ($this->cellCollection->has($cellAddress)) {
|
||||
|
|
@ -1444,7 +1374,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function cellExists($coordinate): bool
|
||||
{
|
||||
$cellAddress = $this->validateCellAddress($coordinate);
|
||||
$cellAddress = Validations::validateCellAddress($coordinate);
|
||||
/** @var Worksheet $sheet */
|
||||
[$sheet, $finalCoordinate] = $this->getWorksheetAndCoordinate($cellAddress);
|
||||
|
||||
|
|
@ -1536,7 +1466,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function getStyle($cellCoordinate): Style
|
||||
{
|
||||
$cellCoordinate = $this->validateCellOrCellRange($cellCoordinate);
|
||||
$cellCoordinate = Validations::validateCellOrCellRange($cellCoordinate);
|
||||
|
||||
// set this sheet as active
|
||||
$this->parent->setActiveSheetIndex($this->parent->getIndex($this));
|
||||
|
|
@ -1774,7 +1704,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function setBreak($coordinate, $break)
|
||||
{
|
||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate));
|
||||
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate));
|
||||
|
||||
if ($break === self::BREAK_NONE) {
|
||||
if (isset($this->breaks[$cellAddress])) {
|
||||
|
|
@ -1826,7 +1756,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
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) {
|
||||
$this->mergeCells[$range] = $range;
|
||||
|
|
@ -1935,7 +1865,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function unmergeCells($range)
|
||||
{
|
||||
$range = Functions::trimSheetFromCellReference($this->validateCellRange($range));
|
||||
$range = Functions::trimSheetFromCellReference(Validations::validateCellRange($range));
|
||||
|
||||
if (strpos($range, ':') !== false) {
|
||||
if (isset($this->mergeCells[$range])) {
|
||||
|
|
@ -2013,7 +1943,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function protectCells($range, $password, $alreadyHashed = false)
|
||||
{
|
||||
$range = Functions::trimSheetFromCellReference($this->validateCellOrCellRange($range));
|
||||
$range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range));
|
||||
|
||||
if (!$alreadyHashed) {
|
||||
$password = Shared\PasswordHasher::hashPassword($password);
|
||||
|
|
@ -2061,7 +1991,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function unprotectCells($range)
|
||||
{
|
||||
$range = Functions::trimSheetFromCellReference($this->validateCellOrCellRange($range));
|
||||
$range = Functions::trimSheetFromCellReference(Validations::validateCellOrCellRange($range));
|
||||
|
||||
if (isset($this->protectedCells[$range])) {
|
||||
unset($this->protectedCells[$range]);
|
||||
|
|
@ -2132,7 +2062,7 @@ class Worksheet implements IComparable
|
|||
if (is_object($autoFilterOrRange) && ($autoFilterOrRange instanceof AutoFilter)) {
|
||||
$this->autoFilter = $autoFilterOrRange;
|
||||
} else {
|
||||
$cellRange = Functions::trimSheetFromCellReference($this->validateCellRange($autoFilterOrRange));
|
||||
$cellRange = Functions::trimSheetFromCellReference(Validations::validateCellRange($autoFilterOrRange));
|
||||
|
||||
$this->autoFilter->setRange($cellRange);
|
||||
}
|
||||
|
|
@ -2278,13 +2208,13 @@ class Worksheet implements IComparable
|
|||
public function freezePane($coordinate, $topLeftCell = null)
|
||||
{
|
||||
$cellAddress = ($coordinate !== null)
|
||||
? Functions::trimSheetFromCellReference($this->validateCellAddress($coordinate))
|
||||
? Functions::trimSheetFromCellReference(Validations::validateCellAddress($coordinate))
|
||||
: null;
|
||||
if ($cellAddress !== null && Coordinate::coordinateIsRange($cellAddress)) {
|
||||
throw new Exception('Freeze pane can not be set on a range of cells.');
|
||||
}
|
||||
$topLeftCell = ($topLeftCell !== null)
|
||||
? Functions::trimSheetFromCellReference($this->validateCellAddress($topLeftCell))
|
||||
? Functions::trimSheetFromCellReference(Validations::validateCellAddress($topLeftCell))
|
||||
: null;
|
||||
|
||||
if ($cellAddress !== null && $topLeftCell === null) {
|
||||
|
|
@ -2688,7 +2618,7 @@ class Worksheet implements IComparable
|
|||
*/
|
||||
public function getComment($cellCoordinate)
|
||||
{
|
||||
$cellAddress = Functions::trimSheetFromCellReference($this->validateCellAddress($cellCoordinate));
|
||||
$cellAddress = Functions::trimSheetFromCellReference(Validations::validateCellAddress($cellCoordinate));
|
||||
|
||||
if (Coordinate::coordinateIsRange($cellAddress)) {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
|
@ -2787,9 +2701,9 @@ class Worksheet implements IComparable
|
|||
public function setSelectedCells($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)) {
|
||||
[$first] = Coordinate::splitRange($coordinate);
|
||||
|
|
|
|||
|
|
@ -132,10 +132,11 @@ class Ods extends BaseWriter
|
|||
|
||||
$zip->addFile('META-INF/manifest.xml', $this->getWriterPartMetaInf()->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('meta.xml', $this->getWriterPartmeta()->write());
|
||||
$zip->addFile('mimetype', $this->getWriterPartmimetype()->write());
|
||||
$zip->addFile('settings.xml', $this->getWriterPartsettings()->write());
|
||||
$zip->addFile('styles.xml', $this->getWriterPartstyles()->write());
|
||||
|
||||
// Close file
|
||||
|
|
|
|||
|
|
@ -2,15 +2,20 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Writer\Ods\Cell;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Helper\Dimension;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Font;
|
||||
use PhpOffice\PhpSpreadsheet\Style\Style as CellStyle;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\ColumnDimension;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\RowDimension;
|
||||
|
||||
class Style
|
||||
{
|
||||
public const CELL_STYLE_PREFIX = 'ce';
|
||||
public const COLUMN_STYLE_PREFIX = 'co';
|
||||
public const ROW_STYLE_PREFIX = 'ro';
|
||||
|
||||
private $writer;
|
||||
|
||||
|
|
@ -159,6 +164,63 @@ class Style
|
|||
$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
|
||||
{
|
||||
$this->writer->startElement('style:style');
|
||||
|
|
|
|||
|
|
@ -119,14 +119,21 @@ class Content extends WriterPart
|
|||
{
|
||||
$spreadsheet = $this->getParentWriter()->getSpreadsheet(); /** @var Spreadsheet $spreadsheet */
|
||||
$sheetCount = $spreadsheet->getSheetCount();
|
||||
for ($i = 0; $i < $sheetCount; ++$i) {
|
||||
for ($sheetIndex = 0; $sheetIndex < $sheetCount; ++$sheetIndex) {
|
||||
$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->startElement('table:table-column');
|
||||
$objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
|
||||
$objWriter->endElement();
|
||||
$this->writeRows($objWriter, $spreadsheet->getSheet($i));
|
||||
foreach ($spreadsheet->getSheet($sheetIndex)->getColumnDimensions() as $columnDimension) {
|
||||
$objWriter->startElement('table:table-column');
|
||||
$objWriter->writeAttribute(
|
||||
'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();
|
||||
}
|
||||
}
|
||||
|
|
@ -134,7 +141,7 @@ class Content extends WriterPart
|
|||
/**
|
||||
* 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;
|
||||
$span_row = 0;
|
||||
|
|
@ -148,8 +155,14 @@ class Content extends WriterPart
|
|||
if ($span_row > 1) {
|
||||
$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->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();
|
||||
$span_row = 0;
|
||||
|
|
@ -275,6 +288,26 @@ class Content extends WriterPart
|
|||
private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void
|
||||
{
|
||||
$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) {
|
||||
$styleWriter->write($style);
|
||||
}
|
||||
|
|
@ -296,7 +329,7 @@ class Content extends WriterPart
|
|||
$columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
|
||||
$rowSpan = ((int) $end[1]) - ((int) $start[1]) + 1;
|
||||
|
||||
$objWriter->writeAttribute('table:number-columns-spanned', $columnSpan);
|
||||
$objWriter->writeAttribute('table:number-rows-spanned', $rowSpan);
|
||||
$objWriter->writeAttribute('table:number-columns-spanned', (string) $columnSpan);
|
||||
$objWriter->writeAttribute('table:number-rows-spanned', (string) $rowSpan);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
namespace PhpOffice\PhpSpreadsheet\Writer\Ods;
|
||||
|
||||
use PhpOffice\PhpSpreadsheet\Cell\CellAddress;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
|
||||
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||
|
||||
class Settings extends WriterPart
|
||||
{
|
||||
|
|
@ -45,28 +48,9 @@ class Settings extends WriterPart
|
|||
$objWriter->text('view1');
|
||||
$objWriter->endElement(); // ViewId
|
||||
$objWriter->startElement('config:config-item-map-named');
|
||||
$objWriter->writeAttribute('config:name', 'Tables');
|
||||
foreach ($spreadsheet->getWorksheetIterator() as $ws) {
|
||||
$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
|
||||
|
||||
$this->writeAllWorksheetSettings($objWriter, $spreadsheet);
|
||||
|
||||
$wstitle = $spreadsheet->getActiveSheet()->getTitle();
|
||||
$objWriter->startElement('config:config-item');
|
||||
$objWriter->writeAttribute('config:name', 'ActiveTable');
|
||||
|
|
@ -85,4 +69,84 @@ class Settings extends WriterPart
|
|||
|
||||
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;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use PhpOffice\PhpSpreadsheet\Document\Properties;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||
|
|
@ -14,20 +15,27 @@ class PropertiesTest extends TestCase
|
|||
*/
|
||||
private $properties;
|
||||
|
||||
/** @var float */
|
||||
private $startTime;
|
||||
|
||||
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
|
||||
{
|
||||
$createdTime = $modifiedTime = time();
|
||||
self::assertSame('Unknown Creator', $this->properties->getCreator());
|
||||
self::assertSame('Unknown Creator', $this->properties->getLastModifiedBy());
|
||||
self::assertSame('Untitled Spreadsheet', $this->properties->getTitle());
|
||||
self::assertSame('', $this->properties->getCompany());
|
||||
self::assertSame($createdTime, $this->properties->getCreated());
|
||||
self::assertSame($modifiedTime, $this->properties->getModified());
|
||||
self::assertEquals($this->startTime, $this->properties->getCreated());
|
||||
self::assertEquals($this->startTime, $this->properties->getModified());
|
||||
}
|
||||
|
||||
public function testSetCreator(): void
|
||||
|
|
@ -46,10 +54,10 @@ class PropertiesTest extends TestCase
|
|||
*/
|
||||
public function testSetCreated($expectedCreationTime, $created): void
|
||||
{
|
||||
$expectedCreationTime = $expectedCreationTime ?? time();
|
||||
$expectedCreationTime = $expectedCreationTime ?? $this->startTime;
|
||||
|
||||
$this->properties->setCreated($created);
|
||||
self::assertSame($expectedCreationTime, $this->properties->getCreated());
|
||||
self::assertEquals($expectedCreationTime, $this->properties->getCreated());
|
||||
}
|
||||
|
||||
public function providerCreationTime(): array
|
||||
|
|
@ -78,10 +86,10 @@ class PropertiesTest extends TestCase
|
|||
*/
|
||||
public function testSetModified($expectedModifiedTime, $modified): void
|
||||
{
|
||||
$expectedModifiedTime = $expectedModifiedTime ?? time();
|
||||
$expectedModifiedTime = $expectedModifiedTime ?? $this->startTime;
|
||||
|
||||
$this->properties->setModified($modified);
|
||||
self::assertSame($expectedModifiedTime, $this->properties->getModified());
|
||||
self::assertEquals($expectedModifiedTime, $this->properties->getModified());
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
$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
|
||||
{
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
|
@ -14,7 +14,6 @@
|
|||
<table:calculation-settings />
|
||||
<table:table table:name="Worksheet">
|
||||
<office:forms />
|
||||
<table:table-column table:number-columns-repeated="1024" />
|
||||
<table:table-row>
|
||||
<table:table-cell table:style-name="ce0" />
|
||||
<table:table-cell table:number-columns-repeated="1023" />
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@
|
|||
<table:calculation-settings/>
|
||||
<table:table table:name="Worksheet">
|
||||
<office:forms/>
|
||||
<table:table-column table:number-columns-repeated="1024"/>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value="1" office:value-type="float" table:style-name="ce2">
|
||||
<text:p>1</text:p>
|
||||
|
|
@ -110,7 +109,6 @@
|
|||
</table:table>
|
||||
<table:table table:name="New Worksheet">
|
||||
<office:forms/>
|
||||
<table:table-column table:number-columns-repeated="1024"/>
|
||||
<table:table-row>
|
||||
<table:table-cell office:value="2" office:value-type="float" table:style-name="ce0">
|
||||
<text:p>2</text:p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue