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
Markiert in:     

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.