cce2c14af1f55865b69286a3b4aea16e58d181a1
[yaffs-website] / vendor / symfony / serializer / Encoder / CsvEncoder.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Symfony\Component\Serializer\Encoder;
13
14 use Symfony\Component\Serializer\Exception\InvalidArgumentException;
15
16 /**
17  * Encodes CSV data.
18  *
19  * @author Kévin Dunglas <dunglas@gmail.com>
20  * @author Oliver Hoff <oliver@hofff.com>
21  */
22 class CsvEncoder implements EncoderInterface, DecoderInterface
23 {
24     const FORMAT = 'csv';
25     const DELIMITER_KEY = 'csv_delimiter';
26     const ENCLOSURE_KEY = 'csv_enclosure';
27     const ESCAPE_CHAR_KEY = 'csv_escape_char';
28     const KEY_SEPARATOR_KEY = 'csv_key_separator';
29     const HEADERS_KEY = 'csv_headers';
30
31     private $delimiter;
32     private $enclosure;
33     private $escapeChar;
34     private $keySeparator;
35
36     /**
37      * @param string $delimiter
38      * @param string $enclosure
39      * @param string $escapeChar
40      * @param string $keySeparator
41      */
42     public function __construct($delimiter = ',', $enclosure = '"', $escapeChar = '\\', $keySeparator = '.')
43     {
44         $this->delimiter = $delimiter;
45         $this->enclosure = $enclosure;
46         $this->escapeChar = $escapeChar;
47         $this->keySeparator = $keySeparator;
48     }
49
50     /**
51      * {@inheritdoc}
52      */
53     public function encode($data, $format, array $context = array())
54     {
55         $handle = fopen('php://temp,', 'w+');
56
57         if (!is_array($data)) {
58             $data = array(array($data));
59         } elseif (empty($data)) {
60             $data = array(array());
61         } else {
62             // Sequential arrays of arrays are considered as collections
63             $i = 0;
64             foreach ($data as $key => $value) {
65                 if ($i !== $key || !is_array($value)) {
66                     $data = array($data);
67                     break;
68                 }
69
70                 ++$i;
71             }
72         }
73
74         list($delimiter, $enclosure, $escapeChar, $keySeparator, $headers) = $this->getCsvOptions($context);
75
76         foreach ($data as &$value) {
77             $flattened = array();
78             $this->flatten($value, $flattened, $keySeparator);
79             $value = $flattened;
80         }
81         unset($value);
82
83         $headers = array_merge(array_values($headers), array_diff($this->extractHeaders($data), $headers));
84
85         fputcsv($handle, $headers, $delimiter, $enclosure, $escapeChar);
86
87         $headers = array_fill_keys($headers, '');
88         foreach ($data as $row) {
89             fputcsv($handle, array_replace($headers, $row), $delimiter, $enclosure, $escapeChar);
90         }
91
92         rewind($handle);
93         $value = stream_get_contents($handle);
94         fclose($handle);
95
96         return $value;
97     }
98
99     /**
100      * {@inheritdoc}
101      */
102     public function supportsEncoding($format)
103     {
104         return self::FORMAT === $format;
105     }
106
107     /**
108      * {@inheritdoc}
109      */
110     public function decode($data, $format, array $context = array())
111     {
112         $handle = fopen('php://temp', 'r+');
113         fwrite($handle, $data);
114         rewind($handle);
115
116         $headers = null;
117         $nbHeaders = 0;
118         $headerCount = array();
119         $result = array();
120
121         list($delimiter, $enclosure, $escapeChar, $keySeparator) = $this->getCsvOptions($context);
122
123         while (false !== ($cols = fgetcsv($handle, 0, $delimiter, $enclosure, $escapeChar))) {
124             $nbCols = count($cols);
125
126             if (null === $headers) {
127                 $nbHeaders = $nbCols;
128
129                 foreach ($cols as $col) {
130                     $header = explode($keySeparator, $col);
131                     $headers[] = $header;
132                     $headerCount[] = count($header);
133                 }
134
135                 continue;
136             }
137
138             $item = array();
139             for ($i = 0; ($i < $nbCols) && ($i < $nbHeaders); ++$i) {
140                 $depth = $headerCount[$i];
141                 $arr = &$item;
142                 for ($j = 0; $j < $depth; ++$j) {
143                     // Handle nested arrays
144                     if ($j === ($depth - 1)) {
145                         $arr[$headers[$i][$j]] = $cols[$i];
146
147                         continue;
148                     }
149
150                     if (!isset($arr[$headers[$i][$j]])) {
151                         $arr[$headers[$i][$j]] = array();
152                     }
153
154                     $arr = &$arr[$headers[$i][$j]];
155                 }
156             }
157
158             $result[] = $item;
159         }
160         fclose($handle);
161
162         if (empty($result) || isset($result[1])) {
163             return $result;
164         }
165
166         // If there is only one data line in the document, return it (the line), the result is not considered as a collection
167         return $result[0];
168     }
169
170     /**
171      * {@inheritdoc}
172      */
173     public function supportsDecoding($format)
174     {
175         return self::FORMAT === $format;
176     }
177
178     /**
179      * Flattens an array and generates keys including the path.
180      *
181      * @param array  $array
182      * @param array  $result
183      * @param string $keySeparator
184      * @param string $parentKey
185      */
186     private function flatten(array $array, array &$result, $keySeparator, $parentKey = '')
187     {
188         foreach ($array as $key => $value) {
189             if (is_array($value)) {
190                 $this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator);
191             } else {
192                 $result[$parentKey.$key] = $value;
193             }
194         }
195     }
196
197     private function getCsvOptions(array $context)
198     {
199         $delimiter = isset($context[self::DELIMITER_KEY]) ? $context[self::DELIMITER_KEY] : $this->delimiter;
200         $enclosure = isset($context[self::ENCLOSURE_KEY]) ? $context[self::ENCLOSURE_KEY] : $this->enclosure;
201         $escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar;
202         $keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator;
203         $headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array();
204
205         if (!is_array($headers)) {
206             throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, gettype($headers)));
207         }
208
209         return array($delimiter, $enclosure, $escapeChar, $keySeparator, $headers);
210     }
211
212     /**
213      * @return string[]
214      */
215     private function extractHeaders(array $data)
216     {
217         $headers = array();
218         $flippedHeaders = array();
219
220         foreach ($data as $row) {
221             $previousHeader = null;
222
223             foreach ($row as $header => $_) {
224                 if (isset($flippedHeaders[$header])) {
225                     $previousHeader = $header;
226                     continue;
227                 }
228
229                 if (null === $previousHeader) {
230                     $n = count($headers);
231                 } else {
232                     $n = $flippedHeaders[$previousHeader] + 1;
233
234                     for ($j = count($headers); $j > $n; --$j) {
235                         ++$flippedHeaders[$headers[$j] = $headers[$j - 1]];
236                     }
237                 }
238
239                 $headers[$n] = $header;
240                 $flippedHeaders[$header] = $n;
241                 $previousHeader = $header;
242             }
243         }
244
245         return $headers;
246     }
247 }