nur-sery/nur_src/io/csv/Assoc2CsvHelper.php

135 lines
4.2 KiB
PHP
Raw Normal View History

2023-12-03 22:10:18 +04:00
<?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;
}
}