Bei der Coding Kata „CSV Viewer“ möchte ich den Inhalt von CSV-Dateien in einer schöneren, übersichtlicheren Form in ASCII-Zeichen ausgeben.
Hierzu bediene ich mich der Vorgaben der Clean Code Developer Angabe. Dabei reduziere ich meine Kata Lösung auf die Umsetzung einer Klasse, die wie folgt aussieht:
class CsvViewer
{
public function show(string $csvContent): string
}
Die Methode liefert bei übergebenen CSV Content den formatierten String zurück, also der CSV Content:
Name;Alter;Ort
Hans;10;Bremen
Franz;20;Hof
Max;30;Verden an der Aller
Moritz;100;Hamburg
sieht dann wie folgt aus:
Name |Alter|Ort |
------+-----+-------------------+
Hans |10 |Bremen |
Franz |20 |Hof |
Max |30 |Verden an der Aller|
Moritz|100 |Hamburg |
Viel Spaß beim Video…
Wer den Code für die Endlösung gar nicht erwarten kann, guckt hier….
<?php declare(strict_types=1);
namespace Kata;
class CsvViewer
{
public const TABLE_SEPARATOR = '|';
public const SEPARATOR = '-';
public const CONCATENATOR = '+';
public const CSV_SEPARATOR = ';';
public const FILL_PAD = ' ';
public function show(string $csvContent): string
{
$csvLines = explode(PHP_EOL, $csvContent);
$csvHeader = explode(self::CSV_SEPARATOR, $csvLines[0]);
$columnWidths = $this->determineColumnWidths(
$csvHeader,
$csvLines
);
$result = $this->createTableHeader($csvHeader,
$columnWidths
);
$result .= $this->createSeparatorLine($csvHeader, $columnWidths);
$result .= $this->addDataLines($csvLines, $columnWidths);
return $result;
}
private function determineColumnWidths(
array $csvHeader,
array $csvLines
): array
{
$columnWidths = [];
foreach ($csvHeader as $index => $headerItem) {
$columnWidths[$index] = strlen($headerItem);
}
foreach ($csvLines as $line) {
$dataItems = explode(self::CSV_SEPARATOR, $line);
foreach ($dataItems as $colIndex => $item) {
$columnWidths[$colIndex]
= max($columnWidths[$colIndex], strlen($item));
}
}
return $columnWidths;
}
private function createTableHeader(
array $csvHeader,
array $columnWidths
): string
{
$result = '';
foreach ($csvHeader as $colIndex => $headerItem) {
$result .= str_pad(
$headerItem,
$columnWidths[$colIndex],
self::FILL_PAD
);
$result .= self::TABLE_SEPARATOR;
}
$result .= PHP_EOL;
return $result;
}
private function createSeparatorLine(
array $csvHeader,
array $columnWidths
): string
{
$result = '';
foreach ($csvHeader as $colIndex => $headerItem) {
$result .= str_repeat(
self::SEPARATOR,
$columnWidths[$colIndex]
);
$result .= self::CONCATENATOR;
}
return $result;
}
private function addDataLines(
array $csvLines,
array $columnWidths
): string
{
$result = '';
foreach ($csvLines as $index => $csvLine) {
if ($index !== 0) {
$csvData = explode(
self::CSV_SEPARATOR,
$csvLine
);
$result .= PHP_EOL;
foreach ($csvData as $colIndex => $data) {
$result .= str_pad(
$data,
$columnWidths[$colIndex],
self::FILL_PAD
);
$result .= self::TABLE_SEPARATOR;
}
}
}
return $result;
}
}
Und natürlich der zugehörige Test:
<?php declare(strict_types=1);
namespace Kata\Tests;
use Kata\CsvViewer;
use PHPUnit\Framework\TestCase;
class CsvViewerTest extends TestCase
{
/**
* @dataProvider sampleCsvData
*/
public function testWithProvider(
string $csvContent,
string $expected
): void
{
$csvViewer = new CsvViewer();
self::assertSame($expected, $csvViewer->show($csvContent));
}
public function sampleCsvData()
{
yield 'empty table with single header' => [
'Name',
<<<CSVTABLE
Name|
----+
CSVTABLE
];
yield 'empty table with different single header' => [
'Alter',
<<<CSVTABLE
Alter|
-----+
CSVTABLE
];
yield 'empty table with multi header' => [
'Name;Alter;Ort',
<<<CSVTABLE
Name|Alter|Ort|
----+-----+---+
CSVTABLE
];
yield 'table with single data line' => [
<<<CSVCONTENT
Name
Hans
CSVCONTENT,
<<<CSVTABLE
Name|
----+
Hans|
CSVTABLE
];
yield 'table with single data line and multi header' => [
<<<CSVCONTENT
Name;Ort
Hans;Hof
CSVCONTENT,
<<<CSVTABLE
Name|Ort|
----+---+
Hans|Hof|
CSVTABLE
];
yield 'table with data line shorter than header line' => [
<<<CSVCONTENT
Name
Max
CSVCONTENT,
<<<CSVTABLE
Name|
----+
Max |
CSVTABLE
];
yield 'table with data line longer than header line' => [
<<<CSVCONTENT
Name
Franz
CSVCONTENT,
<<<CSVTABLE
Name |
-----+
Franz|
CSVTABLE
];
yield 'table with multi data lines' => [
<<<CSVCONTENT
Name
Hans
Franz
Max
Moritz
CSVCONTENT,
<<<CSVTABLE
Name |
------+
Hans |
Franz |
Max |
Moritz|
CSVTABLE
];
yield 'more complexe table with multi data and header lines' => [
<<<CSVCONTENT
Name;Alter;Ort
Hans;10;Bremen
Franz;20;Hof
Max;30;Verden an der Aller
Moritz;100;Hamburg
CSVCONTENT,
<<<CSVTABLE
Name |Alter|Ort |
------+-----+-------------------+
Hans |10 |Bremen |
Franz |20 |Hof |
Max |30 |Verden an der Aller|
Moritz|100 |Hamburg |
CSVTABLE
];
}
}
Viel Spaß beim Nachmachen…
Coding Kata: CSV Viewer