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
====================
![](https://phpci.gitnet.fr//build-status/image/6)
A simple PHP library to parse and generate CSV files.
## Composer installation
@ -53,16 +55,16 @@ $csv->prependData(['Boo', '$3000']);
$csv->setDatas([[...], [...]]);
// Defines the header
$csv->setHeaders(["Product", "Price"]);
$csv->setHeaders(['Product', 'Price']);
// Rendering
$result = $csv->render();
// Rendering to a file
$result = $csv->render("products.csv");
$result = $csv->render('products.csv');
// Appending to a file
$result = $csv->render("products.csv", FILE_APPEND);
$result = $csv->render('products.csv', FILE_APPEND);
```
### Parser
@ -70,10 +72,27 @@ $result = $csv->render("products.csv", FILE_APPEND);
```php
use Deblan\Csv\CsvParser;
$csv = new CsvParser('products.csv');
$csv->setHasLegend(true);
$csv->parse();
$csv = new CsvParser();
$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();
```

View File

@ -2,149 +2,225 @@
namespace Deblan\Csv;
use Deblan\Csv\Exception\CsvParserInvalidParameterException;
use Deblan\Csv\Exception\CsvParserException;
/**
* class Csv.
*
* @author Simon Vieille <simon@deblan.fr>
*/
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();
private $nullValues = array();
public function __construct($filename, $delimiter = ';', $enclosure = '"', $escapeChar = '\\', $hasLegend = false, array $nullValues = array(''))
/**
* Set the value of "delimiter".
*
* @param string $delimiter
*
* @return Csv
*/
public function setDelimiter($delimiter)
{
$this->setFilename($filename);
$this->setDelimiter($delimiter);
$this->setEnclosure($enclosure);
$this->setEscapeChar($escapeChar);
$this->setHasLegend($hasLegend);
$this->setNullValues($nullValues);
}
$this->delimiter = (string) $delimiter;
public function setFilename($v)
{
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;
return $this;
}
/**
* 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()
{
return $this->datas;
}
public function parse()
/*
* Parses a string.
*
* @param string $string
*
* @return CsvParser
*/
public function parseString($string)
{
if (!empty($this->datas)) {
return $this->datas;
}
$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)) {
return true;
}
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;
}
if ($data === null) {
continue;
}
$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
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
use Deblan\Csv\Csv;
use PHPUnit_Framework_TestCase;
/**
* class CsvTest.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class CsvTest extends PHPUnit_Framework_TestCase
class CsvTest extends \PHPUnit_Framework_TestCase
{
public function testGettersAndSettersAndDefaultValues()
{
@ -16,6 +15,11 @@ class CsvTest extends PHPUnit_Framework_TestCase
$this->assertEquals(';', $csv->getDelimiter());
$csv->setDelimiter('#');
$this->assertEquals('#', $csv->getDelimiter());
$csv = new Csv();
$this->assertEquals('"', $csv->getEnclosure());
$csv->setEnclosure('#');
$this->assertEquals('#', $csv->getEnclosure());
$csv = new Csv();
$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'