Skip to content

Commit e1984c6

Browse files
authored
Merge branch 'master' into issue4319
2 parents b4c1a86 + f33a440 commit e1984c6

29 files changed

+573
-49
lines changed

CHANGELOG.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com)
66
and this project adheres to [Semantic Versioning](https://semver.org).
77

8-
## TBD - 3.8.0
8+
## TBD - 3.9.0
99

1010
### Added
1111

12-
- CHOOSECOLS, CHOOSEROWS, DROP, TAKE, and EXPAND. [PR #4286](https://github.com/PHPOffice/PhpSpreadsheet/pull/4286)
12+
- Methods to get style for row or column. [PR #4317](https://github.com/PHPOffice/PhpSpreadsheet/pull/4317)
13+
- Method for duplicating worksheet in spreadsheet. [PR #4315](https://github.com/PHPOffice/PhpSpreadsheet/pull/4315)
1314

1415
### Changed
1516

@@ -25,7 +26,20 @@ and this project adheres to [Semantic Versioning](https://semver.org).
2526

2627
### Fixed
2728

29+
- Ods Reader Sheet Names with Period. [Issue #4311](https://github.com/PHPOffice/PhpSpreadsheet/issues/4311) [PR #4313](https://github.com/PHPOffice/PhpSpreadsheet/pull/4313)
30+
31+
## 2025-01-11 - 3.8.0
32+
33+
### Added
34+
35+
- CHOOSECOLS, CHOOSEROWS, DROP, TAKE, and EXPAND. [PR #4286](https://github.com/PHPOffice/PhpSpreadsheet/pull/4286)
36+
37+
### Fixed
38+
39+
- Security patch for Html navigation.
2840
- Xlsx Reader Shared Formula with Boolean Result. Partial solution for [Issue #4280](https://github.com/PHPOffice/PhpSpreadsheet/issues/4280) [PR #4281](https://github.com/PHPOffice/PhpSpreadsheet/pull/4281)
41+
- Retitling cloned Worksheets. [Issue #641](https://github.com/PHPOffice/PhpSpreadsheet/issues/641) [PR #4302](https://github.com/PHPOffice/PhpSpreadsheet/pull/4302)
42+
- Extremely limited support for GROUPBY function. Partial response to [Issue #4282](https://github.com/PHPOffice/PhpSpreadsheet/issues/4282) [PR #4283](https://github.com/PHPOffice/PhpSpreadsheet/pull/4283)
2943

3044
## 2024-12-26 - 3.7.0
3145

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 PhpSpreadsheet Authors
3+
Copyright (c) 2019-2025 PhpSpreadsheet Authors
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

composer.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/references/function-list-by-category.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ EXPAND | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Choos
249249
FILTER | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Filter::filter
250250
FORMULATEXT | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Formula::text
251251
GETPIVOTDATA | **Not yet Implemented**
252+
GROUPBY | **Not yet Implemented**
252253
HLOOKUP | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\HLookup::lookup
253254
HYPERLINK | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Hyperlink::set
254255
INDEX | \PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Matrix::index

docs/references/function-list-by-name.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ GCD | CATEGORY_MATH_AND_TRIG | \PhpOffice\PhpSpread
239239
GEOMEAN | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Averages\Mean::geometric
240240
GESTEP | CATEGORY_ENGINEERING | \PhpOffice\PhpSpreadsheet\Calculation\Engineering\Compare::GESTEP
241241
GETPIVOTDATA | CATEGORY_LOOKUP_AND_REFERENCE | **Not yet Implemented**
242+
GROUPBY | CATEGORY_LOOKUP_AND_REFERENCE | **Not yet Implemented**
242243
GROWTH | CATEGORY_STATISTICAL | \PhpOffice\PhpSpreadsheet\Calculation\Statistical\Trends::GROWTH
243244

244245
## H

docs/topics/recipes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,9 @@ getStyle('A1:M500'), rather than styling the cells individually in a
925925
loop. This is much faster compared to looping through cells and styling
926926
them individually.
927927

928+
**Tip** If you are styling entire row(s) or column(s), e.g. getStyle('A:A'), it is recommended to use applyFromArray as described below rather than setting the styles individually as described above.
929+
Also, starting with release 3.9.0, you should use getRowStyle or getColumnStyle to get the style for an entire row or column.
930+
928931
There is also an alternative manner to set styles. The following code
929932
sets a cell's style to font bold, alignment right, top border thin and a
930933
gradient fill:

docs/topics/worksheets.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,38 @@ insert the clone into the workbook.
9595

9696
```php
9797
$clonedWorksheet = clone $spreadsheet->getSheetByName('Worksheet 1');
98-
$clonedWorksheet->setTitle('Copy of Worksheet 1');
98+
$clonedWorksheet->setTitle('Copy of Worksheet 1'); // must be unique
9999
$spreadsheet->addSheet($clonedWorksheet);
100100
```
101+
Starting with PhpSpreadsheet 3.9.0, this can be done more simply (copied sheet's title will be set to something unique):
102+
```php
103+
$copiedWorksheet = $spreadsheet->duplicateWorksheetByTitle('sheetname');
104+
```
101105

102106
You can also copy worksheets from one workbook to another, though this
103107
is more complex as PhpSpreadsheet also has to replicate the styling
104108
between the two workbooks. The `addExternalSheet()` method is provided for
105109
this purpose.
106110

107-
$clonedWorksheet = clone $spreadsheet1->getSheetByName('Worksheet 1');
108-
$spreadsheet->addExternalSheet($clonedWorksheet);
111+
```php
112+
$clonedWorksheet = clone $spreadsheet1->getSheetByName('Worksheet 1');
113+
$clonedWorksheet->setTitle('Copy of Worksheet 1'); // must be unique
114+
$spreadsheet1->addSheet($clonedWorksheet);
115+
$spreadsheet->addExternalSheet($clonedWorksheet);
116+
```
117+
Starting with PhpSpreadsheet 3.8.0, this can be simplified:
118+
```php
119+
$clonedWorksheet = clone $spreadsheet1->getSheetByName('Worksheet 1');
120+
$spreadsheet1->addSheet($clonedWorksheet, null, true);
121+
$spreadsheet->addExternalSheet($clonedWorksheet);
122+
```
123+
Starting with PhpSpreadsheet 3.9.0, this can be simplified even further:
124+
```php
125+
$clonedWorksheet = $spreadsheet1->duplicateWorksheetByTitle('sheetname');
126+
$spreadsheet->addExternalSheet($clonedWorksheet);
127+
```
109128

110-
In both cases, it is the developer's responsibility to ensure that
129+
In the cases commented "must be unique", it is the developer's responsibility to ensure that
111130
worksheet names are not duplicated. PhpSpreadsheet will throw an
112131
exception if you attempt to copy worksheets that will result in a
113132
duplicate name.

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,6 +1256,11 @@ public static function getExcelConstants(string $key): bool|null
12561256
'functionCall' => [Functions::class, 'DUMMY'],
12571257
'argumentCount' => '2+',
12581258
],
1259+
'GROUPBY' => [
1260+
'category' => Category::CATEGORY_LOOKUP_AND_REFERENCE,
1261+
'functionCall' => [Functions::class, 'DUMMY'],
1262+
'argumentCount' => '3-7',
1263+
],
12591264
'GROWTH' => [
12601265
'category' => Category::CATEGORY_STATISTICAL,
12611266
'functionCall' => [Statistical\Trends::class, 'GROWTH'],
@@ -4601,7 +4606,7 @@ private static function dataTestReference(array &$operandData): mixed
46014606
private static int $matchIndex10 = 10;
46024607

46034608
/**
4604-
* @return array<int, mixed>|false
4609+
* @return array<int, mixed>|false|string
46054610
*/
46064611
private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell $cell = null)
46074612
{
@@ -5182,6 +5187,9 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
51825187
} elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) {
51835188
// if the token is a named range or formula, evaluate it and push the result onto the stack
51845189
$definedName = $matches[6];
5190+
if (str_starts_with($definedName, '_xleta')) {
5191+
return Functions::NOT_YET_IMPLEMENTED;
5192+
}
51855193
if ($cell === null || $pCellWorksheet === null) {
51865194
return $this->raiseFormulaError("undefined name '$token'");
51875195
}
@@ -5214,6 +5222,7 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
52145222
}
52155223

52165224
$result = $this->evaluateDefinedName($cell, $namedRange, $pCellWorksheet, $stack, $specifiedWorksheet !== '');
5225+
52175226
if (isset($storeKey)) {
52185227
$branchStore[$storeKey] = $result;
52195228
}

src/PhpSpreadsheet/Reader/Ods/FormulaTranslator.php

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,24 @@
66

77
class FormulaTranslator
88
{
9-
public static function convertToExcelAddressValue(string $openOfficeAddress): string
9+
private static function replaceQuotedPeriod(string $value): string
1010
{
11-
$excelAddress = $openOfficeAddress;
11+
$value2 = '';
12+
$quoted = false;
13+
foreach (mb_str_split($value, 1, 'UTF-8') as $char) {
14+
if ($char === "'") {
15+
$quoted = !$quoted;
16+
} elseif ($char === '.' && $quoted) {
17+
$char = "\u{fffe}";
18+
}
19+
$value2 .= $char;
20+
}
1221

22+
return $value2;
23+
}
24+
25+
public static function convertToExcelAddressValue(string $openOfficeAddress): string
26+
{
1327
// Cell range 3-d reference
1428
// As we don't support 3-d ranges, we're just going to take a quick and dirty approach
1529
// and assume that the second worksheet reference is the same as the first
@@ -20,15 +34,17 @@ public static function convertToExcelAddressValue(string $openOfficeAddress): st
2034
'/\$?([^\.]+)\.([^\.]+)/miu', // Cell reference in another sheet
2135
'/\.([^\.]+):\.([^\.]+)/miu', // Cell range reference
2236
'/\.([^\.]+)/miu', // Simple cell reference
37+
'/\\x{FFFE}/miu', // restore quoted periods
2338
],
2439
[
2540
'$1!$2:$4',
2641
'$1!$2:$3',
2742
'$1!$2',
2843
'$1:$2',
2944
'$1',
45+
'.',
3046
],
31-
$excelAddress
47+
self::replaceQuotedPeriod($openOfficeAddress)
3248
);
3349

3450
return $excelAddress;
@@ -52,14 +68,16 @@ public static function convertToExcelFormulaValue(string $openOfficeFormula): st
5268
'/\[\$?([^\.]+)\.([^\.]+)\]/miu', // Cell reference in another sheet
5369
'/\[\.([^\.]+):\.([^\.]+)\]/miu', // Cell range reference
5470
'/\[\.([^\.]+)\]/miu', // Simple cell reference
71+
'/\\x{FFFE}/miu', // restore quoted periods
5572
],
5673
[
5774
'$1!$2:$3',
5875
'$1!$2',
5976
'$1:$2',
6077
'$1',
78+
'.',
6179
],
62-
$value
80+
self::replaceQuotedPeriod($value)
6381
);
6482
// Convert references to defined names/formulae
6583
$value = str_replace('$$', '', $value);

src/PhpSpreadsheet/Spreadsheet.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ public function getActiveSheet(): Worksheet
514514
public function createSheet(?int $sheetIndex = null): Worksheet
515515
{
516516
$newSheet = new Worksheet($this);
517-
$this->addSheet($newSheet, $sheetIndex);
517+
$this->addSheet($newSheet, $sheetIndex, true);
518518

519519
return $newSheet;
520520
}
@@ -529,14 +529,35 @@ public function sheetNameExists(string $worksheetName): bool
529529
return $this->getSheetByName($worksheetName) !== null;
530530
}
531531

532+
public function duplicateWorksheetByTitle(string $title): Worksheet
533+
{
534+
$original = $this->getSheetByNameOrThrow($title);
535+
$index = $this->getIndex($original) + 1;
536+
$clone = clone $original;
537+
538+
return $this->addSheet($clone, $index, true);
539+
}
540+
532541
/**
533542
* Add sheet.
534543
*
535544
* @param Worksheet $worksheet The worksheet to add
536545
* @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
537546
*/
538-
public function addSheet(Worksheet $worksheet, ?int $sheetIndex = null): Worksheet
547+
public function addSheet(Worksheet $worksheet, ?int $sheetIndex = null, bool $retitleIfNeeded = false): Worksheet
539548
{
549+
if ($retitleIfNeeded) {
550+
$title = $worksheet->getTitle();
551+
if ($this->sheetNameExists($title)) {
552+
$i = 1;
553+
$newTitle = "$title $i";
554+
while ($this->sheetNameExists($newTitle)) {
555+
++$i;
556+
$newTitle = "$title $i";
557+
}
558+
$worksheet->setTitle($newTitle);
559+
}
560+
}
540561
if ($this->sheetNameExists($worksheet->getTitle())) {
541562
throw new Exception(
542563
"Workbook already contains a worksheet named '{$worksheet->getTitle()}'. Rename this worksheet first."
@@ -1080,6 +1101,11 @@ public function getCellXfByIndex(int $cellStyleIndex): Style
10801101
return $this->cellXfCollection[$cellStyleIndex];
10811102
}
10821103

1104+
public function getCellXfByIndexOrNull(?int $cellStyleIndex): ?Style
1105+
{
1106+
return ($cellStyleIndex === null) ? null : ($this->cellXfCollection[$cellStyleIndex] ?? null);
1107+
}
1108+
10831109
/**
10841110
* Get cellXf by hash code.
10851111
*

0 commit comments

Comments
 (0)