135 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			135 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nur\io\csv;
 | |
| 
 | |
| use nur\A;
 | |
| use nur\str;
 | |
| 
 | |
| /**
 | |
|  * Class Assoc2CsvHelper: outils pour écrire un flux au format CSV
 | |
|  */
 | |
| class Assoc2CsvHelper {
 | |
|   private static function is_different(array $h1, array $h2): bool {
 | |
|     sort($h1);
 | |
|     sort($h2);
 | |
|     return $h1 != $h2;
 | |
|   }
 | |
| 
 | |
|   private function computeHeaders(array $row): array {
 | |
|     return array_keys($row);
 | |
|   }
 | |
| 
 | |
|   /** @var bool les flux avec plusieurs schémas sont-ils supportés? */
 | |
|   protected $multiSchema = false;
 | |
| 
 | |
|   function setMultiSchema(bool $multiSchema): void {
 | |
|     $this->multiSchema = $multiSchema;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @var array liste et ordre des champs en sortie. si cette valeur n'est pas
 | |
|    * spécifiée, elle est calculée à partir du premier élément du flux.
 | |
|    */
 | |
|   protected $headers;
 | |
| 
 | |
|   function setHeaders(array $headers): void {
 | |
|     $this->headers = $headers;
 | |
|   }
 | |
| 
 | |
|   /** @var bool faut-il afficher les en-têtes en sortie? */
 | |
|   protected $outputHeaders = true;
 | |
| 
 | |
|   function setOutputHeaders(bool $outputHeaders): void {
 | |
|     $this->outputHeaders = $outputHeaders;
 | |
|   }
 | |
| 
 | |
|   /** @var ?array mappings des clés du tableau vers les colonne en sortie */
 | |
|   protected $headerMappings;
 | |
| 
 | |
|   /**
 | |
|    * $headerMappings peut être
 | |
|    * - un tableau de la forme [include, add => null, source => dest]
 | |
|    * - ou une chaine de la forme "include,=add,source=dest"
 | |
|    * source est le nom de la clé dans le tableau en entrée, dest est le nom de
 | |
|    * la colonne dans le flux CSV en sortie
 | |
|    *
 | |
|    * - les éléments 'include' permettent d'inclure le champ spécifié. si aucun
 | |
|    * champ include n'est spécifié, *tous* les champs sont inclus (sauf ceux qui
 | |
|    * sont exclus, bien entendu)
 | |
|    * - les éléments '=add' permettent d'ajouter dans la sortie une colonne avec
 | |
|    * une valeur vide
 | |
|    * - les éléments 'dest=source' permettent de renommer les champs: le champ
 | |
|    * source dans le flux CSV devient le champ dest dans le tableau associatif
 | |
|    */
 | |
|   function setHeaderMappings($headerMappings): void {
 | |
|     if ($headerMappings !== null && !is_array($headerMappings)) {
 | |
|       $mappings = explode(",", strval($headerMappings));
 | |
|       $headerMappings = [];
 | |
|       foreach ($mappings as $mapping) {
 | |
|         if (($index = strpos($mapping, "=")) !== false) {
 | |
|           $source = substr($mapping, 0, $index);
 | |
|           $dest = substr($mapping, $index + 1);
 | |
|           if ($source && $dest) $headerMappings[$source] = $dest;
 | |
|           elseif ($source) $headerMappings[$source] = null;
 | |
|           else $headerMappings[$dest] = null;
 | |
|         } else {
 | |
|           $headerMappings[] = $mapping;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     $this->headerMappings = $headerMappings;
 | |
|   }
 | |
| 
 | |
|   function checkHeaders(?array $row): array {
 | |
|     $skipLine = false;
 | |
|     if ($row === null) {
 | |
|       $outputHeaders = $this->outputHeaders && $this->headers !== null;
 | |
|       return [$skipLine, $outputHeaders, $this->headers];
 | |
|     }
 | |
|     $prevHeaders = $this->headers;
 | |
|     if ($this->multiSchema) {
 | |
|       # vérifier si le schéma a changé
 | |
|       $headers = $this->computeHeaders($row);
 | |
|       if ($prevHeaders === null) $prevHeaders = $headers;
 | |
|       if (self::is_different($prevHeaders, $headers)) {
 | |
|         $skipLine = true;
 | |
|         $this->outputHeaders = true;
 | |
|       } else {
 | |
|         $headers = $prevHeaders;
 | |
|       }
 | |
|     } else {
 | |
|       $headers = $prevHeaders;
 | |
|       if ($headers === null) $headers = $this->computeHeaders($row);
 | |
|     }
 | |
|     return [$skipLine, $this->outputHeaders, $headers];
 | |
|   }
 | |
| 
 | |
|   function resetOutputHeaders(array $headers): void {
 | |
|     $this->headers = $headers;
 | |
|     $this->outputHeaders = false;
 | |
|   }
 | |
| 
 | |
|   function cookHeaders(array $headers): array {
 | |
|     return ut::writer_map_headers($headers, $this->headerMappings);
 | |
|   }
 | |
| 
 | |
|   function cookValues(array $headers, array $row): array {
 | |
|     $values = [];
 | |
|     foreach ($headers as $header) {
 | |
|       $values[$header] = A::get($row, $header);
 | |
|     }
 | |
|     $values = ut::writer_map_keys($values, $this->headerMappings);
 | |
|     return array_values($values);
 | |
|   }
 | |
| 
 | |
|   function getLine(array $values, string $flavour, bool $stripNl=true): string {
 | |
|     [$separator, $enclosure, $escape] = flavours::get_params($flavour);
 | |
|     $tmpf = fopen("php://memory", "w+b");
 | |
|     fputcsv($tmpf, $values, $separator, $enclosure, $escape);
 | |
|     rewind($tmpf);
 | |
|     $line = stream_get_contents($tmpf);
 | |
|     if ($stripNl) $line = str::strip_nl($line);
 | |
|     fclose($tmpf);
 | |
|     return $line;
 | |
|   }
 | |
| }
 |