From db680faa972c1a72eade95359ab1fe9b40955040 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Sun, 12 Mar 2017 15:04:19 +0100 Subject: [PATCH 01/21] Init of the v2 --- src/Deblan/Csv/Csv.php | 398 ++++++++++++++++++++++++++++------------- tests/CsvTest.php | 178 +++++++++++------- 2 files changed, 384 insertions(+), 192 deletions(-) diff --git a/src/Deblan/Csv/Csv.php b/src/Deblan/Csv/Csv.php index 1054f20..6332746 100644 --- a/src/Deblan/Csv/Csv.php +++ b/src/Deblan/Csv/Csv.php @@ -2,192 +2,340 @@ namespace Deblan\Csv; -use Deblan\Csv\Exception\CsvInvalidParameterException; - +/** + * class Csv. + * + * @author Simon Vieille + */ class Csv { - private $delimiter; + /** + * @var string + */ + protected $delimiter = ';'; - private $enclosure; + /** + * @var string + */ + protected $enclosure = '"'; - private $endline; + /** + * @var string + */ + protected $endOfLine = "\n"; - private $datas; + /** + * @var array + */ + protected $datas = []; - private $legend; + /** + * @var array + */ + protected $headers = []; - private $render; + /** + * @var string + */ + protected $charset = 'UTF-8'; - private $encoding; + /** + * @var bool + */ + protected $isModified = false; - private $hasLegend = false; + /** + * @var string + */ + protected $render; - public function __construct($delimiter = ';', $enclosure = '"', $endline = "\n", $encoding = 'UTF-8') + /** + * Set the value of "delimiter". + * + * @param string $delimiter + * + * @return Csv + */ + public function setDelimiter($delimiter) { - $this->setDelimiter($delimiter); - $this->setEnclosure($enclosure); - $this->setEndLine($endline); - $this->setEncoding($encoding); - $this->datas = array(0 => null); - $this->legend = array(); - $this->render = ""; - } - - public function setFilename($v) - { - if (!is_string($v)) { - throw new CsvInvalidParameterException(sprintf('"%s" is not a valid string.', $v)); + if ($this->delimiter !== $delimiter) { + $this->delimiter = $delimiter; + $this->isModified = true; } - $this->filename = $v; - } - - protected function setHasLegend($hasLegend) - { - $this->hasLegend = $hasLegend; - return $this; } - public function getHasLegend() + /** + * Get the value of "delimiter". + * + * @return array + */ + public function getDelimiter() { - return $this->hasLegend; + return $this->delimiter; } - public function hasLegend() + /** + * Set the value of "enclosure". + * + * @param string $enclosure + * + * @return Csv + */ + public function setEnclosure($enclosure) { - return $this->hasLegend; - } + $enclosure = (string) $enclosure; - public function setLegend(array $values) - { - $this->setHasLegend(true); - - $this->legend = $values; - - $this->addLine($values, 0); - } - - public function addLine(array $values, $key = null) - { - if ($key !== null) { - $this->datas[$key] = $values; - - return true; + if ($this->enclosure !== $enclosure) { + $this->enclosure = $enclosure; + $this->isModified = true; } - $this->datas[] = $values; - } - - public function setEncoding($encoding) - { - $this->encoding = $encoding; - return $this; } - public function getEncoding() + /** + * Get the value of "enclosure". + * + * @return string + */ + public function getEnclosure() { - return $this->encoding; + return $this->enclosure; } - public function setDelimiter($v) + /** + * Set the value of "endOfLine". + * + * @param string $endOfLine + * + * @return Csv + */ + public function setEndOfLine($endOfLine) { - if (!is_string($v)) { - throw new CsvInvalidParameterException(sprintf('"%s" is not a valid string.', $v)); + if ($this->endOfLine !== $endOfLine) { + $this->endOfLine = (string) $endOfLine; + $this->isModified = true; } - $this->delimiter = $v; + return $this; } - public function setEndline($v) + /** + * Get the value of "endOfLine". + * + * @return string + */ + public function getEndOfLine() { - if (!is_string($v)) { - throw new CsvInvalidParameterException(sprintf('"%s" is not a valid string.', $v)); + return $this->endOfLine; + } + + /** + * Set the value of "headers". + * + * @param array $headers + * + * @return Csv + */ + public function setHeaders(array $headers) + { + $this->headers = $headers; + + if ($this->headers !== $headers) { + $this->headers = $headers; + $this->isModified = true; } - $this->endline = $v; + return $this; } - public function setEnclosure($v) + /** + * Get the value of "headers". + * + * @return array + */ + public function getHeaders() { - if (!is_string($v)) { - throw new CsvInvalidParameterException(sprintf('"%s" is not a valid string.', $v)); + return $this->headers; + } + + /** + * Set the value of "charset". + * + * @param string $charset + * + * @return Csv + */ + public function setCharset($charset) + { + if ($this->charset !== $charset) { + $this->charset = (string) $charset; + $this->isModified = true; } - $this->enclose = $v; + return $this; } - public function getLegend() + /** + * Get the value of "charset". + * + * @return string + */ + public function getCharset() { - return $this->legend; + return $this->charset; } + /* + * Sets the value of "datas". + * + * @param array $datas + * + * @return Csv + */ + public function setDatas(array $datas) + { + if ($this->datas !== $datas) { + $this->datas = $datas; + $this->isModified = true; + } + + return $this; + } + + /** + * Get the value of "datas". + * + * @return array + */ public function getDatas() { return $this->datas; } - public function compile() + /* + * Appends data. + * + * @param array $data + * + * @return Csv + */ + public function appendData(array $data) { - $this->render = ""; + $this->datas[] = $data; + $this->isModified = true; - if ($this->datas[0] !== null) { - $this->append($this->datasToCsvLine($this->datas[0])); + return $this; + } + + /* + * Alias of "appendData()". + * + * {@inheritdoc self::appendData()} + */ + public function addData(array $data) + { + return $this->appendData($data); + } + + /* + * Prepends data. + * + * @param array $data + * + * @return Csv + */ + public function preprendData(array $data) + { + array_unshift($this->datas, $data); + $this->isModified = true; + + return $this; + } + + /** + * Formats an array data to a CSV string. + * + * @param array $data + * + * @return array + */ + protected function formatData(array $data) + { + $columns = []; + + foreach ($data as $value) { + $value = (string) $value; + + if (!empty($this->enclosure)) { + $value = str_replace($this->enclosure, str_repeat($this->enclosure, 2), $value); + } + + $value = sprintf('%1$s%2$s%1$s', $this->enclosure, (string) $value); + + $columns[] = $value; } - unset($this->datas[0]); + $data = implode($this->delimiter, $columns); + $data = $this->encode($data); - foreach ($this->datas as $v) { - $this->append($this->datasToCsvLine($v)); + return $data; + } + + /* + * Changes the charset if needed. + * + * @param string $value + * + * @return string + */ + public function encode($value) + { + return mb_convert_encoding( + $value, + $this->charset, + mb_detect_encoding($value, mb_list_encodings()) + ); + } + + /* + * Renders the CSV. + * + * @param string $filename @see file_put_contents + * @param int $flags @see file_put_contents + * + * @return string + */ + public function render($filename = null, $flags = null) + { + if ($this->isModified || empty($this->render)) { + $lines = []; + + if (!empty($this->headers)) { + $lines[] = $this->formatData($this->headers); + } + + foreach ($this->datas as $data) { + $lines[] = $this->formatData($data); + } + + $this->render = implode($this->encode($this->endOfLine), $lines); } - if ($this->encoding !== 'UTF-8') { - $this->render = iconv( - mb_detect_encoding($this->render), - $this->encoding, - $this->render - ); + $this->isModified = false; + + if ($filename !== null) { + $content = $this->render; + + if ($flags === FILE_APPEND && file_exists($filename)) { + $content = $this->endOfLine.$content; + } + + file_put_contents($filename, $content, $flags, $context); } return $this->render; } - - public function hasDatas() - { - return count($this->datas) > 1; - } - - protected function datasToCsvLine($datas) - { - foreach ($datas as $k => $v) { - $v = str_replace('\\', '\\\\', $v); - - if ($this->enclose) { - $v = str_replace($this->enclose, '\\'.$this->enclose, $v); - } else { - $v = str_replace($this->delimiter, '\\'.$this->delimiter, $v); - } - - $datas[$k] = $this->enclose.$v.$this->enclose; - } - - $datas = implode($this->delimiter, $datas); - - return $datas; - } - - protected function append($line) - { - $this->render.= sprintf("%s%s", $line, $this->endline); - } - - public function compileToFile($filename) - { - if (file_exists($filename)) { - unlink($filename); - } - - file_put_contents($filename, $this->compile()); - } } diff --git a/tests/CsvTest.php b/tests/CsvTest.php index 4ae789c..11d9da7 100644 --- a/tests/CsvTest.php +++ b/tests/CsvTest.php @@ -1,94 +1,138 @@ + */ +class CsvTest extends PHPUnit_Framework_TestCase { - public function testAddLine() + public function testGettersAndSettersAndDefaultValues() { $csv = new Csv(); - $csv->addLine(array('foo', 'bar')); - $this->assertEquals('"foo";"bar"'."\n", $csv->compile()); + $this->assertEquals(';', $csv->getDelimiter()); + $csv->setDelimiter('#'); + $this->assertEquals('#', $csv->getDelimiter()); + + $csv = new Csv(); + $this->assertEquals("\n", $csv->getEndOfLine()); + $csv->setEndOfLine("\r\n"); + $this->assertEquals("\r\n", $csv->getEndOfLine()); + + $csv = new Csv(); + $this->assertEquals([], $csv->getDatas()); + $csv->setDatas([['a', 'b', 'c'], ['d', 'e', 'f']]); + $this->assertEquals([['a', 'b', 'c'], ['d', 'e', 'f']], $csv->getDatas()); + + $csv = new Csv(); + $this->assertEquals([], $csv->getHeaders()); + $csv->setHeaders(['a', 'b', 'c']); + $this->assertEquals(['a', 'b', 'c'], $csv->getHeaders()); + + $csv = new Csv(); + $this->assertEquals('UTF-8', $csv->getCharset()); + $csv->setCharset('ISO-8859-1'); + $this->assertEquals('ISO-8859-1', $csv->getCharset()); } - public function testSetLegend() + public function testRender() { $csv = new Csv(); + $this->assertEquals(null, $csv->render()); + $csv->addData(['foo', 'bar']); + $this->assertEquals('"foo";"bar"', $csv->render()); - $this->assertEquals(false, $csv->getHasLegend()); + $csv = new Csv(); + $csv->appendData(['foo', 'bar']); + $csv->appendData(['foo2', 'bar2']); + $this->assertEquals('"foo";"bar"'."\n".'"foo2";"bar2"', $csv->render()); - $csv->setLegend($a = array('bim', 'bam')); + $csv = new Csv(); + $csv->preprendData(['foo2', 'bar2']); + $csv->preprendData(['foo', 'bar']); + $this->assertEquals('"foo";"bar"'."\n".'"foo2";"bar2"', $csv->render()); - $this->assertEquals($a, $csv->getLegend()); + $csv = new Csv(); + $csv->addData(['foo', 'bar']); + $csv->preprendData(['foo2', 'bar2']); + $csv->appendData(['foo3', 'bar3']); + $this->assertEquals('"foo2";"bar2"'."\n".'"foo";"bar"'."\n".'"foo3";"bar3"', $csv->render()); + + $csv = new Csv(); + $csv->setHeaders(['a', 'b']); + $csv->addData(['foo', 'bar']); + $this->assertEquals('"a";"b"'."\n".'"foo";"bar"', $csv->render()); + + $csv = new Csv(); + $csv->setHeaders(['a"b', 'cd"']); + $csv->addData(['f"oo', 'b""ar']); + $this->assertEquals('"a""b";"cd"""'."\n".'"f""oo";"b""""ar"', $csv->render()); + + $csv = new Csv(); + $csv->addData(['foo', 'bar']); + $csv->setHeaders(['a', 'b']); + $this->assertEquals('"a";"b"'."\n".'"foo";"bar"', $csv->render()); + + $csv = new Csv(); + $csv->addData(['foo', 'bar']); + $csv->addData(['foo2', 'bar2']); + $csv->setHeaders(['a', 'b']); + $csv->setEndOfLine("\r\n"); + $this->assertEquals('"a";"b"'."\r\n".'"foo";"bar"'."\r\n".'"foo2";"bar2"', $csv->render()); + + $csv = new Csv(); + $csv->setHeaders(['a', "b'd"]); + $csv->addData(["fo'o", 'bar']); + $csv->setEnclosure("'"); + $this->assertEquals("'a';'b''d'"."\n"."'fo''o';'bar'", $csv->render()); + + $csv = new Csv(); + $csv->setHeaders(['a', 'b']); + $csv->addData(['foo', 'bar']); + $csv->setDelimiter('#'); + $this->assertEquals('"a"#"b"'."\n".'"foo"#"bar"', $csv->render()); - $csv->addLine(array('foo', 'bar')); - - $this->assertEquals(true, $csv->getHasLegend()); - $this->assertEquals( - '"bim";"bam"'."\n". - '"foo";"bar"'."\n", - $csv->compile() - ); + $filename = tempnam(sys_get_temp_dir(), 'csvtests'); $csv = new Csv(); + $csv->setHeaders(['a', 'b']); + $csv->addData(['foo', 'bar']); + $render = $csv->render($filename); + $this->assertEquals('"a";"b"'."\n".'"foo";"bar"', $render); + $this->assertEquals('"a";"b"'."\n".'"foo";"bar"', file_get_contents($filename)); + $render = $csv->render($filename); + $this->assertEquals('"a";"b"'."\n".'"foo";"bar"', $render); + $this->assertEquals('"a";"b"'."\n".'"foo";"bar"', file_get_contents($filename)); - $csv->addLine(array('foo', 'bar')); - $csv->setLegend(array('bim', 'bam')); + unlink($filename); - $this->assertEquals(true, $csv->getHasLegend()); - $this->assertEquals( - '"bim";"bam"'."\n". - '"foo";"bar"'."\n", - $csv->compile() - ); + $csv = new Csv(); + $csv->setHeaders(['a', 'b']); + $csv->addData(['foo', 'bar']); + $csv->render($filename); + + $csv = new Csv(); + $csv->addData(['foo2', 'bar2']); + $render = $csv->render($filename, FILE_APPEND); + $this->assertEquals('"a";"b"'."\n".'"foo";"bar"'."\n".'"foo2";"bar2"', file_get_contents($filename)); + + unlink($filename); } - public function testHasDatas() + public function testEncoding() { $csv = new Csv(); - $this->assertEquals(false, $csv->hasDatas()); - - $csv->setLegend(array('bim', 'bam')); - $this->assertEquals(false, $csv->hasDatas()); - - $csv->addLine(array('foo', 'bar')); - $this->assertEquals(true, $csv->hasDatas()); + $csv->addData(['é']); + $render = $csv->render(); + $this->assertEquals('"é"', $csv->render()); $csv = new Csv(); - $csv->addLine(array('foo', 'bar')); - $this->assertEquals(true, $csv->hasDatas()); - } - - public function testDatasToCsvLine() - { - $csv = new Csv(); - $csv->addLine(array('fo\\o', 'bar')); - $this->assertEquals('"fo\\\\o";"bar"'."\n", $csv->compile()); - - $csv = new Csv(); - $csv->setDelimiter(':'); - $csv->addLine(array('foo', 'bar')); - $this->assertEquals('"foo":"bar"'."\n", $csv->compile()); - - $csv = new Csv(); - $csv->setDelimiter(':'); - $csv->addLine(array('fo:o', 'bar')); - $this->assertEquals('"fo:o":"bar"'."\n", $csv->compile()); - - $csv = new Csv(); - $csv->setDelimiter(':'); - $csv->setEnclosure(''); - $csv->addLine(array('fo:o', 'bar')); - $this->assertEquals('fo\\:o:bar'."\n", $csv->compile()); - - $csv = new Csv(); - $csv->setEnclosure('#'); - $csv->addLine(array('foo', 'bar')); - $this->assertEquals('#foo#;#bar#'."\n", $csv->compile()); - - $csv = new Csv(); - $csv->setEnclosure('#'); - $csv->addLine(array('f#oo', 'bar')); - $this->assertEquals('#f\\#oo#;#bar#'."\n", $csv->compile()); + $csv->addData(['é']); + $csv->setCharset('ISO-8859-1'); + $render = $csv->render(); + $this->assertEquals('"é"', utf8_encode($csv->render())); } } From ae1176ce780f8528332298c46833c79d91c10ae0 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Sun, 12 Mar 2017 15:20:09 +0100 Subject: [PATCH 02/21] Documentation --- README.md | 44 +++++++++++++++++++++++++++++------------- src/Deblan/Csv/Csv.php | 2 +- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7c89d21..ba8c062 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,7 @@ Or in your composer.json: ``` { "require": { - [...] - "deblan/csv": "dev-master" + "deblan/csv": "~2" } } ``` @@ -29,22 +28,41 @@ use Deblan\Csv\Csv; $csv = new Csv(); -$csv->addLine(array('Foo', '$1000')); -$csv->addLine(array('Bar', '$600')); +// Defines the delimiter (default is ;) +$csv->setDelimiter(";"); -$result = $csv->compile(); -``` +// Defines the enclosure (default is ") +$csv->setEnclosure('"'); -```php -use Deblan\Csv\Csv; +// Defines the end of line (default is \n) +$csv->setEndOfLine("\n"); -$csv = new Csv(); +// Defines the charset (default is UTF-8) +$csv->setCharset("UTF-8"); -$csv->setLegend(array('product name', 'price')); -$csv->addLine(array('Foo', '$1000')); -$csv->addLine(array('Bar', '$600')); +// Add a new line at the end +$csv->addData(['Foo', '$1000')); -$csv->compileToFile('products.csv'); +// Add a new line at the end +$csv->appendData(['Bar', '$600']); + +// Add a new line at the beginning +$csv->prependData(['Boo', '$3000']); + +// Defines all the datas +$csv->setDatas([[...], [...]]); + +// Defines the header +$csv->setHeaders(["Product", "Price"]); + +// Rendering +$result = $csv->render(); + +// Rendering to a file +$result = $csv->render("products.csv"); + +// Appending to a file +$result = $csv->render("products.csv", FILE_APPEND); ``` ### Parser diff --git a/src/Deblan/Csv/Csv.php b/src/Deblan/Csv/Csv.php index 6332746..f661bf8 100644 --- a/src/Deblan/Csv/Csv.php +++ b/src/Deblan/Csv/Csv.php @@ -332,7 +332,7 @@ class Csv if ($flags === FILE_APPEND && file_exists($filename)) { $content = $this->endOfLine.$content; } - + file_put_contents($filename, $content, $flags, $context); } From 2c25ffd7511c9a8ac93c4f1f758fe63edbed9bec Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Sun, 12 Mar 2017 15:23:41 +0100 Subject: [PATCH 03/21] Tests --- phpci.yml | 14 ++++++++++++++ src/Deblan/Csv/Csv.php | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 phpci.yml diff --git a/phpci.yml b/phpci.yml new file mode 100644 index 0000000..01be34a --- /dev/null +++ b/phpci.yml @@ -0,0 +1,14 @@ +build_settings: + verbose: false + prefer_symlink: false + +setup: + composer: + action: "install" + +test: + php_unit: + directory: "tests/" + args: "--configuration 'phpunit.xml'" + +complete: diff --git a/src/Deblan/Csv/Csv.php b/src/Deblan/Csv/Csv.php index f661bf8..fa57d8f 100644 --- a/src/Deblan/Csv/Csv.php +++ b/src/Deblan/Csv/Csv.php @@ -333,7 +333,7 @@ class Csv $content = $this->endOfLine.$content; } - file_put_contents($filename, $content, $flags, $context); + file_put_contents($filename, $content, $flags); } return $this->render; From 6c4a535ecd121566fd78ab4e876886bdb5b90307 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Sun, 12 Mar 2017 15:25:36 +0100 Subject: [PATCH 04/21] Tests --- phpci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpci.yml b/phpci.yml index 01be34a..81e8d89 100644 --- a/phpci.yml +++ b/phpci.yml @@ -1,5 +1,5 @@ build_settings: - verbose: false + verbose: true prefer_symlink: false setup: From 022c31a7030720df8d1185db5b8b2c48f7a3f121 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Sun, 12 Mar 2017 17:57:30 +0100 Subject: [PATCH 05/21] Init of the v2 (CsvParser) --- README.md | 33 +++- src/Deblan/Csv/CsvParser.php | 304 ++++++++++++++++++++++------------- tests/CsvParserTest.php | 188 +++++++++++++++++++++- tests/CsvTest.php | 8 +- tests/fixtures/example.csv | 4 + tests/fixtures/example2.csv | 3 + tests/fixtures/example3.csv | 5 + tests/fixtures/example4.csv | 5 + 8 files changed, 423 insertions(+), 127 deletions(-) create mode 100644 tests/fixtures/example.csv create mode 100644 tests/fixtures/example2.csv create mode 100644 tests/fixtures/example3.csv create mode 100644 tests/fixtures/example4.csv diff --git a/README.md b/README.md index ba8c062..4b772a5 100644 --- a/README.md +++ b/README.md @@ -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(); ``` diff --git a/src/Deblan/Csv/CsvParser.php b/src/Deblan/Csv/CsvParser.php index 99abe49..756bf1a 100644 --- a/src/Deblan/Csv/CsvParser.php +++ b/src/Deblan/Csv/CsvParser.php @@ -2,149 +2,225 @@ namespace Deblan\Csv; -use Deblan\Csv\Exception\CsvParserInvalidParameterException; -use Deblan\Csv\Exception\CsvParserException; - +/** + * class Csv. + * + * @author Simon Vieille + */ 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)); } } diff --git a/tests/CsvParserTest.php b/tests/CsvParserTest.php index 0c36620..70c9f83 100644 --- a/tests/CsvParserTest.php +++ b/tests/CsvParserTest.php @@ -1,8 +1,188 @@ 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() + ); + } } diff --git a/tests/CsvTest.php b/tests/CsvTest.php index 11d9da7..be67a6a 100644 --- a/tests/CsvTest.php +++ b/tests/CsvTest.php @@ -1,14 +1,13 @@ */ -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()); diff --git a/tests/fixtures/example.csv b/tests/fixtures/example.csv new file mode 100644 index 0000000..d6b62ce --- /dev/null +++ b/tests/fixtures/example.csv @@ -0,0 +1,4 @@ +"FOO";"BAR" +"foo 1";"bar 1" +"foo 2";"bar 2" +"foo 3";"bar 3" diff --git a/tests/fixtures/example2.csv b/tests/fixtures/example2.csv new file mode 100644 index 0000000..a4e9ac5 --- /dev/null +++ b/tests/fixtures/example2.csv @@ -0,0 +1,3 @@ +"foo 1";"bar 1" +"foo 2";"ba""r 2" +"foo 3";"bar 3" diff --git a/tests/fixtures/example3.csv b/tests/fixtures/example3.csv new file mode 100644 index 0000000..9077a11 --- /dev/null +++ b/tests/fixtures/example3.csv @@ -0,0 +1,5 @@ +'FO''O'#'BAR' +'foo 1'# +'foo 1b' +'foo 2'#'bar 2'#'unexpected 3' +'foo 3'#'bar 3' diff --git a/tests/fixtures/example4.csv b/tests/fixtures/example4.csv new file mode 100644 index 0000000..e56aef3 --- /dev/null +++ b/tests/fixtures/example4.csv @@ -0,0 +1,5 @@ +'FO''O'#'BAR' +'foo 1'# +'foo 1b' +'foo 2'#'bar 2'#'unexpected 3' +'foo 3'#'bar 3' From 37be0f572fd8cfd4352af4bdcd8fc2be20189d2a Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Sun, 12 Mar 2017 18:58:44 +0100 Subject: [PATCH 06/21] Cleaning code --- src/Deblan/Csv/CsvParser.php | 2 +- src/Deblan/Csv/Exception/CsvException.php | 7 ------- src/Deblan/Csv/Exception/CsvInvalidParameterException.php | 7 ------- src/Deblan/Csv/Exception/CsvParserException.php | 7 ------- .../Csv/Exception/CsvParserInvalidParameterException.php | 7 ------- tests/CsvTest.php | 6 +++--- 6 files changed, 4 insertions(+), 32 deletions(-) delete mode 100644 src/Deblan/Csv/Exception/CsvException.php delete mode 100644 src/Deblan/Csv/Exception/CsvInvalidParameterException.php delete mode 100644 src/Deblan/Csv/Exception/CsvParserException.php delete mode 100644 src/Deblan/Csv/Exception/CsvParserInvalidParameterException.php diff --git a/src/Deblan/Csv/CsvParser.php b/src/Deblan/Csv/CsvParser.php index 756bf1a..7d92af2 100644 --- a/src/Deblan/Csv/CsvParser.php +++ b/src/Deblan/Csv/CsvParser.php @@ -211,7 +211,7 @@ class CsvParser return $csv; } - + /* * Parses a file. * diff --git a/src/Deblan/Csv/Exception/CsvException.php b/src/Deblan/Csv/Exception/CsvException.php deleted file mode 100644 index c7369b3..0000000 --- a/src/Deblan/Csv/Exception/CsvException.php +++ /dev/null @@ -1,7 +0,0 @@ -assertEquals(';', $csv->getDelimiter()); $csv->setDelimiter('#'); $this->assertEquals('#', $csv->getDelimiter()); - + $csv = new Csv(); $this->assertEquals('"', $csv->getEnclosure()); $csv->setEnclosure('#'); @@ -98,7 +98,7 @@ class CsvTest extends \PHPUnit_Framework_TestCase $csv->addData(['foo', 'bar']); $csv->setDelimiter('#'); $this->assertEquals('"a"#"b"'."\n".'"foo"#"bar"', $csv->render()); - + $filename = tempnam(sys_get_temp_dir(), 'csvtests'); $csv = new Csv(); @@ -122,7 +122,7 @@ class CsvTest extends \PHPUnit_Framework_TestCase $csv->addData(['foo2', 'bar2']); $render = $csv->render($filename, FILE_APPEND); $this->assertEquals('"a";"b"'."\n".'"foo";"bar"'."\n".'"foo2";"bar2"', file_get_contents($filename)); - + unlink($filename); } From 4809f0157b92efed53eedbb54bedd732d4176fc9 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Mon, 13 Mar 2017 13:37:18 +0100 Subject: [PATCH 07/21] Insight analyse fixes --- .gitignore | 3 --- LICENSE | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f2a5df1..57872d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ -*.swp -tags - /vendor/ diff --git a/LICENSE b/LICENSE index b57a6c2..80445fb 100644 --- a/LICENSE +++ b/LICENSE @@ -20,4 +20,5 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + From 496221566a65aa2043f0ca446927bd54f2217722 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Wed, 15 Mar 2017 00:35:23 +0100 Subject: [PATCH 08/21] CI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b772a5..bf8451b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CSV parser/generator ==================== -![](https://phpci.gitnet.fr//build-status/image/6) +[![](https://phpci.gitnet.fr/build-status/image/1)](https://phpci.gitnet.fr/build-status/view/1) A simple PHP library to parse and generate CSV files. From 237290aba908033ce91d547979ae0bca94cd2e54 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Wed, 15 Mar 2017 00:42:56 +0100 Subject: [PATCH 09/21] CI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bf8451b..4c77336 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CSV parser/generator ==================== -[![](https://phpci.gitnet.fr/build-status/image/1)](https://phpci.gitnet.fr/build-status/view/1) +[![](https://phpci.gitnet.fr/build-status/image/1?branch=master&label=PHPCensor&style=flat-square)](https://phpci.gitnet.fr/build-status/view/1) A simple PHP library to parse and generate CSV files. From f8731f6fbef55ffdc0dd9dbad18d99ccb3f61398 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Wed, 15 Mar 2017 01:01:06 +0100 Subject: [PATCH 10/21] CI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4c77336..61015e4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CSV parser/generator ==================== -[![](https://phpci.gitnet.fr/build-status/image/1?branch=master&label=PHPCensor&style=flat-square)](https://phpci.gitnet.fr/build-status/view/1) +![](https://phpci.gitnet.fr/build-status/image/1?branch=master&label=PHPCensor&style=flat-square) A simple PHP library to parse and generate CSV files. From 1ac6c03c1c508d6b567ae6703ba630d3c15c35ab Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Wed, 6 Jun 2018 15:51:31 +0200 Subject: [PATCH 11/21] CI --- README.md | 4 ++-- phpunit.xml | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 61015e4..6ef873b 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ CSV parser/generator ==================== -![](https://phpci.gitnet.fr/build-status/image/1?branch=master&label=PHPCensor&style=flat-square) +![](https://phpci.gitnet.fr/build-status/image/2?branch=master&label=PHPCensor&style=flat-square) A simple PHP library to parse and generate CSV files. ## Composer installation ``` -$ composer require deblan/csv +$ composer require deblan/csv "~2" ``` Or in your composer.json: diff --git a/phpunit.xml b/phpunit.xml index eabb967..a148726 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,12 +7,12 @@ convertWarningsToExceptions = "true" processIsolation = "false" stopOnFailure = "false" - syntaxCheck = "false" + syntaxCheck = "true" bootstrap = "vendor/autoload.php" > - - - tests/ - - + + + tests/ + + From 33e15895d3b62b6c41b0f50a8958b20cc3f26cd6 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Wed, 6 Jun 2018 18:59:00 +0200 Subject: [PATCH 12/21] CI --- phpci.yml => .php-censor.yml | 0 README.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename phpci.yml => .php-censor.yml (100%) diff --git a/phpci.yml b/.php-censor.yml similarity index 100% rename from phpci.yml rename to .php-censor.yml diff --git a/README.md b/README.md index 6ef873b..e8d73d2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CSV parser/generator ==================== -![](https://phpci.gitnet.fr/build-status/image/2?branch=master&label=PHPCensor&style=flat-square) +![](https://phpci.gitnet.fr/build-status/image/2?branch=master&label=PHPCensor&style=flat-square) A simple PHP library to parse and generate CSV files. From 9899e5bfbd8c2fc59d8b0fb313dff92c1a6fae78 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Mon, 10 Feb 2020 16:24:43 +0100 Subject: [PATCH 13/21] add stream parser --- README.md | 30 ++++- phpunit.xml | 1 - src/Deblan/Csv/CsvStreamParser.php | 69 +++++++++++ tests/CsvParserTest.php | 3 +- tests/CsvStreamParserTest.php | 176 +++++++++++++++++++++++++++++ 5 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 src/Deblan/Csv/CsvStreamParser.php create mode 100644 tests/CsvStreamParserTest.php diff --git a/README.md b/README.md index e8d73d2..0f80ce1 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,18 @@ CSV parser/generator ![](https://phpci.gitnet.fr/build-status/image/2?branch=master&label=PHPCensor&style=flat-square) -A simple PHP library to parse and generate CSV files. +A simple PHP library to: + +* parse a CSV file +* parse a stream as CSV datas +* generate CSV files. + +PHP >= 7.1 required. ## Composer installation ``` -$ composer require deblan/csv "~2" +$ composer require deblan/csv "~3" ``` Or in your composer.json: @@ -16,7 +22,7 @@ Or in your composer.json: ``` { "require": { - "deblan/csv": "~2" + "deblan/csv": "~3" } } ``` @@ -67,7 +73,7 @@ $result = $csv->render('products.csv'); $result = $csv->render('products.csv', FILE_APPEND); ``` -### Parser +### Parse a file ```php use Deblan\Csv\CsvParser; @@ -96,3 +102,19 @@ $csv->parseString($myString); $headers = $csv->getHeaders(); $products = $csv->getDatas(); ``` + +### Parse a stram + +```php +use Deblan\Csv\CsvStreamParser; + +// CsvStreamParser is a CsvParser +$csv = new CsvStreamParser(); + +// Parse a stream +$csv->parseStream(fopen('products.csv', 'r')); + +while ($data = $csv->getData()) { + // ... +} +``` diff --git a/phpunit.xml b/phpunit.xml index a148726..2fd344a 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,6 @@ convertWarningsToExceptions = "true" processIsolation = "false" stopOnFailure = "false" - syntaxCheck = "true" bootstrap = "vendor/autoload.php" > diff --git a/src/Deblan/Csv/CsvStreamParser.php b/src/Deblan/Csv/CsvStreamParser.php new file mode 100644 index 0000000..e12b7e4 --- /dev/null +++ b/src/Deblan/Csv/CsvStreamParser.php @@ -0,0 +1,69 @@ + + */ +class CsvStreamParser extends CsvParser +{ + /** + * @var resource + */ + protected $resource; + + /** + * @var int + */ + protected $length; + + /** + * Parse a stream. + * + * @param resource $resource + * @param int $length + */ + public function parseStream($resource, ? int $length = 0): void + { + if (!is_resource($resource)) { + throw new \InvalidArgumentException('First argument must be a valid resource.'); + } + + $this->resource = $resource; + $this->length = $length; + } + + /** + * Get data of the stream parsing. + * + * @return null|array + */ + public function getData(): ? array + { + $csv = fgetcsv($this->resource, $this->length, $this->delimiter, $this->enclosure); + + if ($csv === false) { + return null; + } + + $isHeaders = $this->hasHeaders && empty($this->headers); + + if ($isHeaders) { + $this->headers = $csv; + + return $csv; + } + + if (!$isHeaders && $this->hasHeaders && !empty($this->headers)) { + foreach ($this->headers as $key => $header) { + $csv[$header] = isset($csv[$key]) ? $csv[$key] : null; + } + } + + $this->datas[] = $csv; + + return $csv; + } +} diff --git a/tests/CsvParserTest.php b/tests/CsvParserTest.php index 70c9f83..eab1d3e 100644 --- a/tests/CsvParserTest.php +++ b/tests/CsvParserTest.php @@ -1,8 +1,9 @@ assertEquals([], $parser->getDatas()); + $this->assertEquals([], $parser->getHeaders()); + + $parser = new CsvStreamParser(); + $this->expectException('\InvalidArgumentException'); + $parser->parseStream(null); + } + + public function testStreamParser2() + { + $parser = new CsvStreamParser(); + $parser->setHasHeaders(true); + + $parser->parseStream(fopen(__DIR__.'/fixtures/example.csv', 'r')); + $this->assertEquals([], $parser->getDatas()); + $this->assertEquals([], $parser->getHeaders()); + + $this->assertEquals([ + 'FOO', + 'BAR', + ], $parser->getData()); + $this->assertEquals([], $parser->getDatas()); + $this->assertEquals(['FOO', 'BAR'], $parser->getHeaders()); + + $this->assertEquals([ + 'foo 1', + 'bar 1', + 'FOO' => 'foo 1', + 'BAR' => 'bar 1', + ], $parser->getData()); + $this->assertEquals([ + [ + 'foo 1', + 'bar 1', + 'FOO' => 'foo 1', + 'BAR' => 'bar 1', + ], + ], $parser->getDatas()); + $this->assertEquals(['FOO', 'BAR'], $parser->getHeaders()); + + $parser->getData(); + $this->assertEquals([ + [ + 'foo 1', + 'bar 1', + 'FOO' => 'foo 1', + 'BAR' => 'bar 1', + ], + [ + 'foo 2', + 'bar 2', + 'FOO' => 'foo 2', + 'BAR' => 'bar 2', + ], + ], $parser->getDatas()); + $this->assertEquals(['FOO', 'BAR'], $parser->getHeaders()); + + $parser->getData(); + $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()); + } + + public function testStreamParser3() + { + $parser = new CsvStreamParser(); + $parser->setHasHeaders(false); + + $parser->parseStream(fopen(__DIR__.'/fixtures/example.csv', 'r')); + $this->assertEquals([], $parser->getDatas()); + $this->assertEquals([], $parser->getHeaders()); + + $this->assertEquals([ + 'FOO', + 'BAR' + ], $parser->getData()); + $this->assertEquals([ + [ + 'FOO', + 'BAR' + ] + ], $parser->getDatas()); + $this->assertEquals([], $parser->getHeaders()); + + $this->assertEquals([ + 'foo 1', + 'bar 1', + ], $parser->getData()); + $this->assertEquals([ + [ + 'FOO', + 'BAR', + ], + [ + 'foo 1', + 'bar 1', + ], + ], $parser->getDatas()); + $this->assertEquals([], $parser->getHeaders()); + + $this->assertEquals([ + 'foo 2', + 'bar 2', + ], $parser->getData()); + $this->assertEquals([ + [ + 'FOO', + 'BAR', + ], + [ + 'foo 1', + 'bar 1', + ], + [ + 'foo 2', + 'bar 2', + ], + ], $parser->getDatas()); + $this->assertEquals([], $parser->getHeaders()); + + $this->assertEquals([ + 'foo 3', + 'bar 3', + ], $parser->getData()); + $this->assertEquals([ + [ + 'FOO', + 'BAR', + ], + [ + 'foo 1', + 'bar 1', + ], + [ + 'foo 2', + 'bar 2', + ], + [ + 'foo 3', + 'bar 3', + ], + ], $parser->getDatas()); + $this->assertEquals([], $parser->getHeaders()); + + $this->assertEquals(null, $parser->getData()); + } +} From 0f413d06b9af274a04408029c116d773c301f175 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Mon, 10 Feb 2020 16:24:56 +0100 Subject: [PATCH 14/21] tests for php7 --- tests/CsvTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/CsvTest.php b/tests/CsvTest.php index 15bf155..ebea607 100644 --- a/tests/CsvTest.php +++ b/tests/CsvTest.php @@ -1,13 +1,14 @@ */ -class CsvTest extends \PHPUnit_Framework_TestCase +class CsvTest extends TestCase { public function testGettersAndSettersAndDefaultValues() { From 7719bd720287f90cc4e33b7e733868f26fdb8b9c Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Sun, 24 May 2020 14:31:08 +0200 Subject: [PATCH 15/21] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f80ce1..2126adb 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ $headers = $csv->getHeaders(); $products = $csv->getDatas(); ``` -### Parse a stram +### Parse a stream ```php use Deblan\Csv\CsvStreamParser; From cfb04db402ff01231d83babc7ebcac11ee00592a Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Tue, 1 Sep 2020 15:40:00 +0200 Subject: [PATCH 16/21] add jenkins tests --- Jenkinsfile | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..6e0cddf --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,25 @@ +pipeline { + agent any + + stages { + stage('PHP 7.1') { + steps { + sh '/usr/local/bin/composer-php7.1 install' + sh 'php7.1 /usr/local/bin/phpunit-7' + } + } + stage('PHP 7.3') { + steps { + sh '/usr/local/bin/composer-php7.3 update' + sh 'php7.3 /usr/local/bin/phpunit-9' + } + } + stage('PHP 7.4') { + steps { + sh '/usr/local/bin/composer-php7.4 update' + sh 'php7.4 /usr/local/bin/phpunit-9' + } + } + } +} + From ec870e287cd896b505224076dee4030bff821726 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Tue, 1 Sep 2020 15:41:49 +0200 Subject: [PATCH 17/21] add jenkins status --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2126adb..8147b06 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CSV parser/generator ==================== -![](https://phpci.gitnet.fr/build-status/image/2?branch=master&label=PHPCensor&style=flat-square) +[![Build Status](https://ci.gitnet.fr/buildStatus/icon?job=Gitnet%2Fcsv%2Fv3)](https://ci.gitnet.fr/job/Gitnet/job/csv/job/v3/) A simple PHP library to: From 9cfbebabd68db3146281233003bfc7a89137f540 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Tue, 26 Jul 2022 10:05:14 +0200 Subject: [PATCH 18/21] update ci --- .woodpecker.yml | 17 +++++++++++++++++ Jenkinsfile | 25 ------------------------- 2 files changed, 17 insertions(+), 25 deletions(-) create mode 100644 .woodpecker.yml delete mode 100644 Jenkinsfile diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..a391bd2 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,17 @@ +matrix: + PHP_VERSION: + - 7.3 + - 7.4 + - 8.0 + - 8.1 + +pipeline: + dependencies: + image: gitnet.fr/deblan/php:${PHP_VERSION} + commands: + - php composer install + - php /usr/local/bin/composer require --dev phpunit/phpunit + tests: + image: gitnet.fr/deblan/php:${PHP_VERSION} + commands: + - php ./vendor/bin/phpunit diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 6e0cddf..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,25 +0,0 @@ -pipeline { - agent any - - stages { - stage('PHP 7.1') { - steps { - sh '/usr/local/bin/composer-php7.1 install' - sh 'php7.1 /usr/local/bin/phpunit-7' - } - } - stage('PHP 7.3') { - steps { - sh '/usr/local/bin/composer-php7.3 update' - sh 'php7.3 /usr/local/bin/phpunit-9' - } - } - stage('PHP 7.4') { - steps { - sh '/usr/local/bin/composer-php7.4 update' - sh 'php7.4 /usr/local/bin/phpunit-9' - } - } - } -} - From 0c34dce1cf5be74c41d22f1e131bb6b34557d0f8 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Tue, 26 Jul 2022 10:06:18 +0200 Subject: [PATCH 19/21] update ci --- .woodpecker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index a391bd2..4c636a2 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,7 +9,7 @@ pipeline: dependencies: image: gitnet.fr/deblan/php:${PHP_VERSION} commands: - - php composer install + - php /usr/local/bin/composer composer install - php /usr/local/bin/composer require --dev phpunit/phpunit tests: image: gitnet.fr/deblan/php:${PHP_VERSION} From c2e570f9667838c821e65f3bb1e53d4754f73e36 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Tue, 26 Jul 2022 10:06:58 +0200 Subject: [PATCH 20/21] update ci --- .woodpecker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 4c636a2..f338970 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -9,7 +9,7 @@ pipeline: dependencies: image: gitnet.fr/deblan/php:${PHP_VERSION} commands: - - php /usr/local/bin/composer composer install + - php /usr/local/bin/composer install - php /usr/local/bin/composer require --dev phpunit/phpunit tests: image: gitnet.fr/deblan/php:${PHP_VERSION} From e1cb3d8b9af4167a12dd5d5240dafb98aa31e8e3 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Tue, 26 Jul 2022 10:11:21 +0200 Subject: [PATCH 21/21] update ci --- .php-censor.yml | 14 -------------- README.md | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 .php-censor.yml diff --git a/.php-censor.yml b/.php-censor.yml deleted file mode 100644 index 81e8d89..0000000 --- a/.php-censor.yml +++ /dev/null @@ -1,14 +0,0 @@ -build_settings: - verbose: true - prefer_symlink: false - -setup: - composer: - action: "install" - -test: - php_unit: - directory: "tests/" - args: "--configuration 'phpunit.xml'" - -complete: diff --git a/README.md b/README.md index 8147b06..1afed82 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ CSV parser/generator ==================== -[![Build Status](https://ci.gitnet.fr/buildStatus/icon?job=Gitnet%2Fcsv%2Fv3)](https://ci.gitnet.fr/job/Gitnet/job/csv/job/v3/) +[![Build Status](https://ci.gitnet.fr/api/badges/deblan/csv/status.svg)](https://ci.gitnet.fr/deblan/csv) A simple PHP library to: