Files
oleibman e40438916f Change Style Without Affecting Current Cell/Sheet, and Invalid Formulas
Fix #1310, which was closed as stale in 2020, but which I will now reopen. Supersedes PR #1311 (@jaiminmoslake7020), from which I will remove the stale label but leave closed. The issue and the PR were too limited  - they detected that the use of two equal signs at the start of a string made for an invalid formula, but there are variations, trivial and otherwise, which might also be detected. Using `setValue` with a string which starts with an equal sign will now attempt to parse (not evaluate) the formula; for certain situations in which the parser throws an exception, the string will be treated as a string rather than a formula. An example where it will still be treated as a formula is a 3D range reference, where the problem is not that it can't be parsed, but rather that the formula isn't supported (see unit test Calculation/Engine/RangeTest::test3dRangeEvaluation). Allowing such a formula might cause problems later on, but that is already what happens.

A string beginning with an equal sign but which isn't treated as a formula will automatically set the `quotePrefix` attribute to `true`; all other `setValue` attempts will set it to `false`. This avoids the problem of a lingering value causing problems later on.

It has long been a matter of discontent that setting a style can change the selected cells. A new method is added to `Worksheet`:
```php
applyStylesFromArray(string $coordinate, array $styleArray)
```
This will attempt to guarantee that the active sheet in the current spreadsheet, and the selected cells in the current worksheet, remain undisturbed after the call. The setting of `quotePrefix` above is the first use of the new method.
2024-06-25 22:06:36 -07:00

294 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests\Cell;
use DateTime;
use DateTimeZone;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\Cell\StringValueBinder;
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PHPUnit\Framework\TestCase;
class StringValueBinderTest extends TestCase
{
private \PhpOffice\PhpSpreadsheet\Cell\IValueBinder $valueBinder;
protected function setUp(): void
{
$this->valueBinder = Cell::getValueBinder();
}
protected function tearDown(): void
{
Cell::setValueBinder($this->valueBinder);
}
/**
* @dataProvider providerDataValuesDefault
*/
public function testStringValueBinderDefaultBehaviour(
mixed $value,
mixed $expectedValue,
string $expectedDataType
): void {
Cell::setValueBinder(new StringValueBinder());
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$cell = $sheet->getCell('A1');
$cell->setValue($value);
self::assertSame($expectedValue, $cell->getValue());
self::assertSame($expectedDataType, $cell->getDataType());
$spreadsheet->disconnectWorksheets();
}
public static function providerDataValuesDefault(): array
{
return [
[null, '', DataType::TYPE_STRING],
[true, '1', DataType::TYPE_STRING],
[false, '', DataType::TYPE_STRING],
['', '', DataType::TYPE_STRING],
['123', '123', DataType::TYPE_STRING],
['123.456', '123.456', DataType::TYPE_STRING],
['0.123', '0.123', DataType::TYPE_STRING],
['.123', '.123', DataType::TYPE_STRING],
['-0.123', '-0.123', DataType::TYPE_STRING],
['-.123', '-.123', DataType::TYPE_STRING],
['1.23e-4', '1.23e-4', DataType::TYPE_STRING],
['ABC', 'ABC', DataType::TYPE_STRING],
['=SUM(A1:C3)', '=SUM(A1:C3)', DataType::TYPE_STRING],
[123, '123', DataType::TYPE_STRING],
[123.456, '123.456', DataType::TYPE_STRING],
[0.123, '0.123', DataType::TYPE_STRING],
[.123, '0.123', DataType::TYPE_STRING],
[-0.123, '-0.123', DataType::TYPE_STRING],
[-.123, '-0.123', DataType::TYPE_STRING],
[1.23e-4, '0.000123', DataType::TYPE_STRING],
[1.23e-24, '1.23E-24', DataType::TYPE_STRING],
[new DateTime('2021-06-01 00:00:00', new DateTimeZone('UTC')), '2021-06-01 00:00:00', DataType::TYPE_STRING],
];
}
public function testNonStringableBindValue(): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
Cell::setValueBinder(new StringValueBinder());
try {
$sheet->getCell('A1')->setValue($this);
self::fail('Did not receive expected Exception');
} catch (SpreadsheetException $e) {
self::assertStringContainsString('Unable to bind unstringable', $e->getMessage());
}
$sheet->getCell('A2')->setValue(new StringableObject());
self::assertSame('abc', $sheet->getCell('A2')->getValue());
try {
$sheet->getCell('A3')->setValue([1, 2, 3]);
self::fail('Did not receive expected Exception');
} catch (SpreadsheetException $e) {
self::assertStringContainsString('Unable to bind unstringable', $e->getMessage());
}
$spreadsheet->disconnectWorksheets();
}
/**
* @dataProvider providerDataValuesSuppressNullConversion
*/
public function testStringValueBinderSuppressNullConversion(
mixed $value,
mixed $expectedValue,
string $expectedDataType
): void {
$binder = new StringValueBinder();
$binder->setNullConversion(false);
Cell::setValueBinder($binder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$cell = $sheet->getCell('A1');
$cell->setValue($value);
self::assertSame($expectedValue, $cell->getValue());
self::assertSame($expectedDataType, $cell->getDataType());
$spreadsheet->disconnectWorksheets();
}
public static function providerDataValuesSuppressNullConversion(): array
{
return [
[null, null, DataType::TYPE_NULL],
[true, '1', DataType::TYPE_STRING],
[123, '123', DataType::TYPE_STRING],
];
}
/**
* @dataProvider providerDataValuesSuppressBooleanConversion
*/
public function testStringValueBinderSuppressBooleanConversion(
mixed $value,
mixed $expectedValue,
string $expectedDataType
): void {
$binder = new StringValueBinder();
$binder->setBooleanConversion(false);
Cell::setValueBinder($binder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$cell = $sheet->getCell('A1');
$cell->setValue($value);
self::assertSame($expectedValue, $cell->getValue());
self::assertSame($expectedDataType, $cell->getDataType());
$spreadsheet->disconnectWorksheets();
}
public static function providerDataValuesSuppressBooleanConversion(): array
{
return [
[true, true, DataType::TYPE_BOOL],
[false, false, DataType::TYPE_BOOL],
[null, '', DataType::TYPE_STRING],
[123, '123', DataType::TYPE_STRING],
];
}
/**
* @dataProvider providerDataValuesSuppressNumericConversion
*/
public function testStringValueBinderSuppressNumericConversion(
mixed $value,
mixed $expectedValue,
string $expectedDataType
): void {
$binder = new StringValueBinder();
$binder->setNumericConversion(false);
Cell::setValueBinder($binder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$cell = $sheet->getCell('A1');
$cell->setValue($value);
self::assertSame($expectedValue, $cell->getValue());
self::assertSame($expectedDataType, $cell->getDataType());
$spreadsheet->disconnectWorksheets();
}
public static function providerDataValuesSuppressNumericConversion(): array
{
return [
[123, 123, DataType::TYPE_NUMERIC],
[123.456, 123.456, DataType::TYPE_NUMERIC],
[0.123, 0.123, DataType::TYPE_NUMERIC],
[.123, 0.123, DataType::TYPE_NUMERIC],
[-0.123, -0.123, DataType::TYPE_NUMERIC],
[-.123, -0.123, DataType::TYPE_NUMERIC],
[1.23e-4, 0.000123, DataType::TYPE_NUMERIC],
[1.23e-24, 1.23E-24, DataType::TYPE_NUMERIC],
[true, '1', DataType::TYPE_STRING],
[false, '', DataType::TYPE_STRING],
[null, '', DataType::TYPE_STRING],
];
}
/**
* @dataProvider providerDataValuesSuppressFormulaConversion
*/
public function testStringValueBinderSuppressFormulaConversion(
mixed $value,
mixed $expectedValue,
string $expectedDataType
): void {
$binder = new StringValueBinder();
$binder->setFormulaConversion(false);
Cell::setValueBinder($binder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$cell = $sheet->getCell('A1');
$cell->setValue($value);
self::assertSame($expectedValue, $cell->getValue());
self::assertSame($expectedDataType, $cell->getDataType());
if ($expectedDataType === DataType::TYPE_FORMULA) {
self::assertFalse($sheet->getStyle('A1')->getQuotePrefix());
} else {
self::assertTrue($sheet->getStyle('A1')->getQuotePrefix());
}
$spreadsheet->disconnectWorksheets();
}
public static function providerDataValuesSuppressFormulaConversion(): array
{
return [
'normal formula' => ['=SUM(A1:C3)', '=SUM(A1:C3)', DataType::TYPE_FORMULA],
'issue 1310' => ['======', '======', DataType::TYPE_STRING],
];
}
/**
* @dataProvider providerDataValuesSuppressAllConversion
*/
public function testStringValueBinderSuppressAllConversion(
mixed $value,
mixed $expectedValue,
string $expectedDataType
): void {
$binder = new StringValueBinder();
$binder->setConversionForAllValueTypes(false);
Cell::setValueBinder($binder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$cell = $sheet->getCell('A1');
$cell->setValue($value);
self::assertSame($expectedValue, $cell->getValue());
self::assertSame($expectedDataType, $cell->getDataType());
$spreadsheet->disconnectWorksheets();
}
public static function providerDataValuesSuppressAllConversion(): array
{
return [
[null, null, DataType::TYPE_NULL],
[true, true, DataType::TYPE_BOOL],
[false, false, DataType::TYPE_BOOL],
['', '', DataType::TYPE_STRING],
['123', '123', DataType::TYPE_STRING],
['123.456', '123.456', DataType::TYPE_STRING],
['0.123', '0.123', DataType::TYPE_STRING],
['.123', '.123', DataType::TYPE_STRING],
['-0.123', '-0.123', DataType::TYPE_STRING],
['-.123', '-.123', DataType::TYPE_STRING],
['1.23e-4', '1.23e-4', DataType::TYPE_STRING],
['ABC', 'ABC', DataType::TYPE_STRING],
['=SUM(A1:C3)', '=SUM(A1:C3)', DataType::TYPE_FORMULA, false],
[123, 123, DataType::TYPE_NUMERIC],
[123.456, 123.456, DataType::TYPE_NUMERIC],
[0.123, 0.123, DataType::TYPE_NUMERIC],
[.123, 0.123, DataType::TYPE_NUMERIC],
[-0.123, -0.123, DataType::TYPE_NUMERIC],
[-.123, -0.123, DataType::TYPE_NUMERIC],
[1.23e-4, 0.000123, DataType::TYPE_NUMERIC],
[1.23e-24, 1.23E-24, DataType::TYPE_NUMERIC],
];
}
public function testStringValueBinderForRichTextObject(): void
{
$objRichText = new RichText();
$objRichText->createText('Hello World');
$binder = new StringValueBinder();
$binder->setConversionForAllValueTypes(false);
Cell::setValueBinder($binder);
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$cell = $sheet->getCell('A1');
$cell->setValue($objRichText);
self::assertSame('inlineStr', $cell->getDataType());
self::assertSame('Hello World', $cell->getCalculatedValue());
$spreadsheet->disconnectWorksheets();
}
}