141 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nur\io\csv;
 | |
| 
 | |
| use nur\A;
 | |
| 
 | |
| /**
 | |
|  * Class Csv2AssocHelper: outils pour lire un flux au format CSV
 | |
|  */
 | |
| class Csv2AssocHelper {
 | |
|   /** @var bool */
 | |
|   protected $multiSchema = false;
 | |
| 
 | |
|   function setMultiSchema(bool $multiSchema): void {
 | |
|     $this->multiSchema = $multiSchema;
 | |
|   }
 | |
| 
 | |
|   /** @var int */
 | |
|   protected $skipLines = 0;
 | |
| 
 | |
|   function setSkipLines(int $skipLines): void {
 | |
|     $this->skipLines = $skipLines;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @var ?bool faut-il analyser le premier élément du flux pour calculer la
 | |
|    * liste des clés en entrée? null signifie que la valeur est dynamique: elle
 | |
|    * vaut ($this->ppHeaders === null)
 | |
|    *
 | |
|    * Si ce champ est vrai, le premier élément est toujours consommé. cependant,
 | |
|    * la liste des champs n'est analysée que si elle n'a pas été spécifiée au
 | |
|    * préalable avec {@link setHeaders()}
 | |
|    */
 | |
|   protected $parseHeaders;
 | |
| 
 | |
|   function setParseHeaders(?bool $parseHeaders): void {
 | |
|     $this->parseHeaders = $parseHeaders;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @var array liste des champs en entrée dans l'ordre. 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 ?array mappings des colonnes vers les clés du tableau résultat */
 | |
|   protected $headerMappings;
 | |
| 
 | |
|   /**
 | |
|    * $headerMappings peut être
 | |
|    * - un tableau de la forme [include, exclude => null, dest => source]
 | |
|    * - ou une chaine de la forme "include,=exclude,dest=source"
 | |
|    * source est le nom de la colonne du flux CSV, dest est la clé dans le
 | |
|    * tableau retourné
 | |
|    *
 | |
|    * - 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 '=exclude' permettent d'exclure le champ spécifié.
 | |
|    * - 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) {
 | |
|           $dest = substr($mapping, 0, $index);
 | |
|           $source = substr($mapping, $index + 1);
 | |
|           if ($dest && $source) $headerMappings[$dest] = $source;
 | |
|           elseif ($dest) $headerMappings[$dest] = null;
 | |
|           else $headerMappings[$source] = null;
 | |
|         } else {
 | |
|           $headerMappings[] = $mapping;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     $this->headerMappings = $headerMappings;
 | |
|   }
 | |
| 
 | |
|   /** @var bool */
 | |
|   protected $shouldMapEmpty = false;
 | |
| 
 | |
|   /** @var mixed */
 | |
|   protected $mapEmpty = null;
 | |
| 
 | |
|   function setMapEmpty($mapEmpty): void {
 | |
|     $this->shouldMapEmpty = true;
 | |
|     $this->mapEmpty = $mapEmpty;
 | |
|   }
 | |
| 
 | |
|   function _parseLine(): bool {
 | |
|     if ($this->skipLines > 0) {
 | |
|       $this->skipLines--;
 | |
|       return false;
 | |
|     }
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   function checkHeaders(array $values): bool {
 | |
|     if ($this->multiSchema && count($values) === 1 && $values[0] === null) {
 | |
|       # ligne vide, changer de schéma
 | |
|       $this->headers = null;
 | |
|       return true;
 | |
|     }
 | |
|     $parseHeaders = $this->parseHeaders;
 | |
|     $setParseHeaders = $parseHeaders !== null;
 | |
|     if ($parseHeaders === null) $parseHeaders = $this->headers === null;
 | |
|     if ($parseHeaders) {
 | |
|       if ($this->headers === null) $this->headers = $values;
 | |
|       if ($setParseHeaders) $this->parseHeaders = false;
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   function mapValues(array $values): array {
 | |
|     $headers = $this->headers;
 | |
|     if ($headers === null) return $values;
 | |
|     $row = [];
 | |
|     $index = 0;
 | |
|     foreach ($headers as $header) {
 | |
|       $row[$header] = A::get($values, $index++, false);
 | |
|     }
 | |
|     return $row;
 | |
|   }
 | |
| 
 | |
|   function mapRow(array $values): array {
 | |
|     $row = $this->mapValues($values);
 | |
|     $row = ut::reader_map_keys($row, $this->headerMappings);
 | |
|     foreach ($row as &$value) {
 | |
|       if ($value === "" && $this->shouldMapEmpty) $value = $this->mapEmpty;
 | |
|     }; unset($value);
 | |
|     return $row;
 | |
|   }
 | |
| }
 |