mirror of
https://github.com/PHPOffice/PhpSpreadsheet.git
synced 2026-08-25 14:18:19 +00:00
ae80c12ef0
This PR came about as I pondered how feasible it was to change the default escape character from backslash to null string, since the latter emulates Excel's own actions. Also, surveying issues relating to CSV, it seems that people are often in a situation where the current defaults aren't optimal for them (e.g. they are in a region where semicolon rather than comma is a better default delimiter). My case and that case can both be handled by methods after a reader is constructed. However, the issues also show that many use `IOFactory::load` rather than `new Csv()`, and the methods to affect the defaults are not available in that case. Adding a static callback that can be invoked by the constructor addresses all these problems. This can be set as part of the user application's normal initialization, and no special attention needs to be paid to CSV loads thereafter, no matter how they are invoked. This also makes it feasible to use 'guess' as inputEncoding, by providing a new setFallbackEncoding (default CP1252) method to use if none of the heuristic tests pass. There was already the ability to guess the encoding before `$reader->load()`, but not before `IOFactory::load`. Almost all typehints in Reader/Csv and Reader/Csv/Delimiter are now part of the function signature rather than in the DocBlock. The exceptions are one method in Delimiter which uses a `resource` parameter, and the `canRead` and `load` methods, which must match the signature in IOFactory. I will look into changing those later. The Csv Reader tests are moved into their own directory. All Phpstan baseline entries involving Csv Reader are eliminated.
94 lines
3.4 KiB
PHP
94 lines
3.4 KiB
PHP
<?php
|
|
|
|
namespace PhpOffice\PhpSpreadsheetTests\Reader\Csv;
|
|
|
|
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
use PhpOffice\PhpSpreadsheet\Reader\Csv;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class CsvCallbackTest extends TestCase
|
|
{
|
|
protected function tearDown(): void
|
|
{
|
|
Csv::setConstructorCallback(null);
|
|
}
|
|
|
|
/**
|
|
* @param mixed $obj
|
|
*/
|
|
public function callbackDoNothing($obj): void
|
|
{
|
|
self::assertInstanceOf(Csv::class, $obj);
|
|
}
|
|
|
|
public function testCallbackDoNothing(): void
|
|
{
|
|
Csv::setConstructorCallback([$this, 'callbackDoNothing']);
|
|
$filename = 'tests/data/Reader/CSV/encoding.iso88591.csv';
|
|
$reader = new Csv();
|
|
$reader->setInputEncoding(Csv::GUESS_ENCODING);
|
|
$spreadsheet = $reader->load($filename);
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
self::assertEquals('Å', $sheet->getCell('A1')->getValue());
|
|
}
|
|
|
|
public function callbackSetFallbackEncoding(Csv $reader): void
|
|
{
|
|
$reader->setFallbackEncoding('ISO-8859-2');
|
|
$reader->setInputEncoding(Csv::GUESS_ENCODING);
|
|
$reader->setEscapeCharacter((version_compare(PHP_VERSION, '7.4') < 0) ? "\x0" : '');
|
|
}
|
|
|
|
public function testFallbackEncodingDefltIso2(): void
|
|
{
|
|
Csv::setConstructorCallback([$this, 'callbackSetFallbackEncoding']);
|
|
$filename = 'tests/data/Reader/CSV/premiere.win1252.csv';
|
|
$reader = new Csv();
|
|
$spreadsheet = $reader->load($filename);
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
self::assertEquals('premičre', $sheet->getCell('A1')->getValue());
|
|
self::assertEquals('sixičme', $sheet->getCell('C2')->getValue());
|
|
}
|
|
|
|
public function testIOFactory(): void
|
|
{
|
|
Csv::setConstructorCallback([$this, 'callbackSetFallbackEncoding']);
|
|
$filename = 'tests/data/Reader/CSV/premiere.win1252.csv';
|
|
$spreadsheet = IOFactory::load($filename);
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
self::assertEquals('premičre', $sheet->getCell('A1')->getValue());
|
|
self::assertEquals('sixičme', $sheet->getCell('C2')->getValue());
|
|
}
|
|
|
|
public function testNonFallbackEncoding(): void
|
|
{
|
|
Csv::setConstructorCallback([$this, 'callbackSetFallbackEncoding']);
|
|
$filename = 'tests/data/Reader/CSV/premiere.utf16be.csv';
|
|
$reader = new Csv();
|
|
$spreadsheet = $reader->load($filename);
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
self::assertEquals('première', $sheet->getCell('A1')->getValue());
|
|
self::assertEquals('sixième', $sheet->getCell('C2')->getValue());
|
|
}
|
|
|
|
public function testDefaultEscape(): void
|
|
{
|
|
self::assertNull(Csv::getConstructorCallback());
|
|
$filename = 'tests/data/Reader/CSV/escape.csv';
|
|
$spreadsheet = IOFactory::load($filename);
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
// this is not how Excel views the file
|
|
self::assertEquals('a\"hello', $sheet->getCell('A1')->getValue());
|
|
}
|
|
|
|
public function testBetterEscape(): void
|
|
{
|
|
Csv::setConstructorCallback([$this, 'callbackSetFallbackEncoding']);
|
|
$filename = 'tests/data/Reader/CSV/escape.csv';
|
|
$spreadsheet = IOFactory::load($filename);
|
|
$sheet = $spreadsheet->getActiveSheet();
|
|
// this is how Excel views the file
|
|
self::assertEquals('a\"hello;hello;hello;\"', $sheet->getCell('A1')->getValue());
|
|
}
|
|
}
|