Files
oleibman 981d429630 Make Reader/Csv Easily Extendable
Fix #4836, an issue which I was about to dismiss out-of-hand, but I decided that we could and should be making it a bit easier for our users to avoid problems. To that end, I have changed most of the private properties in Reader/Csv from `private` to `protected`. This allows users to extend the class easily; it is a better option than the existing `constructorCallback` - less awkward, not reliant on static settings, etc. (I know more now than I did when I introduced it.) Taking advantage of the new extendability, a new class `Reader\CsvNoEscape` is added which forces the use of null-string as the escape character, and prevents the use of auto-detection of Mac line-endings; both of these are schedule for removal with Php9.
This actually makes `CsvNoEscape` a better option than `Csv`;
it should probably be used for all new development.

Because `IOFactory` is heavily used for loading files, it has some changes to better accommodate `CsvNoEscape` (and other classes). In particular, methods `createReader`, `load`, `identify`, and `createReaderForFile` now allow a new `mergeArray` parameter, where that parameter is merged with the static `IOFactory::$readers` array when deciding on a Reader class to be used. This allows the user to override the default Csv Reader, without having to change a static array, and without affecting IOFactory's ability to find other matching readers if no suitable entry is found in `mergeArray`.
2026-03-12 16:50:21 -07:00

211 lines
7.3 KiB
PHP

<?php
declare(strict_types=1);
namespace PhpOffice\PhpSpreadsheetTests;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
class IOFactoryTest extends TestCase
{
#[DataProvider('providerCreateWriter')]
public function testCreateWriter(string $name, string $expected): void
{
$spreadsheet = new Spreadsheet();
$actual = IOFactory::createWriter($spreadsheet, $name);
self::assertSame($expected, $actual::class);
}
public static function providerCreateWriter(): array
{
return [
['Xls', Writer\Xls::class],
['Xlsx', Writer\Xlsx::class],
['Ods', Writer\Ods::class],
['Csv', Writer\Csv::class],
['Html', Writer\Html::class],
['Mpdf', Writer\Pdf\Mpdf::class],
['Tcpdf', Writer\Pdf\Tcpdf::class],
['Dompdf', Writer\Pdf\Dompdf::class],
];
}
public function testRegisterWriter(): void
{
IOFactory::registerWriter('Pdf', Writer\Pdf\Mpdf::class);
$spreadsheet = new Spreadsheet();
$actual = IOFactory::createWriter($spreadsheet, 'Pdf');
self::assertInstanceOf(Writer\Pdf\Mpdf::class, $actual);
}
#[DataProvider('providerCreateReader')]
public function testCreateReader(string $name, string $expected): void
{
$actual = IOFactory::createReader($name);
self::assertSame($expected, $actual::class);
}
public static function providerCreateReader(): array
{
return [
['Xls', Reader\Xls::class],
['Xlsx', Reader\Xlsx::class],
['Xml', Reader\Xml::class],
['Ods', Reader\Ods::class],
['Gnumeric', Reader\Gnumeric::class],
['Csv', Reader\Csv::class],
['Slk', Reader\Slk::class],
['Html', Reader\Html::class],
];
}
public function testRegisterReader(): void
{
IOFactory::registerReader('Custom', Reader\Html::class);
$actual = IOFactory::createReader('Custom');
self::assertInstanceOf(Reader\Html::class, $actual);
}
#[DataProvider('providerIdentify')]
public function testIdentifyCreateLoad(string $file, string $expectedName, string $expectedClass): void
{
$actual = IOFactory::identify($file);
self::assertSame($expectedName, $actual);
$actual = IOFactory::createReaderForFile($file);
self::assertSame($expectedClass, $actual::class);
IOFactory::load($file);
}
public static function providerIdentify(): array
{
return [
['samples/templates/26template.xlsx', 'Xlsx', Reader\Xlsx::class],
['samples/templates/GnumericTest.gnumeric', 'Gnumeric', Reader\Gnumeric::class],
['tests/data/Reader/Gnumeric/PageSetup.gnumeric.unzipped.xml', 'Gnumeric', Reader\Gnumeric::class],
['samples/templates/old.gnumeric', 'Gnumeric', Reader\Gnumeric::class],
['samples/templates/30template.xls', 'Xls', Reader\Xls::class],
['samples/templates/OOCalcTest.ods', 'Ods', Reader\Ods::class],
['samples/templates/SylkTest.slk', 'Slk', Reader\Slk::class],
['samples/templates/excel2003.xml', 'Xml', Reader\Xml::class],
// Following not readable by Excel.
//['samples/templates/Excel2003XMLTest.xml', 'Xml', Reader\Xml::class],
['samples/templates/46readHtml.html', 'Html', Reader\Html::class],
['tests/data/Reader/CSV/encoding.utf8bom.csv', 'Csv', Reader\Csv::class],
['tests/data/Reader/HTML/charset.UTF-16.lebom.html', 'Html', Reader\Html::class],
['tests/data/Reader/HTML/charset.UTF-8.bom.html', 'Html', Reader\Html::class],
];
}
public function testIdentifyInvalid(): void
{
$file = __DIR__ . '/../data/Reader/NotASpreadsheetFile.doc';
$this->expectException(ReaderException::class);
$this->expectExceptionMessage('Unable to identify a reader for this file');
IOFactory::identify($file);
}
public function testCreateInvalid(): void
{
$file = __DIR__ . '/../data/Reader/NotASpreadsheetFile.doc';
$this->expectException(ReaderException::class);
$this->expectExceptionMessage('Unable to identify a reader for this file');
IOFactory::createReaderForFile($file);
}
public function testLoadInvalid(): void
{
$file = __DIR__ . '/../data/Reader/NotASpreadsheetFile.doc';
$this->expectException(ReaderException::class);
$this->expectExceptionMessage('Unable to identify a reader for this file');
IOFactory::load($file);
}
public function testFormatAsExpected(): void
{
$fileName = 'samples/templates/30template.xls';
$actual = IOFactory::identify($fileName, [IOFactory::READER_XLS]);
self::assertSame('Xls', $actual);
}
public function testFormatAsExpectedAfterMerge(): void
{
$fileName = 'samples/templates/30template.xls';
$actual = IOFactory::identify($fileName, mergeArray: ['Csv' => Reader\CsvNoEscape::class]);
self::assertSame('Xls', $actual);
}
public function testFormatNotAsExpectedThrowsException(): void
{
$fileName = 'samples/templates/30template.xls';
$this->expectException(ReaderException::class);
IOFactory::identify($fileName, [IOFactory::READER_ODS]);
}
public function testIdentifyNonExistingFileThrowException(): void
{
$this->expectException(ReaderException::class);
IOFactory::identify('/non/existing/file');
}
public function testIdentifyExistingDirectoryThrowExceptions(): void
{
$this->expectException(ReaderException::class);
IOFactory::identify('.');
}
public function testCreateInvalidWriter(): void
{
$this->expectException(Writer\Exception::class);
$spreadsheet = new Spreadsheet();
IOFactory::createWriter($spreadsheet, 'bad');
}
public function testCreateInvalidReader(): void
{
$this->expectException(ReaderException::class);
IOFactory::createReader('bad');
}
public function testCreateReaderUnknownExtension(): void
{
$filename = 'samples/Reader2/sampleData/example1.tsv';
$reader = IOFactory::createReaderForFile($filename);
self::assertEquals('PhpOffice\PhpSpreadsheet\Reader\Csv', $reader::class);
}
public function testCreateReaderCsvExtension(): void
{
$filename = 'samples/Reader2/sampleData/example1.csv';
$reader = IOFactory::createReaderForFile($filename);
self::assertEquals('PhpOffice\PhpSpreadsheet\Reader\Csv', $reader::class);
}
public function testCreateReaderNoExtension(): void
{
$filename = 'samples/Reader/sampleData/example1xls';
$reader = IOFactory::createReaderForFile($filename);
self::assertEquals('PhpOffice\PhpSpreadsheet\Reader\Xls', $reader::class);
}
public function testCreateReaderNotSpreadsheet(): void
{
$this->expectException(ReaderException::class);
$filename = __FILE__;
IOFactory::createReaderForFile($filename);
}
}