Skip to content

Commit

Permalink
Reorganize Reader methods
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Feb 18, 2024
1 parent 51968b6 commit abdc930
Showing 1 changed file with 131 additions and 128 deletions.
259 changes: 131 additions & 128 deletions src/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,66 @@ public function addFormatter(callable $formatter): self
return $this;
}

/**
* Selects the record to be used as the CSV header.
*
* Because the header is represented as an array, to be valid
* a header MUST contain only unique string value.
*
* @param int|null $offset the header record offset
*
* @throws Exception if the offset is a negative integer
*/
public function setHeaderOffset(?int $offset): static
{
if ($offset === $this->header_offset) {
return $this;
}

if (null !== $offset && 0 > $offset) {
throw InvalidArgument::dueToInvalidHeaderOffset($offset, __METHOD__);
}

$this->header_offset = $offset;
$this->resetProperties();

return $this;
}

/**
* Enables skipping empty records.
*/
public function skipEmptyRecords(): static
{
if ($this->is_empty_records_included) {
$this->is_empty_records_included = false;
$this->nb_records = -1;
}

return $this;
}

/**
* Disables skipping empty records.
*/
public function includeEmptyRecords(): static
{
if (!$this->is_empty_records_included) {
$this->is_empty_records_included = true;
$this->nb_records = -1;
}

return $this;
}

/**
* Tells whether empty records are skipped by the instance.
*/
public function isEmptyRecordsIncluded(): bool
{
return $this->is_empty_records_included;
}

protected function resetProperties(): void
{
parent::resetProperties();
Expand Down Expand Up @@ -100,49 +160,24 @@ public function getHeader(): array
*/
protected function setHeader(int $offset): array
{
$inputBom = '';
$header = $this->seekRow($offset);
if (in_array($header, [[], [null], [false]], true)) {
throw SyntaxError::dueToHeaderNotFound($offset);
}

if (0 !== $offset) {
return $header;
if (0 === $offset) {
$inputBom = $this->getInputBOM();
$header = $this->removeBOM(
$header,
!$this->is_input_bom_included ? strlen($inputBom) : 0,
$this->enclosure
);
}

$header = $this->removeBOM(
$header,
!$this->is_input_bom_included ? strlen($this->getInputBOM()) : 0,
$this->enclosure
);

if ([''] === $header) {
throw SyntaxError::dueToHeaderNotFound($offset);
}

return $header;
}

/**
* @throws Exception
*/
private function prepareRecords(): Iterator
{
$normalized = fn ($record): bool => is_array($record) && ($this->is_empty_records_included || $record !== [null]);
$bom = '';
if (!$this->is_input_bom_included) {
$bom = $this->getInputBOM();
}

$records = $this->stripBOM(new CallbackFilterIterator($this->getDocument(), $normalized), $bom);
if (null !== $this->header_offset) {
$records = new CallbackFilterIterator($records, fn (array $record, int $offset): bool => $offset !== $this->header_offset);
}

if ($this->is_empty_records_included) {
$records = new MapIterator($records, fn (array $record): array => ([null] === $record) ? [] : $record);
}

return $records;
return match (true) {
[] === $header,
[null] === $header,
[false] === $header,
[''] === $header && 0 === $offset && '' !== $inputBom => throw SyntaxError::dueToHeaderNotFound($offset),
default => $header,
};
}

/**
Expand Down Expand Up @@ -184,7 +219,7 @@ protected function getDocument(): SplFileObject|Stream
*/
protected function removeBOM(array $record, int $bom_length, string $enclosure): array
{
if (0 === $bom_length) {
if ([] === $record || !is_string($record[0]) || 0 === $bom_length || strlen($record[0]) < $bom_length) {
return $record;
}

Expand All @@ -198,6 +233,11 @@ protected function removeBOM(array $record, int $bom_length, string $enclosure):
return $record;
}

public function fetchColumn(string|int $index = 0): Iterator
{
return ResultSet::createFromTabularDataReader($this)->fetchColumn($index);
}

/**
* @throws Exception
*/
Expand Down Expand Up @@ -412,22 +452,6 @@ public function getRecords(array $header = []): Iterator
);
}

/**
* @param array<string> $header
*
* @throws SyntaxError
*
* @return array<int|string>
*/
protected function prepareHeader($header = []): array
{
if ($header !== (array_filter($header, is_string(...)))) {
throw SyntaxError::dueToInvalidHeaderColumnNames();
}

return $this->computeHeader($header);
}

/**
* @template T of object
* @param class-string<T> $className
Expand All @@ -452,25 +476,26 @@ public function getRecordsAsObject(string $className, array $header = []): Itera
}

/**
* Returns the header to be used for iteration.
*
* @param array<int|string> $header
*
* @throws SyntaxError If the header contains non unique column name
*
* @return array<int|string>
* @throws Exception
*/
protected function computeHeader(array $header): array
protected function prepareRecords(): Iterator
{
if ([] === $header) {
$header = $this->getHeader();
$normalized = fn ($record): bool => is_array($record) && ($this->is_empty_records_included || $record !== [null]);
$bom = '';
if (!$this->is_input_bom_included) {
$bom = $this->getInputBOM();
}

return match (true) {
$header !== array_unique($header) => throw SyntaxError::dueToDuplicateHeaderColumnNames($header),
[] !== array_filter(array_keys($header), fn (string|int $value) => !is_int($value) || $value < 0) => throw new SyntaxError('The header mapper indexes should only contain positive integer or 0.'),
default => $header,
};
$records = $this->stripBOM(new CallbackFilterIterator($this->getDocument(), $normalized), $bom);
if (null !== $this->header_offset) {
$records = new CallbackFilterIterator($records, fn (array $record, int $offset): bool => $offset !== $this->header_offset);
}

if ($this->is_empty_records_included) {
$records = new MapIterator($records, fn (array $record): array => ([null] === $record) ? [] : $record);
}

return $records;
}

/**
Expand Down Expand Up @@ -503,77 +528,43 @@ protected function stripBOM(Iterator $iterator, string $bom): Iterator
}

/**
* Selects the record to be used as the CSV header.
*
* Because the header is represented as an array, to be valid
* a header MUST contain only unique string value.
* @param array<string> $header
*
* @param int|null $offset the header record offset
* @throws SyntaxError
*
* @throws Exception if the offset is a negative integer
*/
public function setHeaderOffset(?int $offset): static
{
if ($offset === $this->header_offset) {
return $this;
}

if (null !== $offset && 0 > $offset) {
throw InvalidArgument::dueToInvalidHeaderOffset($offset, __METHOD__);
}

$this->header_offset = $offset;
$this->resetProperties();

return $this;
}

/**
* Enables skipping empty records.
* @return array<int|string>
*/
public function skipEmptyRecords(): static
protected function prepareHeader($header = []): array
{
if ($this->is_empty_records_included) {
$this->is_empty_records_included = false;
$this->nb_records = -1;
if ($header !== (array_filter($header, is_string(...)))) {
throw SyntaxError::dueToInvalidHeaderColumnNames();
}

return $this;
return $this->computeHeader($header);
}

/**
* Disables skipping empty records.
* Returns the header to be used for iteration.
*
* @param array<int|string> $header
*
* @throws SyntaxError If the header contains non unique column name
*
* @return array<int|string>
*/
public function includeEmptyRecords(): static
protected function computeHeader(array $header): array
{
if (!$this->is_empty_records_included) {
$this->is_empty_records_included = true;
$this->nb_records = -1;
if ([] === $header) {
$header = $this->getHeader();
}

return $this;
}

/**
* Tells whether empty records are skipped by the instance.
*/
public function isEmptyRecordsIncluded(): bool
{
return $this->is_empty_records_included;
}

public function fetchColumn(string|int $index = 0): Iterator
{
return ResultSet::createFromTabularDataReader($this)->fetchColumn($index);
}

/** @codeCoverageIgnore */
public function fetchOne(int $nth_record = 0): array
{
return $this->nth($nth_record);
return match (true) {
$header !== array_unique($header) => throw SyntaxError::dueToDuplicateHeaderColumnNames($header),
[] !== array_filter(array_keys($header), fn (string|int $value) => !is_int($value) || $value < 0) => throw new SyntaxError('The header mapper indexes should only contain positive integer or 0.'),
default => $header,
};
}

/** @codeCoverageIgnore */
protected function combineHeader(Iterator $iterator, array $header): Iterator
{
$formatter = fn (array $record): array => array_reduce(
Expand All @@ -595,6 +586,18 @@ protected function combineHeader(Iterator $iterator, array $header): Iterator
};
}

/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
* @see Reader::nth()
* @deprecated since version 9.9.0
* @codeCoverageIgnore
*/
public function fetchOne(int $nth_record = 0): array
{
return $this->nth($nth_record);
}

/**
* DEPRECATION WARNING! This method will be removed in the next major point release.
*
Expand Down

0 comments on commit abdc930

Please sign in to comment.