PHP's built-in functions fputcsv and fgetcsv are not RFC-4180 compliant and include an escape character that's not defined in the spec. This results in escaping characters that should not be escaped. This commit disables this escaping mechanism.
225 lines
7.4 KiB
PHP
225 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace Box\Spout\Writer\CSV;
|
|
|
|
use Box\Spout\Common\Exception\InvalidArgumentException;
|
|
use Box\Spout\Common\Exception\IOException;
|
|
use Box\Spout\Common\Helper\EncodingHelper;
|
|
use Box\Spout\Common\Type;
|
|
use Box\Spout\TestUsingResource;
|
|
use Box\Spout\Writer\Common\Creator\EntityFactory;
|
|
use Box\Spout\Writer\Common\Entity\Row;
|
|
use Box\Spout\Writer\Exception\WriterNotOpenedException;
|
|
use Box\Spout\Writer\RowCreationHelper;
|
|
|
|
/**
|
|
* Class WriterTest
|
|
*/
|
|
class WriterTest extends \PHPUnit_Framework_TestCase
|
|
{
|
|
use TestUsingResource;
|
|
use RowCreationHelper;
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldThrowExceptionIfCannotOpenFileForWriting()
|
|
{
|
|
$this->expectException(IOException::class);
|
|
|
|
$fileName = 'file_that_wont_be_written.csv';
|
|
$this->createUnwritableFolderIfNeeded();
|
|
$filePath = $this->getGeneratedUnwritableResourcePath($fileName);
|
|
|
|
$writer = EntityFactory::createWriter(Type::CSV);
|
|
@$writer->openToFile($filePath);
|
|
$writer->addRow($this->createRowFromValues(['csv--11', 'csv--12']));
|
|
$writer->close();
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldThrowExceptionIfCallAddRowBeforeOpeningWriter()
|
|
{
|
|
$this->expectException(WriterNotOpenedException::class);
|
|
|
|
$writer = EntityFactory::createWriter(Type::CSV);
|
|
$writer->addRow($this->createRowFromValues(['csv--11', 'csv--12']));
|
|
$writer->close();
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldThrowExceptionIfCallAddRowsBeforeOpeningWriter()
|
|
{
|
|
$this->expectException(WriterNotOpenedException::class);
|
|
|
|
$writer = EntityFactory::createWriter(Type::CSV);
|
|
$writer->addRow($this->createRowFromValues(['csv--11', 'csv--12']));
|
|
$writer->close();
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testAddRowsShouldThrowExceptionIfRowsAreNotArrayOfArrays()
|
|
{
|
|
$this->expectException(InvalidArgumentException::class);
|
|
|
|
$writer = EntityFactory::createWriter(Type::CSV);
|
|
$writer->addRows([['csv--11', 'csv--12']]);
|
|
$writer->close();
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testCloseShouldNoopWhenWriterIsNotOpened()
|
|
{
|
|
$fileName = 'test_double_close_calls.csv';
|
|
$this->createGeneratedFolderIfNeeded($fileName);
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
$writer = EntityFactory::createWriter(Type::CSV);
|
|
$writer->close(); // This call should not cause any error
|
|
|
|
$writer->openToFile($resourcePath);
|
|
$writer->close();
|
|
$writer->close(); // This call should not cause any error
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldAddUtf8Bom()
|
|
{
|
|
$allRows = $this->createRowsFromValues([
|
|
['csv--11', 'csv--12'],
|
|
]);
|
|
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_utf8_bom.csv');
|
|
|
|
$this->assertContains(EncodingHelper::BOM_UTF8, $writtenContent, 'The CSV file should contain a UTF-8 BOM');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldNotAddUtf8Bom()
|
|
{
|
|
$allRows = $this->createRowsFromValues([
|
|
['csv--11', 'csv--12'],
|
|
]);
|
|
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_no_bom.csv', ',', '"', false);
|
|
|
|
$this->assertNotContains(EncodingHelper::BOM_UTF8, $writtenContent, 'The CSV file should not contain a UTF-8 BOM');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldSupportNullValues()
|
|
{
|
|
$allRows = $this->createRowsFromValues([
|
|
['csv--11', null, 'csv--13'],
|
|
]);
|
|
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_null_values.csv');
|
|
$writtenContent = $this->trimWrittenContent($writtenContent);
|
|
|
|
$this->assertEquals('csv--11,,csv--13', $writtenContent, 'The null values should be replaced by empty values');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldNotSkipEmptyRows()
|
|
{
|
|
$allRows = $this->createRowsFromValues([
|
|
['csv--11', 'csv--12'],
|
|
[],
|
|
['csv--31', 'csv--32'],
|
|
]);
|
|
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_empty_rows.csv');
|
|
$writtenContent = $this->trimWrittenContent($writtenContent);
|
|
|
|
$this->assertEquals("csv--11,csv--12\n\ncsv--31,csv--32", $writtenContent, 'Empty rows should be skipped');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldSupportCustomFieldDelimiter()
|
|
{
|
|
$allRows = $this->createRowsFromValues([
|
|
['csv--11', 'csv--12', 'csv--13'],
|
|
['csv--21', 'csv--22', 'csv--23'],
|
|
]);
|
|
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_pipe_delimiters.csv', '|');
|
|
$writtenContent = $this->trimWrittenContent($writtenContent);
|
|
|
|
$this->assertEquals("csv--11|csv--12|csv--13\ncsv--21|csv--22|csv--23", $writtenContent, 'The fields should be delimited with |');
|
|
}
|
|
|
|
/**
|
|
* @return void
|
|
*/
|
|
public function testWriteShouldSupportCustomFieldEnclosure()
|
|
{
|
|
$allRows = $this->createRowsFromValues([
|
|
['This is, a comma', 'csv--12', 'csv--13'],
|
|
]);
|
|
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_pound_enclosures.csv', ',', '#');
|
|
$writtenContent = $this->trimWrittenContent($writtenContent);
|
|
|
|
$this->assertEquals('#This is, a comma#,csv--12,csv--13', $writtenContent, 'The fields should be enclosed with #');
|
|
}
|
|
|
|
public function testWriteShouldSupportedEscapedCharacters()
|
|
{
|
|
$allRows = $this->createRowsFromValues([
|
|
['"csv--11"', 'csv--12\\', 'csv--13\\\\', 'csv--14\\\\\\'],
|
|
]);
|
|
$writtenContent = $this->writeToCsvFileAndReturnWrittenContent($allRows, 'csv_with_escaped_characters.csv');
|
|
$writtenContent = $this->trimWrittenContent($writtenContent);
|
|
|
|
$this->assertEquals('"""csv--11""",csv--12\\,csv--13\\\\,csv--14\\\\\\', $writtenContent, 'The \'"\' and \'\\\' characters should be properly escaped');
|
|
}
|
|
|
|
/**
|
|
* @param Row[] $allRows
|
|
* @param string $fileName
|
|
* @param string $fieldDelimiter
|
|
* @param string $fieldEnclosure
|
|
* @param bool $shouldAddBOM
|
|
* @return string|null
|
|
*/
|
|
private function writeToCsvFileAndReturnWrittenContent($allRows, $fileName, $fieldDelimiter = ',', $fieldEnclosure = '"', $shouldAddBOM = true)
|
|
{
|
|
$this->createGeneratedFolderIfNeeded($fileName);
|
|
$resourcePath = $this->getGeneratedResourcePath($fileName);
|
|
|
|
/** @var \Box\Spout\Writer\CSV\Writer $writer */
|
|
$writer = EntityFactory::createWriter(Type::CSV);
|
|
$writer->setFieldDelimiter($fieldDelimiter);
|
|
$writer->setFieldEnclosure($fieldEnclosure);
|
|
$writer->setShouldAddBOM($shouldAddBOM);
|
|
|
|
$writer->openToFile($resourcePath);
|
|
$writer->addRows($allRows);
|
|
$writer->close();
|
|
|
|
return file_get_contents($resourcePath);
|
|
}
|
|
|
|
/**
|
|
* @param string $writtenContent
|
|
* @return string
|
|
*/
|
|
private function trimWrittenContent($writtenContent)
|
|
{
|
|
// remove line feeds and UTF-8 BOM
|
|
return trim($writtenContent, PHP_EOL . EncodingHelper::BOM_UTF8);
|
|
}
|
|
}
|