Skip to content

Commit

Permalink
Improve code
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Jun 9, 2017
1 parent d80f07f commit 2436daf
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 94 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -35,7 +35,7 @@ All Notable changes to `Csv` will be documented in this file
- `League\Csv\Writer::setFlushThreshold`
- `League\Csv\Writer::getFlushThreshold`
- Improve RFC4180 compliance
- `League\Csv\RFC4180Field` to format field according to RFC4180 rules
- `League\Csv\RFC4180FieldFormatter` to format field according to RFC4180 rules

### Deprecated

Expand Down
35 changes: 19 additions & 16 deletions docs/9.0/interoperability/index.md
Expand Up @@ -9,9 +9,9 @@ Depending on your operating system and on the software you are using to read/imp

- the BOM sequence used
- the encoding character
- the escape control character
- the field formatting

<p class="message-info">Out of the box, <code>League\Csv</code> connections do not alter the CSV document original encoding.</p>
<p class="message-info">Out of the box, <code>League\Csv</code> connections do not alter the CSV document presentation.</p>

In the examples below we will be using an existing CSV in ISO-8859-15 charset encoding as a starting point. The code will vary if your CSV document is in a different charset.

Expand Down Expand Up @@ -66,45 +66,48 @@ $writer->output('mycsvfile.csv');

## RFC4180 compliance

To comply to RFC4180 a CSV Document **MUST** use:
Because `League\Csv` uses PHP's csv native functions, out of the box the library does not follow [RFC4180](https://tools.ietf.org/html/rfc4180#section-2). But you can easily create a compliant CSV document using

- *\r\n* as new line sequence;
- bug fix default PHP behaviour around the escape character usage.

Using the `RFC4180Field` stream filter when fix PHP's `fputcsv` behaviour to comply to RFC4180.
- `League\Csv\RFC4180FieldFormatter` stream filter.
- `League\Csv\Writer::setNewline` method.

<p class="message-notice">The <code>Writer</code> must supports stream filter</p>

~~~php
<?php

use League\Csv\RFC4180Field;
use League\Csv\Reader;
use League\Csv\RFC4180FieldFormatter;
use League\Csv\Writer;

$writer = Writer::createFromStream(fopen('php://temp', 'r+'));
//the current CSV is ISO-8859-15 encoded with a ";" delimiter
$origin = Reader::createFromPath('/path/to/french.csv', 'r');
$origin->setDelimiter(';');

$writer = Writer::createFromPath('php://temp');
$writer->setNewline("\r\n"); //RFC4180 Line feed
$writer->setOutputBOM(Reader::BOM_UTF16_LE);
$writer->setDelimiter("\t");
$writer->addStreamFilter('convert.iconv.ISO-8859-15/UTF-16');
RFC4180Field::addTo($writer); //adding the stream filter to fix the escape character usage
RFC4180FieldFormatter::addTo($writer); //adding the stream filter to fix field formatting
$writer->insertAll($origin);
$writer->output('mycsvfile.csv'); //outputting a RFC4180 compliant CSV Document
~~~

To read a RFC4180 compliant CSV document, you should set the CSV document enclosure and escape character should have the same value when using the `League\Csv\Reader` object.
Conversely, to read a RFC4180 compliant CSV document, when using the `League\Csv\Reader` object, you only need to set its enclosure and escape control characters with the same value.

~~~php
<?php

use League\Csv\Reader;

//the current CSV is ISO-8859-15 encoded with a ";" delimiter
$origin = Reader::createFromPath('/path/to/rfc4180-compliant.csv', 'r');
$origin->setDelimiter(';');
$origin->setEnclosure('"');
$origin->setEscape('"');
$csv = Reader::createFromPath('/path/to/rfc4180-compliant.csv');
$csv->setDelimiter(';');
$csv->setEnclosure('"');
$csv->setEscape($origin->getEnclosure());

foreach ($origin as $record) {
foreach ($csv as $record) {
//do something meaningful here...
}
~~~
@@ -1,9 +1,9 @@
---
layout: default
title: Bundled record filters on insertion
title: Bundled Writer helpers
---

# Bundled insertion filters
# Bundled insertion helpers

## Column consistency checker

Expand Down Expand Up @@ -77,30 +77,30 @@ $writer->insertOne(["foo", "bébé", "jouet"]);
//all 'utf-8' caracters are now automatically encoded into 'iso-8859-15' charset
~~~

## RFC4180 escape character fix
## RFC4180 field formatter

~~~php
<?php

public static RFC4180Field::addTo(Writer $csv): void
public static RFC4180FieldFormatter::addTo(Writer $csv): void
~~~

If your CSV object supports PHP stream filters then you can register the `RFC4180Field` class as a PHP stream filter and use the library [stream filtering mechanism](/9.0/connections/filters/) to correct field formatting to comply with [RFC4180](https://tools.ietf.org/html/rfc4180#section-2).
If your CSV object supports PHP stream filters then you can register the `RFC4180FieldFormatter` class as a PHP stream filter and use the library [stream filtering mechanism](/9.0/connections/filters/) to correct field formatting to comply with [RFC4180](https://tools.ietf.org/html/rfc4180#section-2).

The `RFC4180Field::addTo` static method:
The `RFC4180FieldFormatter::addTo` static method:

- registers the `RFC4180Field` class under the following generic filtername `rfc4180.league.csv`.
- registers the `RFC4180FieldFormatter` class under the following generic filtername `rfc4180.league.csv`.
- adds the stream filter to your current `Writer` object using the object CSV control properties.

~~~php
<?php

use League\Csv\RFC4180Field;
use League\Csv\RFC4180FieldFormatter;
use League\Csv\Writer;

$writer = Writer::createFromStream(fopen('php://temp', 'r+'));
$writer->setNewline("\r\n"); //RFC4180 Line feed
RFC4180Field::addTo($writer); //adds the stream filter to the Writer object fix escape character usage
RFC4180FieldFormatter::addTo($writer); //adds the stream filter to the Writer object fix escape character usage
$writer->insertAll($data);
$writer->output('mycsvfile.csv'); //outputting a RFC4180 compliant CSV Document
~~~
6 changes: 3 additions & 3 deletions docs/_data/menu.yml
Expand Up @@ -13,19 +13,19 @@ version:
Exceptions: '/9.0/connections/exceptions/'
Inserting Records:
Writer Connection: '/9.0/writer/'
Bundled Filters: '/9.0/writer/bundled-filters/'
Bundled Helpers: '/9.0/writer/helpers/'
Selecting Records:
Reader Connection: '/9.0/reader/'
Constraint Builder: '/9.0/reader/statement/'
Result Set: '/9.0/reader/resultset/'
Interoperability:
Overview : '/9.0/interoperability/'
Converting Records:
Overview: '/9.0/converter/'
Charset Converter: '/9.0/converter/charset/'
Json Converter: '/9.0/converter/json/'
XML Converter: '/9.0/converter/xml/'
HTML Converter: '/9.0/converter/html/'
Interoperability:
Overview : '/9.0/interoperability/'
'8.0':
Getting Started:
Overview: '/8.0/'
Expand Down
5 changes: 3 additions & 2 deletions src/CharsetConverter.php
Expand Up @@ -57,8 +57,9 @@ class CharsetConverter extends php_user_filter
*/
public static function addTo(AbstractCsv $csv, string $input_encoding, string $output_encoding)
{
if (!in_array(self::STREAM_FILTERNAME, stream_get_filters())) {
stream_filter_register(self::STREAM_FILTERNAME.'.*', __CLASS__);
$filtername = self::STREAM_FILTERNAME.'.*';
if (!in_array($filtername, stream_get_filters())) {
stream_filter_register($filtername, __CLASS__);
}

$csv->addStreamFilter(sprintf(
Expand Down
2 changes: 1 addition & 1 deletion src/RFC4180Field.php → src/RFC4180FieldFormatter.php
Expand Up @@ -25,7 +25,7 @@
* @since 9.0.0
* @author Ignace Nyamagana Butera <nyamsprod@gmail.com>
*/
class RFC4180Field extends php_user_filter
class RFC4180FieldFormatter extends php_user_filter
{
use ValidatorTrait;

Expand Down
4 changes: 1 addition & 3 deletions src/Reader.php
Expand Up @@ -184,9 +184,7 @@ public function __call($method, array $arguments)
{
$whitelisted = ['fetchColumn' => 1, 'fetchPairs' => 1, 'fetchOne' => 1, 'fetchAll' => 1];
if (isset($whitelisted[$method])) {
return (new ResultSet($this->getRecords(), $this->getHeader()))
->$method(...$arguments)
;
return (new ResultSet($this->getRecords(), $this->getHeader()))->$method(...$arguments);
}

throw new BadMethodCallException(sprintf('Reader::%s does not exist', $method));
Expand Down
74 changes: 74 additions & 0 deletions tests/RFC4180FieldFormatterTest.php
@@ -0,0 +1,74 @@
<?php

namespace LeagueTest\Csv;

use League\Csv\RFC4180FieldFormatter;
use League\Csv\Writer;
use PHPUnit\Framework\TestCase;

/**
* @group writer
* @coversDefaultClass League\Csv\RFC4180FieldFormatter
*/
class RFC4180FieldFormatterTest extends TestCase
{
/**
* @see https://bugs.php.net/bug.php?id=43225
* @see https://bugs.php.net/bug.php?id=74713
*
* @covers ::addTo
* @covers ::onCreate
* @covers ::filter
*
* @dataProvider bugsProvider
*
* @param string $expected
* @param array $record
*/
public function testStreamFilter($expected, array $record)
{
$csv = Writer::createFromPath('php://temp');
RFC4180FieldFormatter::addTo($csv);
$this->assertContains(RFC4180FieldFormatter::STREAM_FILTERNAME, stream_get_filters());
$csv->setNewline("\r\n");
$csv->insertOne($record);
$this->assertSame($expected, (string) $csv);
}

public function bugsProvider()
{
return [
'bug #43225' => [
'expected' => '"a\""",bbb'."\r\n",
'record' => ['a\\"', 'bbb'],
],
'bug #74713' => [
'expected' => '"""@@"",""B"""'."\r\n",
'record' => ['"@@","B"'],
],
];
}


/**
* @covers ::onCreate
*/
public function testOnCreateFailedWithoutParams()
{
$filter = new RFC4180FieldFormatter();
$this->assertFalse($filter->onCreate());
}

/**
* @covers ::onCreate
*/
public function testOnCreateFailedWithWrongParams()
{
$filter = new RFC4180FieldFormatter();
$filter->params = [
'enclosure' => '"',
'escape' => 'foo',
];
$this->assertFalse($filter->onCreate());
}
}
59 changes: 0 additions & 59 deletions tests/RFC4180FieldTest.php

This file was deleted.

0 comments on commit 2436daf

Please sign in to comment.