Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / symfony / serializer / Encoder / CsvEncoder.php
index f30d08f941da7cf69850f69296116d9902207b1e..cce2c14af1f55865b69286a3b4aea16e58d181a1 100644 (file)
@@ -17,10 +17,16 @@ use Symfony\Component\Serializer\Exception\InvalidArgumentException;
  * Encodes CSV data.
  *
  * @author Kévin Dunglas <dunglas@gmail.com>
+ * @author Oliver Hoff <oliver@hofff.com>
  */
 class CsvEncoder implements EncoderInterface, DecoderInterface
 {
     const FORMAT = 'csv';
+    const DELIMITER_KEY = 'csv_delimiter';
+    const ENCLOSURE_KEY = 'csv_enclosure';
+    const ESCAPE_CHAR_KEY = 'csv_escape_char';
+    const KEY_SEPARATOR_KEY = 'csv_key_separator';
+    const HEADERS_KEY = 'csv_headers';
 
     private $delimiter;
     private $enclosure;
@@ -65,19 +71,22 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
             }
         }
 
-        $headers = null;
-        foreach ($data as $value) {
-            $result = array();
-            $this->flatten($value, $result);
+        list($delimiter, $enclosure, $escapeChar, $keySeparator, $headers) = $this->getCsvOptions($context);
 
-            if (null === $headers) {
-                $headers = array_keys($result);
-                fputcsv($handle, $headers, $this->delimiter, $this->enclosure, $this->escapeChar);
-            } elseif (array_keys($result) !== $headers) {
-                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.');
-            }
+        foreach ($data as &$value) {
+            $flattened = array();
+            $this->flatten($value, $flattened, $keySeparator);
+            $value = $flattened;
+        }
+        unset($value);
 
-            fputcsv($handle, $result, $this->delimiter, $this->enclosure, $this->escapeChar);
+        $headers = array_merge(array_values($headers), array_diff($this->extractHeaders($data), $headers));
+
+        fputcsv($handle, $headers, $delimiter, $enclosure, $escapeChar);
+
+        $headers = array_fill_keys($headers, '');
+        foreach ($data as $row) {
+            fputcsv($handle, array_replace($headers, $row), $delimiter, $enclosure, $escapeChar);
         }
 
         rewind($handle);
@@ -106,16 +115,21 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
 
         $headers = null;
         $nbHeaders = 0;
+        $headerCount = array();
         $result = array();
 
-        while (false !== ($cols = fgetcsv($handle, 0, $this->delimiter, $this->enclosure, $this->escapeChar))) {
+        list($delimiter, $enclosure, $escapeChar, $keySeparator) = $this->getCsvOptions($context);
+
+        while (false !== ($cols = fgetcsv($handle, 0, $delimiter, $enclosure, $escapeChar))) {
             $nbCols = count($cols);
 
             if (null === $headers) {
                 $nbHeaders = $nbCols;
 
                 foreach ($cols as $col) {
-                    $headers[] = explode($this->keySeparator, $col);
+                    $header = explode($keySeparator, $col);
+                    $headers[] = $header;
+                    $headerCount[] = count($header);
                 }
 
                 continue;
@@ -123,7 +137,7 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
 
             $item = array();
             for ($i = 0; ($i < $nbCols) && ($i < $nbHeaders); ++$i) {
-                $depth = count($headers[$i]);
+                $depth = $headerCount[$i];
                 $arr = &$item;
                 for ($j = 0; $j < $depth; ++$j) {
                     // Handle nested arrays
@@ -166,16 +180,68 @@ class CsvEncoder implements EncoderInterface, DecoderInterface
      *
      * @param array  $array
      * @param array  $result
+     * @param string $keySeparator
      * @param string $parentKey
      */
-    private function flatten(array $array, array &$result, $parentKey = '')
+    private function flatten(array $array, array &$result, $keySeparator, $parentKey = '')
     {
         foreach ($array as $key => $value) {
             if (is_array($value)) {
-                $this->flatten($value, $result, $parentKey.$key.$this->keySeparator);
+                $this->flatten($value, $result, $keySeparator, $parentKey.$key.$keySeparator);
             } else {
                 $result[$parentKey.$key] = $value;
             }
         }
     }
+
+    private function getCsvOptions(array $context)
+    {
+        $delimiter = isset($context[self::DELIMITER_KEY]) ? $context[self::DELIMITER_KEY] : $this->delimiter;
+        $enclosure = isset($context[self::ENCLOSURE_KEY]) ? $context[self::ENCLOSURE_KEY] : $this->enclosure;
+        $escapeChar = isset($context[self::ESCAPE_CHAR_KEY]) ? $context[self::ESCAPE_CHAR_KEY] : $this->escapeChar;
+        $keySeparator = isset($context[self::KEY_SEPARATOR_KEY]) ? $context[self::KEY_SEPARATOR_KEY] : $this->keySeparator;
+        $headers = isset($context[self::HEADERS_KEY]) ? $context[self::HEADERS_KEY] : array();
+
+        if (!is_array($headers)) {
+            throw new InvalidArgumentException(sprintf('The "%s" context variable must be an array or null, given "%s".', self::HEADERS_KEY, gettype($headers)));
+        }
+
+        return array($delimiter, $enclosure, $escapeChar, $keySeparator, $headers);
+    }
+
+    /**
+     * @return string[]
+     */
+    private function extractHeaders(array $data)
+    {
+        $headers = array();
+        $flippedHeaders = array();
+
+        foreach ($data as $row) {
+            $previousHeader = null;
+
+            foreach ($row as $header => $_) {
+                if (isset($flippedHeaders[$header])) {
+                    $previousHeader = $header;
+                    continue;
+                }
+
+                if (null === $previousHeader) {
+                    $n = count($headers);
+                } else {
+                    $n = $flippedHeaders[$previousHeader] + 1;
+
+                    for ($j = count($headers); $j > $n; --$j) {
+                        ++$flippedHeaders[$headers[$j] = $headers[$j - 1]];
+                    }
+                }
+
+                $headers[$n] = $header;
+                $flippedHeaders[$header] = $n;
+                $previousHeader = $header;
+            }
+        }
+
+        return $headers;
+    }
 }