annotate vendor/symfony/serializer/Encoder/CsvEncoder.php @ 19:fa3358dc1485 tip

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