Skip to content

Commit

Permalink
Merge pull request PHPOffice#4141 from oleibman/svbignoreerrors2
Browse files Browse the repository at this point in the history
String Value Binder Allow Setting "Ignore Number Stored As Text"
  • Loading branch information
oleibman authored Aug 24, 2024
2 parents 9c2c976 + 176c2c8 commit e1dae99
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### Added

- Excel Dynamic Arrays. [Issue #3901](https://github.com/PHPOffice/PhpSpreadsheet/issues/3901) [Issue #3659](https://github.com/PHPOffice/PhpSpreadsheet/issues/3659) [Issue #1834](https://github.com/PHPOffice/PhpSpreadsheet/issues/1834) [PR #3962](https://github.com/PHPOffice/PhpSpreadsheet/pull/3962)
- String Value Binder Allow Setting "Ignore Number Stored as Text". [PR #4141](https://github.com/PHPOffice/PhpSpreadsheet/pull/4141)

### Changed

Expand Down
1 change: 1 addition & 0 deletions docs/topics/accessing-cells.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ By default, the StringValueBinder will cast any datatype passed to it into a str
// Set value binder
$stringValueBinder = new \PhpOffice\PhpSpreadsheet\Cell\StringValueBinder();
$stringValueBinder->setNumericConversion(false)
->setSetIgnoredErrors(true) // suppresses "number stored as text" indicators
->setBooleanConversion(false)
->setNullConversion(false)
->setFormulaConversion(false);
Expand Down
20 changes: 20 additions & 0 deletions src/PhpSpreadsheet/Cell/StringValueBinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ class StringValueBinder extends DefaultValueBinder implements IValueBinder

protected bool $convertFormula = true;

protected bool $setIgnoredErrors = false;

public function setSetIgnoredErrors(bool $setIgnoredErrors = false): self
{
$this->setIgnoredErrors = $setIgnoredErrors;

return $this;
}

public function setNullConversion(bool $suppressConversion = false): self
{
$this->convertNull = $suppressConversion;
Expand Down Expand Up @@ -81,6 +90,7 @@ public function bindValue(Cell $cell, mixed $value): bool
$value = StringHelper::sanitizeUTF8($value);
}

$ignoredErrors = false;
if ($value === null && $this->convertNull === false) {
$cell->setValueExplicit($value, DataType::TYPE_NULL);
} elseif (is_bool($value) && $this->convertBoolean === false) {
Expand All @@ -90,25 +100,35 @@ public function bindValue(Cell $cell, mixed $value): bool
} elseif (is_string($value) && strlen($value) > 1 && $value[0] === '=' && $this->convertFormula === false && parent::dataTypeForValue($value) === DataType::TYPE_FORMULA) {
$cell->setValueExplicit($value, DataType::TYPE_FORMULA);
} else {
$ignoredErrors = is_numeric($value);
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
}
if ($this->setIgnoredErrors) {
$cell->getIgnoredErrors()->setNumberStoredAsText($ignoredErrors);
}

return true;
}

protected function bindObjectValue(Cell $cell, object $value): bool
{
// Handle any objects that might be injected
$ignoredErrors = false;
if ($value instanceof DateTimeInterface) {
$value = $value->format('Y-m-d H:i:s');
$cell->setValueExplicit($value, DataType::TYPE_STRING);
} elseif ($value instanceof RichText) {
$cell->setValueExplicit($value, DataType::TYPE_INLINE);
$ignoredErrors = is_numeric($value->getPlainText());
} elseif ($value instanceof Stringable) {
$cell->setValueExplicit((string) $value, DataType::TYPE_STRING);
$ignoredErrors = is_numeric((string) $value);
} else {
throw new SpreadsheetException('Unable to bind unstringable object of type ' . get_class($value));
}
if ($this->setIgnoredErrors) {
$cell->getIgnoredErrors()->setNumberStoredAsText($ignoredErrors);
}

return true;
}
Expand Down
129 changes: 129 additions & 0 deletions tests/PhpSpreadsheetTests/Cell/StringValueBinder2Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Cell;

use DateTime;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\IValueBinder;
use PhpOffice\PhpSpreadsheet\Cell\StringValueBinder;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PHPUnit\Framework\TestCase;

class StringValueBinder2Test extends TestCase
{
private IValueBinder $valueBinder;

protected function setUp(): void
{
$this->valueBinder = Cell::getValueBinder();
}

protected function tearDown(): void
{
Cell::setValueBinder($this->valueBinder);
}

public function testStringValueBinderIgnoredErrorsDefault(): void
{
$valueBinder = new StringValueBinder();
Cell::setValueBinder($valueBinder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$richText = new RichText();
$richText->createTextRun('6');
$richText2 = new RichText();
$richText2->createTextRun('a');
$sheet->fromArray([
[1, 'x', 3.2],
['y', -5, 'z'],
[new DateTime(), $richText, $richText2],
[new StringableObject('a'), new StringableObject(2), 'z'],
]);
$ignoredCells = [];
foreach ($sheet->getRowIterator() as $row) {
foreach ($row->getCellIterator() as $cell) {
$coordinate = $cell->getCoordinate();
$dataType = $cell->getDataType();
if ($dataType !== DataType::TYPE_INLINE) {
self::assertSame(DataType::TYPE_STRING, $dataType, "not string for cell $coordinate");
}
if ($cell->getIgnoredErrors()->getNumberStoredAsText()) {
$ignoredCells[] = $coordinate;
}
}
}
self::assertSame([], $ignoredCells);
$spreadsheet->disconnectWorksheets();
}

public function testStringValueBinderIgnoredErrorsTrue(): void
{
$valueBinder = new StringValueBinder();
$valueBinder->setSetIgnoredErrors(true);
Cell::setValueBinder($valueBinder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$richText = new RichText();
$richText->createTextRun('6');
$richText2 = new RichText();
$richText2->createTextRun('a');
$sheet->fromArray([
[1, 'x', 3.2],
['y', -5, 'z'],
[new DateTime(), $richText, $richText2],
[new StringableObject('a'), new StringableObject(2), 'z'],
]);
$ignoredCells = [];
foreach ($sheet->getRowIterator() as $row) {
foreach ($row->getCellIterator() as $cell) {
$coordinate = $cell->getCoordinate();
$dataType = $cell->getDataType();
if ($dataType !== DataType::TYPE_INLINE) {
self::assertSame(DataType::TYPE_STRING, $dataType, "not string for cell $coordinate");
}
if ($cell->getIgnoredErrors()->getNumberStoredAsText()) {
$ignoredCells[] = $coordinate;
}
}
}
self::assertSame(['A1', 'C1', 'B2', 'B3', 'B4'], $ignoredCells);
$spreadsheet->disconnectWorksheets();
}

public function testStringValueBinderPreserveNumeric(): void
{
$valueBinder = new StringValueBinder();
$valueBinder->setNumericConversion(false);
$valueBinder->setSetIgnoredErrors(true);
Cell::setValueBinder($valueBinder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$richText = new RichText();
$richText->createTextRun('6');
$richText2 = new RichText();
$richText2->createTextRun('a');
$sheet->fromArray([
[1, 'x', 3.2],
['y', -5, 'z'],
[new DateTime(), $richText, $richText2],
[new StringableObject('a'), new StringableObject(2), 'z'],
]);
$ignoredCells = [];
foreach ($sheet->getRowIterator() as $row) {
foreach ($row->getCellIterator() as $cell) {
$coordinate = $cell->getCoordinate();
$expected = (is_int($cell->getValue()) || is_float($cell->getValue())) ? DataType::TYPE_NUMERIC : (($cell->getValue() instanceof RichText) ? DataType::TYPE_INLINE : DataType::TYPE_STRING);
self::assertSame($expected, $cell->getDataType(), "wrong type for cell $coordinate");
if ($cell->getIgnoredErrors()->getNumberStoredAsText()) {
$ignoredCells[] = $coordinate;
}
}
}
self::assertSame(['B3', 'B4'], $ignoredCells);
$spreadsheet->disconnectWorksheets();
}
}
9 changes: 8 additions & 1 deletion tests/PhpSpreadsheetTests/Cell/StringableObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@

class StringableObject
{
private int|string $value;

public function __construct(int|string $value = 'abc')
{
$this->value = $value;
}

public function __toString(): string
{
return 'abc';
return (string) $this->value;
}
}

0 comments on commit e1dae99

Please sign in to comment.