Browse Source

added classes table\Table, table\Row and table\Cell for csv export

Fabian Peter Hammerle 7 years ago
parent
commit
6447588451
6 changed files with 768 additions and 0 deletions
  1. 61 0
      table/Cell.php
  2. 82 0
      table/Row.php
  3. 102 0
      table/Table.php
  4. 147 0
      tests/table/CellTest.php
  5. 197 0
      tests/table/RowTest.php
  6. 179 0
      tests/table/TableTest.php

+ 61 - 0
table/Cell.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace fphammerle\helpers\table;
+
+use \fphammerle\helpers\StringHelper;
+
+class Cell
+{
+    use \fphammerle\helpers\PropertyAccessTrait;
+
+    private $_value = [];
+
+    /**
+     * @param mixed $value
+     */
+    public function __construct($value = null)
+    {
+        $this->value = $value;
+    }
+
+    /**
+     * @return mixed
+     */
+    public function getValue()
+    {
+        return $this->_value;
+    }
+
+    /**
+     * @param mixed $value
+     */
+    public function setValue($value)
+    {
+        $this->_value = $value;
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     * @return string
+     */
+    public function toCSV($delimiter = ',', $quotes = '"')
+    {
+        if(empty($delimiter)) {
+            throw new \InvalidArgumentException('empty delimiter');
+        } elseif(empty($quotes)) {
+            throw new \InvalidArgumentException('empty quotes');
+        } elseif($delimiter == $quotes) {
+            throw new \InvalidArgumentException('delimiter equals quotes');
+        }
+
+        if($this->value === false) {
+            return '0';
+        } else {
+            $csv = (string)$this->value;
+            if(StringHelper::containsAny(["\n", "\r", $quotes, $delimiter], $csv)) {
+                $csv = $quotes . str_replace($quotes, $quotes.$quotes, $csv) . $quotes;
+            }
+            return $csv;
+        }
+    }
+}

+ 82 - 0
table/Row.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace fphammerle\helpers\table;
+
+use \fphammerle\helpers\StringHelper;
+
+class Row
+{
+    use \fphammerle\helpers\PropertyAccessTrait;
+
+    private $_cells = [];
+
+    /**
+     * @param array<mixed> $cell_values
+     */
+    public function __construct($cell_values = [])
+    {
+        foreach($cell_values as $column_index => $cell_value) {
+            $this->setCellValue($column_index, $cell_value);
+        }
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     * @param integer $column_index
+     * @return Cell
+     */
+    public function getCell($column_index)
+    {
+        if(!is_int($column_index) || $column_index < 0) {
+            throw new \InvalidArgumentException('column index must be an integer >= 0');
+        }
+
+        if(!isset($this->_cells[$column_index])) {
+            $this->_cells[$column_index] = new Cell;
+        }
+
+        return $this->_cells[$column_index];
+    }
+
+    /**
+     * @param integer $column_index
+     * @param mixed $value
+     */
+    public function setCellValue($column_index, $value)
+    {
+        $this->getCell($column_index)->value = $value;
+    }
+
+    /**
+     * @return integer
+     */
+    public function getColumnsCount()
+    {
+        return sizeof($this->_cells) > 0
+            ? max(array_keys($this->_cells)) + 1
+            : 0;
+    }
+
+    /**
+     * @return string
+     */
+    public function toCSV($delimiter = ',', $columns_number = null)
+    {
+        if(empty($delimiter)) {
+            throw new \InvalidArgumentException('empty delimiter');
+        }
+
+        if($columns_number === null) {
+            $columns_number = $this->columnsCount;
+        }
+
+        $_empty_cell_csv = (new Cell)->toCSV($delimiter);
+        $_cells_csv = [];
+        for($column_index = 0; $column_index < $columns_number; $column_index++) {
+            $_cells_csv[] = isset($this->_cells[$column_index])
+                ? $this->_cells[$column_index]->toCSV($delimiter)
+                : $_empty_cell_csv;
+        }
+        return implode($delimiter, $_cells_csv) . "\r\n";
+    }
+}

+ 102 - 0
table/Table.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace fphammerle\helpers\table;
+
+class Table
+{
+    use \fphammerle\helpers\PropertyAccessTrait;
+
+    private $_rows = [];
+
+    /**
+     * @param array<array<mixed>> $cell_values
+     */
+    public function __construct($cell_values = [])
+    {
+        foreach($cell_values as $row_index => $row_values) {
+            $this->setRow($row_index, new Row($row_values));
+        }
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     * @param integer $row_index
+     * @return Row
+     */
+    public function getRow($row_index)
+    {
+        if(!is_int($row_index) || $row_index < 0) {
+            throw new \InvalidArgumentException(
+                sprintf('row index must be an integer >= 0, %s given', print_r($row_index, true))
+                );
+        }
+
+        if(!isset($this->_rows[$row_index])) {
+            $this->_rows[$row_index] = new Row;
+        }
+
+        return $this->_rows[$row_index];
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     * @param integer $row_index
+     * @param Row $row
+     */
+    public function setRow($row_index, Row $row)
+    {
+        if(!is_int($row_index) || $row_index < 0) {
+            throw new \InvalidArgumentException(
+                sprintf('row index must be an integer >= 0, %s given', print_r($row_index, true))
+                );
+        }
+
+        $this->_rows[$row_index] = $row;
+    }
+
+    /**
+     * @param integer $row_index
+     * @param integer $column_index
+     * @return Cell
+     */
+    public function getCell($row_index, $column_index)
+    {
+        return $this->getRow($row_index)->getCell($column_index);
+    }
+
+    /**
+     * @param integer $column_index
+     * @param mixed $value
+     */
+    public function setCellValue($row_index, $column_index, $value)
+    {
+        $this->getCell($row_index, $column_index)->value = $value;
+    }
+
+    /**
+     * @return integer
+     */
+    public function getColumnsCount()
+    {
+        return sizeof($this->_rows) > 0
+            ? max(array_map(function($r) { return $r->columnsCount; }, $this->_rows))
+            : 0;
+    }
+
+    /**
+     * @return string
+     */
+    public function toCSV($delimiter = ',')
+    {
+        $columns_number = $this->columnsCount;
+        $empty_row_csv = (new Row)->toCSV($delimiter, $columns_number);
+        $rows_csv = [];
+        $rows_number = sizeof($this->_rows) > 0 ? max(array_keys($this->_rows)) + 1 : 0;
+        for($row_index = 0; $row_index < $rows_number; $row_index++) {
+            $rows_csv[] = isset($this->_rows[$row_index])
+                ? $this->_rows[$row_index]->toCSV($delimiter, $columns_number)
+                : $empty_row_csv;
+        }
+        return implode('', $rows_csv);
+    }
+}

+ 147 - 0
tests/table/CellTest.php

@@ -0,0 +1,147 @@
+<?php
+
+namespace fphammerle\helpers\tests\table;
+
+use \fphammerle\helpers\table\Cell;
+
+class CellTest extends \PHPUnit_Framework_TestCase
+{
+    public function setValueProvider()
+    {
+        return [
+            [1],
+            [1.23],
+            ['string'],
+            [true],
+            ];
+    }
+
+    /**
+     * @dataProvider setValueProvider
+     */
+    public function testSetValue($v)
+    {
+        $c = new Cell;
+        $c->setValue($v);
+        $this->assertSame($c->value, $v);
+    }
+
+    /**
+     * @dataProvider setValueProvider
+     */
+    public function testConstruct($v)
+    {
+        $this->assertSame((new Cell($v))->value, $v);
+    }
+
+    public function testConstructDefault()
+    {
+        $this->assertNull((new Cell)->value);
+    }
+
+    public function toCSVProvider()
+    {
+        return [
+            [0, '0'],
+            [1, '1'],
+            [1.23, '1.23'],
+            [true, '1'],
+            [false, '0'],
+            [null, ''],
+            ['', ''],
+            ['string', 'string'],
+            ['str"ing', '"str""ing"'],
+            ['str"ing"', '"str""ing"""'],
+            ['"string"', '"""string"""'],
+            ['str,ing', '"str,ing"'],
+            ['str,ing,', '"str,ing,"'],
+            [',string,', '",string,"'],
+            ["str\ning", "\"str\ning\""],
+            ["str\ning\n", "\"str\ning\n\""],
+            ["string\n", "\"string\n\""],
+            ["\nstring", "\"\nstring\""],
+            ["str\ring", "\"str\ring\""],
+            ["str\r\ning", "\"str\r\ning\""],
+            ["str\rin\ng", "\"str\rin\ng\""],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVProvider
+     */
+    public function testToCSV($v, $csv)
+    {
+        $c = new Cell($v);
+        $this->assertSame($c->toCSV(), $csv);
+    }
+
+    public function toCSVDelimiterProvider()
+    {
+        return [
+            [1, "\t", "1"],
+            ["1\t2", "\t", "\"1\t2\""],
+            ["1\t2", "\t", "\"1\t2\""],
+            ["12\t", "\t", "\"12\t\""],
+            ['a#$b', '#$', '"a#$b"'],
+            ['a#$$b', '#$', '"a#$$b"'],
+            ['1.23', '.', '"1.23"'],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVDelimiterProvider
+     */
+    public function testToCSVDelimiter($v, $d, $csv)
+    {
+        $c = new Cell($v);
+        $this->assertSame($c->toCSV($d), $csv);
+    }
+
+    public function toCSVQuotesProvider()
+    {
+        return [
+            [1, '*', '1'],
+            ['1*2', '*', '*1**2*'],
+            ['12*', '*', '*12***'],
+            ['1"2"', '*', '1"2"'],
+            ['1*"2"', '*', '*1**"2"*'],
+            ['', '*', ''],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVQuotesProvider
+     */
+    public function testToCSVQuotes($v, $q, $csv)
+    {
+        $c = new Cell($v);
+        $this->assertSame($c->toCSV(',', $q), $csv);
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testToCsvDelimiterEqualsQuotes()
+    {
+        $c = new Cell;
+        $c->toCSV('*', '*');
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testToCsvDelimiterEmpty()
+    {
+        $c = new Cell;
+        $c->toCSV('', '*');
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testToCsvQuotesEmpty()
+    {
+        $c = new Cell;
+        $c->toCSV('*', '');
+    }
+}

+ 197 - 0
tests/table/RowTest.php

@@ -0,0 +1,197 @@
+<?php
+
+namespace fphammerle\helpers\tests\table;
+
+use \fphammerle\helpers\table\Row;
+
+class RowTest extends \PHPUnit_Framework_TestCase
+{
+    public function setCellValueProvider()
+    {
+        return [
+            [0, 1],
+            [0, 1.23],
+            [0, 'string'],
+            [0, true],
+            [3, 1],
+            [3, 1.23],
+            [3, 'string'],
+            [3, true],
+            ];
+    }
+
+    /**
+     * @dataProvider setCellValueProvider
+     */
+    public function testSetCellValue($c, $v)
+    {
+        $r = new Row;
+        $r->setCellValue($c, $v);
+        $this->assertSame($v, $r->getCell($c)->value);
+    }
+
+    public function setCellValueMultipleProvider()
+    {
+        return [
+            [[[0, 'c0']]],
+            [[[0, 'c0'], [1, 'c1']]],
+            [[[0, 'c0'], [1, 'c1'], [3, 'c3']]],
+            [[[2, 'c2'], [3, 'c3']]],
+            ];
+    }
+
+    /**
+     * @dataProvider setCellValueMultipleProvider
+     */
+    public function testSetCellMultipleValue($vv)
+    {
+        $r = new Row;
+        foreach($vv as $cv) {
+            $r->setCellValue($cv[0], $cv[1]);
+        }
+        foreach($vv as $cv) {
+            $this->assertSame($cv[1], $r->getCell($cv[0])->value);
+        }
+    }
+
+    public function constructProvider()
+    {
+        return [
+            [[]],
+            [['c0']],
+            [['c0', 'c1']],
+            [['c0', 'c1', 'c2']],
+            ];
+    }
+
+    /**
+     * @dataProvider constructProvider
+     */
+    public function testConstruct($vv)
+    {
+        $r = new Row($vv);
+        foreach($vv as $c => $v) {
+            $this->assertSame($v, $r->getCell($c)->value);
+        }
+    }
+
+    public function getColumnsCountProvider()
+    {
+        return [
+            [[], 0],
+            [[[0, 'c0']], 1],
+            [[[0, 'c0'], [1, 'c1']], 2],
+            [[[0, 'c0'], [1, 'c1'], [3, 'c3']], 4],
+            [[[5, 'c5']], 6],
+            ];
+    }
+
+    /**
+     * @dataProvider getColumnsCountProvider
+     */
+    public function testGetColumnsCountValue($vv, $count)
+    {
+        $r = new Row;
+        foreach($vv as $cv) {
+            $r->setCellValue($cv[0], $cv[1]);
+        }
+        $this->assertSame($count, $r->columnsCount);
+    }
+
+    public function toCSVProvider()
+    {
+        return [
+            [[], "\r\n"],
+            [[[0, 'c0']], "c0\r\n"],
+            [[[0, 'c0'], [1, 'c1']], "c0,c1\r\n"],
+            [[[0, 'c0'], [1, 'c1'], [3, 'c3']], "c0,c1,,c3\r\n"],
+            [[[2, 'c2'], [3, 'c3']], ",,c2,c3\r\n"],
+            [[[2, 'c,2'], [3, 'c3']], ",,\"c,2\",c3\r\n"],
+            [[[2, "c2\n"], [3, 'c"3"']], ",,\"c2\n\",\"c\"\"3\"\"\"\r\n"],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVProvider
+     */
+    public function testToCSVValue($vv, $csv)
+    {
+        $r = new Row;
+        foreach($vv as $cv) {
+            $r->setCellValue($cv[0], $cv[1]);
+        }
+        $this->assertSame($csv, $r->toCSV());
+    }
+
+    public function toCSVAfterConstructProvider()
+    {
+        return [
+            [[], "\r\n"],
+            [['c0'], "c0\r\n"],
+            [['c0', 'c1'], "c0,c1\r\n"],
+            [['c0', 'c1', null, 'c3'], "c0,c1,,c3\r\n"],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVAfterConstructProvider
+     */
+    public function testToCSVAfterConstructValue($vv, $csv)
+    {
+        $r = new Row($vv);
+        $this->assertSame($csv, $r->toCSV());
+    }
+
+    public function toCSVDelimiterProvider()
+    {
+        return [
+            [[], "\t", "\r\n"],
+            [['c0'], "\t", "c0\r\n"],
+            [['c0', 'c1'], "**", "c0**c1\r\n"],
+            [['c0', 'c1', null, 'c3'], "\t", "c0\tc1\t\tc3\r\n"],
+            [['c0', 'c1', null, "c\t3", 'c4'], "\t", "c0\tc1\t\t\"c\t3\"\tc4\r\n"],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVDelimiterProvider
+     */
+    public function testToCSVDelimiterValue($vv, $d, $csv)
+    {
+        $r = new Row($vv);
+        $this->assertSame($csv, $r->toCSV($d));
+    }
+
+    public function toCSVColumnsNumberProvider()
+    {
+        return [
+            [[], 0, "\r\n"],
+            [[], 1, "\r\n"],
+            [[], 2, "|\r\n"],
+            [[], 4, "|||\r\n"],
+            [['a', 'b'], null, "a|b\r\n"],
+            [['a', 'b'], 1, "a\r\n"],
+            [['a', 'b'], 2, "a|b\r\n"],
+            [['a', 'b'], 3, "a|b|\r\n"],
+            [['a', 'b'], 5, "a|b|||\r\n"],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVColumnsNumberProvider
+     */
+    public function testToCSVColumnsNumberValue($vv, $n, $csv)
+    {
+        $r = new Row($vv);
+        $this->assertSame($csv, $r->toCSV('|', $n));
+    }
+
+    /**
+     * @expectedException InvalidArgumentException
+     */
+    public function testToCsvDelimiterEmpty()
+    {
+        $c = new Row;
+        $c->toCSV('');
+    }
+}

+ 179 - 0
tests/table/TableTest.php

@@ -0,0 +1,179 @@
+<?php
+
+namespace fphammerle\helpers\tests\table;
+
+use \fphammerle\helpers\table\Row;
+use \fphammerle\helpers\table\Table;
+
+class TableTest extends \PHPUnit_Framework_TestCase
+{
+    public function setCellValueProvider()
+    {
+        return [
+            [0, 0, 1],
+            [0, 0, 1.23],
+            [0, 0, 'string'],
+            [0, 0, true],
+            [2, 3, 1],
+            [2, 3, 1.23],
+            [2, 3, 'string'],
+            [2, 3, true],
+            ];
+    }
+
+    /**
+     * @dataProvider setCellValueProvider
+     */
+    public function testSetCellValue($r, $c, $v)
+    {
+        $t = new Table;
+        $t->setCellValue($r, $c, $v);
+        $this->assertSame($v, $t->getCell($r, $c)->value);
+    }
+
+    public function setCellValueMultipleProvider()
+    {
+        return [
+            [[[0, 0, 'r0c0']]],
+            [[[0, 0, 'r0c0'], [0, 1, 'r0c1']]],
+            [[[1, 2, 'r1c2'], [2, 2, 'r2c2'], [0, 4, 'r0c4']]],
+            ];
+    }
+
+    /**
+     * @dataProvider setCellValueMultipleProvider
+     */
+    public function testSetCellMultipleValue($vv)
+    {
+        $t = new Table;
+        foreach($vv as $cv) {
+            $t->setCellValue($cv[0], $cv[1], $cv[2]);
+        }
+        foreach($vv as $cv) {
+            $this->assertSame($cv[2], $t->getCell($cv[0], $cv[1])->value);
+        }
+    }
+
+    public function setRowProvider()
+    {
+        return [
+            [[]],
+            [[0 => new Row]],
+            [[2 => new Row, 4 => new Row(['aaa', 'bbb'])]],
+            ];
+    }
+
+    /**
+     * @dataProvider setRowProvider
+     */
+    public function testSetRow($rr)
+    {
+        $t = new Table;
+        foreach($rr as $ri => $r) {
+            $t->setRow($ri, $r);
+        }
+        foreach($rr as $ri => $r) {
+            $this->assertSame($r, $t->getRow($ri));
+        }
+    }
+
+    public function constructProvider()
+    {
+        return [
+            [[]],
+            [[[]]],
+            [[['r0c0', 'r0c1']]],
+            [[['r0c0', 'r0c1'], ['r1c0']]],
+            [[['r0c0', 'r0c1'], ['r1c0'], 4 => ['r4c0', 3 => 'r4c3']]],
+            ];
+    }
+
+    /**
+     * @dataProvider constructProvider
+     */
+    public function testConstruct($rr)
+    {
+        $t = new Table($rr);
+        foreach($rr as $row_index => $row_values) {
+            foreach($row_values as $column_index => $cell_value) {
+                $this->assertSame($cell_value, $t->getCell($row_index, $column_index)->value);
+            }
+        }
+    }
+
+    public function getColumnsCountProvider()
+    {
+        return [
+            [[], 0],
+            [[[]], 0],
+            [[['r0c0', 'r0c1']], 2],
+            [[['r0c0', 'r0c1'], ['r1c0']], 2],
+            [[['r0c0', 'r0c1'], [3 => 'r1c0']], 4],
+            [[2 => [0 => 'r2c0'], 1 => [2 => 'r1c2']], 3],
+            ];
+    }
+
+    /**
+     * @dataProvider getColumnsCountProvider
+     */
+    public function testGetColumnsCountValue($rr, $count)
+    {
+        $t = new Table($rr);
+        $this->assertSame($count, $t->columnsCount);
+    }
+
+    public function toCSVProvider()
+    {
+        return [
+            [[], ""],
+            [[[]], "\r\n"],
+            [[['r0c0', 'r0c1']], "r0c0,r0c1\r\n"],
+            [[['r0c0', 'r"0c1"']], "r0c0,\"r\"\"0c1\"\"\"\r\n"],
+            [[['r0c0', 'r0c1'], ['r1c0']], "r0c0,r0c1\r\nr1c0,\r\n"],
+            [[2 => [0 => 'r2c0'], 1 => [2 => 'r1c2']], ",,\r\n,,r1c2\r\nr2c0,,\r\n"],
+            [
+                [['r0c0', 'r0c1'], ['r1c0'], 4 => ['r4c0', 3 => 'r4c3']],
+                "r0c0,r0c1,,\r\nr1c0,,,\r\n,,,\r\n,,,\r\nr4c0,,,r4c3\r\n",
+                ],
+            [
+                [
+                    [1, 2, 3, 4],
+                    ['a', 'b', null, 'd'],
+                    ['A,B', 'CD', "EF\n", 'G'],
+                    [3.14, '$#"%'],
+                    ],
+                "1,2,3,4\r\na,b,,d\r\n\"A,B\",CD,\"EF\r\",G\r\n3.14,\"$#\"\"%\",,\r\n",
+                ],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVProvider
+     */
+    public function testToCSVValue($rr, $csv)
+    {
+        $t = new Table($rr);
+        $this->assertSame($csv, $t->toCSV());
+    }
+
+    public function toCSVDelimiterProvider()
+    {
+        return [
+            [[], '#', ""],
+            [[[]], '#', "\r\n"],
+            [[['r0c0', 'r0c1']], '#', "r0c0#r0c1\r\n"],
+            [[['r0c0', 'r"0c1"']], '#', "r0c0#\"r\"\"0c1\"\"\"\r\n"],
+            [[['r0c0', 'r0c1'], ['r1c0']], '#', "r0c0#r0c1\r\nr1c0#\r\n"],
+            [[2 => [0 => 'r2c0'], 1 => [2 => 'r1c2']], '@', "@@\r\n@@r1c2\r\nr2c0@@\r\n"],
+            ];
+    }
+
+    /**
+     * @dataProvider toCSVDelimiterProvider
+     */
+    public function testToCSVDelimiter($rr, $d, $csv)
+    {
+        $t = new Table($rr);
+        $this->assertSame($csv, $t->toCSV($d));
+    }
+}