modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2024-06-10 15:53:58 +04:00
parent d612253c1d
commit 3461d2eec8
12 changed files with 218 additions and 46 deletions

View File

@ -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) {

View File

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

View File

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

View File

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

View File

@ -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 */

View File

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

View File

@ -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)) {

View File

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

141
src/file/csv/CsvBuilder.php Normal file
View 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();
}
}

View File

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

View File

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

View File

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