Skip to content

Commit

Permalink
Adding a Converter class
Browse files Browse the repository at this point in the history
  • Loading branch information
nyamsprod committed Mar 16, 2017
1 parent c4412d9 commit af30678
Show file tree
Hide file tree
Showing 13 changed files with 649 additions and 647 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ All Notable changes to `Csv` will be documented in this file
- `League\Csv\BOM`
- `League\Csv\Statement`
- `League\Csv\RecordSet`
- `League\Csv\XMLEncoder`
- `League\Csv\Converter`
- `League\Csv\is_bom` function
- `League\Csv\bom_match` function
- `League\Csv\Exception\CsvException`
Expand Down
149 changes: 149 additions & 0 deletions docs/9.0/reader/converter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
layout: default
title: Records conversion in popular formats
---

# Records conversion

The `League\Csv\Converter` is a class which convert any a collection of CSV records into a specified format. This collection can be:

- a [Reader](/9.0/reader/) object
- a [RecordSet](/9.0/reader/records/) object or any results from its extracting methods;
- any `Traversable` or `array` which represents a collection of records;

<p class="message-warning">A <code>Writer</code> object can not be converted.</p>

## Convertion settings

~~~php
<?php
public Converter::inputEncoding(string $charset): self
public Converter::xmlElements(string $root_name = 'csv', string $record_name = 'row', string $item_name = 'cell'): self
public Converter::xmlAttributes(string $offset_name = '', string $column_name = ''): self
public Converter::jsonOptions(bool $preserve_offset, int options = 0, int $depth = 512): self
~~~

Before convertion your records, you will want to configure your converter.

When building a `Converter` object, the methods do not need to be called in any particular order, and may be called multiple times. Because the `Converter` object is immutable, each time its setter methods are called they will return a new `Converter` object without modifying the current `Converter` object.

### Records Input Encoding

<p class="message-warning">Out of the box, <code>League\Csv\Converter</code> assumes that your are submitting <code>UTF-8</code> encoded records. If your data is not <code>UTF-8</code> encoded some unexpected results or errors may be thrown when trying to convert your data.</p>

If the submitted records comes from a `Reader` object which supports PHP stream filters then it's recommended to use the library [stream filtering mechanism](/9.0/connections/filters/) to convert your data. Otherwise you can fallback to using the `Converter::inputEncoding` method.


~~~php
<?php

use League\Csv\Converter;
use League\Csv\Reader;

$csv = new Reader::createFromFileObject(new SplFileObject('/path/to/french.csv', 'r'));
$converter = new Converter();
if (!$csv->supportsStreamFilter()) {
$converter = $encoder->inputEncoding('iso-8859-15');
}

$xml = $converter->toXML($csv);
~~~

### XML and HTML element and attribute names

~~~php
<?php

public Converter::xmlElements(string $root_name, string $record_name, string $item_name): self
public Converter::xmlAttributes(string $offset_name = '', string $column_name = ''): self
~~~

If you want to convert your CSV document into a `DOMDocument` you will need to set the XML element names using `Converter::xmlElements`:

- `$root_name` sets the XML root name which defaults to `csv`;
- `$record_name` sets the XML node element representing a CSV row which defaults to `row`;
- `$item_name` sets the XML node element for each CSV cell which defaults value is `cell`;

<p class="message-warning"><code>Converter::xmlElements</code> settings are not taken into account when converting your CSV into an HTML table.</p>

You can optionnaly sets the following XML element attributes using `Converter::xmlAttributes`.

- `$offset_name`, sets the XML/HTML node element attribute for each CSV record offset if a header was prodived which defaults value is `name`;
- `$column_name`, sets the XML/HTML node element attribute for each CSV cell name;

These attributes will only be used if their value differs from the empty string.

### Json options

~~~php
<?php
public Converter::jsonOptions(bool $preserve_offset, int options = 0, int $depth = 512): self
~~~

If you want to convert your CSV document into a json string you want to set the following json options:

- `$preserve_offset`, to preserve the CSV record offset;
- `$options`, to set `json_encode` optional `$options` argument;
- `$depth`, to set `json_encode` optional `$depth` argument;

## Convertion methods

~~~php
<?php
public Converter::toJson(iterable $records: string
public Converter::toXML(iterable $records): DOMDocument
public Converter::toHTML(iterable $records): string
~~~

All convertion methods only accepts an `iterable` which represents the records collection.

~~~php
<?php

use League\Csv\Converter;
use League\Csv\Statement;
use League\Csv\Reader;

$csv = Reader::createFromPath('/path/to/prenoms.csv')
->setDelimiter(';')
->setHeaderOffset(0)
->addStreamFilter('convert.iconv.ISO-8859-1/UTF-8')
;

$stmt = (new Statement())
->where(function (array $record) {
return 'Anaïs' === $record['prenoms'];
})
->offset(0)
->limit(2)
;

$converter = (new Converter())
->xmlElements('csv', 'record', 'field')
->xmlAttributes('offset', 'name')
;

$records = $stmt->process($csv);
$records->preserveOffset(true);

$dom = $converter->toXML($records);
$dom->formatOutput = true;

echo '<pre>', PHP_EOL;
echo htmlentities($dom->saveXML());
// <?xml version="1.0" encoding="UTF-8"?>
// <csv>
// <record offset="71">
// <field name="prenoms">Anaïs</field>
// <field name="nombre">137</field>
// <field name="sexe">F</field>
// <field name="annee">2004</field>
// </record>
// <record offset="1099">
// <field name="prenoms">Anaïs</field>
// <field name="nombre">124</field>
// <field name="sexe">F</field>
// <field name="annee">2005</field>
// </record>
// </csv>
~~~
125 changes: 1 addition & 124 deletions docs/9.0/reader/records.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,13 @@ title: Accessing Records from a CSV document
<?php
public RecordSet::count(): int
public RecordSet::isOffsetPreserved(): bool
public RecordSet::getConversionInputEncoding(): string
public RecordSet::getColumnNames(): array
public RecordSet::getIterator(): Generator
public RecordSet::fetchAll(): array
public RecordSet::fetchOne(int $offset = 0): array
public RecordSet::fetchPairs(string|int $offsetIndex = 0, string|int $valueIndex = 1): Generator
public RecordSet::fetchColumn(string|int $columnIndex = 0): Generator
public RecordSet::jsonSerialize(): array
public RecordSet::toXML(
string $root_name = 'csv',
string $row_name = 'row',
string $cell_name = 'cell',
string $column_attr = 'name',
string $offset_attr = 'offset',
): DOMDocument
public RecordSet::toHTML(
string $class_attr = 'table-csv-data',
string $column_attr = 'title',
string $offset_attr = 'data-record-offset'
): string
public RecordSet::preserveOffset(bool $status): RecordSet
public RecordSet::setConversionInputEncoding(string $charset): RecordSet


~~~

The `League\Csv\RecordSet` is a class which manipulates a collection of CSV document records. This object is returned from [Reader::select](/9.0/reader/#selecting-csv-records) or [Statement::process](/9.0/reader/statement/#apply-the-constraints-to-a-csv-document) execution.
Expand Down Expand Up @@ -116,9 +99,7 @@ $records->getColumnNames(); // returns ['firstname', 'lastname', 'email'];
<?php

public RecordSet::isOffsetPreserved(): bool
public RecordSet::getConversionInputEncoding(): string
public RecordSet::preserveOffset(bool $status): RecordSet
public RecordSet::setConversionInputEncoding(string $charset): RecordSet
~~~

`RecordSet::preserveOffset` indicates if the `RecordSet` must keep the original CSV document records offset or can re-index them. When the `$status` is `true`, the original CSV document record offset will be preserve and output in methods where it makes sense.
Expand All @@ -127,14 +108,6 @@ At any given time you can tell whether the CSV document offset is kept by callin

<p class="message-notice">By default, the <code>RecordSet</code> object does not preserve the original offset.</p>

`RecordSet::setConversionInputEncoding` performs a charset conversion so that the records are all in `UTF-8` prior to converting the collection into XML or JSON. Without this step, errors may occurs while converting your data.

At any given time you can retrive the current conversion input encoding charset by calling `RecordSet::getConversionInputEncoding`.

<p class="message-notice">By default, the <code>RecordSet</code> object expect your records to be in <code>UTF-8</code>.</p>

<p class="message-info"><strong>Tips:</strong> if the <code>Reader</code> supports stream filtering, use <a href="/9.0/connections/filters/">Reader::addStreamFilter</a> instead to perform this charset conversion.</p>

## Iterating over the collection

~~~php
Expand Down Expand Up @@ -400,100 +373,4 @@ foreach ((new Statement())->process($reader)->fetchPairs() as $firstname => $las
- If no `$offsetIndex` is provided it default to `0`;
- If no `$valueIndex` is provided it default to `1`;
- If no cell is found corresponding to `$offsetIndex` the row is skipped;
- If no cell is found corresponding to `$valueIndex` the `null` value is used;

## Converting the collection into JSON/XML/HTML

To convert your the `RecordSet` collection into JSON, XML and HTML formats your records collection must be `UTF-8` encoded.

- If your `Reader` object supports PHP stream filters then it's recommended to use the library stream filtering mechanism to convert your data.

- Otherwise you can fallback to using the `RecordSet::setConversionInputEncoding` method.

<p class="message-warning">If your CSV is not <code>UTF-8</code> encoded some unexpected results or errors may be thrown when trying to convert your data.</p>

~~~php
<?php

public RecordSet::jsonSerialize(): array

public RecordSet::toXML(
string $root_name = 'csv',
string $row_name = 'row',
string $cell_name = 'cell',
string $column_attr = 'name',
string $offset_attr = 'offset',
): DOMDocument

public RecordSet::toHTML(
string $class_attr = 'table-csv-data',
string $column_attr = 'title',
string $offset_attr = 'data-record-offset'
): string
~~~

- `RecordSet` implements the `JsonSerializable` interface. As such you can use the `json_encode` function directly on the instantiated object.
- `RecordSet::toXML` converts the `RecordSet` into a `DomDocument` object.
- `RecordSet::toHTML` converts the `RecordSet` into an HTML table.

The `RecordSet::toXML` method accepts five (5) optionals arguments to help you customize your XML tree:

- `$root_name`, the XML root name which defaults to `csv`;
- `$row_name`, the XML node element representing a CSV row which defaults to `row`;
- `$cell_name`, the XML node element for each CSV cell which defaults value is `cell`;
- `$column_attr`, the XML node element attribute for each CSV cell if a header was prodived which defaults value is `name`;
- `$offset_attr`, the XML node element attribute for each CSV record if the offset must be preserved which defaults value is `offset`;


The `RecordSet::toHTML` method accepts three (3) optional arguments:

- `$class_attr` to help you customize the table rendering. By defaut the classname given to the table is `table-csv-data`.
- `$column_attr`, the attribute attach to each `<td>` to indicate the column name if it is provided. The default value is `title`;
- `$offset_attr`, the attribute attach to each `<tr>` to indicate the CSV document original offset index. The default value is `data-record-offset`

<p class="message-notice">The <code>$column_attr</code> argument from <code>RecordSet::toXML</code> and <code>RecordSet::toHTML</code> will only appear if the <code>RecordSet::getColumnNames</code> returns an non empty <code>array</code>.</p>

<p class="message-notice">The <code>$offset_attr</code> argument from <code>RecordSet::toXML</code> and <code>RecordSet::toHTML</code> will only appear in the converted document if the <code>RecordSet::preserveOffset</code> status is <code>true</code>.</p>

~~~php
<?php

use League\Csv\Statement;
use League\Csv\Reader;

$csv = Reader::createFromPath('/path/to/prenoms.csv')
->setDelimiter(';')
->setHeaderOffset(0)
->addStreamFilter('convert.iconv.ISO-8859-1/UTF-8')
;

$stmt = (new Statement())
->where(function (array $record) {
return 'Anaïs' === $record['prenoms'];
})
->offset(0)
->limit(2)
;

$records = $stmt->process($csv);
$records->preserveOffset(true);
$dom = $records->toXML('csv', 'record', 'field');
$dom->formatOutput = true;
echo '<pre>', PHP_EOL;
echo htmlentities($dom->saveXML());
// <?xml version="1.0" encoding="UTF-8"?>
// <csv>
// <record offset="71">
// <field name="prenoms">Anaïs</field>
// <field name="nombre">137</field>
// <field name="sexe">F</field>
// <field name="annee">2004</field>
// </record>
// <record offset="1099">
// <field name="prenoms">Anaïs</field>
// <field name="nombre">124</field>
// <field name="sexe">F</field>
// <field name="annee">2005</field>
// </record>
// </csv>
~~~
- If no cell is found corresponding to `$valueIndex` the `null` value is used;
1 change: 1 addition & 0 deletions docs/_data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
Reader Connection: '/9.0/reader/'
Constraint Builder: '/9.0/reader/statement/'
Records Collection: '/9.0/reader/records/'
Records Convertion: '/9.0/reader/converter/'
Upgrading Guide:
Introduction: '/upgrading/'
Changelog: '/changelog/'
Expand Down
9 changes: 4 additions & 5 deletions docs/upgrading/upgrading-9.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,13 +454,12 @@ $csv = null;

### Conversion methods

All convertion methods are no longer attached to the `Reader` or the `Writer` classes you need a `RecordSet` object to have access to them. The following methods are removed
All convertion methods are no longer attached to the `Reader` or the `Writer` classes you need a [Converter](/9.0/reader/converter/) object to have access to them. The following methods are removed

- `AbstractCsv::jsonSerialize`
- `AbstractCsv::toHTML`
- `AbstractCsv::toXML`


Before:

~~~php
Expand All @@ -469,19 +468,19 @@ Before:
use League\Csv\Writer;

$csv = Writer::createFromPath('/path/to/file.csv');
$records = $csv->toXML();
$dom = $csv->toXML(); //$dom is a DOMDocument
~~~

After:

~~~php
<?php

use League\Csv\Converter;
use League\Csv\Reader;
use League\Csv\Statement;

$csv = Reader::createFromPath('/path/to/file.csv');
$records = (new Statement())->process($csv)->toXML();
$dom = (new Converter())->toXML($csv); //$dom is a DOMDocument
~~~

### Switching between connections
Expand Down
Loading

0 comments on commit af30678

Please sign in to comment.