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