Skip to content

Commit 0153987

Browse files
authored
Merge branch 'master' into issue4375
2 parents 2fbc9ad + 0c5808e commit 0153987

File tree

145 files changed

+1247
-1331
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

145 files changed

+1247
-1331
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ and this project adheres to [Semantic Versioning](https://semver.org).
99

1010
### Added
1111

12-
- Nothing yet.
12+
- Allow Spreadsheet clone. [PR #437-](https://github.com/PHPOffice/PhpSpreadsheet/pull/4370)
1313

1414
### Removed
1515

1616
- Nothing yet.
1717

1818
### Changed
1919

20+
- ListWorksheetInfo will now return sheetState (visible, hidden, veryHidden). [Issue #4345](https://github.com/PHPOffice/PhpSpreadsheet/issues/4345) [PR #4366](https://github.com/PHPOffice/PhpSpreadsheet/pull/4366)
2021
- Start migration to Phpstan 2. [PR #4359](https://github.com/PHPOffice/PhpSpreadsheet/pull/4359)
22+
- IOFactory identify can return, and createReader and CreateWriter can accept, a class name rather than a file type. [Issue #4357](https://github.com/PHPOffice/PhpSpreadsheet/issues/4357) [PR #4361](https://github.com/PHPOffice/PhpSpreadsheet/pull/4361)
2123

2224
### Moved
2325

@@ -31,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
3133

3234
- Refactor Helper/Html. [PR #4359](https://github.com/PHPOffice/PhpSpreadsheet/pull/4359)
3335
- Ignore ignoredErrors when not applicable. [Issue #4375](https://github.com/PHPOffice/PhpSpreadsheet/issues/4375) [PR #4377](https://github.com/PHPOffice/PhpSpreadsheet/pull/4377)
36+
- Better handling of defined names on sheets whose titles include apostrophes. [Issue #4356](https://github.com/PHPOffice/PhpSpreadsheet/issues/4356) [Issue #4362](https://github.com/PHPOffice/PhpSpreadsheet/issues/4362) [Issue #4376](https://github.com/PHPOffice/PhpSpreadsheet/issues/4376) [PR #4360](https://github.com/PHPOffice/PhpSpreadsheet/pull/4360)
3437

3538
## 2025-02-08 - 4.0.0
3639

docs/topics/reading-files.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,10 @@ method to identify the reader that you need, before using the
123123
```php
124124
$inputFileName = './sampleData/example1.xls';
125125

126-
/** Identify the type of $inputFileName **/
126+
/**
127+
* Identify the type of $inputFileName.
128+
* See below for a possible improvement for release 4.1.0+.
129+
*/
127130
$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($inputFileName);
128131
/** Create a new Reader of the type that has been identified **/
129132
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader($inputFileType);
@@ -134,6 +137,13 @@ $spreadsheet = $reader->load($inputFileName);
134137
See `samples/Reader/04_Simple_file_reader_using_the_IOFactory_to_identify_a_reader_to_use.php`
135138
for a working example of this code.
136139

140+
Prior to release 4.1.0, `identify` returns a file type.
141+
It may be more useful to return a fully-qualified class name,
142+
which can be accomplished using a parameter introduced in 4.1.0:
143+
```php
144+
$inputFileType = \PhpOffice\PhpSpreadsheet\IOFactory::identify($inputFileName, null, true);
145+
```
146+
137147
As with the IOFactory `load()` method, you can also pass an array of formats
138148
for the `identify()` method to check against if you know that it will only
139149
be in a subset of the possible formats that PhpSpreadsheet supports.

phpstan-baseline.neon

Lines changed: 0 additions & 630 deletions
Large diffs are not rendered by default.

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4408,7 +4408,9 @@ private function internalParseFormula(string $formula, ?Cell $cell = null): bool
44084408
if ($rangeWS1 !== '') {
44094409
$rangeWS1 .= '!';
44104410
}
4411-
$rangeSheetRef = trim($rangeSheetRef, "'");
4411+
if (str_starts_with($rangeSheetRef, "'")) {
4412+
$rangeSheetRef = Worksheet::unApostrophizeTitle($rangeSheetRef);
4413+
}
44124414
[$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true);
44134415
if ($rangeWS2 !== '') {
44144416
$rangeWS2 .= '!';
@@ -4424,11 +4426,10 @@ private function internalParseFormula(string $formula, ?Cell $cell = null): bool
44244426
if (ctype_digit($val) && $val <= 1048576) {
44254427
// Row range
44264428
$stackItemType = 'Row Reference';
4427-
/** @var int $valx */
44284429
$valx = $val;
44294430
$endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataColumn($valx) : AddressRange::MAX_COLUMN; // Max 16,384 columns for Excel2007
44304431
$val = "{$rangeWS2}{$endRowColRef}{$val}";
4431-
} elseif (ctype_alpha($val) && is_string($val) && strlen($val) <= 3) {
4432+
} elseif (ctype_alpha($val) && strlen($val) <= 3) {
44324433
// Column range
44334434
$stackItemType = 'Column Reference';
44344435
$endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataRow($val) : AddressRange::MAX_ROW; // Max 1,048,576 rows for Excel2007
@@ -4562,7 +4563,7 @@ private function internalParseFormula(string $formula, ?Cell $cell = null): bool
45624563

45634564
while (($op = $stack->pop()) !== null) {
45644565
// pop everything off the stack and push onto output
4565-
if ((is_array($op) && $op['value'] == '(')) {
4566+
if ($op['value'] == '(') {
45664567
return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
45674568
}
45684569
$output[] = $op;
@@ -4766,18 +4767,18 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
47664767
}
47674768
}
47684769
if (str_contains($operand1Data['reference'] ?? '', '!')) {
4769-
[$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true);
4770+
[$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true, true);
47704771
} else {
47714772
$sheet1 = ($pCellWorksheet !== null) ? $pCellWorksheet->getTitle() : '';
47724773
}
47734774
$sheet1 ??= '';
47744775

4775-
[$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true);
4776+
[$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true, true);
47764777
if (empty($sheet2)) {
47774778
$sheet2 = $sheet1;
47784779
}
47794780

4780-
if (trim($sheet1, "'") === trim($sheet2, "'")) {
4781+
if ($sheet1 === $sheet2) {
47814782
if ($operand1Data['reference'] === null && $cell !== null) {
47824783
if (is_array($operand1Data['value'])) {
47834784
$operand1Data['reference'] = $cell->getCoordinate();
@@ -4819,7 +4820,7 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
48194820
if ($breakNeeded) {
48204821
break;
48214822
}
4822-
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
4823+
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); // @phpstan-ignore-line
48234824
if ($pCellParent !== null && $this->spreadsheet !== null) {
48244825
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
48254826
} else {
@@ -4917,8 +4918,8 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
49174918
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellIntersect));
49184919
$stack->push('Error', ExcelError::null(), null);
49194920
} else {
4920-
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':'
4921-
. Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
4921+
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' // @phpstan-ignore-line
4922+
. Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow); // @phpstan-ignore-line
49224923
$this->debugLog->writeDebugLog('Evaluation Result is %s', $this->showTypeDetails($cellIntersect));
49234924
$stack->push('Value', $cellIntersect, $cellRef);
49244925
}
@@ -5058,6 +5059,7 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
50585059
}
50595060

50605061
$functionName = $matches[1];
5062+
/** @var array $argCount */
50615063
$argCount = $stack->pop();
50625064
$argCount = $argCount['value'];
50635065
if ($functionName !== 'MKMATRIX') {
@@ -5088,9 +5090,10 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
50885090
&& (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a]))
50895091
&& (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])
50905092
) {
5093+
/** @var array $arg */
50915094
if ($arg['reference'] === null) {
50925095
$nextArg = $cellID;
5093-
if ($functionName === 'ISREF' && is_array($arg) && ($arg['type'] ?? '') === 'Value') {
5096+
if ($functionName === 'ISREF' && ($arg['type'] ?? '') === 'Value') {
50945097
if (array_key_exists('value', $arg)) {
50955098
$argValue = $arg['value'];
50965099
if (is_scalar($argValue)) {
@@ -5111,6 +5114,7 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
51115114
}
51125115
}
51135116
} else {
5117+
/** @var array $arg */
51145118
if ($arg['type'] === 'Empty Argument' && in_array($functionName, ['MIN', 'MINA', 'MAX', 'MAXA', 'IF'], true)) {
51155119
$emptyArguments[] = false;
51165120
$args[] = $arg['value'] = 0;
@@ -5233,6 +5237,7 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
52335237
if ($stack->count() != 1) {
52345238
return $this->raiseFormulaError('internal error');
52355239
}
5240+
/** @var array $output */
52365241
$output = $stack->pop();
52375242
$output = $output['value'];
52385243

@@ -5285,6 +5290,7 @@ private function executeArrayComparison(mixed $operand1, mixed $operand2, string
52855290
foreach ($operand1 as $x => $operandData) {
52865291
$this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operandData), $operation, $this->showValue($operand2));
52875292
$this->executeBinaryComparisonOperation($operandData, $operand2, $operation, $stack);
5293+
/** @var array $r */
52885294
$r = $stack->pop();
52895295
$result[$x] = $r['value'];
52905296
}
@@ -5293,6 +5299,7 @@ private function executeArrayComparison(mixed $operand1, mixed $operand2, string
52935299
foreach ($operand2 as $x => $operandData) {
52945300
$this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operand1), $operation, $this->showValue($operandData));
52955301
$this->executeBinaryComparisonOperation($operand1, $operandData, $operation, $stack);
5302+
/** @var array $r */
52965303
$r = $stack->pop();
52975304
$result[$x] = $r['value'];
52985305
}
@@ -5304,6 +5311,7 @@ private function executeArrayComparison(mixed $operand1, mixed $operand2, string
53045311
foreach ($operand1 as $x => $operandData) {
53055312
$this->debugLog->writeDebugLog('Evaluating Comparison %s %s %s', $this->showValue($operandData), $operation, $this->showValue($operand2[$x]));
53065313
$this->executeBinaryComparisonOperation($operandData, $operand2[$x], $operation, $stack, true);
5314+
/** @var array $r */
53075315
$r = $stack->pop();
53085316
$result[$x] = $r['value'];
53095317
}
@@ -5495,7 +5503,7 @@ public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet =
54955503
$worksheetName = $worksheet->getTitle();
54965504

54975505
if (str_contains($range, '!')) {
5498-
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true);
5506+
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true, true);
54995507
$worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName);
55005508
}
55015509

@@ -5557,7 +5565,7 @@ public function extractNamedRange(string &$range = 'A1', ?Worksheet $worksheet =
55575565

55585566
if ($worksheet !== null) {
55595567
if (str_contains($range, '!')) {
5560-
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true);
5568+
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true, true);
55615569
$worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName);
55625570
}
55635571

src/PhpSpreadsheet/Calculation/Database/DatabaseAbstract.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ private static function buildQuery(array $criteriaNames, array $criteria): strin
108108
}
109109

110110
$rowQuery = array_map(
111-
fn ($rowValue): string => (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''),
111+
fn ($rowValue): string => (count($rowValue) > 1) ? 'AND(' . implode(',', $rowValue) . ')' : ($rowValue[0] ?? ''), // @phpstan-ignore-line
112112
$baseQuery
113113
);
114114

src/PhpSpreadsheet/Calculation/DateTimeExcel/Date.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ private static function adjustYearMonth(int &$year, int &$month, int $baseYear):
151151
if ($month < 1) {
152152
// Handle year/month adjustment if month < 1
153153
--$month;
154-
$year += ceil($month / 12) - 1;
154+
$year += (int) (ceil($month / 12) - 1);
155155
$month = 13 - abs($month % 12);
156156
} elseif ($month > 12) {
157157
// Handle year/month adjustment if month > 12
158-
$year += floor($month / 12);
158+
$year += intdiv($month, 12);
159159
$month = ($month % 12);
160160
}
161161

src/PhpSpreadsheet/Calculation/DateTimeExcel/Days.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public static function between(array|DateTimeInterface|float|int|string $endDate
5050

5151
$days = ExcelError::VALUE();
5252
$diff = $PHPStartDateObject->diff($PHPEndDateObject);
53-
if ($diff !== false && !is_bool($diff->days)) {
53+
if (!is_bool($diff->days)) {
5454
$days = $diff->days;
5555
if ($diff->invert) {
5656
$days = -$days;

src/PhpSpreadsheet/Calculation/DateTimeExcel/Helpers.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public static function adjustYear(string $testVal1, string $testVal2, string &$t
119119
if (!is_numeric($testVal1) || $testVal1 < 31) {
120120
if (!is_numeric($testVal2) || $testVal2 < 12) {
121121
if (is_numeric($testVal3) && $testVal3 < 12) {
122-
$testVal3 += 2000;
122+
$testVal3 = (string) ($testVal3 + 2000);
123123
}
124124
}
125125
}

src/PhpSpreadsheet/Calculation/DateTimeExcel/Time.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,27 +87,27 @@ public static function fromHMS(array|int|float|bool|null|string $hour, array|int
8787
private static function adjustSecond(int &$second, int &$minute): void
8888
{
8989
if ($second < 0) {
90-
$minute += floor($second / 60);
90+
$minute += (int) floor($second / 60);
9191
$second = 60 - abs($second % 60);
9292
if ($second == 60) {
9393
$second = 0;
9494
}
9595
} elseif ($second >= 60) {
96-
$minute += floor($second / 60);
96+
$minute += intdiv($second, 60);
9797
$second = $second % 60;
9898
}
9999
}
100100

101101
private static function adjustMinute(int &$minute, int &$hour): void
102102
{
103103
if ($minute < 0) {
104-
$hour += floor($minute / 60);
104+
$hour += (int) floor($minute / 60);
105105
$minute = 60 - abs($minute % 60);
106106
if ($minute == 60) {
107107
$minute = 0;
108108
}
109109
} elseif ($minute >= 60) {
110-
$hour += floor($minute / 60);
110+
$hour += intdiv($minute, 60);
111111
$minute = $minute % 60;
112112
}
113113
}

src/PhpSpreadsheet/Calculation/Engine/FormattedNumber.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@ public static function convertToNumberIfFormatted(string &$operand): bool
4444
* Identify whether a string contains a numeric value,
4545
* and convert it to a numeric if it is.
4646
*
47-
* @param string $operand string value to test
47+
* @param float|string $operand string value to test
4848
*/
49-
public static function convertToNumberIfNumeric(string &$operand): bool
49+
public static function convertToNumberIfNumeric(float|string &$operand): bool
5050
{
5151
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
52-
$value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim($operand));
52+
$value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim("$operand"));
5353
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
5454
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
5555

@@ -86,12 +86,12 @@ public static function convertToNumberIfFraction(string &$operand): bool
8686
* Identify whether a string contains a percentage, and if so,
8787
* convert it to a numeric.
8888
*
89-
* @param string $operand string value to test
89+
* @param float|string $operand string value to test
9090
*/
91-
public static function convertToNumberIfPercent(string &$operand): bool
91+
public static function convertToNumberIfPercent(float|string &$operand): bool
9292
{
9393
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
94-
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim($operand));
94+
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim("$operand"));
9595
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/');
9696
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? '');
9797

@@ -111,13 +111,13 @@ public static function convertToNumberIfPercent(string &$operand): bool
111111
* Identify whether a string contains a currency value, and if so,
112112
* convert it to a numeric.
113113
*
114-
* @param string $operand string value to test
114+
* @param float|string $operand string value to test
115115
*/
116-
public static function convertToNumberIfCurrency(string &$operand): bool
116+
public static function convertToNumberIfCurrency(float|string &$operand): bool
117117
{
118118
$currencyRegexp = self::currencyMatcherRegexp();
119119
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
120-
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $operand);
120+
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', "$operand");
121121

122122
$match = [];
123123
if ($value !== null && preg_match($currencyRegexp, $value, $match, PREG_UNMATCHED_AS_NULL)) {

0 commit comments

Comments
 (0)