Skip to content

Commit

Permalink
Added Writer class write methods
Browse files Browse the repository at this point in the history
  • Loading branch information
nozavroni committed Jul 21, 2018
1 parent f475d07 commit e0b98ac
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 4 deletions.
2 changes: 2 additions & 0 deletions src/CSVelte/Dialect.php
Expand Up @@ -22,6 +22,8 @@
* Due to CSV being without any definitive format definition for so long, many dialects of it exist. This class allows
* the creation of reusable "dialects" for commonly used flavors of CSV. You can make one for Excel CSV, another for
* tab delimited CSV, another for pipe-delimited, etc.
*
* @todo Where is escapeChar? Do we just use a backslash if doubleQuote is false?
*/
class Dialect
{
Expand Down
5 changes: 4 additions & 1 deletion src/CSVelte/Reader.php
Expand Up @@ -161,7 +161,10 @@ protected function parseLine($line)
->trimRight($d->getLineTerminator())
->split($d->getDelimiter() . "(?=([^\"]*\"[^\"]*\")*[^\"]*$)"));
if (!is_null($this->header)) {
$fields = $fields->rekey($this->header);
// @todo there may be cases where this gives a false positive...
if (count($fields) == count($this->header)) {
$fields = $fields->rekey($this->header);
}
}
return $fields->map(function(Stringy $field, $pos) use ($d) {
if ($d->isDoubleQuote()) {
Expand Down
72 changes: 71 additions & 1 deletion src/CSVelte/Writer.php
Expand Up @@ -15,6 +15,10 @@
use CSVelte\Contract\Streamable;
use Noz\Collection\Collection;

use function Noz\collect;
use function Stringy\create as s;
use Traversable;

class Writer
{
/** @var Streamable The output stream to write to */
Expand Down Expand Up @@ -77,7 +81,73 @@ public function getDialect()
*/
protected function setOutputStream(Streamable $stream)
{
$this->input = $stream;
$this->output = $stream;
return $this;
}

/**
* Insert a single record into CSV output
*
* Returns total bytes written to the output stream.
*
* @param array|Traversable $data A row of data to write to the CSV output
*
* @return false|int
*/
public function insertRow($data)
{
$d = $this->getDialect();
$data = collect($data)
->map(function($field) use ($d) {
if ($qstyle = $d->getQuoteStyle()) {
$wrap = false;
switch ($qstyle) {
case Dialect::QUOTE_ALL:
$wrap = true;
break;
case Dialect::QUOTE_MINIMAL:
if (s($field)->containsAny([$d->getQuoteChar(), $d->getDelimiter(), $d->getLineTerminator()])) {
$wrap = true;
}
break;
case Dialect::QUOTE_NONNUMERIC:
if (is_numeric((string) $field)) {
$wrap = true;
}
break;
}
if ($wrap) {
$field = s($field);
if ($field->contains($d->getQuoteChar())) {
$escapeChar = $d->isDoubleQuote() ? $d->getQuoteChar() : '\\' /*$d->getEscapeChar()*/;
$field = $field->replace($d->getQuoteChar(), $d->getQuoteChar() . $d->getQuoteChar());
}
$field = $field->surround($d->getQuoteChar());
}
}
return (string) $field;
});
$str = s($data->join($d->getDelimiter()))
->append($d->getLineTerminator());

return $this->output->write((string) $str);
}

/**
* Write multiple rows to CSV output
*
* Returns total bytes written to the output stream.
*
* @param array|Traversable $data An array of rows of data to write to the CSV output
*
* @return int
*/
public function insertAll($data)
{
return collect($data)
->map(function($row, $lineNo, $i) {
return $this->insertRow($row);
})
->sum();
}
}
52 changes: 50 additions & 2 deletions tests/CSVelte/WriterTest.php
Expand Up @@ -13,6 +13,7 @@
namespace CSVelteTest;

use CSVelte\Dialect;
use CSVelte\Reader;
use CSVelte\Writer;

use function CSVelte\to_stream;
Expand All @@ -29,10 +30,57 @@ public function testInstantiateWriterWithNoDialectUsesDefault()
public function testInstantiateWriterWithDialectCanChangeDialectWithSetDialect()
{
$stream = to_stream(fopen('php://temp', 'w+'));
$dialect = new Dialect(['delimiter' => ',']);
$dialect = new Dialect(['delimiter' => "\t"]);
$writer = new Writer($stream);
$this->assertNotSame($dialect, $writer->getDialect());
$this->assertSame($writer, $writer->setDialect($dialect));
$this->assertSame($dialect, $writer->getDialect());
$this->assertEquals("\t", $writer->getDialect()->getDelimiter());
}
}

public function testWriterWritesToOutputStreamAccordingToDialect()
{
$stream = to_stream(fopen('php://temp', 'w+'));
$dialect = new Dialect(['header' => false]);
$writer = new Writer($stream, $dialect);
$this->assertEquals(12, $writer->insertRow([
'foo',
'bar',
'baz'
]));
$this->assertEquals("foo,bar,baz\n", (string) $stream);
$this->assertEquals(71, $writer->insertRow([
'test',
'this is a test, with a comma in it',
'this is a "quoted" field'
]));
$this->assertEquals("foo,bar,baz\ntest,\"this is a test, with a comma in it\",\"this is a \"\"quoted\"\" field\"\n", (string) $stream);
$this->assertEquals(11, $writer->insertRow([
1,
'2',
'3.5556'
]));
$this->assertEquals("foo,bar,baz\ntest,\"this is a test, with a comma in it\",\"this is a \"\"quoted\"\" field\"\n1,2,3.5556\n", (string) $stream);
$this->assertEquals(103, $writer->insertRow([
"this field\nhas\nline breaks",
'this field has a \' single quote',
'this has? weird^*& characters!! ,.:?!2@'
]));
$this->assertEquals("foo,bar,baz\ntest,\"this is a test, with a comma in it\",\"this is a \"\"quoted\"\" field\"\n1,2,3.5556\n\"this field\nhas\nline breaks\",this field has a ' single quote,\"this has? weird^*& characters!! ,.:?!2@\"\n", (string) $stream);
}

public function testInsertAllWritesMultipleRowsAndReturnsTotalWrittenBytes()
{
$stream = to_stream(fopen('php://temp', 'w+'));
$dialect = new Dialect(['header' => false]);
$writer = new Writer($stream, $dialect);
$data = [
['1', 'luke@example.com', 'A short description', 'ON'],
['2', 'bob@example.com', 'What about "bob"?', 'OFF'],
['3', 'steve@example.com', 'The problem with steve, is steve.', 'ON'],
['4', 'joe@example.com', 'Hey Joe, where you goin\' with that gun in yo hand?', 'ON'],
];
$this->assertEquals(219, $writer->insertAll($data));
$this->assertEquals("1,luke@example.com,A short description,ON\n2,bob@example.com,\"What about \"\"bob\"\"?\",OFF\n3,steve@example.com,\"The problem with steve, is steve.\",ON\n4,joe@example.com,\"Hey Joe, where you goin' with that gun in yo hand?\",ON\n", (string) $stream);
}
}

0 comments on commit e0b98ac

Please sign in to comment.