modifs.mineures sans commentaires
This commit is contained in:
parent
d612253c1d
commit
3461d2eec8
|
@ -13,7 +13,7 @@ class FileReader extends _File {
|
|||
/** @var bool */
|
||||
protected $ignoreBom;
|
||||
|
||||
function __construct($input, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null, ?bool $ignoreBom=null) {
|
||||
function __construct($input, ?string $mode=null, ?bool $throwOnError=null, ?bool $allowLocking=null, ?bool $ignoreBom=null) {
|
||||
if ($ignoreBom === null) $ignoreBom = static::IGNORE_BOM;
|
||||
$this->ignoreBom = $ignoreBom;
|
||||
if ($input === null) {
|
||||
|
|
|
@ -10,7 +10,7 @@ use nur\sery\os\sh;
|
|||
class FileWriter extends _File {
|
||||
const DEFAULT_MODE = "a+b";
|
||||
|
||||
function __construct($output, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||
function __construct($output, ?string $mode=null, ?bool $throwOnError=null, ?bool $allowLocking=null) {
|
||||
if ($output === null) {
|
||||
$fd = STDOUT;
|
||||
$close = false;
|
||||
|
|
|
@ -10,7 +10,7 @@ class MemoryStream extends Stream {
|
|||
return fopen("php://memory", "w+b");
|
||||
}
|
||||
|
||||
function __construct(bool $throwOnError=true) {
|
||||
function __construct(?bool $throwOnError=null) {
|
||||
parent::__construct(self::memory_fd(), true, $throwOnError);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ class SharedFile extends FileWriter {
|
|||
|
||||
const DEFAULT_MODE = "c+b";
|
||||
|
||||
function __construct($file, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||
function __construct($file, ?string $mode=null, ?bool $throwOnError=null, ?bool $allowLocking=null) {
|
||||
if ($file === null) throw ValueException::null("file");
|
||||
parent::__construct($file, $mode, $throwOnError, $allowLocking);
|
||||
}
|
||||
|
|
|
@ -36,6 +36,9 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
/** @var bool les opérations de verrouillages sont-elle activées? */
|
||||
const USE_LOCKING = false;
|
||||
|
||||
/** @var bool faut-il lancer une exception s'il y a une erreur? */
|
||||
const THROW_ON_ERROR = true;
|
||||
|
||||
/** @var resource */
|
||||
protected $fd;
|
||||
|
||||
|
@ -57,13 +60,12 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
/** @var array */
|
||||
protected $stat;
|
||||
|
||||
function __construct($fd, bool $close=true, bool $throwOnError=true, ?bool $useLocking=null) {
|
||||
function __construct($fd, bool $close=true, ?bool $throwOnError=null, ?bool $useLocking=null) {
|
||||
if ($fd === null) throw ValueException::null("resource");
|
||||
$this->fd = $fd;
|
||||
$this->close = $close;
|
||||
$this->throwOnError = $throwOnError;
|
||||
if ($useLocking === null) $useLocking = static::USE_LOCKING;
|
||||
$this->useLocking = $useLocking;
|
||||
$this->throwOnError = $throwOnError ?? static::THROW_ON_ERROR;
|
||||
$this->useLocking = $useLocking ?? static::USE_LOCKING;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
@ -316,10 +318,10 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
#############################################################################
|
||||
# Iterator
|
||||
|
||||
protected function _setup(): void {
|
||||
protected function iter_setup(): void {
|
||||
}
|
||||
|
||||
protected function _next(&$key) {
|
||||
protected function iter_next(&$key) {
|
||||
try {
|
||||
return $this->fgets();
|
||||
} catch (EOFException $e) {
|
||||
|
@ -332,7 +334,7 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
if ($seekable) $this->fseek(0);
|
||||
}
|
||||
|
||||
protected function _teardown(): void {
|
||||
protected function iter_teardown(): void {
|
||||
$this->_rewindFd();
|
||||
}
|
||||
|
||||
|
@ -366,7 +368,16 @@ class Stream extends AbstractIterator implements IReader, IWriter {
|
|||
function fputcsv(array $row): void {
|
||||
$fd = $this->getResource();
|
||||
$params = $this->getCsvParams($fd);
|
||||
IOException::ensure_valid(fputcsv($fd, $row, $params[0], $params[1], $params[2]));
|
||||
if (csv_flavours::is_dumb($this->csvFlavour, $sep)) {
|
||||
$line = [];
|
||||
foreach ($row as $col) {
|
||||
$line[] = strval($col);
|
||||
}
|
||||
$line = implode($sep, $line);
|
||||
IOException::ensure_valid(fwrite($fd, "$line\n"), $this->throwOnError);
|
||||
} else {
|
||||
IOException::ensure_valid(fputcsv($fd, $row, $params[0], $params[1], $params[2]), $this->throwOnError);
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws IOException */
|
||||
|
|
|
@ -9,9 +9,8 @@ namespace nur\sery\file;
|
|||
class TempStream extends Stream {
|
||||
const MAX_MEMORY = 2 * 1024 * 1024;
|
||||
|
||||
function __construct(?int $maxMemory=null, bool $throwOnError=true) {
|
||||
if ($maxMemory === null) $maxMemory = static::MAX_MEMORY;
|
||||
$this->maxMemory = $maxMemory;
|
||||
function __construct(?int $maxMemory=null, ?bool $throwOnError=null) {
|
||||
$this->maxMemory = $maxMemory ?? static::MAX_MEMORY;
|
||||
parent::__construct($this->tempFd(), true, $throwOnError);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use nur\sery\os\path;
|
|||
class TmpfileWriter extends FileWriter {
|
||||
const DEFAULT_MODE = "w+b";
|
||||
|
||||
function __construct(?string $destdir=null, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||
function __construct(?string $destdir=null, ?string $mode=null, ?bool $throwOnError=null, ?bool $allowLocking=null) {
|
||||
$tmpDir = sys_get_temp_dir();
|
||||
if ($destdir === null) $destdir = $tmpDir;
|
||||
if (is_dir($destdir)) {
|
||||
|
|
|
@ -5,10 +5,6 @@ use nur\sery\os\IOException;
|
|||
use nur\sery\web\http;
|
||||
|
||||
abstract class _File extends Stream {
|
||||
function __construct($fd, bool $close, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||
parent::__construct($fd, $close, $throwOnError, $allowLocking);
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $file;
|
||||
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
namespace nur\sery\file\csv;
|
||||
|
||||
use DateTimeInterface;
|
||||
use nur\sery\cl;
|
||||
use nur\sery\file\TempStream;
|
||||
use nur\sery\php\func;
|
||||
use nur\sery\php\time\DateTime;
|
||||
use nur\sery\ValueException;
|
||||
use nur\sery\web\http;
|
||||
|
||||
/**
|
||||
* Class CsvBuilder: construction d'un fichier CSV, pour envoi à l'utilisateur
|
||||
*/
|
||||
class CsvBuilder extends TempStream {
|
||||
static function with($builder): self {
|
||||
if ($builder instanceof self) return $builder;
|
||||
elseif (is_string($builder)) return new static($builder);
|
||||
elseif (is_array($builder)) return new static(null, $builder);
|
||||
else throw ValueException::invalid_type($builder, self::class);
|
||||
}
|
||||
|
||||
/** @var ?array schéma des données à écrire */
|
||||
const SCHEMA = null;
|
||||
|
||||
/** @var ?array liste des colonnes à écrire */
|
||||
const HEADERS = null;
|
||||
|
||||
/** @var ?string nom du fichier téléchargé */
|
||||
const OUTPUT = null;
|
||||
|
||||
function __construct(?string $output, ?array $params=null) {
|
||||
if ($output !== null) $params["output"] = $output;
|
||||
$csvFlavour = $params["csv_flavour"] ?? null;
|
||||
$this->csvFlavour = csv_flavours::verifix($csvFlavour);
|
||||
$this->schema = $params["schema"] ?? static::SCHEMA;
|
||||
$this->headers = $params["headers"] ?? static::HEADERS;
|
||||
$this->rows = $params["rows"] ?? null;
|
||||
$cookFunc = $params["cook_func"] ?? null;
|
||||
$cookCtx = $cookArgs = null;
|
||||
if ($cookFunc !== null) {
|
||||
func::ensure_func($cookFunc, $this, $cookArgs);
|
||||
$cookCtx = func::_prepare($cookFunc);
|
||||
}
|
||||
$this->cookCtx = $cookCtx;
|
||||
$this->cookArgs = $cookArgs;
|
||||
$this->output = $params["output"] ?? static::OUTPUT;
|
||||
$maxMemory = $params["max_memory"] ?? null;
|
||||
$throwOnError = $params["throw_on_error"] ?? null;
|
||||
parent::__construct($maxMemory, $throwOnError);
|
||||
}
|
||||
|
||||
protected ?array $schema;
|
||||
|
||||
protected ?array $headers;
|
||||
|
||||
protected ?iterable $rows;
|
||||
|
||||
protected ?string $output;
|
||||
|
||||
protected ?array $cookCtx;
|
||||
|
||||
protected ?array $cookArgs;
|
||||
|
||||
protected function ensureHeaders(?array $row=null): void {
|
||||
if ($this->headers !== null) return;
|
||||
if ($this->schema === null) $headers = null;
|
||||
else $headers = array_keys($this->schema);
|
||||
if ($headers === null && $row !== null) $headers = array_keys($row);
|
||||
$this->headers = $headers;
|
||||
}
|
||||
|
||||
protected bool $csvHeadersSent = false;
|
||||
|
||||
function writeHeaders(?array $headers=null): void {
|
||||
if ($this->csvHeadersSent) return;
|
||||
if ($headers !== null) $this->headers = $headers;
|
||||
else $this->ensureHeaders();
|
||||
if ($this->headers !== null) $this->fputcsv($this->headers);
|
||||
$this->csvHeadersSent = true;
|
||||
}
|
||||
|
||||
protected function cookRow(?array $row): ?array {
|
||||
if ($this->cookCtx !== null) {
|
||||
$args = cl::merge([$row], $this->cookArgs);
|
||||
$row = func::_call($this->cookCtx, $args);
|
||||
}
|
||||
if ($row !== null) {
|
||||
foreach ($row as &$value) {
|
||||
# formatter les dates
|
||||
if ($value instanceof DateTime) {
|
||||
$value = $value->format();
|
||||
} elseif ($value instanceof DateTimeInterface) {
|
||||
$value = DateTime::with($value)->format();
|
||||
}
|
||||
}; unset($value);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
function write(?array $row): void {
|
||||
$row = $this->cookRow($row);
|
||||
if ($row === null) return;
|
||||
$this->ensureHeaders($row);
|
||||
$this->fputcsv($row);
|
||||
}
|
||||
|
||||
function writeAll(?iterable $rows=null): void {
|
||||
$unsetRows = false;
|
||||
if ($rows === null) {
|
||||
$rows = $this->rows;
|
||||
$unsetRows = true;
|
||||
}
|
||||
if ($rows !== null) {
|
||||
foreach ($rows as $row) {
|
||||
$this->write($row);
|
||||
}
|
||||
}
|
||||
if ($unsetRows) $this->rows = null;
|
||||
}
|
||||
|
||||
protected bool $httpHeadersSent = false;
|
||||
|
||||
function sendHeaders(): void {
|
||||
if ($this->httpHeadersSent) return;
|
||||
http::content_type("text/csv");
|
||||
$output = $this->output;
|
||||
if ($output !== null) http::download_as($output);
|
||||
$this->httpHeadersSent = true;
|
||||
}
|
||||
|
||||
function sendFile(?iterable $rows=null): int {
|
||||
$this->writeAll($rows);
|
||||
$this->writeHeaders();
|
||||
$size = $this->ftell();
|
||||
if ($size === 0) return 0;
|
||||
$this->rewind();
|
||||
$this->sendHeaders();
|
||||
return $this->fpassthru();
|
||||
}
|
||||
}
|
|
@ -3,23 +3,29 @@ namespace nur\sery\file\csv;
|
|||
|
||||
use nur\sery\cl;
|
||||
use nur\sery\ref\file\csv\ref_csv;
|
||||
use nur\sery\str;
|
||||
|
||||
class csv_flavours {
|
||||
const MAP = [
|
||||
"oo" => ref_csv::OO_FLAVOUR,
|
||||
"ooffice" => ref_csv::OO_FLAVOUR,
|
||||
ref_csv::OO_NAME => ref_csv::OO_FLAVOUR,
|
||||
ref_csv::OOCALC => ref_csv::OO_FLAVOUR,
|
||||
"xl" => ref_csv::XL_FLAVOUR,
|
||||
"excel" => ref_csv::XL_FLAVOUR,
|
||||
ref_csv::XL_NAME => ref_csv::XL_FLAVOUR,
|
||||
ref_csv::MSEXCEL => ref_csv::XL_FLAVOUR,
|
||||
"dumb;" => ref_csv::DUMB_XL_FLAVOUR,
|
||||
"dumb," => ref_csv::DUMB_OO_FLAVOUR,
|
||||
"dumb" => ref_csv::DUMB_FLAVOUR,
|
||||
];
|
||||
|
||||
const ENCODINGS = [
|
||||
ref_csv::OO_FLAVOUR => ref_csv::OO_ENCODING,
|
||||
ref_csv::XL_FLAVOUR => ref_csv::XL_ENCODING,
|
||||
ref_csv::DUMB_FLAVOUR => ref_csv::DUMB_ENCODING,
|
||||
];
|
||||
|
||||
static final function verifix(string $flavour): string {
|
||||
static final function verifix(?string $flavour): ?string {
|
||||
if ($flavour === null) return null;
|
||||
$lflavour = strtolower($flavour);
|
||||
if (array_key_exists($lflavour, self::MAP)) {
|
||||
$flavour = self::MAP[$lflavour];
|
||||
|
@ -31,8 +37,8 @@ class csv_flavours {
|
|||
}
|
||||
|
||||
static final function get_name(string $flavour): string {
|
||||
if ($flavour == ref_csv::OO_FLAVOUR) return ref_csv::OO_NAME;
|
||||
elseif ($flavour == ref_csv::XL_FLAVOUR) return ref_csv::XL_NAME;
|
||||
if ($flavour == ref_csv::OO_FLAVOUR) return ref_csv::OOCALC;
|
||||
elseif ($flavour == ref_csv::XL_FLAVOUR) return ref_csv::MSEXCEL;
|
||||
else return $flavour;
|
||||
}
|
||||
|
||||
|
@ -43,4 +49,11 @@ class csv_flavours {
|
|||
static final function get_encoding(string $flavour): ?string {
|
||||
return cl::get(self::ENCODINGS, $flavour);
|
||||
}
|
||||
|
||||
static final function is_dumb(string $flavour, ?string &$sep): bool {
|
||||
if (!str::del_prefix($flavour, "xxx")) return false;
|
||||
$sep = $flavour;
|
||||
if (!$sep) $sep = ";";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,18 +15,18 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
|||
*
|
||||
* les exceptions lancées par cette méthode sont ignorées.
|
||||
*/
|
||||
protected function _setup(): void {}
|
||||
protected function iter_setup(): void {}
|
||||
|
||||
/**
|
||||
* lancer un traitement avant de commencer l'itération.
|
||||
*
|
||||
* cette méthode est appelée après {@link _setup()} et l'objet est garanti
|
||||
* cette méthode est appelée après {@link iter_setup()} et l'objet est garanti
|
||||
* d'être dans un état valide.
|
||||
*
|
||||
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
|
||||
* doit gérer lui-même les exceptions éventuelles.
|
||||
*/
|
||||
protected function beforeIter() {}
|
||||
protected function iter_beforeStart() {}
|
||||
|
||||
/**
|
||||
* retourner le prochain élément.
|
||||
|
@ -37,35 +37,35 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
|||
*
|
||||
* @throws NoMoreDataException
|
||||
*/
|
||||
abstract protected function _next(&$key);
|
||||
abstract protected function iter_next(&$key);
|
||||
|
||||
/**
|
||||
* modifier l'élement retourné par {@link _next()} avant de le retourner.
|
||||
* modifier l'élement retourné par {@link iter_next()} avant de le retourner.
|
||||
*
|
||||
*
|
||||
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
|
||||
* doit gérer lui-même les exceptions éventuelles.
|
||||
*/
|
||||
protected function cook(&$item) {}
|
||||
protected function iter_cook(&$item): void {}
|
||||
|
||||
/**
|
||||
* lancer un traitement avant de terminer l'itération et de libérer les
|
||||
* resources
|
||||
*
|
||||
* cette méthode est appelée avant {@link _teardown()} et l'objet est garanti
|
||||
* d'être dans un état valide.
|
||||
* cette méthode est appelée avant {@link iter_teardown()} et l'objet est
|
||||
* garanti d'être dans un état valide.
|
||||
*
|
||||
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
|
||||
* doit gérer lui-même les exceptions éventuelles.
|
||||
*/
|
||||
protected function beforeClose(): void {}
|
||||
protected function iter_beforeClose(): void {}
|
||||
|
||||
/**
|
||||
* libérer les ressources allouées.
|
||||
*
|
||||
* les exceptions lancées par cette méthode sont ignorées.
|
||||
*/
|
||||
protected function _teardown(): void {}
|
||||
protected function iter_teardown(): void {}
|
||||
|
||||
function close(): void {
|
||||
$this->rewind();
|
||||
|
@ -99,17 +99,17 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
|||
if ($this->toredown) return;
|
||||
$this->valid = false;
|
||||
try {
|
||||
$item = $this->_next($key);
|
||||
$item = $this->iter_next($key);
|
||||
} catch (NoMoreDataException $e) {
|
||||
$this->beforeClose();
|
||||
$this->iter_beforeClose();
|
||||
try {
|
||||
$this->_teardown();
|
||||
$this->iter_teardown();
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
$this->toredown = true;
|
||||
return;
|
||||
}
|
||||
$this->cook($item);
|
||||
$this->iter_cook($item);
|
||||
$this->item = $item;
|
||||
if ($key !== null) {
|
||||
$this->key = $key;
|
||||
|
@ -123,9 +123,9 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
|||
function rewind(): void {
|
||||
if ($this->setup) {
|
||||
if (!$this->toredown) {
|
||||
$this->beforeClose();
|
||||
$this->iter_beforeClose();
|
||||
try {
|
||||
$this->_teardown();
|
||||
$this->iter_teardown();
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
@ -141,12 +141,12 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
|||
function valid(): bool {
|
||||
if (!$this->setup) {
|
||||
try {
|
||||
$this->_setup();
|
||||
$this->iter_setup();
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
$this->setup = true;
|
||||
$this->toredown = false;
|
||||
$this->beforeIter();
|
||||
$this->iter_beforeStart();
|
||||
$this->next();
|
||||
}
|
||||
return $this->valid;
|
||||
|
|
|
@ -10,11 +10,23 @@ class ref_csv {
|
|||
const CP1252 = "cp1252";
|
||||
const LATIN1 = "iso-8859-1";
|
||||
|
||||
const OO_NAME = "oocalc";
|
||||
const OOCALC = "oocalc";
|
||||
const OO_FLAVOUR = ",\"\\";
|
||||
const OO_ENCODING = self::UTF8;
|
||||
|
||||
const XL_NAME = "msexcel";
|
||||
|
||||
const MSEXCEL = "msexcel";
|
||||
const XL_FLAVOUR = ";\"\\";
|
||||
const XL_ENCODING = self::CP1252;
|
||||
|
||||
const DUMBOO = "xxx,";
|
||||
const DUMB_OO_FLAVOUR = "xxx,";
|
||||
const DUMB_OO_ENCODING = self::UTF8;
|
||||
|
||||
const DUMBXL = "xxx;";
|
||||
const DUMB_XL_FLAVOUR = "xxx;";
|
||||
const DUMB_XL_ENCODING = self::UTF8;
|
||||
|
||||
const DUMB = self::DUMBXL;
|
||||
const DUMB_FLAVOUR = self::DUMB_XL_FLAVOUR;
|
||||
const DUMB_ENCODING = self::DUMB_XL_ENCODING;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue