Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\Serializer\Encoder; Chris@0: Chris@0: use Symfony\Component\Serializer\Exception\InvalidArgumentException; Chris@0: Chris@0: /** Chris@0: * Encodes CSV data. Chris@0: * Chris@0: * @author Kévin Dunglas Chris@0: */ Chris@0: class CsvEncoder implements EncoderInterface, DecoderInterface Chris@0: { Chris@0: const FORMAT = 'csv'; Chris@0: Chris@0: private $delimiter; Chris@0: private $enclosure; Chris@0: private $escapeChar; Chris@0: private $keySeparator; Chris@0: Chris@0: /** Chris@0: * @param string $delimiter Chris@0: * @param string $enclosure Chris@0: * @param string $escapeChar Chris@0: * @param string $keySeparator Chris@0: */ Chris@0: public function __construct($delimiter = ',', $enclosure = '"', $escapeChar = '\\', $keySeparator = '.') Chris@0: { Chris@0: $this->delimiter = $delimiter; Chris@0: $this->enclosure = $enclosure; Chris@0: $this->escapeChar = $escapeChar; Chris@0: $this->keySeparator = $keySeparator; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function encode($data, $format, array $context = array()) Chris@0: { Chris@0: $handle = fopen('php://temp,', 'w+'); Chris@0: Chris@0: if (!is_array($data)) { Chris@0: $data = array(array($data)); Chris@0: } elseif (empty($data)) { Chris@0: $data = array(array()); Chris@0: } else { Chris@0: // Sequential arrays of arrays are considered as collections Chris@0: $i = 0; Chris@0: foreach ($data as $key => $value) { Chris@0: if ($i !== $key || !is_array($value)) { Chris@0: $data = array($data); Chris@0: break; Chris@0: } Chris@0: Chris@0: ++$i; Chris@0: } Chris@0: } Chris@0: Chris@0: $headers = null; Chris@0: foreach ($data as $value) { Chris@0: $result = array(); Chris@0: $this->flatten($value, $result); Chris@0: Chris@0: if (null === $headers) { Chris@0: $headers = array_keys($result); Chris@0: fputcsv($handle, $headers, $this->delimiter, $this->enclosure, $this->escapeChar); Chris@0: } elseif (array_keys($result) !== $headers) { Chris@0: throw new InvalidArgumentException('To use the CSV encoder, each line in the data array must have the same structure. You may want to use a custom normalizer class to normalize the data format before passing it to the CSV encoder.'); Chris@0: } Chris@0: Chris@0: fputcsv($handle, $result, $this->delimiter, $this->enclosure, $this->escapeChar); Chris@0: } Chris@0: Chris@0: rewind($handle); Chris@0: $value = stream_get_contents($handle); Chris@0: fclose($handle); Chris@0: Chris@0: return $value; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function supportsEncoding($format) Chris@0: { Chris@0: return self::FORMAT === $format; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function decode($data, $format, array $context = array()) Chris@0: { Chris@0: $handle = fopen('php://temp', 'r+'); Chris@0: fwrite($handle, $data); Chris@0: rewind($handle); Chris@0: Chris@0: $headers = null; Chris@0: $nbHeaders = 0; Chris@0: $result = array(); Chris@0: Chris@0: while (false !== ($cols = fgetcsv($handle, 0, $this->delimiter, $this->enclosure, $this->escapeChar))) { Chris@0: $nbCols = count($cols); Chris@0: Chris@0: if (null === $headers) { Chris@0: $nbHeaders = $nbCols; Chris@0: Chris@0: foreach ($cols as $col) { Chris@0: $headers[] = explode($this->keySeparator, $col); Chris@0: } Chris@0: Chris@0: continue; Chris@0: } Chris@0: Chris@0: $item = array(); Chris@0: for ($i = 0; ($i < $nbCols) && ($i < $nbHeaders); ++$i) { Chris@0: $depth = count($headers[$i]); Chris@0: $arr = &$item; Chris@0: for ($j = 0; $j < $depth; ++$j) { Chris@0: // Handle nested arrays Chris@0: if ($j === ($depth - 1)) { Chris@0: $arr[$headers[$i][$j]] = $cols[$i]; Chris@0: Chris@0: continue; Chris@0: } Chris@0: Chris@0: if (!isset($arr[$headers[$i][$j]])) { Chris@0: $arr[$headers[$i][$j]] = array(); Chris@0: } Chris@0: Chris@0: $arr = &$arr[$headers[$i][$j]]; Chris@0: } Chris@0: } Chris@0: Chris@0: $result[] = $item; Chris@0: } Chris@0: fclose($handle); Chris@0: Chris@0: if (empty($result) || isset($result[1])) { Chris@0: return $result; Chris@0: } Chris@0: Chris@0: // If there is only one data line in the document, return it (the line), the result is not considered as a collection Chris@0: return $result[0]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function supportsDecoding($format) Chris@0: { Chris@0: return self::FORMAT === $format; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Flattens an array and generates keys including the path. Chris@0: * Chris@0: * @param array $array Chris@0: * @param array $result Chris@0: * @param string $parentKey Chris@0: */ Chris@0: private function flatten(array $array, array &$result, $parentKey = '') Chris@0: { Chris@0: foreach ($array as $key => $value) { Chris@0: if (is_array($value)) { Chris@0: $this->flatten($value, $result, $parentKey.$key.$this->keySeparator); Chris@0: } else { Chris@0: $result[$parentKey.$key] = $value; Chris@0: } Chris@0: } Chris@0: } Chris@0: }