Init of the v2 (CsvParser)

This commit is contained in:
Simon Vieille 2017-03-12 17:57:30 +01:00
parent 6c4a535ecd
commit 022c31a703
8 changed files with 423 additions and 127 deletions

View file

@ -1,6 +1,8 @@
CSV parser/generator CSV parser/generator
==================== ====================
![](https://phpci.gitnet.fr//build-status/image/6)
A simple PHP library to parse and generate CSV files. A simple PHP library to parse and generate CSV files.
## Composer installation ## Composer installation
@ -53,16 +55,16 @@ $csv->prependData(['Boo', '$3000']);
$csv->setDatas([[...], [...]]); $csv->setDatas([[...], [...]]);
// Defines the header // Defines the header
$csv->setHeaders(["Product", "Price"]); $csv->setHeaders(['Product', 'Price']);
// Rendering // Rendering
$result = $csv->render(); $result = $csv->render();
// Rendering to a file // Rendering to a file
$result = $csv->render("products.csv"); $result = $csv->render('products.csv');
// Appending to a file // Appending to a file
$result = $csv->render("products.csv", FILE_APPEND); $result = $csv->render('products.csv', FILE_APPEND);
``` ```
### Parser ### Parser
@ -70,10 +72,27 @@ $result = $csv->render("products.csv", FILE_APPEND);
```php ```php
use Deblan\Csv\CsvParser; use Deblan\Csv\CsvParser;
$csv = new CsvParser('products.csv'); $csv = new CsvParser();
$csv->setHasLegend(true);
$csv->parse();
$legend = $csv->getLegend(); // Defines the delimiter (default is ;)
$csv->setDelimiter(";");
// Defines the enclosure (default is ")
$csv->setEnclosure('"');
// Defines the end of line (default is \n)
$csv->setEndOfLine("\n");
// Headers?
$csv->setHasHeaders(true);
// Parse a file
$csv->parseFile('products.csv');
// Parse a string
$csv->parseString($myString);
// Headers and datas
$headers = $csv->getHeaders();
$products = $csv->getDatas(); $products = $csv->getDatas();
``` ```

View file

@ -2,149 +2,225 @@
namespace Deblan\Csv; namespace Deblan\Csv;
use Deblan\Csv\Exception\CsvParserInvalidParameterException; /**
use Deblan\Csv\Exception\CsvParserException; * class Csv.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class CsvParser class CsvParser
{ {
private $filename; /**
* @var string
*/
protected $delimiter = ';';
private $delimiter; /**
* @var string
*/
protected $enclosure = '"';
private $enclosure; /**
* @var string
*/
protected $endOfLine = "\n";
private $escapeChar; /**
* @var array
*/
protected $datas = [];
private $hasLegend; /**
* @var array
*/
protected $headers = [];
private $datas = array(); /**
* @var bool
*/
protected $hasHeaders = false;
private $legend = array(); /**
* Set the value of "delimiter".
private $nullValues = array(); *
* @param string $delimiter
public function __construct($filename, $delimiter = ';', $enclosure = '"', $escapeChar = '\\', $hasLegend = false, array $nullValues = array('')) *
* @return Csv
*/
public function setDelimiter($delimiter)
{ {
$this->setFilename($filename); $this->delimiter = (string) $delimiter;
$this->setDelimiter($delimiter);
$this->setEnclosure($enclosure);
$this->setEscapeChar($escapeChar);
$this->setHasLegend($hasLegend);
$this->setNullValues($nullValues);
}
public function setFilename($v) return $this;
{
if (!is_string($v)) {
throw new CsvParserInvalidParameterException(sprintf('"%s" is not a valid string.', $v));
}
if (!file_exists($v)) {
throw new CsvParserException(sprintf('"%s" does not exist.', $v));
}
if (!is_readable($v)) {
throw new CsvParserException(sprintf('"%s" is not readable.', $v));
}
$this->filename = $v;
}
public function setDelimiter($v)
{
if (!is_string($v)) {
throw new CsvParserInvalidParameterException(sprintf('"%s" is not a valid string.', $v));
}
$this->delimiter = $v;
}
public function setEnclosure($v)
{
if (!is_string($v)) {
throw new CsvParserInvalidParameterException(sprintf('"%s" is not a valid string.', $v));
}
$this->enclosure = $v;
}
public function setEscapeChar($v)
{
if (!is_string($v)) {
throw new CsvParserInvalidParameterException(sprintf('"%s" is not a valid string.', $v));
}
$this->escapeChar = $v;
}
public function setHasLegend($v)
{
if (!is_bool($v)) {
throw new CsvParserInvalidParameterException(sprintf('"%s" is not a valid bool.', $v));
}
$this->hasLegend = $v;
}
public function getHasLegend()
{
return $this->hasLegend;
}
public function getLegend()
{
return $this->legend;
}
public function setNullValues(array $v)
{
$this->nullValues = $v;
} }
/** /**
* Get the value of "delimiter".
* *
* To improve... * @return array
*
*/ */
protected function cleanNullValues($line) public function getDelimiter()
{ {
return str_replace($this->nullValues, '', $line); return $this->delimiter;
} }
/**
* Set the value of "enclosure".
*
* @param string $enclosure
*
* @return Csv
*/
public function setEnclosure($enclosure)
{
$this->enclosure = (string) $enclosure;
return $this;
}
/**
* Get the value of "enclosure".
*
* @return string
*/
public function getEnclosure()
{
return $this->enclosure;
}
/**
* Set the value of "endOfLine".
*
* @param string $endOfLine
*
* @return Csv
*/
public function setEndOfLine($endOfLine)
{
$this->endOfLine = (string) $endOfLine;
return $this;
}
/**
* Get the value of "endOfLine".
*
* @return string
*/
public function getEndOfLine()
{
return $this->endOfLine;
}
/**
* Get the value of "hasHeaders".
*
* @return bool
*/
public function getHasHeaders()
{
return $this->hasHeaders;
}
/**
* Set the value of "headers".
*
* @param bool $hasHeaders
*
* @return Csv
*/
public function setHasHeaders($hasHeaders)
{
$this->hasHeaders = (bool) $hasHeaders;
return $this;
}
/**
* Get the value of "headers".
*
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Get the value of "datas".
*
* @return array
*/
public function getDatas() public function getDatas()
{ {
return $this->datas; return $this->datas;
} }
public function parse() /*
* Parses a string.
*
* @param string $string
*
* @return CsvParser
*/
public function parseString($string)
{ {
if (!empty($this->datas)) { $this->datas = [];
return $this->datas; $this->headers = [];
} $lines = str_getcsv($string, $this->endOfLine);
$lines = file($this->filename); foreach ($lines as $key => $line) {
$data = $this->parseLine($line, $this->hasHeaders && $key === 0);
if (empty($lines)) { if ($data === null) {
return true; continue;
}
if ($this->hasLegend) {
$this->legend = str_getcsv($lines[0], $this->delimiter, $this->enclosure, $this->escapeChar);
unset($lines[0]);
}
foreach ($lines as $l => $line) {
$datas = str_getcsv($this->cleanNullValues($line), $this->delimiter, $this->enclosure, $this->escapeChar);
if ($this->hasLegend) {
foreach ($this->legend as $k => $v) {
$datas[$v] = isset($datas[$k]) ? $datas[$k] : null;
}
} }
$this->datas[] = $datas; if ($this->hasHeaders && $key === 0) {
$this->headers = $data;
} else {
$this->datas[] = $data;
}
} }
return true; return $this;
}
/*
* Parses a line.
*
* @param string $line
* @param bool $isHeaders
*
* @return array
*/
public function parseLine($line, $isHeaders = false)
{
$line = trim($line);
if (empty($line)) {
return null;
}
$csv = str_getcsv($line, $this->delimiter, $this->enclosure);
if (!$isHeaders && $this->hasHeaders && !empty($this->headers)) {
foreach ($this->headers as $key => $header) {
$csv[$header] = isset($csv[$key]) ? $csv[$key] : null;
}
}
return $csv;
}
/*
* Parses a file.
*
* @param string $filaname
*
* @return CsvParser
*/
public function parseFile($filename)
{
return $this->parseString(file_get_contents($filename));
} }
} }

View file

@ -1,8 +1,188 @@
<?php <?php
class CsvParserTest extends \PHPUnit_Framework_TestCase use Deblan\Csv\CsvParser;
class CsvParserParserTest extends \PHPUnit_Framework_TestCase
{ {
public function testTest() public function testGettersAndSettersAndDefaultValues()
{ {
} $parser = new CsvParser();
$this->assertEquals(';', $parser->getDelimiter());
$parser->setDelimiter('#');
$this->assertEquals('#', $parser->getDelimiter());
$parser = new CsvParser();
$this->assertEquals("\n", $parser->getEndOfLine());
$parser->setEndOfLine("\r\n");
$this->assertEquals("\r\n", $parser->getEndOfLine());
$parser = new CsvParser();
$this->assertEquals('"', $parser->getEnclosure());
$parser->setEnclosure("'");
$this->assertEquals("'", $parser->getEnclosure());
$parser = new CsvParser();
$this->assertEquals([], $parser->getDatas());
$this->assertEquals([], $parser->getHeaders());
$this->assertEquals(false, $parser->getHasHeaders());
$parser->setHasHeaders(true);
$this->assertEquals(true, $parser->getHasHeaders());
}
public function testParser()
{
$parser = new CsvParser();
$this->assertEquals(['foo', 'bar'], $parser->parseLine('"foo";"bar"'));
$this->assertEquals([], $parser->getDatas());
$this->assertEquals([], $parser->getHeaders());
$parser = new CsvParser();
$parser->parseString('"foo";"bar"');
$this->assertEquals([['foo', 'bar']], $parser->getDatas());
$this->assertEquals([], $parser->getHeaders());
$parser = new CsvParser();
$parser->parseString('"foo";"bar"'."\n".'"foo2";"bar2"');
$this->assertEquals([['foo', 'bar'], ['foo2', 'bar2']], $parser->getDatas());
$this->assertEquals([], $parser->getHeaders());
$parser = new CsvParser();
$parser->setHasHeaders(true);
$parser->parseString('"foo";"bar"'."\n".'"foo2";"bar2"');
$this->assertEquals([['foo2', 'bar2', 'foo' => 'foo2', 'bar' => 'bar2']], $parser->getDatas());
$this->assertEquals(['foo', 'bar'], $parser->getHeaders());
$parser = new CsvParser();
$parser->setHasHeaders(true);
$parser->setEnclosure(null);
$parser->parseString('foo;bar'."\n".'foo2;bar2;boo2');
$this->assertEquals([['foo2', 'bar2', 'boo2', 'foo' => 'foo2', 'bar' => 'bar2']], $parser->getDatas());
$this->assertEquals(['foo', 'bar'], $parser->getHeaders());
$parser = new CsvParser();
$parser->setHasHeaders(true);
$parser->parseString('foo;bar'."\n".'foo2');
$this->assertEquals([['foo2', 'foo' => 'foo2', 'bar' => null]], $parser->getDatas());
$this->assertEquals(['foo', 'bar'], $parser->getHeaders());
$parser = new CsvParser();
$parser->setHasHeaders(true);
$parser->parseFile(__DIR__.'/fixtures/example.csv');
$this->assertEquals(
[
[
'foo 1',
'bar 1',
'FOO' => 'foo 1',
'BAR' => 'bar 1',
],
[
'foo 2',
'bar 2',
'FOO' => 'foo 2',
'BAR' => 'bar 2',
],
[
'foo 3',
'bar 3',
'FOO' => 'foo 3',
'BAR' => 'bar 3',
],
],
$parser->getDatas()
);
$this->assertEquals(['FOO', 'BAR'], $parser->getHeaders());
$parser = new CsvParser();
$parser->setHasHeaders(false);
$parser->parseFile(__DIR__.'/fixtures/example2.csv');
$this->assertEquals(
[
[
'foo 1',
'bar 1',
],
[
'foo 2',
'ba"r 2',
],
[
'foo 3',
'bar 3',
],
],
$parser->getDatas()
);
$parser = new CsvParser();
$parser->setHasHeaders(true);
$parser->setEnclosure("'");
$parser->setDelimiter('#');
$parser->parseFile(__DIR__.'/fixtures/example3.csv');
$this->assertEquals(
[
[
'foo 1',
'',
"FO'O" => 'foo 1',
'BAR' => '',
],
[
'foo 1b',
"FO'O" => 'foo 1b',
'BAR' => null,
],
[
'foo 2',
'bar 2',
'unexpected 3',
"FO'O" => 'foo 2',
'BAR' => 'bar 2',
],
[
'foo 3',
'bar 3',
"FO'O" => 'foo 3',
'BAR' => 'bar 3',
],
],
$parser->getDatas()
);
$parser = new CsvParser();
$parser->setHasHeaders(true);
$parser->setEnclosure("'");
$parser->setDelimiter('#');
$parser->setEndOfLine("\r\n");
$parser->parseFile(__DIR__.'/fixtures/example4.csv');
$this->assertEquals(
[
[
'foo 1',
'',
"FO'O" => 'foo 1',
'BAR' => '',
],
[
'foo 1b',
"FO'O" => 'foo 1b',
'BAR' => null,
],
[
'foo 2',
'bar 2',
'unexpected 3',
"FO'O" => 'foo 2',
'BAR' => 'bar 2',
],
[
'foo 3',
'bar 3',
"FO'O" => 'foo 3',
'BAR' => 'bar 3',
],
],
$parser->getDatas()
);
}
} }

View file

@ -1,14 +1,13 @@
<?php <?php
use Deblan\Csv\Csv; use Deblan\Csv\Csv;
use PHPUnit_Framework_TestCase;
/** /**
* class CsvTest. * class CsvTest.
* *
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class CsvTest extends PHPUnit_Framework_TestCase class CsvTest extends \PHPUnit_Framework_TestCase
{ {
public function testGettersAndSettersAndDefaultValues() public function testGettersAndSettersAndDefaultValues()
{ {
@ -16,6 +15,11 @@ class CsvTest extends PHPUnit_Framework_TestCase
$this->assertEquals(';', $csv->getDelimiter()); $this->assertEquals(';', $csv->getDelimiter());
$csv->setDelimiter('#'); $csv->setDelimiter('#');
$this->assertEquals('#', $csv->getDelimiter()); $this->assertEquals('#', $csv->getDelimiter());
$csv = new Csv();
$this->assertEquals('"', $csv->getEnclosure());
$csv->setEnclosure('#');
$this->assertEquals('#', $csv->getEnclosure());
$csv = new Csv(); $csv = new Csv();
$this->assertEquals("\n", $csv->getEndOfLine()); $this->assertEquals("\n", $csv->getEndOfLine());

4
tests/fixtures/example.csv vendored Normal file
View file

@ -0,0 +1,4 @@
"FOO";"BAR"
"foo 1";"bar 1"
"foo 2";"bar 2"
"foo 3";"bar 3"
1 FOO BAR
2 foo 1 bar 1
3 foo 2 bar 2
4 foo 3 bar 3

3
tests/fixtures/example2.csv vendored Normal file
View file

@ -0,0 +1,3 @@
"foo 1";"bar 1"
"foo 2";"ba""r 2"
"foo 3";"bar 3"
1 foo 1 bar 1
2 foo 2 ba"r 2
3 foo 3 bar 3

5
tests/fixtures/example3.csv vendored Normal file
View file

@ -0,0 +1,5 @@
'FO''O'#'BAR'
'foo 1'#
'foo 1b'
'foo 2'#'bar 2'#'unexpected 3'
'foo 3'#'bar 3'
1 'FO''O'#'BAR'
2 'foo 1'#
3 'foo 1b'
4 'foo 2'#'bar 2'#'unexpected 3'
5 'foo 3'#'bar 3'

5
tests/fixtures/example4.csv vendored Normal file
View file

@ -0,0 +1,5 @@
'FO''O'#'BAR'
'foo 1'#
'foo 1b'
'foo 2'#'bar 2'#'unexpected 3'
'foo 3'#'bar 3'
1 'FO''O'#'BAR'
2 'foo 1'#
3 'foo 1b'
4 'foo 2'#'bar 2'#'unexpected 3'
5 'foo 3'#'bar 3'