Skip to content

Commit

Permalink
Merge branch 'master' into pr1415
Browse files Browse the repository at this point in the history
  • Loading branch information
oleibman committed Jun 30, 2024
2 parents 1ef0633 + aae4992 commit e8bb091
Show file tree
Hide file tree
Showing 42 changed files with 777 additions and 99 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/github-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ jobs:
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.3
coverage: none # remove xdebug

- name: Build API documentation
run: |
curl -LO https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.4.1/phpDocumentor.phar
curl -LO https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.5.0/phpDocumentor.phar
php phpDocumentor.phar --directory src/ --target docs/api
- name: Deploy to GithHub Pages
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.3
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: none

Expand All @@ -91,7 +91,7 @@ jobs:
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.3
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: none
tools: cs2pr
Expand Down Expand Up @@ -122,7 +122,7 @@ jobs:
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.3
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: none
tools: cs2pr
Expand Down Expand Up @@ -153,7 +153,7 @@ jobs:
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.3
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: none
tools: cs2pr
Expand Down Expand Up @@ -184,7 +184,7 @@ jobs:
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.3
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: none
tools: cs2pr
Expand Down Expand Up @@ -217,7 +217,7 @@ jobs:
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: 8.1
php-version: 8.3
extensions: ctype, dom, gd, iconv, fileinfo, libxml, mbstring, simplexml, xml, xmlreader, xmlwriter, zip, zlib
coverage: pcov

Expand Down
2 changes: 1 addition & 1 deletion .scrutinizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ build:
analysis:
image: default-bionic
environment:
php: 8.1
php: 8.2
tests:
override:
- php-scrutinizer-run
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Changed

- On read, Xlsx Reader had been breaking up union ranges into separate individual ranges. It will now try to preserve range as it was read in. [PR #4042](https://github.com/PHPOffice/PhpSpreadsheet/pull/4042)
- Xlsx/Xls spreadsheet calculation and formatting of dates will use base date of spreadsheet even when spreadsheets with different base dates are simultaneously open. [Issue #1036](https://github.com/PHPOffice/PhpSpreadsheet/issues/1036) [Issue #1635](https://github.com/PHPOffice/PhpSpreadsheet/issues/1635) [PR #4071](https://github.com/PHPOffice/PhpSpreadsheet/pull/4071)

### Deprecated

Expand All @@ -35,6 +36,8 @@ and this project adheres to [Semantic Versioning](https://semver.org).
- Conditional Range Unions and Intersections [Issue #4039](https://github.com/PHPOffice/PhpSpreadsheet/issues/4039) [PR #4042](https://github.com/PHPOffice/PhpSpreadsheet/pull/4042)
- Csv Reader allow use of html mimetype. [Issue #4036](https://github.com/PHPOffice/PhpSpreadsheet/issues/4036) [PR #4040](https://github.com/PHPOffice/PhpSpreadsheet/pull/4040)
- Problem rendering line chart with missing plot label. [PR #4074](https://github.com/PHPOffice/PhpSpreadsheet/pull/4074)
- More RTL in Xlsx/Html Comments [Issue #4004](https://github.com/PHPOffice/PhpSpreadsheet/issues/4004) [PR #4065](https://github.com/PHPOffice/PhpSpreadsheet/pull/4065)
- Empty String in sharedStrings. [Issue #4063](https://github.com/PHPOffice/PhpSpreadsheet/issues/4063) [PR #4064](https://github.com/PHPOffice/PhpSpreadsheet/pull/4064)

## 2024-05-11 - 2.1.0

Expand Down
22 changes: 22 additions & 0 deletions docs/topics/Looping the Loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,28 @@ But a peak memory usage of 49,152KB compared with the 57,344KB used by `toArray(
Like `toArray()`, `rangeToArray()` is easy to use, but it has the same limitations for flexibility. It provides the same limited control over how the data from each cell is returned in the array as `toArray()`.
The same additional arguments that can be provided for the `toArray()` method can also be provided to `rangeToArray()`.


## Using `rangeToArrayYieldRows()`

Since v2.1.0 the worksheet method `rangeToArrayYieldRows()` is available.
It allows you to iterate over all sheet's rows with little memory consumption,
while obtaining each row as an array:

```php
$rowGenerator = $sheet->rangeToArrayYieldRows(
'A1:' . $sheet->getHighestDataColumn() . $sheet->getHighestDataRow(),
null,
false,
false
);
foreach ($rowGenerator as $row) {
echo $row[0] . ' | ' . $row[1] . "\n";
}
```

See `samples/Reader2/23_iterateRowsYield.php`.


## Using Iterators

You don't need to build an array from the worksheet to loop through the rows and columns and do whatever processing you need; you can loop through the rows and columns in the Worksheet directly and more efficiently using PhpSpreadsheet's built-in iterators.
Expand Down
25 changes: 25 additions & 0 deletions samples/Reader2/23_iterateRowsYield.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

/**
* Use rangeToArrayYieldRows() to efficiently iterate over all rows.
*/

require __DIR__ . '/../Header.php';

$inputFileName = __DIR__ . '/../Reader/sampleData/example1.xls';

$spreadsheet = PhpOffice\PhpSpreadsheet\IOFactory::load(
$inputFileName,
PhpOffice\PhpSpreadsheet\Reader\IReader::READ_DATA_ONLY
);
$sheet = $spreadsheet->getSheet(0);

$rowGenerator = $sheet->rangeToArrayYieldRows(
$spreadsheet->getActiveSheet()->calculateWorksheetDataDimension(),
null,
false,
false
);
foreach ($rowGenerator as $row) {
echo '| ' . $row[0] . ' | ' . $row[1] . "|\n";
}
8 changes: 4 additions & 4 deletions src/PhpSpreadsheet/Calculation/Calculation.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ class Calculation
*/
private Logger $debugLog;

private bool $suppressFormulaErrorsNew = false;
private bool $suppressFormulaErrors = false;

/**
* Error message for any error that was raised/thrown by the calculation engine.
Expand Down Expand Up @@ -5350,7 +5350,7 @@ protected function raiseFormulaError(string $errorMessage, int $code = 0, ?Throw
{
$this->formulaError = $errorMessage;
$this->cyclicReferenceStack->clear();
$suppress = $this->suppressFormulaErrors ?? $this->suppressFormulaErrorsNew;
$suppress = $this->suppressFormulaErrors;
if (!$suppress) {
throw new Exception($errorMessage, $code, $exception);
}
Expand Down Expand Up @@ -5634,12 +5634,12 @@ private function evaluateDefinedName(Cell $cell, DefinedName $namedRange, Worksh

public function setSuppressFormulaErrors(bool $suppressFormulaErrors): void
{
$this->suppressFormulaErrorsNew = $suppressFormulaErrors;
$this->suppressFormulaErrors = $suppressFormulaErrors;
}

public function getSuppressFormulaErrors(): bool
{
return $this->suppressFormulaErrorsNew;
return $this->suppressFormulaErrors;
}

private static function boolToString(mixed $operand1): mixed
Expand Down
11 changes: 5 additions & 6 deletions src/PhpSpreadsheet/Cell/AdvancedValueBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Engine\FormattedNumber;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
Expand All @@ -30,7 +29,7 @@ public function bindValue(Cell $cell, mixed $value = null): bool
$dataType = parent::dataTypeForValue($value);

// Style logic - strings
if ($dataType === DataType::TYPE_STRING && !$value instanceof RichText) {
if ($dataType === DataType::TYPE_STRING && is_string($value)) {
// Test for booleans using locale-setting
if (StringHelper::strToUpper($value) === Calculation::getTRUE()) {
$cell->setValueExplicit(true, DataType::TYPE_BOOL);
Expand All @@ -54,17 +53,17 @@ public function bindValue(Cell $cell, mixed $value = null): bool
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');

// Check for percentage
if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) {
return $this->setPercentage(preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $cell);
if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) {
return $this->setPercentage((string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $cell);
}

// Check for currency
if (preg_match(FormattedNumber::currencyMatcherRegexp(), preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $matches, PREG_UNMATCHED_AS_NULL)) {
if (preg_match(FormattedNumber::currencyMatcherRegexp(), (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value), $matches, PREG_UNMATCHED_AS_NULL)) {
// Convert value to number
$sign = ($matches['PrefixedSign'] ?? $matches['PrefixedSign2'] ?? $matches['PostfixedSign']) ?? null;
$currencyCode = $matches['PrefixedCurrency'] ?? $matches['PostfixedCurrency'];
/** @var string */
$temp = str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value));
$temp = str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], (string) preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value));
$value = (float) ($sign . trim($temp));

return $this->setCurrency($value, $cell, $currencyCode ?? '');
Expand Down
25 changes: 22 additions & 3 deletions src/PhpSpreadsheet/Cell/Cell.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,14 +187,21 @@ public function getValueString(): string
*/
public function getFormattedValue(): string
{
return (string) NumberFormat::toFormattedString(
$currentCalendar = SharedDate::getExcelCalendar();
SharedDate::setExcelCalendar($this->getWorksheet()->getParent()?->getExcelCalendar());
$formattedValue = (string) NumberFormat::toFormattedString(
$this->getCalculatedValue(),
(string) $this->getStyle()->getNumberFormat()->getFormatCode(true)
);
SharedDate::setExcelCalendar($currentCalendar);

return $formattedValue;
}

protected static function updateIfCellIsTableHeader(?Worksheet $workSheet, self $cell, mixed $oldValue, mixed $newValue): void
{
$oldValue = (is_scalar($oldValue) || $oldValue instanceof Stringable) ? ((string) $oldValue) : null;
$newValue = (is_scalar($newValue) || $newValue instanceof Stringable) ? ((string) $newValue) : null;
if (StringHelper::strToLower($oldValue ?? '') === StringHelper::strToLower($newValue ?? '') || $workSheet === null) {
return;
}
Expand Down Expand Up @@ -262,7 +269,10 @@ public function setValueExplicit(mixed $value, string $dataType = DataType::TYPE
// Synonym for string
case DataType::TYPE_INLINE:
// Rich text
$this->value = DataType::checkString($value);
if ($value !== null && !is_scalar($value) && !($value instanceof Stringable)) {
throw new SpreadsheetException('Invalid unstringable value for datatype Inline/String/String2');
}
$this->value = DataType::checkString(($value instanceof RichText) ? $value : ((string) $value));

break;
case DataType::TYPE_NUMERIC:
Expand All @@ -273,6 +283,9 @@ public function setValueExplicit(mixed $value, string $dataType = DataType::TYPE

break;
case DataType::TYPE_FORMULA:
if ($value !== null && !is_scalar($value) && !($value instanceof Stringable)) {
throw new SpreadsheetException('Invalid unstringable value for datatype Formula');
}
$this->value = (string) $value;

break;
Expand Down Expand Up @@ -364,6 +377,8 @@ public function getCalculatedValue(bool $resetLog = true): mixed
{
if ($this->dataType === DataType::TYPE_FORMULA) {
try {
$currentCalendar = SharedDate::getExcelCalendar();
SharedDate::setExcelCalendar($this->getWorksheet()->getParent()?->getExcelCalendar());
$index = $this->getWorksheet()->getParentOrThrow()->getActiveSheetIndex();
$selected = $this->getWorksheet()->getSelectedCells();
$result = Calculation::getInstance(
Expand All @@ -379,6 +394,7 @@ public function getCalculatedValue(bool $resetLog = true): mixed
}
}
} catch (SpreadsheetException $ex) {
SharedDate::setExcelCalendar($currentCalendar);
if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
return $this->calculatedValue; // Fallback for calculations referencing external files.
} elseif (preg_match('/[Uu]ndefined (name|offset: 2|array key 2)/', $ex->getMessage()) === 1) {
Expand All @@ -391,6 +407,7 @@ public function getCalculatedValue(bool $resetLog = true): mixed
$ex
);
}
SharedDate::setExcelCalendar($currentCalendar);

if ($result === '#Not Yet Implemented') {
return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
Expand Down Expand Up @@ -790,7 +807,9 @@ public function getFormulaAttributes(): mixed
*/
public function __toString(): string
{
return (string) $this->getValue();
$retVal = $this->value;

return ($retVal === null || is_scalar($retVal) || $retVal instanceof Stringable) ? ((string) $retVal) : '';
}

public function getIgnoredErrors(): IgnoredErrors
Expand Down
3 changes: 2 additions & 1 deletion src/PhpSpreadsheet/Cell/DataType.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use Stringable;

class DataType
{
Expand Down Expand Up @@ -78,7 +79,7 @@ public static function checkString(null|RichText|string $textValue): RichText|st
*/
public static function checkErrorCode(mixed $value): string
{
$value = (string) $value;
$value = (is_scalar($value) || $value instanceof Stringable) ? ((string) $value) : '#NULL!';

if (!isset(self::$errorCodes[$value])) {
$value = '#NULL!';
Expand Down
6 changes: 3 additions & 3 deletions src/PhpSpreadsheet/Cell/DataValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function isValid(Cell $cell): bool
$returnValue = $this->numericOperator($dataValidation, (float) $cellValue);
}
} elseif ($type === DataValidation::TYPE_TEXTLENGTH) {
$returnValue = $this->numericOperator($dataValidation, mb_strlen((string) $cellValue));
$returnValue = $this->numericOperator($dataValidation, mb_strlen($cell->getValueString()));
}

return $returnValue;
Expand Down Expand Up @@ -86,14 +86,14 @@ private function numericOperator(DataValidation $dataValidation, int|float $cell
*/
private function isValueInList(Cell $cell): bool
{
$cellValue = $cell->getValue();
$cellValueString = $cell->getValueString();
$dataValidation = $cell->getDataValidation();

$formula1 = $dataValidation->getFormula1();
if (!empty($formula1)) {
// inline values list
if ($formula1[0] === '"') {
return in_array(strtolower($cellValue), explode(',', strtolower(trim($formula1, '"'))), true);
return in_array(strtolower($cellValueString), explode(',', strtolower(trim($formula1, '"'))), true);
} elseif (strpos($formula1, ':') > 0) {
// values list cells
$matchFormula = '=MATCH(' . $cell->getCoordinate() . ', ' . $formula1 . ', 0)';
Expand Down
Loading

0 comments on commit e8bb091

Please sign in to comment.