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 */ /** @var bool */
protected $ignoreBom; 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; if ($ignoreBom === null) $ignoreBom = static::IGNORE_BOM;
$this->ignoreBom = $ignoreBom; $this->ignoreBom = $ignoreBom;
if ($input === null) { if ($input === null) {

View File

@ -10,7 +10,7 @@ use nur\sery\os\sh;
class FileWriter extends _File { class FileWriter extends _File {
const DEFAULT_MODE = "a+b"; 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) { if ($output === null) {
$fd = STDOUT; $fd = STDOUT;
$close = false; $close = false;

View File

@ -10,7 +10,7 @@ class MemoryStream extends Stream {
return fopen("php://memory", "w+b"); return fopen("php://memory", "w+b");
} }
function __construct(bool $throwOnError=true) { function __construct(?bool $throwOnError=null) {
parent::__construct(self::memory_fd(), true, $throwOnError); parent::__construct(self::memory_fd(), true, $throwOnError);
} }

View File

@ -8,7 +8,7 @@ class SharedFile extends FileWriter {
const DEFAULT_MODE = "c+b"; 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"); if ($file === null) throw ValueException::null("file");
parent::__construct($file, $mode, $throwOnError, $allowLocking); 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? */ /** @var bool les opérations de verrouillages sont-elle activées? */
const USE_LOCKING = false; const USE_LOCKING = false;
/** @var bool faut-il lancer une exception s'il y a une erreur? */
const THROW_ON_ERROR = true;
/** @var resource */ /** @var resource */
protected $fd; protected $fd;
@ -57,13 +60,12 @@ class Stream extends AbstractIterator implements IReader, IWriter {
/** @var array */ /** @var array */
protected $stat; 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"); if ($fd === null) throw ValueException::null("resource");
$this->fd = $fd; $this->fd = $fd;
$this->close = $close; $this->close = $close;
$this->throwOnError = $throwOnError; $this->throwOnError = $throwOnError ?? static::THROW_ON_ERROR;
if ($useLocking === null) $useLocking = static::USE_LOCKING; $this->useLocking = $useLocking ?? static::USE_LOCKING;
$this->useLocking = $useLocking;
} }
############################################################################# #############################################################################
@ -316,10 +318,10 @@ class Stream extends AbstractIterator implements IReader, IWriter {
############################################################################# #############################################################################
# Iterator # Iterator
protected function _setup(): void { protected function iter_setup(): void {
} }
protected function _next(&$key) { protected function iter_next(&$key) {
try { try {
return $this->fgets(); return $this->fgets();
} catch (EOFException $e) { } catch (EOFException $e) {
@ -332,7 +334,7 @@ class Stream extends AbstractIterator implements IReader, IWriter {
if ($seekable) $this->fseek(0); if ($seekable) $this->fseek(0);
} }
protected function _teardown(): void { protected function iter_teardown(): void {
$this->_rewindFd(); $this->_rewindFd();
} }
@ -366,7 +368,16 @@ class Stream extends AbstractIterator implements IReader, IWriter {
function fputcsv(array $row): void { function fputcsv(array $row): void {
$fd = $this->getResource(); $fd = $this->getResource();
$params = $this->getCsvParams($fd); $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 */ /** @throws IOException */

View File

@ -9,9 +9,8 @@ namespace nur\sery\file;
class TempStream extends Stream { class TempStream extends Stream {
const MAX_MEMORY = 2 * 1024 * 1024; const MAX_MEMORY = 2 * 1024 * 1024;
function __construct(?int $maxMemory=null, bool $throwOnError=true) { function __construct(?int $maxMemory=null, ?bool $throwOnError=null) {
if ($maxMemory === null) $maxMemory = static::MAX_MEMORY; $this->maxMemory = $maxMemory ?? static::MAX_MEMORY;
$this->maxMemory = $maxMemory;
parent::__construct($this->tempFd(), true, $throwOnError); parent::__construct($this->tempFd(), true, $throwOnError);
} }

View File

@ -10,7 +10,7 @@ use nur\sery\os\path;
class TmpfileWriter extends FileWriter { class TmpfileWriter extends FileWriter {
const DEFAULT_MODE = "w+b"; 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(); $tmpDir = sys_get_temp_dir();
if ($destdir === null) $destdir = $tmpDir; if ($destdir === null) $destdir = $tmpDir;
if (is_dir($destdir)) { if (is_dir($destdir)) {

View File

@ -5,10 +5,6 @@ use nur\sery\os\IOException;
use nur\sery\web\http; use nur\sery\web\http;
abstract class _File extends Stream { abstract class _File extends Stream {
function __construct($fd, bool $close, bool $throwOnError=true, ?bool $allowLocking=null) {
parent::__construct($fd, $close, $throwOnError, $allowLocking);
}
/** @var string */ /** @var string */
protected $file; 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\cl;
use nur\sery\ref\file\csv\ref_csv; use nur\sery\ref\file\csv\ref_csv;
use nur\sery\str;
class csv_flavours { class csv_flavours {
const MAP = [ const MAP = [
"oo" => ref_csv::OO_FLAVOUR, "oo" => ref_csv::OO_FLAVOUR,
"ooffice" => 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, "xl" => ref_csv::XL_FLAVOUR,
"excel" => 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 = [ const ENCODINGS = [
ref_csv::OO_FLAVOUR => ref_csv::OO_ENCODING, ref_csv::OO_FLAVOUR => ref_csv::OO_ENCODING,
ref_csv::XL_FLAVOUR => ref_csv::XL_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); $lflavour = strtolower($flavour);
if (array_key_exists($lflavour, self::MAP)) { if (array_key_exists($lflavour, self::MAP)) {
$flavour = self::MAP[$lflavour]; $flavour = self::MAP[$lflavour];
@ -31,8 +37,8 @@ class csv_flavours {
} }
static final function get_name(string $flavour): string { static final function get_name(string $flavour): string {
if ($flavour == ref_csv::OO_FLAVOUR) return ref_csv::OO_NAME; if ($flavour == ref_csv::OO_FLAVOUR) return ref_csv::OOCALC;
elseif ($flavour == ref_csv::XL_FLAVOUR) return ref_csv::XL_NAME; elseif ($flavour == ref_csv::XL_FLAVOUR) return ref_csv::MSEXCEL;
else return $flavour; else return $flavour;
} }
@ -43,4 +49,11 @@ class csv_flavours {
static final function get_encoding(string $flavour): ?string { static final function get_encoding(string $flavour): ?string {
return cl::get(self::ENCODINGS, $flavour); 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. * 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. * 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. * d'être dans un état valide.
* *
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il * cette méthode est prévue pour être surchargée par l'utilisateur, mais il
* doit gérer lui-même les exceptions éventuelles. * doit gérer lui-même les exceptions éventuelles.
*/ */
protected function beforeIter() {} protected function iter_beforeStart() {}
/** /**
* retourner le prochain élément. * retourner le prochain élément.
@ -37,35 +37,35 @@ abstract class AbstractIterator implements Iterator, ICloseable {
* *
* @throws NoMoreDataException * @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 * cette méthode est prévue pour être surchargée par l'utilisateur, mais il
* doit gérer lui-même les exceptions éventuelles. * 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 * lancer un traitement avant de terminer l'itération et de libérer les
* resources * resources
* *
* cette méthode est appelée avant {@link _teardown()} et l'objet est garanti * cette méthode est appelée avant {@link iter_teardown()} et l'objet est
* d'être dans un état valide. * garanti d'être dans un état valide.
* *
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il * cette méthode est prévue pour être surchargée par l'utilisateur, mais il
* doit gérer lui-même les exceptions éventuelles. * doit gérer lui-même les exceptions éventuelles.
*/ */
protected function beforeClose(): void {} protected function iter_beforeClose(): void {}
/** /**
* libérer les ressources allouées. * libérer les ressources allouées.
* *
* les exceptions lancées par cette méthode sont ignorées. * les exceptions lancées par cette méthode sont ignorées.
*/ */
protected function _teardown(): void {} protected function iter_teardown(): void {}
function close(): void { function close(): void {
$this->rewind(); $this->rewind();
@ -99,17 +99,17 @@ abstract class AbstractIterator implements Iterator, ICloseable {
if ($this->toredown) return; if ($this->toredown) return;
$this->valid = false; $this->valid = false;
try { try {
$item = $this->_next($key); $item = $this->iter_next($key);
} catch (NoMoreDataException $e) { } catch (NoMoreDataException $e) {
$this->beforeClose(); $this->iter_beforeClose();
try { try {
$this->_teardown(); $this->iter_teardown();
} catch (Exception $e) { } catch (Exception $e) {
} }
$this->toredown = true; $this->toredown = true;
return; return;
} }
$this->cook($item); $this->iter_cook($item);
$this->item = $item; $this->item = $item;
if ($key !== null) { if ($key !== null) {
$this->key = $key; $this->key = $key;
@ -123,9 +123,9 @@ abstract class AbstractIterator implements Iterator, ICloseable {
function rewind(): void { function rewind(): void {
if ($this->setup) { if ($this->setup) {
if (!$this->toredown) { if (!$this->toredown) {
$this->beforeClose(); $this->iter_beforeClose();
try { try {
$this->_teardown(); $this->iter_teardown();
} catch (Exception $e) { } catch (Exception $e) {
} }
} }
@ -141,12 +141,12 @@ abstract class AbstractIterator implements Iterator, ICloseable {
function valid(): bool { function valid(): bool {
if (!$this->setup) { if (!$this->setup) {
try { try {
$this->_setup(); $this->iter_setup();
} catch (Exception $e) { } catch (Exception $e) {
} }
$this->setup = true; $this->setup = true;
$this->toredown = false; $this->toredown = false;
$this->beforeIter(); $this->iter_beforeStart();
$this->next(); $this->next();
} }
return $this->valid; return $this->valid;

View File

@ -10,11 +10,23 @@ class ref_csv {
const CP1252 = "cp1252"; const CP1252 = "cp1252";
const LATIN1 = "iso-8859-1"; const LATIN1 = "iso-8859-1";
const OO_NAME = "oocalc"; const OOCALC = "oocalc";
const OO_FLAVOUR = ",\"\\"; const OO_FLAVOUR = ",\"\\";
const OO_ENCODING = self::UTF8; const OO_ENCODING = self::UTF8;
const XL_NAME = "msexcel"; const MSEXCEL = "msexcel";
const XL_FLAVOUR = ";\"\\"; const XL_FLAVOUR = ";\"\\";
const XL_ENCODING = self::CP1252; 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;
} }