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;
 | 
						|
  }
 | 
						|
}
 |