Skip to content

Commit

Permalink
Adding Writer::noEnclosure method
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Dec 10, 2024
1 parent 42a8b36 commit 5056727
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 9 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ All Notable changes to `Csv` will be documented in this file

- `XMLConverter::formatter`
- `HTMLConverter::formatter`
- `Writer::encloseNone`
- `Writer::encloseRelax`
- `Writer::noEnclosure`

### Deprecated

Expand Down
15 changes: 12 additions & 3 deletions docs/9.0/writer/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,28 @@ By default, `getFlushTreshold` returns `null`.
<p class="message-info"><code>Writer::insertAll</code> always flushes its buffer when all records are inserted, regardless of the threshold value.</p>
<p class="message-info">If set to <code>null</code> the inner flush mechanism of PHP's <code>fputcsv</code> will be used.</p>

## Force Enclosure
## Enclosure

<p class="message-info">Since version <code>9.10.0</code> you can provide control the presence of enclosure around all records.</p>
<p class="message-info">Since version <code>9.20.0</code> generating CSV without enclosure is allowed.</p>

```php
public Writer::forceEnclosure(): self
public Writer::relaxEnclosure(): self
public Writer::noEnclosure(): self
public Writer::encloseAll(): bool
public Writer::encloseRelax(): bool
public Writer::encloseNone(): bool
```

By default, the `Writer` adds enclosures only around records that requires them. For all other records no enclosure character is present,
With this feature, you can force the enclosure to be present on every record entry or CSV cell. The inclusion will respect
the presence of enclosures inside the cell content as well as the presence of PHP's escape character.
With this feature, you can:

- force the enclosure to be present on every record entry or CSV cell.
- completely remove the presence of enclosure

<p class="message-warning">When this feature is activated the <code>$escape</code> character is completely ignored (it is the same as setting it to the empty string.)</p>
<p class="message-warning">When no enclosure is used, the library DOES NOT CHECK for multiline fields.</p>

```php
<?php
Expand Down
41 changes: 35 additions & 6 deletions src/Writer.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
*/
class Writer extends AbstractCsv implements TabularDataWriter
{
protected const ENCLOSE_ALL = 1;
protected const ENCLOSE_RELAX = 2;
protected const ENCLOSE_NONE = 3;

protected const STREAM_FILTER_MODE = STREAM_FILTER_WRITE;
/** @var array<callable> callable collection to format the record before insertion. */
protected array $formatters = [];
Expand All @@ -38,7 +42,7 @@ class Writer extends AbstractCsv implements TabularDataWriter
protected string $newline = "\n";
protected int $flush_counter = 0;
protected ?int $flush_threshold = null;
protected bool $enclose_all = false;
protected int $enclose_all = 2;
/** @var array{0:array<string>,1:array<string>} */
protected array $enclosure_replace = [[], []];
/** @var Closure(array): (int|false) */
Expand All @@ -54,14 +58,15 @@ protected function resetProperties(): void
];

$this->insertRecord = fn (array $record): int|false => match ($this->enclose_all) {
true => $this->document->fwrite(implode(
self::ENCLOSE_ALL => $this->document->fwrite(implode(
$this->delimiter,
array_map(
fn ($content) => $this->enclosure.$content.$this->enclosure,
str_replace($this->enclosure_replace[0], $this->enclosure_replace[1], $record)
)
).$this->newline),
false => $this->document->fputcsv($record, $this->delimiter, $this->enclosure, $this->escape, $this->newline),
self::ENCLOSE_NONE => $this->document->fwrite(implode($this->delimiter, $record).$this->newline),
default => $this->document->fputcsv($record, $this->delimiter, $this->enclosure, $this->escape, $this->newline),
};
}

Expand All @@ -86,7 +91,23 @@ public function getFlushThreshold(): ?int
*/
public function encloseAll(): bool
{
return $this->enclose_all;
return self::ENCLOSE_ALL === $this->enclose_all;
}

/**
* Tells whether new entries will all be enclosed on writing.
*/
public function encloseNone(): bool
{
return self::ENCLOSE_NONE === $this->enclose_all;
}

/**
* Tells whether new entries will all be enclosed on writing.
*/
public function encloseRelax(): bool
{
return self::ENCLOSE_RELAX === $this->enclose_all;
}

/**
Expand Down Expand Up @@ -206,15 +227,23 @@ public function setFlushThreshold(?int $threshold): self

public function relaxEnclosure(): self
{
$this->enclose_all = false;
$this->enclose_all = self::ENCLOSE_RELAX;
$this->resetProperties();

return $this;
}

public function forceEnclosure(): self
{
$this->enclose_all = true;
$this->enclose_all = self::ENCLOSE_ALL;
$this->resetProperties();

return $this;
}

public function noEnclosure(): self
{
$this->enclose_all = self::ENCLOSE_NONE;
$this->resetProperties();

return $this;
Expand Down
42 changes: 42 additions & 0 deletions src/WriterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ public function testInsertNormalFile(): void
{
$csv = Writer::createFromPath(__DIR__.'/../test_files/foo.csv', 'a+');
$csv->insertOne(['jane', 'doe', '[email protected]']);

self::assertFalse($csv->encloseAll());
self::assertFalse($csv->encloseNone());
self::assertTrue($csv->encloseRelax());
self::assertStringContainsString('jane,doe,[email protected]', $csv->toString());
}

Expand Down Expand Up @@ -303,6 +307,44 @@ public function testEncloseAll(): void
"1996"|"Jeep"|"Grand Cherokee"|"MUST SELL!
air, moon roof, loaded"|"4799.00"
CSV;

self::assertTrue($csv->encloseAll());
self::assertFalse($csv->encloseNone());
self::assertFalse($csv->encloseRelax());
self::assertStringContainsString($expected, $csv->toString());
}

public function testEncloseNothing(): void
{
/**
* @see https://en.wikipedia.org/wiki/Comma-separated_values#Example
*/
$records = [
['Year', 'Make', 'Model', 'Description', 'Price'],
[1997, 'Ford', 'E350', 'ac,abs,moon', '3000.00'],
[1999, 'Chevy', 'Venture "Extended Edition"', null, '4900.00'],
[1999, 'Chevy', 'Venture "Extended Edition, Very Large"', null, '5000.00'],
[1996, 'Jeep', 'Grand Cherokee', 'MUST SELL!
air, moon roof, loaded', '4799.00'],
];

$csv = Writer::createFromString();
$csv->setDelimiter('|');
$csv->noEnclosure();
$csv->insertAll($records);

$expected = <<<CSV
Year|Make|Model|Description|Price
1997|Ford|E350|ac,abs,moon|3000.00
1999|Chevy|Venture "Extended Edition"||4900.00
1999|Chevy|Venture "Extended Edition, Very Large"||5000.00
1996|Jeep|Grand Cherokee|MUST SELL!
air, moon roof, loaded|4799.00
CSV;

self::assertFalse($csv->encloseAll());
self::assertTrue($csv->encloseNone());
self::assertFalse($csv->encloseRelax());
self::assertStringContainsString($expected, $csv->toString());
}
}

0 comments on commit 5056727

Please sign in to comment.