diff --git a/composer.json b/composer.json index 1f99543..c2702b1 100644 --- a/composer.json +++ b/composer.json @@ -23,8 +23,7 @@ "docs": "https://portphp.readthedocs.io" }, "require": { - "php": "^8.2", - "portphp/portphp": "^2.0" + "portphp/portphp": "dev-master" }, "autoload": { "psr-4": { diff --git a/src/CsvReader.php b/src/CsvReader.php index 72abfff..e015d94 100644 --- a/src/CsvReader.php +++ b/src/CsvReader.php @@ -73,6 +73,13 @@ class CsvReader implements CountableReader, \SeekableIterator */ protected $duplicateHeadersFlag; + + protected $padValue = null; + + protected $truncateHeader = false; + + protected $removeEmptyField = false; + /** * @param \SplFileObject $file * @param string $delimiter @@ -81,6 +88,8 @@ class CsvReader implements CountableReader, \SeekableIterator */ public function __construct(\SplFileObject $file, $delimiter = ',', $enclosure = '"', $escape = '\\') { + // ini_set('auto_detect_line_endings', true); + $this->file = $file; $this->file->setFlags( \SplFileObject::READ_CSV | @@ -99,6 +108,8 @@ public function __construct(\SplFileObject $file, $delimiter = ',', $enclosure = * Return the current row as an array * * If a header row has been set, an associative array will be returned + * + * @return array */ public function current(): ?array { @@ -114,10 +125,15 @@ public function current(): ?array while ($this->valid()) { $line = $this->file->current(); + $columnHeaders = $this->columnHeaders; + // In non-strict mode pad/slice the line to match the column headers if (!$this->isStrict()) { if ($this->headersCount > count($line)) { - $line = array_pad($line, $this->headersCount, null); // Line too short + if($this->truncateHeader){ + $columnHeaders = array_slice($columnHeaders, 0, count($line)); + } + else $line = array_pad($line, $this->headersCount, $this->getPadValue()); // Line too short } else { $line = array_slice($line, 0, $this->headersCount); // Line too long } @@ -129,8 +145,12 @@ public function current(): ?array } // Count the number of elements in both: they must be equal. - if (count($this->columnHeaders) === count($line)) { - return array_combine(array_keys($this->columnHeaders), $line); + if (count($columnHeaders) === count($line)) { + $finalLine = array_combine(array_keys($columnHeaders), $line); + if($this->isRemoveEmptyField()){ + return array_filter($finalLine, function($value) { return $value !== ''; }); + } + else return $finalLine; } // They are not equal, so log the row as error and skip it. @@ -160,6 +180,7 @@ public function getColumnHeaders() */ public function setColumnHeaders(array $columnHeaders) { + $columnHeaders = array_map('trim', $columnHeaders); $this->columnHeaders = array_count_values($columnHeaders); $this->headersCount = count($columnHeaders); } @@ -203,6 +224,9 @@ public function rewind(): void } } + /** + * {@inheritdoc} + */ public function count(): int { if (null === $this->count) { @@ -216,21 +240,33 @@ public function count(): int return $this->count; } + /** + * {@inheritdoc} + */ public function next(): void { $this->file->next(); } + /** + * {@inheritdoc} + */ public function valid(): bool { return $this->file->valid(); } + /** + * {@inheritdoc} + */ public function key(): int { return $this->file->key(); } + /** + * {@inheritdoc} + */ public function seek($pointer): void { $this->file->seek($pointer); @@ -309,13 +345,15 @@ protected function readHeaderRow($rowNumber) $this->file->seek($rowNumber); $headers = $this->file->current(); + /** BOM file encoding issue */ + $headers[0] = $this->prepareJSON($headers[0]); // Test for duplicate column headers $diff = array_diff_assoc($headers, array_unique($headers)); if (count($diff) > 0) { switch ($this->duplicateHeadersFlag) { case self::DUPLICATE_HEADERS_INCREMENT: $headers = $this->incrementHeaders($headers); - // Fall through + // Fall through case self::DUPLICATE_HEADERS_MERGE: break; default: @@ -342,15 +380,17 @@ protected function readHeaderRow($rowNumber) */ protected function incrementHeaders(array $headers) { + $counts = []; $incrementedHeaders = []; - foreach (array_count_values($headers) as $header => $count) { - if ($count > 1) { + + foreach ($headers as $header) { + + if (!isset($counts[$header])) { + $counts[$header] = 0; $incrementedHeaders[] = $header; - for ($i = 1; $i < $count; $i++) { - $incrementedHeaders[] = $header . $i; - } } else { - $incrementedHeaders[] = $header; + $counts[$header]++; + $incrementedHeaders[] = $header . $counts[$header]; } } @@ -388,4 +428,66 @@ protected function mergeDuplicates(array $line) return $values; } + + /** + * @return null + */ + public function getPadValue() + { + return $this->padValue; + } + + /** + * @param null $padValue + */ + public function setPadValue($padValue) + { + $this->padValue = $padValue; + } + + /** + * @return bool + */ + public function isTruncateHeader(): bool + { + return $this->truncateHeader; + } + + /** + * @param bool $truncateHeader + */ + public function setTruncateHeader(bool $truncateHeader) + { + $this->truncateHeader = $truncateHeader; + } + + /** + * @return bool + */ + public function isRemoveEmptyField(): bool + { + return $this->removeEmptyField; + } + + /** + * @param bool $removeEmptyField + */ + public function setRemoveEmptyField(bool $removeEmptyField) + { + $this->removeEmptyField = $removeEmptyField; + } + + function prepareJSON($input) { + //This will convert ASCII/ISO-8859-1 to UTF-8. + //Be careful with the third parameter (encoding detect list), because + //if set wrong, some input encodings will get garbled (including UTF-8!) + $imput = mb_convert_encoding($input, 'UTF-8', 'ASCII,UTF-8,ISO-8859-1'); + //Remove UTF-8 BOM if present, json_decode() does not like it. + if(substr($input, 0, 3) == pack("CCC", 0xEF, 0xBB, 0xBF)) $input = substr($input, 3); + return $input; + } + + + + } diff --git a/src/CsvWriter.php b/src/CsvWriter.php index 1eb53a6..90b1a6c 100644 --- a/src/CsvWriter.php +++ b/src/CsvWriter.php @@ -70,7 +70,7 @@ public function prepare(): void /** * {@inheritdoc} */ - public function writeItem(array $item): void + public function writeItem(mixed $item) { if ($this->prependHeaderRow && 1 == $this->row++) { $headers = array_keys($item);