142 lines
4.2 KiB
PHP
142 lines
4.2 KiB
PHP
<?php
|
|
namespace nur\io\csv;
|
|
|
|
use nur\A;
|
|
use nur\SL;
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
}
|