From d7da839c5f7545490712647eed55067fc6a68a70 Mon Sep 17 00:00:00 2001 From: Beno!t POLASZEK Date: Thu, 21 Dec 2023 12:04:27 +0100 Subject: [PATCH] Feat(CSV): Add a `skipFirstRow` option (#56) --- src/Iterator/CSVIterator.php | 31 +++++++++++++++++++++---- tests/Unit/Iterator/CSVIteratorTest.php | 30 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/Iterator/CSVIterator.php b/src/Iterator/CSVIterator.php index 8b2dd09..d0ec0df 100644 --- a/src/Iterator/CSVIterator.php +++ b/src/Iterator/CSVIterator.php @@ -28,13 +28,27 @@ final readonly class CSVIterator implements IteratorAggregate { /** - * @var array{delimiter: string, enclosure: string, escapeString: string, columns: 'auto'|string[]|null, normalizers: ValueNormalizerInterface[]} + * @var array{ + * delimiter: string, + * enclosure: string, + * escapeString: string, + * columns: 'auto'|string[]|null, + * normalizers: ValueNormalizerInterface[], + * skipFirstRow: bool, + * } */ private array $options; /** - * @param Traversable $text - * @param array{delimiter?: string, enclosure?: string, escapeString?: string, columns?: 'auto'|string[]|null, normalizers?: ValueNormalizerInterface[]} $options + * @param Traversable $text + * @param array{ + * delimiter?: string, + * enclosure?: string, + * escapeString?: string, + * columns?: 'auto'|string[]|null, + * normalizers?: ValueNormalizerInterface[], + * skipFirstRow?: bool, + * } $options */ public function __construct( private Traversable $text, @@ -50,6 +64,7 @@ public function __construct( new NumericStringToNumberNormalizer(), new EmptyStringToNullNormalizer(), ], + 'skipFirstRow' => false, ]); $resolver->setAllowedTypes('delimiter', 'string'); $resolver->setAllowedTypes('enclosure', 'string'); @@ -59,6 +74,7 @@ public function __construct( $resolver->setAllowedValues('columns', function (array|string|null $value) { return 'auto' === $value || null === $value || is_array($value); }); + $resolver->setAllowedTypes('skipFirstRow', 'bool'); $this->options = $resolver->resolve($options); } @@ -92,6 +108,11 @@ public function getIterator(): Traversable return $this->iterateFromContent($this->text); } + private function shouldSkipFirstRow(): bool + { + return $this->options['skipFirstRow'] || 'auto' === $this->options['columns']; + } + /** * @return Traversable */ @@ -112,7 +133,7 @@ private function iterateFromFile(SplFileObject $file): Traversable if ([null] === $fields) { continue; } - if ('auto' === $this->options['columns'] && 0 === $file->key()) { + if (0 === $file->key() && $this->shouldSkipFirstRow()) { $columns ??= $fields; continue; } @@ -139,7 +160,7 @@ private function iterateFromContent(Traversable $content): Traversable $this->options['enclosure'], $this->options['escapeString'], ); - if ('auto' === $this->options['columns'] && 0 === $r) { + if (0 === $r && $this->shouldSkipFirstRow()) { $columns ??= $fields; continue; } diff --git a/tests/Unit/Iterator/CSVIteratorTest.php b/tests/Unit/Iterator/CSVIteratorTest.php index 50c53f7..a1a6358 100644 --- a/tests/Unit/Iterator/CSVIteratorTest.php +++ b/tests/Unit/Iterator/CSVIteratorTest.php @@ -90,6 +90,36 @@ yield 'file' => new CSVIterator(new SplFileObject($filename), ['columns' => $columns]); }); +it('skips the 1st row when asked to', function (CSVIterator $iterator) { + $rows = [...$iterator]; + + expect($rows[0])->toBe([ + 'cityEnglishName' => 'New York', + 'cityLocalName' => 'New York', + 'countryIsoCode' => 'US', + 'continent' => 'North America', + 'population' => 8537673, + ]) + ->and($rows[2])->toBe([ + 'cityEnglishName' => 'Tokyo', + 'cityLocalName' => '東京', + 'countryIsoCode' => 'JP', + 'continent' => 'Asia', + 'population' => 13929286, + ]); +})->with(function () { + $columns = [ + 'cityEnglishName', + 'cityLocalName', + 'countryIsoCode', + 'continent', + 'population', + ]; + $filename = dirname(__DIR__, 2).'/Data/10-biggest-cities.csv'; + yield 'string content' => new CSVIterator(new StrTokIterator(file_get_contents($filename)), ['columns' => $columns, 'skipFirstRow' => true]); + yield 'file' => new CSVIterator(new SplFileObject($filename), ['columns' => $columns, 'skipFirstRow' => true]); +}); + it('adds fields when the row has not enough columns', function (CSVIterator $iterator) { $rows = [...$iterator];