ajout t et mapper

This commit is contained in:
Jephté Clain 2024-04-04 16:26:22 +04:00
parent 93790d7be5
commit 86ce10cf95
125 changed files with 9314 additions and 16 deletions

View File

@ -2,15 +2,7 @@
<?php
require(__DIR__.'/../vendor/autoload.php');
use nur\io\csv\CsvReader;
use nur\io\csv\CsvWriter;
use nur\io\fsv\FsvReader;
use nur\io\fsv\FsvWriter;
use nur\io\json\JsonReader;
use nur\io\json\YamlReader;
use nur\io\line\LineReader;
use nur\php\UpdateClassesApp;
use nur\v\bs3\Bs3IconManager;
UpdateClassesApp::run(new class extends UpdateClassesApp {
const MAPPINGS = [
@ -18,20 +10,56 @@ UpdateClassesApp::run(new class extends UpdateClassesApp {
"package" => "nur\\io\\",
"path" => __DIR__."/../nur_src/io",
"classes" => [
LineReader::class,
CsvReader::class,
CsvWriter::class,
FsvReader::class,
FsvWriter::class,
JsonReader::class,
YamlReader::class,
nur\io\line\LineReader::class,
nur\io\csv\CsvReader::class,
nur\io\csv\CsvWriter::class,
nur\io\fsv\FsvReader::class,
nur\io\fsv\FsvWriter::class,
nur\io\json\JsonReader::class,
nur\io\json\YamlReader::class,
],
],
"v-bs3" => [
"package" => "nur\\v\\bs3\\",
"path" => __DIR__."/../nur_src/v/bs3",
"classes" => [
Bs3IconManager::class,
nur\v\bs3\Bs3IconManager::class,
],
],
"mapper" => [
"package" => "nur\\mapper\\",
"path" => __DIR__."/../nur_src/mapper",
"classes" => [
# base
nur\mapper\base\Mapper::class,
nur\mapper\base\FuncMapper::class,
# line
nur\mapper\line\IconvMapper::class,
# item
nur\mapper\item\StringMapper::class,
nur\mapper\item\TextMapper::class,
#nur\mapper\item\NumberMapper::class,
nur\mapper\item\StreamMapper::class,
nur\mapper\item\Seq2AssocMapper::class,
nur\mapper\item\Assoc2SeqMapper::class,
nur\mapper\item\SchemaMapper::class,
nur\mapper\item\ItemFilterMapper::class,
nur\mapper\item\AttributeFilterMapper::class,
# csv
nur\mapper\csv\Csv2AssocMapper::class,
nur\mapper\csv\Assoc2CsvMapper::class,
nur\mapper\csv\CsvReader::class, # après Csv2AssocMapper
nur\mapper\csv\CsvWriter::class, # après Assoc2CsvMapper
# fsv
nur\mapper\fsv\Fsv2AssocMapper::class,
nur\mapper\fsv\Assoc2FsvMapper::class,
nur\mapper\fsv\FsvReader::class, # après Fsv2AssocMapper
nur\mapper\fsv\FsvWriter::class, # après Assoc2FsvMapper
# json
nur\mapper\json\JsonReader::class,
nur\mapper\json\JsonWriter::class,
nur\mapper\json\YamlReader::class,
nur\mapper\json\YamlWriter::class,
],
],
];

View File

@ -29,6 +29,7 @@ use nur\ref\ref_csv;
* @method int setSkipLines(int $value)
* @method bool|null setParseHeaders(?bool $value)
* @method array|null setHeaders(?array $value)
* @method setHeaderMappings($value)
* @method string|null setMapEmpty(?string $value)
*/
class CsvReader extends Parametrable implements IteratorAggregate, ICloseable {
@ -187,6 +188,7 @@ class CsvReader extends Parametrable implements IteratorAggregate, ICloseable {
'setSkipLines' => 'skip_lines',
'setParseHeaders' => 'parse_headers',
'setHeaders' => 'headers',
'setHeaderMappings' => 'header_mappings',
'setMapEmpty' => 'map_empty',
];
#--autogen-dynamic--

View File

@ -24,6 +24,7 @@ use nur\ref\ref_csv;
* @method bool setMultiSchema(bool $value)
* @method array|null setHeaders(?array $value)
* @method bool setOutputHeaders(bool $value)
* @method setHeaderMappings($value)
*/
class CsvWriter extends Parametrable {
use Tparametrable, Ttmpwriter, Tencoding, Tfilter;
@ -186,6 +187,7 @@ class CsvWriter extends Parametrable {
'setMultiSchema' => 'multi_schema',
'setHeaders' => 'headers',
'setOutputHeaders' => 'output_headers',
'setHeaderMappings' => 'header_mappings',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,33 @@
<?php
namespace nur\mapper\app;
use nur\cli\Application;
use nur\mapper\base\mappers_command;
class DatareaderApp extends Application {
const ARGS = [
"purpose" => "Gérer un flux csv",
"usage" => "[-f input] [-o output]",
"dynamic_command" => datareader_command::class,
"autoremains" => false,
"sections" => [
Application::VERBOSITY_SECTION,
],
datareader_command::IF_OPTION,
datareader_command::IFO_OPTION,
datareader_command::IE_OPTION,
datareader_command::OF_OPTION,
datareader_command::OFO_OPTION,
datareader_command::OE_OPTION,
["args" => [["value"]], "action" => [datareader_command::class, "set_input_file"]],
];
function main() {
[$producer, $consumer] = datareader_command::get();
$consumer->setUseTmpfile($producer->getInput());
$consumer->consume($producer, ...mappers_command::$mappers);
}
}

View File

@ -0,0 +1,259 @@
<?php
namespace nur\mapper\app;
use nur\A;
use nur\b\ValueException;
use nur\mapper\base\mappers_command;
use nur\mapper\csv\CsvReader;
use nur\mapper\csv\CsvReader_command;
use nur\mapper\csv\CsvWriter;
use nur\mapper\csv\CsvWriter_command;
use nur\mapper\fsv\FsvReader;
use nur\mapper\fsv\FsvReader_command;
use nur\mapper\fsv\FsvWriter;
use nur\mapper\fsv\FsvWriter_command;
use nur\mapper\json\JsonReader;
use nur\mapper\json\JsonReader_command;
use nur\mapper\json\JsonWriter;
use nur\mapper\json\JsonWriter_command;
use nur\mapper\json\YamlReader;
use nur\mapper\json\YamlReader_command;
use nur\mapper\json\YamlWriter;
use nur\mapper\json\YamlWriter_command;
use nur\mapper\line\LineReader;
use nur\mapper\line\LineReader_command;
use nur\mapper\line\LineWriter;
use nur\mapper\line\LineWriter_command;
use nur\str;
class datareader_command extends mappers_command {
const PRODUCER_CLASSES = [
LineReader_command::class,
CsvReader_command::class,
FsvReader_command::class,
JsonReader_command::class,
YamlReader_command::class,
];
const CONSUMER_CLASSES = [
LineWriter_command::class,
CsvWriter_command::class,
FsvWriter_command::class,
JsonWriter_command::class,
YamlWriter_command::class,
];
const MAPPER_CLASSES = [
];
protected function DYNAMIC_COMMAND_CLASSES(): array {
return array_merge(
self::PRODUCER_CLASSES, parent::PRODUCER_CLASSES,
self::CONSUMER_CLASSES, parent::CONSUMER_CLASSES,
self::MAPPER_CLASSES, parent::MAPPER_CLASSES
);
}
const DEFS = [
"csv" => [
"exts" => [".csv"],
"formats" => ["csv", "c"],
"reader" => [CsvReader_command::class, CsvReader::class],
"writer" => [CsvWriter_command::class, CsvWriter::class],
"set_input_encoding" => "setEncodingFilter",
"set_output_encoding" => "setEncodingFilter",
],
"fsv" => [
"exts" => [".fsv"],
"formats" => ["fsv", "v"],
"reader" => [FsvReader_command::class, FsvReader::class],
"writer" => [FsvWriter_command::class, FsvWriter::class],
"set_input_encoding" => "setInputEncoding",
"set_output_encoding" => "setOutputEncoding",
],
"json" => [
"exts" => [".json"],
"formats" => ["json", "j"],
"reader" => [JsonReader_command::class, JsonReader::class],
"writer" => [JsonWriter_command::class, JsonWriter::class],
"set_input_encoding" => false,
"set_output_encoding" => false,
],
"yaml" => [
"exts" => [".yaml", ".yml"],
"formats" => ["yaml", "l"],
"reader" => [YamlReader_command::class, YamlReader::class],
"writer" => [YamlWriter_command::class, YamlWriter::class],
"set_input_encoding" => false,
"set_output_encoding" => false,
],
"line" => [
"exts" => [],
"formats" => ["line"],
"reader" => [LineReader_command::class, LineReader::class],
"writer" => [LineWriter_command::class, LineWriter::class],
"set_input_encoding" => "setEncodingFilter",
"set_output_encoding" => "setEncodingFilter",
],
];
const IF_OPTION = ["-f", "--input", "args" => "file",
"action" => [self::class, "set_input_file"],
];
const IFO_OPTION = ["-F", "--input-format", "args" => "value",
"action" => [self::class, "set_input_format"],
];
const IE_OPTION = ["-e", "--input-encoding", "args" => "value",
"action" => [self::class, "set_input_encoding"],
];
const OF_OPTION = ["-o", "--output", "args" => "file",
"action" => [self::class, "set_output_file"],
];
const OFO_OPTION = ["-O", "--output-format", "args" => "value",
"action" => [self::class, "set_output_format"],
];
const OE_OPTION = ["-t", "--output-encoding", "args" => "value",
"action" => [self::class, "set_output_encoding"],
];
private static $input_file;
static function set_input_file($file): void {
self::$input_file = A::last(A::with($file));
}
private static $input_format;
static function set_input_format(string $input_format) {
self::$input_format = $input_format;
}
private static $input_encoding;
static function set_input_encoding(string $input_encoding) {
self::$input_encoding = $input_encoding;
}
private static $output_file;
static function set_output_file($file): void {
self::$output_file = A::last(A::with($file));
}
private static $output_format;
static function set_output_format(string $output_format) {
self::$output_format = $output_format;
}
private static $output_encoding;
static function set_output_encoding(string $output_encoding) {
self::$output_encoding = $output_encoding;
}
static function get(): array {
$producer = null;
$input_file = self::$input_file;
$input_format = self::$input_format;
if ($input_file !== null || $input_format !== null) {
if ($input_file !== null && $input_format === null) {
foreach (self::DEFS as $def) {
foreach (A::with($def["exts"]) as $ext) {
if (str::_ends_with($ext, $input_file)) {
$input_format = A::first(A::with($def["formats"]));
break 2;
}
}
}
if ($input_format === null) $input_format = "line";
}
$found = false;
foreach (self::DEFS as $def) {
foreach (A::with($def["formats"]) as $format) {
if ($input_format == $format) {
$found = true;
$producerClass = $def["reader"][1];
$producer = new $producerClass($input_file);
$input_encoding = self::$input_encoding;
if ($input_encoding !== null) {
$setInputEncoding = $def["set_input_encoding"];
if ($setInputEncoding) {
$producer->$setInputEncoding($input_encoding);
}
}
break 2;
}
}
}
if (!$found) {
throw new ValueException("$input_format: format invalide");
}
}
if ($producer === null) {
foreach (self::DEFS as $def) {
$commandClass = $def["reader"][0];
$producer = $commandClass::get(false);
if ($producer !== null) break;
}
}
$consumer = null;
$output_file = self::$output_file;
$output_format = self::$output_format;
if ($output_file !== null || $output_format !== null) {
if ($output_file !== null && $output_format === null) {
foreach (self::DEFS as $def) {
foreach (A::with($def["exts"]) as $ext) {
if (str::_ends_with($ext, $output_file)) {
$output_format = A::first(A::with($def["formats"]));
break 2;
}
}
}
if ($output_format === null) $output_format = "line";
}
$found = false;
foreach (self::DEFS as $def) {
foreach (A::with($def["formats"]) as $format) {
if ($output_format == $format) {
$found = true;
$consumerClass = $def["writer"][1];
$consumer = new $consumerClass($output_file);
$output_encoding = self::$output_encoding;
if ($output_encoding !== null) {
$setOutputEncoding = $def["set_output_encoding"];
if ($setOutputEncoding) {
$consumer->$setOutputEncoding($output_encoding);
}
}
break 2;
}
}
}
if (!$found) {
throw new ValueException("$output_format: format invalide");
}
}
if ($consumer === null) {
foreach (self::DEFS as $def) {
$commandClass = $def["writer"][0];
$consumer = $commandClass::get(false);
if ($consumer !== null) break;
}
}
if ($consumer === null) {
foreach (self::DEFS as $def) {
$producerClass = $def["reader"][1];
$commandClass = $def["writer"][0];
if ($producer instanceof $producerClass) {
$consumer = $commandClass::get();
break;
}
}
}
if ($producer === null) $producer = LineReader_command::get();
if ($consumer === null) $consumer = LineWriter_command::get();
return [$producer, $consumer];
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace nur\mapper\base;
use nur\mapper\base\capacitor\Capacitor;
use nur\mapper\base\capacitor\ICapacitor;
class CapacitorConsumer extends Consumer {
function __construct(?ICapacitor $capacitor=null, $producer=null, ...$mappers) {
parent::__construct($producer, ...$mappers);
$this->setCapacitor($capacitor);
}
/** @var ICapacitor */
protected $capacitor;
function setCapacitor(?ICapacitor $capacitor): self {
if ($capacitor === null) $capacitor = new Capacitor();
$this->capacitor = $capacitor;
$this->setupCapacitor = true;
return $this;
}
/** @var string|null */
protected $channel;
function setChannel(?string $channel): self {
$this->channel = $channel;
return $this;
}
protected function _setupCapacitor(): void {
}
/** @var bool */
protected $setupCapacitor;
protected function capacitor(): ICapacitor {
if ($this->setupCapacitor) {
$this->_setupCapacitor();
$this->setupCapacitor = false;
}
return $this->capacitor;
}
function cook($item) {
$this->capacitor()->charge($item, $this->channel);
}
function getItem($pkvalues, ?string $channel=null) {
if ($channel === null) $channel = $this->channel;
return $this->capacitor()->getItem($pkvalues, $channel);
}
function discharge(bool $remove=true, ?string $channel=null): iterable {
if ($channel === null) $channel = $this->channel;
return $this->capacitor()->discharge($channel, $remove);
}
}

View File

@ -0,0 +1,222 @@
<?php
namespace nur\mapper\base;
use ArrayAccess;
use Countable;
use nur\b\coll\IArray;
use nur\b\coll\TBaseArray;
use nur\b\coll\TGenericArray;
use nur\b\ICloseable;
use nur\b\params\Parametrable;
use nur\b\params\Tparametrable;
use nur\b\ValueException;
use nur\func;
use nur\mapper\base\oobd\IOobdManager;
use nur\mapper\base\oobd\TOobdManager;
/**
* Class Consumer: une classe qui applique des mappers sur les données d'un
* producer.
*/
class Consumer extends Parametrable implements ArrayAccess, Countable, IArray, ICloseable, IOobdManager {
use TBaseArray, TGenericArray, Tparametrable, TOobdManager;
function __construct($producer=null, ...$mappers) {
parent::__construct();
$this->data = [];
if ($producer !== null) $this->setProducer($producer);
$this->addAll($mappers);
}
function _haveMethod(string $method): bool {
return method_exists($this, $method);
}
/** @var iterable */
private $producer;
/** @var PushProducer */
private $pushProducer;
function setProducer($producer, ...$args): self {
$producer = producer_utils::ensure_producer($producer, $args);
$this->producer = $producer;
$this->pushProducer = $producer instanceof PushProducer? $producer: null;
return $this;
}
function set($key, $mapper): self { return $this->_set($key, mapper_utils::ensure_mapper_class($mapper)); }
function add($mapper): self { return $this->_set(null, mapper_utils::ensure_mapper_class($mapper)); }
/** @var bool */
private $setup = false;
private function ensureSetup(): bool {
if (!$this->setup) {
$this->setup();
$this->setup = true;
return true;
}
return false;
}
protected function setup(): void {
}
/** @var iterable */
private $pushIterator;
private function _ensureSharedOobdManager(iterable $iterator): void {
if ($iterator instanceof IOobdManager) {
# récupérer le gestionnaire partagé le cas échéant
$sharedOobdManager = $iterator->getSharedOobdManager();
if ($sharedOobdManager !== null) $this->setSharedOobdManager($sharedOobdManager);
}
}
private function buildIterator(bool $ensurePushable=false): array {
$this->ensureSetup();
if ($this->pushIterator === null) {
if (!$ensurePushable) {
$iterator = $this->producer;
if ($iterator === null) {
throw new ValueException("a producer is required");
}
if ($this->pushProducer === null) {
if ($iterator instanceof IOobdManager) {
# s'assurer qu'il y a toujours un gestionnaire partagé
$iterator->ensureSharedOobdManager();
}
$iterator = mapper_utils::assemble_mappers($this->data, $iterator);
$this->_ensureSharedOobdManager($iterator);
return [true, $iterator];
}
}
$iterator = $this->pushProducer;
if ($iterator === null) $iterator = new PushProducer();
# s'assurer qu'il y a toujours un gestionnaire partagé
$iterator->ensureSharedOobdManager();
$this->pushProducer = $iterator;
$this->pushIterator = mapper_utils::assemble_mappers($this->data, $iterator);
$this->_ensureSharedOobdManager($this->pushIterator);
}
return [false, $this->pushIterator];
}
private $iterator;
function hasOvalue(string $name): bool {
if ($this->_hasOobdValue($name)) {
return true;
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return true;
} elseif ($this->iterator instanceof IOobdManager) {
return $this->iterator->hasOvalue($name);
} elseif ($this->pushIterator instanceof IOobdManager) {
return $this->pushIterator->hasOvalue($name);
}
return false;
}
/**
* c'est la méthode {@link consume()} qui construit $this->iterator. l'accès
* aux données OOB de la chaine de mapper n'est donc valide que dans les
* méthodes {@link _consume()} et {@link cook()}
*
* De même, en mode push, ce n'est qu'après l'appel d'une des méthodes
* {@link ensurePushable()}, {@link push()} ou {@link pushAll()} que les
* données OOB de la chaine de mapper sont disponibles
*/
function getOvalue(string $name, $default=null) {
if ($this->_hasOobdValue($name)) {
return $this->_getOobdValue($name, $default);
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return $this->sharedOobdManager->getOvalue($name, $default);
} elseif ($this->iterator instanceof IOobdManager) {
return $this->iterator->getOvalue($name, $default);
} elseif ($this->pushIterator instanceof IOobdManager) {
return $this->pushIterator->getOvalue($name, $default);
}
return null;
}
function cook($item) {
}
protected function _consume(iterable $items): void {
$cookFunc = func::_prepare([$this, "cook"]);
foreach ($items as $key => $item) {
func::_call($cookFunc, [$item, $key]);
}
}
/**
* consommer toutes les valeurs du producer
*
* les méthodes {@link setup()} et {@link teardown()} sont appelées
* automatiquement
*/
function consume($producer=null, ...$mappers): void {
if ($producer !== null) $this->setProducer($producer);
if ($mappers) $this->resetAll($mappers);
$close = $this->ensureSetup();
try {
[$close, $iterator] = $this->buildIterator();
$this->iterator = $iterator;
$this->_consume($iterator);
} finally {
if ($close) $this->close();
}
}
protected function teardown(): void {
if ($this->iterator instanceof ICloseable) $this->iterator->close();
$this->iterator = null;
if ($this->pushIterator !== null) {
if ($this->pushIterator instanceof ICloseable) $this->pushIterator->close();
$this->pushProducer = null;
$this->pushIterator = null;
}
}
/** appeler la méthode {@link teardown()} si nécessaire */
function close(): void {
if ($this->setup) {
$this->teardown();
$this->setup = false;
}
}
/**
* s'assurer que les données OOB sont disponibles avant l'utilisation de
* {@link push()} ou {@link pushAll()}. cette méthode n'a pas d'autre utilité
*/
function ensurePushable(): void {
$this->buildIterator(true);
}
/**
* insérer une valeur, comme si elle provenait du producer.
*
* la méthodes {@link setup()} est appelée automatiquement si nécessaire, mais
* pas {@link teardown()}. il faut donc penser à appeler {@link close()} quand
* on a terminé
*/
function push($item, $key=null): void {
$this->buildIterator(true);
$this->pushProducer->push($item, $key);
$this->_consume($this->pushIterator);
}
/**
* insérer des valeurs, comme si elles provenaient du producer
*/
function pushAll(iterable $items): void {
$this->buildIterator(true);
$this->pushProducer->pushAll($items);
$this->_consume($this->pushIterator);
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace nur\mapper\base;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\func;
/**
* Class FuncMapper: appeler une fonction pour chaque valeur
*
* --autogen-properties-and-methods--
* @method bool isMarkedOnly()
* @method callable|null setFunc(?callable $value)
* @method bool setMarkedOnly(bool $value)
*/
class FuncMapper extends Mapper {
use Tparametrable;
function __construct(?callable $func=null, ?iterable $source=null) {
parent::__construct($source);
if ($func !== null) $this->pp_setFunc($func);
}
/** @var array */
protected $func;
function pp_setFunc(callable $func): void {
$this->func = func::_prepare($func);
}
/** @var bool */
protected $ppMarkedOnly;
const PARAMETRABLE_PARAMS_SCHEMA = [
"func" => ["callable", null, "fonction à appeler"],
"marked_only" => ["bool", null, "n'appeler la fonction que pour les éléments marqués"],
];
protected function setup(): void {
parent::setup();
if ($this->ppMarkedOnly === null) {
$this->ppMarkedOnly = mark_utils::is_use_marks($this);
}
}
function mapper($item, $key=null) {
if (!$this->ppMarkedOnly || $this->isItemMarked($item)) {
$item = func::_call($this->func, [$item, $key, $this]);
}
return $item;
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getFunc' => 'func',
'isMarkedOnly' => 'marked_only',
];
const _AUTO_SETTERS = /*autogen*/[
'setFunc' => 'func',
'setMarkedOnly' => 'marked_only',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,58 @@
<?php
namespace nur\mapper\base;
/**
* Class FuncMapper_command: classe de support pour construire une instance de
* mapper pour une application recevant des commandes utilisateurs
*/
class FuncMapper_command {
use Tparams_command;
const NAME = "func";
const DEF = [self::NAME,
"help" => "appliquer une fonction",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-r", "--run-code", "args" => "value",
"action" => [self::class, "set_run_code"],
"help" => "exécuter le code avec eval() pour chaque élément du flux",
],
["-f", "--run-file", "args" => "file",
"action" => [self::class, "set_run_file"],
"help" => "exécuter le fichier avec require() pour chaque élément du flux",
],
["--mo", "--marked-only", "args" => "value", "type" => "bool",
"action" => [self::class, "set_marked_only"],
"help" => "indiquer si la fonction ne doit être appelée que pour les éléments marqués",
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var FuncMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new FuncMapper());
}
static function set_run_code(string $code) {
self::get()->setFunc(function() use($code) {
return eval($code);
});
}
static function set_run_file(string $file) {
self::get()->setFunc(function() use($file) {
return require($file);
});
}
static function set_marked_only(?bool $marked_only) {
self::get()->setMarkedOnly($marked_only);
}
}

View File

@ -0,0 +1,362 @@
<?php
namespace nur\mapper\base;
use IteratorAggregate;
use nur\b\ICloseable;
use nur\b\params\IParametrable;
use nur\b\params\Parametrable;
use nur\b\params\Tparametrable;
use nur\func;
use nur\mapper\base\oobd\IOobdManager;
use nur\mapper\base\oobd\TOobdManager;
/**
* Class Mapper: un mappeur / filtre de données
*
* La méthode {@link mapper()} permet d'associer un élément à zéro, un ou
* plusieurs autres éléments. il faut:
* - soit retourner une valeur correspondante. si la valeur retournée est null,
* alors cela revient à ignorer la valeur en entrée
* - soit appeler l'une des méthodes {@link mapTo()}. il est possible de mapper
* vers une valeur, un tableau de valeurs, ou un iterateur/générateur
*
* Pour pouvoir être utilisé avec {@link Consumer}, les constructeurs des
* classes dérivées doivent par convention mentionner $source comme dernier
* argument.
*
* --autogen-properties-and-methods--
* @method iterable getSource()
* @method iterable setSource(iterable $value)
*/
abstract class Mapper extends Parametrable implements IteratorAggregate, ICloseable, IParametrable, IOobdManager {
use Tparametrable, TOobdManager;
/**
* @return bool indiquer s'il faut appeler {@link mapper()} une première fois
* au début de l'itération avec {null => null}. cela permet d'implémenter des
* méthodes de mapping plus complexes.
*/
protected function MAP_SOF(): bool {
return static::MAP_SOF;
} const MAP_SOF = false;
/**
* @return bool indiquer s'il faut appeler {@link mapper()} une dernière fois
* à la fin de l'itération avec {null => null}. cela permet d'implémenter des
* méthodes de mapping plus complexes.
*/
protected function MAP_EOF(): bool {
return static::MAP_EOF;
} const MAP_EOF = false;
/**
* @param iterable|null $source dans les classes dérivées, cet argument doit
* par convention être le dernier de la liste
*/
function __construct(?iterable $source=null) {
$params = null;
if ($source !== null) $params["source"] = $source;
parent::__construct($params);
}
/**
* @var array liste de paramètres pour la configuration générique de cet objet
*/
const PARAMETRABLE_PARAMS_SCHEMA = [
"source" => ["iterable", null, "source de données"],
];
/** @var iterable */
protected $ppSource;
function hasOvalue(string $name): bool {
if ($this->_hasOobdValue($name)) {
return true;
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return true;
} elseif ($this->ppSource instanceof IOobdManager) {
return $this->ppSource->hasOvalue($name);
}
return false;
}
function getOvalue(string $name, $default=null) {
if ($this->_hasOobdValue($name)) {
return $this->_getOobdValue($name, $default);
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return $this->sharedOobdManager->getOvalue($name, $default);
} elseif ($this->ppSource instanceof IOobdManager) {
return $this->ppSource->getOvalue($name, $default);
}
return null;
}
/** @var bool */
private $setup = false;
function ensureSetup(): void {
if (!$this->setup) {
$this->setup();
$this->setup = true;
}
}
/**
* initialiser l'itérateur. cette méthode est appelée avant de lancer
* l'itération
*/
protected function setup(): void {
}
function getIterator(): iterable {
$this->ensureSetup();
$this->sof = false;
$this->eof = false;
try {
$mapFunc = func::_prepare([$this, "mapper"]);
$outIndex = 0;
$srcIndex = 0;
if ($this->MAP_SOF()) {
$this->sof = true;
while (true) {
$this->action = self::USE_MAPPED_VALUE_ACTION;
$this->mappedIterables = null;
$mappedValue = func::_call($mapFunc, [null, null]);
if ($this->action !== self::RETRY_ACTION) break;
}
switch ($this->action) {
case self::USE_MAPPED_VALUE_ACTION:
if ($mappedValue !== null) {
yield $outIndex => $mappedValue;
$outIndex++;
}
break;
case self::USE_MAPPED_ITERABLES_ACTION:
foreach ($this->mappedIterables as $mappedIterable) {
$seqIndex = 0;
foreach ($mappedIterable as $mappedKey => $mappedValue) {
if ($mappedKey === $seqIndex) {
# seq
yield $outIndex => $mappedValue;
$outIndex++;
$seqIndex++;
} else {
# assoc
yield $mappedKey => $mappedValue;
}
}
}
break;
}
$this->sof = false;
}
if ($this->ppSource !== null) {
foreach ($this->ppSource as $key => $value) {
while (true) {
$this->mappedIterables = null;
$this->action = self::USE_MAPPED_VALUE_ACTION;
$mappedValue = func::_call($mapFunc, [$value, $key]);
if ($this->action !== self::RETRY_ACTION) break;
}
switch ($this->action) {
case self::USE_MAPPED_VALUE_ACTION:
if ($mappedValue !== null) {
if ($key === $srcIndex) {
yield $outIndex => $mappedValue;
$outIndex++;
} else {
yield $key => $mappedValue;
}
}
break;
case self::USE_MAPPED_ITERABLES_ACTION:
foreach ($this->mappedIterables as $mappedIterable) {
$seqIndex = 0;
foreach ($mappedIterable as $mappedKey => $mappedValue) {
if ($mappedKey === $seqIndex) {
# seq
yield $outIndex => $mappedValue;
$outIndex++;
$seqIndex++;
} else {
# assoc
yield $mappedKey => $mappedValue;
}
}
}
break;
}
if ($key === $srcIndex) $srcIndex++;
}
}
if ($this->MAP_EOF()) {
$this->eof = true;
while (true) {
$this->action = self::USE_MAPPED_VALUE_ACTION;
$this->mappedIterables = null;
$mappedValue = func::_call($mapFunc, [null, null]);
if ($this->action !== self::RETRY_ACTION) break;
}
switch ($this->action) {
case self::USE_MAPPED_VALUE_ACTION:
if ($mappedValue !== null) {
yield $outIndex => $mappedValue;
}
break;
case self::USE_MAPPED_ITERABLES_ACTION:
foreach ($this->mappedIterables as $mappedIterable) {
$seqIndex = 0;
foreach ($mappedIterable as $mappedKey => $mappedValue) {
if ($mappedKey === $seqIndex) {
# seq
yield $outIndex => $mappedValue;
$outIndex++;
$seqIndex++;
} else {
# assoc
yield $mappedKey => $mappedValue;
}
}
}
break;
}
}
} finally {
$this->close();
}
}
/** terminer l'itération. cette méthode est appelée à la fin de l'itération */
protected function teardown(): void {
if ($this->ppSource instanceof ICloseable) $this->ppSource->close();
}
function close(): void {
if ($this->setup) {
$this->teardown();
$this->setup = false;
}
}
/**
* @var bool indique que l'on est au début de l'itération si et seulement si
* {@link MAP_SOF()} retourne true
*/
protected $sof;
/**
* @var bool indique que l'on est à la fin de l'itération si et seulement si
* {@link MAP_EOF()} retourne true
*/
protected $eof;
const USE_MAPPED_VALUE_ACTION = 0;
const USE_MAPPED_ITERABLES_ACTION = 1;
const RETRY_ACTION = 2;
/** @var bool action à effectuer au retour de la fonction {@link mapper()} */
private $action;
/** @var array valeurs générées par les méthodes {@link mapTo()} */
private $mappedIterables;
/**
* indiquer que la valeur courante doit être mappée vers $values. si $values
* est null, la valeur courante n'est pas mappée.
* Cette méthode doit être appelée depuis le corps de {@link mapper()}
*/
function mapTo(?iterable $values) {
if ($this->action !== self::USE_MAPPED_ITERABLES_ACTION) {
$this->action = self::USE_MAPPED_ITERABLES_ACTION;
$this->mappedIterables = [];
}
if ($values !== null) {
$this->mappedIterables[] = $values;
}
return null;
}
/**
* indiquer que la valeur courante doit être mappée vers $value avec
* éventuellement la clé $key. $value est fournie telle quelle: notamment,
* cette méthode peut être utilisée pour mapper la valeur courante vers null.
* Cette méthode doit être appelée depuis le corps de {@link mapper()}
*/
function mapToValue($value, $key=null) {
if ($key !== null) return $this->mapTo([$key => $value]);
else return $this->mapTo([$value]);
}
/**
* indiquer que la valeur courante n'est pas mappée.
* Cette méthode doit être appelée depuis le corps de {@link mapper()}
*/
function mapToNone() {
return $this->mapTo(null);
}
/**
* indiquer que la valeur courante ne peut pas être traitée pour le moment et
* qu'elle doit de nouveau être présentée à la fonction {@link mapper()} la
* fois suivante. cela permet d'implémenter des méthodes de mapping plus
* complexes. attention tout de même à ne pas créer de boucles infinies.
* Cette méthode doit être appelée depuis le corps de {@link mapper()}
*/
function retry() {
$this->action = self::RETRY_ACTION;
return null;
}
/**
* mapper la valeur courante $item. il faut
* - soit retourner la valeur mappée. si on retourne null, la valeur n'est pas
* mappée (il n'y a pas de valeur correspondante, elle est donc ignorée).
* avec cette façon de faire, les clés originales sont conservées
* - soit appeler l'une des méthodes {@link mapTo()}, {@link mapToValue()},
* {@link mapToNone()} pour indiquer la ou les valeurs correspondantes (ainsi
* que les clés correspondantes le cas échéant). avec cette façon de faire,
* les clés originales sont perdues
*
* Dans les classes dérivées, il est possible de rajouter "$key=null" à la
* signature de cette fonction pour avoir aussi la clé
*/
abstract function mapper($item);
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getSource' => 'source',
];
const _AUTO_SETTERS = /*autogen*/[
'setSource' => 'source',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,71 @@
<?php
namespace nur\mapper\base;
use ArrayAccess;
use nur\b\coll\TBaseArray;
use nur\b\coll\TGenericArray;
use nur\mapper\base\oobd\IOobdManager;
/**
* Class MapperAggregate: un aggrégateur de mappers
*/
class MapperAggregate extends Mapper implements ArrayAccess {
use TBaseArray, TGenericArray;
function set($key, $mapper): self { return $this->_set($key, mapper_utils::ensure_mapper_class($mapper)); }
function add($mapper): self { return $this->_set(null, mapper_utils::ensure_mapper_class($mapper)); }
function setup(): void {
parent::setup();
$this->producer = new PushProducer();
$this->iterator = mapper_utils::assemble_mappers($this->data, $this->producer);
}
/** @var PushProducer */
private $producer;
/** @var Mapper */
private $iterator;
function hasOvalue(string $name): bool {
if ($this->_hasOobdValue($name)) {
return true;
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return true;
} elseif ($this->iterator instanceof IOobdManager) {
return $this->iterator->hasOvalue($name);
}
return false;
}
/**
* c'est la méthode {@link setup()} qui contruit $this->iterator. l'accès
* aux données OOB de la chaine de mapper n'est donc possible que dans la
* méthodes {@link mapper()}
*/
function getOvalue(string $name, $default=null) {
if ($this->_hasOobdValue($name)) {
return $this->_getOobdValue($name, $default);
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return $this->sharedOobdManager->getOvalue($name, $default);
} elseif ($this->iterator instanceof IOobdManager) {
return $this->iterator->getOvalue($name, $default);
}
return null;
}
function mapper($item) {
$this->producer->push($item);
return $this->mapTo($this->iterator);
}
function teardown(): void {
parent::teardown();
$this->producer->close();
$this->producer = null;
$this->iterator->close();
$this->iterator = null;
}
}

View File

@ -0,0 +1,88 @@
<?php
namespace nur\mapper\base;
use Generator;
use IteratorAggregate;
use nur\b\ICloseable;
use nur\b\params\IParametrable;
use nur\b\params\Parametrable;
use nur\mapper\base\oobd\IOobdManager;
use nur\mapper\base\oobd\TOobdManager;
/**
* Class Producer: un générateur qu'il est possible de piloter
* - notamment, il est possible de le fermer avant d'arriver à la fin de la
* génération avec la méthode {@link close()}
*
* La méthode {@link close()} doit être appelée à la fin de la génération, ce
* qui est automatique si on utilise des outils qui supportent {@link ICloseable}.
* le cas échéant, il faut le faire manuellement:
* ~~~
* $producer = new MyProducer();
* try {
* foreach ($producer as $value) {
* ...
* }
* } finally {
* $producer->close();
* }
* ~~~
*/
abstract class Producer extends Parametrable implements IteratorAggregate, ICloseable, IParametrable, IOobdManager {
use TOobdManager;
/**
* initialiser le générateur. cette méthode est appelée avant de lancer
* la génération
*/
protected function setup(): void {
}
/**
* terminer le générateur. cette méthode est appelée à la fin de la génération
*/
protected function teardown(): void {
}
/**
* retourner un générateur qui produit les données. ce générateur doit être
* préparé à prendre une exception {@link StopException} à chaque occurrence
* de yield
*/
abstract function producer();
/** @var Generator */
private $generator;
function getIterator() {
$this->setup();
$this->return = null;
return $this->generator = $this->producer();
}
function close(): void {
if ($this->generator !== null) {
if ($this->generator instanceof Generator) {
try {
$this->generator->throw(new StopException());
$hasReturned = true;
} catch (StopException $e) {
$hasReturned = false;
}
$this->return = $hasReturned? $this->generator->getReturn(): null;
}
$this->generator = null;
}
$this->teardown();
}
protected $return;
/**
* retourner la valeur de retour du générateur. il faut d'abord appeler la
* méthode {@link close()}
*/
function getReturn() {
return $this->return;
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace nur\mapper\base;
use ArrayAccess;
use nur\b\coll\TBaseArray;
use nur\b\coll\TGenericArray;
use nur\mapper\base\oobd\IOobdManager;
/**
* Class ProducerAggregate: un aggrégateur de producer, qui fournit les données
* des producers qu'il aggrège
*/
class ProducerAggregate extends Producer implements ArrayAccess {
use TBaseArray, TGenericArray;
function setup(): void {
parent::setup();
foreach ($this->data as &$producer) {
$producer = producer_utils::ensure_producer($producer);
}; unset($producer);
}
function hasOvalue(string $name): bool {
if ($this->_hasOobdValue($name)) {
return true;
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return true;
}
foreach ($this->data as $producer) {
if ($producer instanceof IOobdManager && $producer->hasOvalue($name)) {
return true;
}
}
return false;
}
/**
* l'accès aux données OOB de la chaine de producer n'est possible qu'après
* l'appel de la méthode {@link setup()}
*/
function getOvalue(string $name, $default=null) {
if ($this->_hasOobdValue($name)) {
return $this->_getOobdValue($name, $default);
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return $this->sharedOobdManager->getOvalue($name, $default);
}
foreach ($this->data as $producer) {
if ($producer instanceof IOobdManager && $producer->hasOvalue($name)) {
return $producer->getOvalue($name, $default);
}
}
return null;
}
function producer() {
foreach ($this->data as $producer) {
if ($producer instanceof Producer) {
try {
yield from $producer;
} finally {
$producer->close();
}
$this->return = $producer->getReturn();
} else {
yield from $producer;
}
}
}
protected function teardown(): void {
parent::teardown();
foreach ($this->data as $producer) {
$producer->close();
}
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace nur\mapper\base;
use nur\A;
/**
* Class PushProducer: un producer à qui on peut fournir les valeurs à produire
* au fil de l'eau
*/
class PushProducer extends Producer {
private $iterables;
function push($value, $key=null): void {
$values = $key !== null? [$key => $value]: [$value];
A::append($this->iterables, $values);
}
function pushAll(iterable $values): void {
A::append($this->iterables, $values);
}
function producer() {
if ($this->iterables !== null) {
try {
foreach ($this->iterables as $iterable) {
yield from $iterable;
}
} finally {
$this->iterables = null;
}
}
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace nur\mapper\base;
use nur\b\io\EOFException;
/**
* Class StopException: une exception qui indique qu'un générateur doit
* s'arrêter
*/
class StopException extends EOFException {
public function __construct() {
parent::__construct(null);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace nur\mapper\base;
trait Tencoding {
/** @var string */
protected $ppInputEncoding;
/** @var string */
protected $ppOutputEncoding;
protected function encodingInput__afterSetParametrableParams(array $modifiedKeys): void {
$input_encoding = in_array("input_encoding", $modifiedKeys)? $this->ppInputEncoding: null;
$output_encoding = in_array("output_encoding", $modifiedKeys)? $this->ppOutputEncoding: null;
if ($input_encoding !== null) {
$args = [$input_encoding];
if ($output_encoding !== null) $args[] = $output_encoding;
$this->setEncodingFilter(...$args);
}
}
protected function encodingOutput__afterSetParametrableParams(array $modifiedKeys): void {
$output_encoding = in_array("output_encoding", $modifiedKeys)? $this->ppOutputEncoding: null;
$input_encoding = in_array("input_encoding", $modifiedKeys)? $this->ppInputEncoding: null;
if ($output_encoding !== null) {
$args = [$output_encoding];
if ($input_encoding !== null) $args[] = $input_encoding;
$this->setEncodingFilter(...$args);
}
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace nur\mapper\base;
use nur\A;
use nur\types;
trait Tparams_command {
static function get_def(): array {
return static::DEF;
}
static function get(bool $create=true) {
if (self::$command === null && $create) static::create_command();
return self::$command;
}
static function add_params($params) {
foreach (A::with($params) as $param) {
mapper_utils::split_param($param, $name, $type, $value);
if ($type !== null) $value = types::with($type, $value);
self::$command->setParametrableParams([$name => $value]);
}
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace nur\mapper\base;
use nur\b\io\IWriter;
use nur\b\io\TmpfileWriter;
use nur\writer;
trait Ttmpwriter {
protected $ppOutput;
function pp_setOutput($output): void {
if ($output instanceof IWriter) $this->writer = $output;
else $this->ppOutput = $output;
}
protected $useTmpfile;
/**
* utiliser un fichier temporaire pour la sortie, puis créer $output en une
* seule fois à la fin, lors de la fermeture de l'objet.
*
* $useTmpfile peut être un booléen, mais aussi un nom de fichier: dans ce
* cas la fonctionnalité n'est activée que si $useTmpfile et $output
* désignent le même fichier
*/
function setUseTmpfile($useTmpfile=true): self {
if (is_string($useTmpfile)) {
if (writer::is_file($useTmpfile)) {
$useTmpfile = realpath($useTmpfile) === realpath($this->ppOutput);
} else {
$useTmpfile = false;
}
}
$this->useTmpfile = boolval($useTmpfile);
return $this;
}
/** @var IWriter */
protected $writer;
protected function buildWriter(bool $appendFilters, ?string $mode): void {
if ($mode === null) $mode = "w+b";
$this->writer = writer::with($this->ppOutput, $mode);
if ($appendFilters) $this->_rwAppendFilters($this->writer);
}
/** @var TmpfileWriter */
protected $tmpwriter;
protected function getWriter() {
return $this->useTmpfile? $this->tmpwriter: $this->writer;
}
protected function setupWriter(bool $useFilters, ?string $mode=null): void {
if ($this->useTmpfile) {
$this->tmpwriter = new TmpfileWriter();
} else if ($this->writer === null) {
$this->buildWriter($useFilters, $mode);
}
}
protected function teardownWriter(bool $useFilters, ?string $mode=null): void {
if ($this->tmpwriter !== null) {
$this->tmpwriter->close();
$this->buildWriter($useFilters, $mode);
$this->tmpwriter->getReader()->copyTo($this->writer);
$this->tmpwriter = null;
}
if ($this->writer !== null) {
$this->writer->close();
$this->writer = null;
}
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace nur\mapper\base\capacitor;
use ArrayAccess;
use nur\A;
use nur\data\types\Metadata;
/**
* Class Capacitor: classe outil qui permet d'accumuler des données pour les
* fournir en une seule fois au moment voulu
*/
class Capacitor implements ICapacitor, ArrayAccess {
use TCapacitor;
/** @var Metadata */
private static $key_md;
private static function key_md(): Metadata {
if (self::$key_md === null) {
self::$key_md = new Metadata(self::KEY_SCHEMA);
}
return self::$key_md;
}
/** @var Metadata */
private static $sort_md;
private static function sort_md(): Metadata {
if (self::$sort_md === null) {
self::$sort_md = new Metadata(self::SORT_SCHEMA);
}
return self::$sort_md;
}
/** @var array */
protected $kinfos;
/**
* spécifier les clés à traquer lors du chargement d'une donnée.
*
* si une clé est marquée comme primaire, alors les doublons éventuels sont
* supprimés au fur et à mesure du chargement.
*/
function setKeys(array $keys, ?string $channel=null): void {
self::key_md()->eachEnsureSchema($keys);
$namedkeys = [];
$pkeys = null;
foreach ($keys as $key) {
$namedkeys[$key["name"]] = $key;
if ($key["primary"]) $pkeys[] = $key;
}
$this->kinfos[$channel] = ["keys" => $namedkeys, "pkeys" => $pkeys];
}
private static function get_kvalues(array $keys, $item): array {
$item = A::with($item);
$kvalues = [];
foreach ($keys as $kname => $key) {
$kvalues[$kname] = A::get($item, $key["name"]);
}
return $kvalues;
}
private static function compute_itemkey(array $pkvalues): string {
return implode("-", $pkvalues);
}
private static function get_itemkey(?array $kinfos, $item): ?string {
if ($kinfos === null) return null;
$pkeys = $kinfos["pkeys"];
if ($pkeys === null) return null;
$pkvalues = self::get_kvalues($pkeys, $item);
return self::compute_itemkey($pkvalues);
}
/** @var array */
protected $data;
/**
* trier les données selon les clés spécifiées. NB: cette implémentation
* autorise qu'on utilise des clés qui n'ont pas été déclarées avec
* {@link setKeys()}
*
* @see ICapacitor::sort()
*/
function sort(?array $keys=null, ?string $channel=null): void {
$kinfos = A::get($this->kinfos, $channel);
$asort = $kinfos !== null && $kinfos["pkeys"] !== null;
$defaultKeys = $kinfos !== null? $kinfos["keys"]: null;
if ($keys !== null) {
self::sort_md()->eachEnsureSchema($keys);
} else {
$keys = $defaultKeys;
if ($keys === null) return;
}
foreach ($keys as $kname => &$key) {
if ($key["reverse"] === null && $defaultKeys !== null && array_key_exists($kname, $defaultKeys)) {
$key["reverse"] = $defaultKeys[$kname]["reverse"];
}
if ($key["reverse"] === null) $key["reverse"] = false;
}; unset($key);
$sortfunc = function ($a, $b) use ($keys) {
$akvs = self::get_kvalues($keys, $a);
$bkvs = self::get_kvalues($keys, $b);
foreach ($keys as $kname => $key) {
$akv = $akvs[$kname];
$bkv = $bkvs[$kname];
if ($akv == $bkv) continue;
if ($akv < $bkv) {
return $key["reverse"] ? 1 : -1;
} else {
return $key["reverse"] ? -1 : 1;
}
}
return 0;
};
if ($asort) uasort($this->data[$channel], $sortfunc);
else usort($this->data[$channel], $sortfunc);
}
function charge($item, ?string $channel=null, $pkvalues=null): void {
$kinfos = A::get($this->kinfos, $channel);
$itemkey = self::get_itemkey($kinfos, $item);
if ($itemkey === null && $item === null) {
if ($pkvalues === null) return;
$itemkey = self::compute_itemkey($pkvalues);
}
A::set($this->data[$channel], $itemkey, $item);
}
function getItem($pkvalues, ?string $channel=null, $default=null) {
if (is_array($pkvalues)) {
$itemkey = self::compute_itemkey($pkvalues);
} else {
$itemkey = $pkvalues;
}
return A::_pget($this->data, [$channel, $itemkey], $default);
}
function discharge(?string $channel=null, bool $remove=true): iterable {
$items = A::get($this->data, $channel, []);
if ($remove) A::del($this->data, $channel);
return $items;
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace nur\mapper\base\capacitor;
/**
* Interface ICapacitor: objet qui permet d'accumuler des données pour les
* fournir en une seule fois au moment voulu
*/
interface ICapacitor {
const KEY_SCHEMA = [
"name" => ["key", null, "nom de la clé", "required" => true],
"primary" => ["bool", false, "est-ce une clé primaire?"],
"reverse" => ["?bool", null, "sens du tri par défaut"],
];
/**
* spécifier les clés à traquer lors du chargement d'une donnée.
*
* si une clé est marquée comme primaire, alors les doublons éventuels sont
* supprimés au fur et à mesure du chargement.
*/
function setKeys(array $keys, ?string $channel=null): void;
const SORT_SCHEMA = [
"name" => ["key", null, "nom de la clé", "required" => true],
"reverse" => ["?bool", null, "sens du tri"],
];
/**
* trier les données selon les clés spécifiées. si $keys===null, alors toutes
* les clés définies au préalable avec {@link setKeys()} sont utilisées.
*
* $keys ne doit mentionner que des clés déclarées avec {@link setKeys()}
*/
function sort(?array $keys=null, ?string $channel=null): void;
/**
* charger l'accumulateur avec un élément.
*
* Dans le cas $item === null, le comportement est particulier:
* - Si aucune clé primaire n'a été définie, l'élément est systématiquement
* chargé
* - Si une clé primaire a été définie, l'élément est ignoré, sauf si
* $pkvalues est spécifiée. Dans ce cas, $pkvalues est utilisé comme clé
* primaire
*/
function charge($item, ?string $channel=null, $pkvalues=null): void;
/** méthode de convenance pour charger l'accumulateur avec plusieurs éléments */
function chargeAll(iterable $items, ?string $channel=null): void;
/**
* obtenir un élément à partir de sa clé primaire. une clé primaire doit avoir
* été définie au préalable avec {@link setKeys()}
*
* si aucune clé primaire n'a été définie, alors le seul moyen d'atteindre un
* élément est par son index
*/
function getItem($pkvalues, ?string $channel=null, $default=null);
/** décharger l'accumulateur en une seule fois */
function discharge(?string $channel=null, bool $remove=true): iterable;
}

View File

@ -0,0 +1,30 @@
<?php
namespace nur\mapper\base\capacitor;
use ArrayAccess;
/**
* Class SqliteCapacitor: un {@link Capacitor} qui stocke les données
* sérialisées dans une base de données sqlite temporaire. L'avantage est de
* pouvoir faire des traitements supplémentaires sur la base de données avant
* de décharger les données
*/
class SqliteCapacitor implements ICapacitor, ArrayAccess {
use TCapacitor;
/**
* spécifier les clés à traquer lors du chargement d'une donnée.
*
* la table destination contiendra une colonne pour chaque clé spécifiée
*/
function setKeys(array $keys, ?string $channel=null): void {
}
/**
* spécifier l'ordre de tri pour la restitution avec {@link discharge()}.
* cette méthode se contente simplement de sauvegarder l'information de tri.
* ce n'est que lors de la restitution que le tri est effectivement effectué
*/
function sort(?array $keys=null, ?string $channel=null): void {
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace nur\mapper\base\capacitor;
use nur\b\IllegalAccessException;
trait TCapacitor {
function chargeAll(iterable $items, ?string $channel=null): void {
foreach ($items as $item) {
$this->charge($item, $channel);
}
}
function offsetExists($offset) { throw IllegalAccessException::not_implemented(); }
function offsetGet($offset) { throw IllegalAccessException::not_implemented(); }
function offsetUnset($offset) { throw IllegalAccessException::not_implemented(); }
function offsetSet($offset, $value) { $this->charge($value); }
}

View File

@ -0,0 +1,9 @@
<?php
namespace nur\mapper\base;
class encoding_utils {
const PARAMETRABLE_PARAMS_SCHEMA = [
"input_encoding" => ["?string", null, "encoding en entrée"],
"output_encoding" => ["?string", null, "encoding en sortie"],
];
}

View File

@ -0,0 +1,91 @@
<?php
namespace nur\mapper\base;
use nur\A;
use nur\b\ValueException;
use nur\base;
use nur\func;
use nur\mapper\base\oobd\IOobdManager;
use nur\str;
use ReflectionClass;
class mapper_utils {
static function ensure_mapper_class($mapper) {
if ($mapper instanceof Mapper) return $mapper;
if (is_callable($mapper)) return $mapper;
if (is_string($mapper) && is_subclass_of($mapper, Mapper::class)) {
return $mapper;
}
if (is_array($mapper) &&
array_key_exists(0, $mapper) &&
is_subclass_of($mapper[0], Mapper::class)) {
return $mapper;
}
throw ValueException::unexpected_type(Mapper::class, $mapper);
}
static function ensure_mapper($mapper, iterable $iterator): Mapper {
if ($mapper instanceof Mapper) {
if ($iterator !== null) $mapper->setSource($iterator);
} elseif (is_callable($mapper)) {
$mapper = new FuncMapper($mapper, $iterator);
} else {
if (!is_array($mapper)) $mapper = [$mapper];
[$args, $params] = A::split_assoc($mapper);
if ($args === null) throw new ValueException("mapper class is required");
$c = new ReflectionClass($args[0]);
$rf = $c->getConstructor();
if ($rf !== null && !$rf->isVariadic()) {
# NB: $maxArgs ne doit pas tenir compte du dernier argument $source,
# mais comme le premier élément de $args est la classe, ça colle
$maxArgs = $rf->getNumberOfParameters();
$args = array_slice($mapper, 0, $maxArgs);
while (count($args) < $maxArgs) {
$args[] = null;
}
}
$args[] = $iterator;
$mapper = func::cons(...$args);
if ($params !== null) $mapper->setParametrableParams($params);
}
if ($iterator instanceof IOobdManager) {
# récupérer le gestionnaire partagé le cas échéant
$sharedOobdManager = $iterator->getSharedOobdManager();
if ($sharedOobdManager !== null) $mapper->setSharedOobdManager($sharedOobdManager);
}
return $mapper;
}
static function assemble_mappers(array $mappers, iterable $iterator): iterable {
foreach ($mappers as $mapper) {
$iterator = self::ensure_mapper($mapper, $iterator);
}
return $iterator;
}
public static function check_prefix(string $value, ?string &$params, string ...$prefixes): bool {
foreach ($prefixes as $prefix) {
if (preg_match('/^[A-Za-z0-9_]+$/', $prefix)) {
# correspondance exacte pour une commande alpha-numérique
if ($value == $prefix) {
$params = null;
return true;
}
} elseif (str::_starts_with($prefix, $value)) {
$params = str::without_prefix($prefix, $value);
return true;
}
}
return false;
}
static function split_param(string $param, ?string &$name, ?string &$type, ?string &$value): bool {
if (preg_match('/^([A-Za-z0-9_]*)(?::([^=]+))?(?:=(.*))?$/', $param, $vs)) {
$name = base::vn(A::get($vs, 1));
$type = base::vn(A::get($vs, 2));
$value = base::vn(A::get($vs, 3));
return true;
}
return false;
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace nur\mapper\base;
use nur\cli\DynamicCommand;
use nur\mapper\csv\Assoc2CsvMapper_command;
use nur\mapper\csv\Csv2AssocMapper_command;
use nur\mapper\fsv\Assoc2FsvMapper_command;
use nur\mapper\fsv\Fsv2AssocMapper_command;
use nur\mapper\item\Assoc2SeqMapper_command;
use nur\mapper\item\AttributeFilterMapper_command;
use nur\mapper\item\GenericMapper_command;
use nur\mapper\item\ItemFilterMapper_command;
use nur\mapper\item\NumberMapper_command;
use nur\mapper\item\SchemaMapper_command;
use nur\mapper\item\Seq2AssocMapper_command;
use nur\mapper\item\StreamMapper_command;
use nur\mapper\item\StringMapper_command;
use nur\mapper\item\TextMapper_command;
use nur\mapper\line\IconvMapper_command;
/**
* Class mappers_command: classe de support pour construire un ensemble de
* mappers pour une application recevant des commandes utilisateurs
*/
class mappers_command extends DynamicCommand {
const PRODUCER_CLASSES = [
];
const CONSUMER_CLASSES = [
];
const MAPPER_CLASSES = [
# base
FuncMapper_command::class,
# item
StreamMapper_command::class,
StringMapper_command::class,
TextMapper_command::class,
NumberMapper_command::class,
GenericMapper_command::class,
Seq2AssocMapper_command::class,
SchemaMapper_command::class,
ItemFilterMapper_command::class,
AttributeFilterMapper_command::class,
Assoc2SeqMapper_command::class,
# line
IconvMapper_command::class,
# csv
Csv2AssocMapper_command::class,
Assoc2CsvMapper_command::class,
# fsv
Fsv2AssocMapper_command::class,
Assoc2FsvMapper_command::class,
];
protected function DYNAMIC_COMMAND_CLASSES(): array {
return array_merge(self::PRODUCER_CLASSES, self::CONSUMER_CLASSES, self::MAPPER_CLASSES);
}
function COMMANDS(): array {
$commands = [];
foreach ($this->DYNAMIC_COMMAND_CLASSES() as $cc) {
$commands[] = $cc::get_def();
}
return $commands;
}
static $mappers = [];
static function add(Mapper $mapper): Mapper {
self::$mappers[] = $mapper;
return $mapper;
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace nur\mapper\base;
use nur\A;
use nur\mapper\base\oobd\IOobdManager;
class mark_utils {
static function compute($item, ?array $keys): string {
$item = A::with($item);
if ($keys === null) $keys = array_keys($item);
$parts = [];
foreach ($keys as $key) {
$part = A::get($item, $key);
if (is_array($part)) {
$part = "[".self::compute($part, null)."]";
} else {
$part = strval($part);
}
$parts[] = $part;
}
return sha1(implode("|", $parts));
}
static function set_use_marks(IOobdManager $manager, ?string $prefix=null, bool $shared=true): void {
$manager->setOvalue("mark:$prefix:have_marks", true, $shared);
}
static function is_use_marks(IOobdManager $manager, ?string $prefix=null): bool {
return boolval($manager->getOvalue("mark:$prefix:have_marks"));
}
static function set_use_keys_for_item_marked(IOobdManager $manager, ?array $keys, ?string $prefix=null): void {
$manager->setOvalue("mark:$prefix:keys", $keys);
}
static function set_item_marked(IOobdManager $manager, $item, ?string $prefix=null, bool $shared=true): void {
$keys = $manager->getOvalue("mark:$prefix:keys");
$mark = self::compute($item, $keys);
$manager->setOvalue("mark:$prefix:marks:$mark", true, $shared);
$manager->setOvalue("mark:$prefix:have_marks", true, $shared);
}
static function is_item_marked(IOobdManager $manager, $item, ?string $prefix=null): bool {
$keys = $manager->getOvalue("mark:$prefix:keys");
$mark = self::compute($item, $keys);
return boolval($manager->getOvalue("mark:$prefix:marks:$mark"));
}
}

View File

@ -0,0 +1,72 @@
<?php
namespace nur\mapper\base\oobd;
/**
* Class IOobdManager: un gestionnaire de données out-of-band
*/
interface IOobdManager {
/** obtenir l'instance partagée, ou null s'il n'y en a pas */
function getSharedOobdManager(): ?IOobdManager;
/** spécifier l'instance partagée */
function setSharedOobdManager(IOobdManager $sharedOobdManager);
/** s'assurer de l'existence d'une instance partagée */
function ensureSharedOobdManager(): void;
/** vérifier si la valeur spécifiée existe */
function hasOvalue(string $name): bool;
/** obtenir la valeur spécifiée, ou $default si elle n'existe pas */
function getOvalue(string $name, $default=null);
/**
* spécifier la valeur
*
* si $value est une valeur booléenne, le traitement est particulier: la
* valeur courante n'est modifiée que si elle ne vaut pas exactement true.
* cela permet de spécifier un flag qui ne change plus une fois qu'il reçoit
* la valeur vraie
*
* si $shared==true, attaquer l'éventuelle instance partagée. sinon, c'est la
* valeur locale qui est modifiée.
*
* retourner la valeur finale du paramètre
*/
function setOvalue(string $name, $value, bool $shared=true);
/**
* incrémenter la valeur. si la valeur n'existait pas au préalable, considèrer
* que sa valeur était zéro
*
* si $shared==true, attaquer l'éventuelle instance partagée. sinon, c'est la
* valeur locale qui est modifiée.
*
* retourner la valeur finale du paramètre
*/
function incOvalue(string $name, bool $shared=true): int;
/**
* décrémenter la valeur. si la valeur n'existait pas au préalable, considèrer
* que sa valeur était zéro
*
* si $shared==true, attaquer l'éventuelle instance partagée. sinon, c'est la
* valeur locale qui est modifiée.
*
* retourner la valeur finale du paramètre
*/
function decOvalue(string $name, bool $shared=true): int;
/**
* spécifier la valeur de façon inconditionnelle
*
* si $value===null, la valeur courante est supprimée. il n'y a pas de
* traitement particulier si $value est une valeur booléenne
*
* si $shared==true, attaquer l'éventuelle instance partagée. sinon, c'est la
* valeur locale qui est modifiée.
*
* retourner la valeur finale du paramètre
*/
function resetOvalue(string $name, $value=null, bool $shared=true);
}

View File

@ -0,0 +1,20 @@
<?php
namespace nur\mapper\base\oobd;
use nur\str;
class OobdManager implements IOobdManager {
use TOobdManager;
function getKeyPrefixValues(?string $keyPrefix=null): ?array {
if ($keyPrefix === null) return $this->oobdValues;
$values = null;
foreach ($this->oobdValues as $key => $value) {
if (is_string($key) && str::_starts_with($keyPrefix, $key)) {
$key = str::without_prefix($keyPrefix, $key);
$values[$key] = $value;
}
}
return $values;
}
}

View File

@ -0,0 +1,119 @@
<?php
namespace nur\mapper\base\oobd;
use nur\A;
use nur\mapper\base\mark_utils;
trait TOobdManager {
/** @var IOobdManager instance partagée */
protected $sharedOobdManager;
function getSharedOobdManager(): ?IOobdManager {
return $this->sharedOobdManager;
}
function setSharedOobdManager(IOobdManager $sharedOobdManager): self {
$this->sharedOobdManager = $sharedOobdManager;
return $this;
}
function ensureSharedOobdManager(): void {
if ($this->sharedOobdManager === null) {
$this->sharedOobdManager = new OobdManager();
}
}
protected $oobdValues;
protected function _hasOobdValue(string $name): bool {
return A::has($this->oobdValues, $name);
}
protected function _getOobdValue(string $name, $default) {
return A::get($this->oobdValues, $name, $default);
}
protected function _setOobdValue(string $name, $value): void {
$this->oobdValues[$name] = $value;
}
protected function _unsetOobdValue(string $name): void {
unset($this->oobdValues[$name]);
}
function hasOvalue(string $name): bool {
if ($this->_hasOobdValue($name)) {
return true;
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return true;
}
return false;
}
function getOvalue(string $name, $default=null) {
if ($this->_hasOobdValue($name)) {
return $this->_getOobdValue($name, $default);
} elseif ($this->sharedOobdManager !== null
&& $this->sharedOobdManager->hasOvalue($name)) {
return $this->sharedOobdManager->getOvalue($name, $default);
}
return null;
}
function setOvalue(string $name, $value, bool $shared=true) {
if ($shared && $this->sharedOobdManager !== null) {
return $this->sharedOobdManager->setOvalue($name, $value, false);
} else {
$oldValue = $this->_getOobdValue($name, null);
if (is_bool($value) && $oldValue === true) {
$value = $oldValue;
} else {
$this->_setOobdValue($name, $value);
}
return $value;
}
}
function incOvalue(string $name, bool $shared=true): int {
if ($shared && $this->sharedOobdManager !== null) {
return $this->sharedOobdManager->incOvalue($name, false);
} else {
$value = intval($this->_getOobdValue($name, 0)) + 1;
$this->_setOobdValue($name, $value);
return $value;
}
}
function decOvalue(string $name, bool $shared=true): int {
if ($shared && $this->sharedOobdManager !== null) {
return $this->sharedOobdManager->incOvalue($name, false);
} else {
$value = intval($this->_getOobdValue($name, 0)) - 1;
$this->_setOobdValue($name, $value);
return $value;
}
}
function resetOvalue(string $name, $value=null, bool $shared=true) {
if ($shared && $this->sharedOobdManager !== null) {
return $this->sharedOobdManager->resetOvalue($name, $value, false);
} else {
if ($value === null) $this->_unsetOobdValue($name);
else $this->_setOobdValue($name, $value);
return $value;
}
}
function setUseKeysForItemMarked(?array $keys, ?string $prefix=null): void {
mark_utils::set_use_keys_for_item_marked($this, $keys, $prefix);
}
function setItemMarked($item, ?string $prefix=null, bool $shared=true): void {
mark_utils::set_item_marked($this, $item, $prefix, $shared);
}
function isItemMarked($item, ?string $prefix=null): bool {
return mark_utils::is_item_marked($this, $item, $prefix);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace nur\mapper\base;
use nur\b\ValueException;
use nur\func;
use Traversable;
class producer_utils {
static function ensure_producer($producer, ?array $args=null): iterable {
if (is_array($producer) &&
array_key_exists(0, $producer) &&
is_subclass_of($producer[0], Producer::class)) {
$producer = array_merge($producer, $args);
return func::cons(...$producer);
}
if (is_iterable($producer)) return $producer;
if (is_string($producer) && is_subclass_of($producer, Traversable::class)) {
if ($args === null) $args = [];
return func::cons($producer, ...$args);
}
throw ValueException::unexpected_type(Producer::class, $producer);
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace nur\mapper\csv;
use nur\A;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\b\ValueException;
use nur\mapper\base\Mapper;
abstract class AbstractCsvMapper extends Mapper {
use Tparametrable;
const SEPARATOR = csv_defaults::OO_SEPARATOR;
const ENCLOSURE = csv_defaults::OO_ENCLOSURE;
const ESCAPE = csv_defaults::OO_ESCAPE;
function __construct(?iterable $source=null) {
parent::__construct($source);
$this->separator = static::SEPARATOR;
$this->enclosure = static::ENCLOSURE;
$this->escape = static::ESCAPE;
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"csv_flavour" => [null, null, "type de fichier CSV: excel ou ooffice"],
"multi_schema" => ["bool", false, "les flux multi-schémas sont-ils supportés?"],
];
protected $separator;
protected $enclosure;
protected $escape;
function getCsvFlavour(): array {
return [$this->separator, $this->enclosure, $this->escape];
}
/** @param $csvFlavour string|array */
function pp_setCsvFlavour($csvFlavour): void {
if ($csvFlavour === null) {
$csvFlavour = [static::SEPARATOR, static::ENCLOSURE, static::ESCAPE];
}
if (is_string($csvFlavour)) {
$csvFlavour = csv_defaults::verifix_name($csvFlavour);
switch ($csvFlavour) {
case csv_defaults::OO_NAME:
$csvFlavour = csv_defaults::OO_FLAVOUR;
break;
case csv_defaults::XL_NAME:
$csvFlavour = csv_defaults::XL_FLAVOUR;
break;
default:
if (strlen($csvFlavour) <= 3) {
$separator = substr($csvFlavour, 0, 1);
if (!$separator) $separator = static::SEPARATOR;
$enclosure = substr($csvFlavour, 1, 1);
if (!$enclosure) $enclosure = static::ENCLOSURE;
$escape = substr($csvFlavour, 2, 1);
if (!$escape) $escape = static::ESCAPE;
$csvFlavour = [$separator, $enclosure, $escape];
}
break;
}
}
if (is_array($csvFlavour)) {
[$this->separator, $this->enclosure, $this->escape] = $csvFlavour;
} else {
throw ValueException::invalid_value($csvFlavour, "csv flavour");
}
}
/** @param $csvFlavour string|array */
function setCsvFlavour($csvFlavour): self {
$this->pp_setCsvFlavour($csvFlavour);
return $this;
}
/** @var bool les fichiers avec plusieurs schémas sont-ils supportés? */
protected $ppMultiSchema;
function setMultiSchema(bool $multiSchema=true): self {
$this->ppMultiSchema = $multiSchema;
return $this;
}
}

View File

@ -0,0 +1,153 @@
<?php
namespace nur\mapper\csv;
use nur\A;
use nur\b\params\Tparametrable;
use nur\str;
/**
* Class Assoc2CsvMapper: mapper qui convertir un flux de tableaux associatifs
* en flux de lignes au format CSV
*
* --autogen-properties-and-methods--
* @method array getHeaders()
* @method bool isOutputHeaders()
* @method array setHeaders(array $value)
* @method bool setOutputHeaders(bool $value)
*/
class Assoc2CsvMapper extends AbstractCsvMapper {
use Tparametrable;
const MAP_EOF = true;
const PARAMETRABLE_PARAMS_SCHEMA = [
"headers" => ["array", null, "liste et ordre des champs en sortie"],
"output_headers" => ["bool", true, "faut-il afficher les en-têtes en sortie?"],
];
/**
* @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 $ppHeaders;
/** @var bool faut-il afficher les en-têtes en sortie? */
protected $ppOutputHeaders = true;
private static final function is_different(array $h1, array $h2): bool {
sort($h1);
sort($h2);
return $h1 != $h2;
}
private function computeHeaders(array $row) {
return array_keys($row);
}
function _checkHeaders(?array $row): array {
$skipLine = false;
if ($row === null) {
$outputHeaders = $this->ppOutputHeaders && $this->ppHeaders !== null;
return [$skipLine, $outputHeaders, $this->ppHeaders];
}
$prevHeaders = $this->ppHeaders;
if ($this->ppMultiSchema) {
# 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->ppOutputHeaders = true;
} else {
$headers = $prevHeaders;
}
} else {
$headers = $prevHeaders;
if ($headers === null) $headers = $this->computeHeaders($row);
}
return [$skipLine, $this->ppOutputHeaders, $headers];
}
function _setOutputHeaders(array $headers): void {
$this->ppHeaders = $headers;
$this->ppOutputHeaders = false;
}
function _cookHeaders(array $headers): array {
return $headers;
}
function _cookValues(array $headers, array $row): array {
$values = [];
foreach ($headers as $header) {
$values[] = A::get($row, $header, false);
}
return $values;
}
function getLine(array $values, bool $stripNl=true): string {
$tmpf = fopen("php://memory", "w+b");
fputcsv($tmpf, $values, $this->separator, $this->enclosure, $this->escape);
rewind($tmpf);
$line = stream_get_contents($tmpf);
if ($stripNl) $line = str::strip_nl($line);
fclose($tmpf);
return $line;
}
function mapper($item) {
if ($this->eof) $row = null;
else $row = A::with($item);
$lines = [];
[$skipLine, $outputHeaders, $headers] = $this->_checkHeaders($row);
if ($skipLine) $lines[] = "";
if ($outputHeaders) {
$lines[] = $this->getLine($this->_cookHeaders($headers));
$this->_setOutputHeaders($headers);
}
if ($row !== null) {
$lines[] = $this->getLine($this->_cookValues($headers, $row));
}
$this->mapTo($lines);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getHeaders' => 'headers',
'isOutputHeaders' => 'output_headers',
];
const _AUTO_SETTERS = /*autogen*/[
'setHeaders' => 'headers',
'setOutputHeaders' => 'output_headers',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,33 @@
<?php
namespace nur\mapper\csv;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class Assoc2CsvMapper_command: classe de support pour construire une instance
* de mapper pour une application recevant des commandes utilisateurs
*/
class Assoc2CsvMapper_command {
use Tparams_command;
const NAME = "assoc2csv";
const DEF = [self::NAME,
"help" => "convertir en CSV",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var Assoc2CsvMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new Assoc2CsvMapper());
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace nur\mapper\csv;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\mapper\item\Seq2AssocMapper;
/**
* Class Csv2AssocMapper: mapper qui analyse un flux de lignes au format CSV et
* qui produit un flux de tableaux associatifs
*
* NB: cette classe ne supporte pas les flux CSV multi-lignes, contrairement à
* {@link CsvReader}
*
* --autogen-properties-and-methods--
* @method int getSkipLines()
* @method bool|null setParseHeaders(?bool $value)
* @method array|null setHeaders(?array $value)
* @method int setSkipLines(int $value)
* @method string|null setMapEmpty(?string $value)
*/
class Csv2AssocMapper extends AbstractCsvMapper {
use Tparametrable;
function __construct(?iterable $source=null) {
$this->seq2assoc = new Seq2AssocMapper();
parent::__construct($source);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"parse_headers" => ["?bool", null, "faut-il analyser le premier élément du flux pour calculer la liste des en-têtes en entrée?"],
"headers" => ["?array", null, "liste et ordre des en-têtes en entrée"],
"skip_lines" => ["int", 0, "nombre de lignes à sauter en entrée"],
"map_empty" => ["?string", null, "valeur adoptée pour les chaines vides"],
];
/** @var Seq2AssocMapper */
private $seq2assoc;
function pp_setParseHeaders(?bool $parseHeaders): void {
$this->seq2assoc->setParseKeys($parseHeaders);
}
function pp_setHeaders(?array $headers): void {
$this->seq2assoc->setKeys($headers);
}
/** @var int */
protected $ppSkipLines;
/** @var bool */
protected $shouldMapEmpty = false;
protected $ppMapEmpty = null;
function pp_setMapEmpty($mapEmpty=null): void {
$this->shouldMapEmpty = true;
$this->ppMapEmpty = $mapEmpty;
}
protected function setup(): void {
parent::setup();
$this->seq2assoc->ensureSetup();
}
function _parseLine(): bool {
if ($this->ppSkipLines > 0) {
$this->ppSkipLines--;
return false;
}
return true;
}
/** @var array */
private $headers;
function _checkHeader(array $values): bool {
if ($this->ppMultiSchema && count($values) === 1 && $values[0] === null) {
# ligne vide, changer de schéma
$this->seq2assoc->setKeys(null);
return true;
}
return $this->seq2assoc->_checkKeys($values);
}
function _mapRow(array $values): array {
$row = $this->seq2assoc->_mapValues($values);
foreach ($row as &$value) {
if ($value === "" && $this->shouldMapEmpty) $value = $this->ppMapEmpty;
}; unset($value);
return $row;
}
function mapper($item) {
if (!$this->_parseLine()) return $this->mapToNone();
$values = str_getcsv($item, $this->separator, $this->enclosure, $this->escape);
if ($this->_checkHeader($values)) return $this->mapToNone();
return $this->_mapRow($values);
}
protected function teardown(): void {
parent::teardown();
$this->seq2assoc->close();
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getParseHeaders' => 'parse_headers',
'getHeaders' => 'headers',
'getSkipLines' => 'skip_lines',
'getMapEmpty' => 'map_empty',
];
const _AUTO_SETTERS = /*autogen*/[
'setParseHeaders' => 'parse_headers',
'setHeaders' => 'headers',
'setSkipLines' => 'skip_lines',
'setMapEmpty' => 'map_empty',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,33 @@
<?php
namespace nur\mapper\csv;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class Csv2AssocMapper_command: classe de support pour construire une instance
* de mapper pour une application recevant des commandes utilisateurs
*/
class Csv2AssocMapper_command {
use Tparams_command;
const NAME = "csv2assoc";
const DEF = [self::NAME,
"help" => "analyser un flux CSV",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var Csv2AssocMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new Csv2AssocMapper());
}
}

View File

@ -0,0 +1,212 @@
<?php
namespace nur\mapper\csv;
use nur\A;
use nur\b\io\EOFException;
use nur\b\io\IReader;
use nur\b\io\Tfilter;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\data\types\Metadata;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Producer;
use nur\mapper\base\Tencoding;
use nur\mapper\line\IconvMapper;
use nur\reader;
/**
* Class CsvReader: produit un flux de données à partir d'une source au format
* CSV.
*
* NB: cette classe supporte les fichiers CSV multi-lignes, contrairement à
* {@link Csv2AssocMapper}
*
* --autogen-properties-and-methods--
* @method string|null getInputEncoding()
* @method string|null getOutputEncoding()
* @method setInput($value)
* @method string|null setInputEncoding(?string $value)
* @method string|null setOutputEncoding(?string $value)
* @method setCsvFlavour($value)
* @method bool setMultiSchema(bool $value)
* @method bool|null setParseHeaders(?bool $value)
* @method array|null setHeaders(?array $value)
* @method int setSkipLines(int $value)
* @method string|null setMapEmpty(?string $value)
*/
class CsvReader extends Producer {
use Tparametrable, Tencoding, Tfilter;
const SEPARATOR = csv_defaults::OO_SEPARATOR;
const ENCLOSURE = csv_defaults::OO_ENCLOSURE;
const ESCAPE = csv_defaults::OO_ESCAPE;
function __construct($input=null) {
$this->csv2assoc = new Csv2AssocMapper();
$this->csv2assoc->setCsvFlavour([static::SEPARATOR, static::ENCLOSURE, static::ESCAPE]);
$params = null;
if ($input !== null) $params["input"] = $input;
parent::__construct($params);
}
function setEncodingFilter(string $from, string $to="utf-8"): void {
$this->_setEncodingFilter($from, $to);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"input" => [null, null, "fichier en entrée"],
];
static function _get_parametrable_params_schema(): array {
return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA
, encoding_utils::PARAMETRABLE_PARAMS_SCHEMA
, AbstractCsvMapper::PARAMETRABLE_PARAMS_SCHEMA
, Csv2AssocMapper::PARAMETRABLE_PARAMS_SCHEMA);
}
protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void {
if (!in_array("input_encoding", $modifiedKeys)
&& in_array("csv_flavour", $modifiedKeys)) {
$flavourName = $this->csvFlavourName;
if ($flavourName !== null) {
$flavourName = csv_defaults::verifix_name($flavourName);
$this->ppInputEncoding = csv_defaults::get_encoding($flavourName);
$modifiedKeys[] = "input_encoding";
}
}
$this->encodingInput__afterSetParametrableParams($modifiedKeys);
}
protected $ppInput;
function pp_setInput($input): void {
if ($input instanceof IReader) $this->reader = $input;
else $this->ppInput = $input;
}
/** @var Csv2AssocMapper */
protected $csv2assoc;
/** @var string */
private $csvFlavourName;
function pp_setCsvFlavour($csvFlavour): void {
if (is_string($csvFlavour)) $this->csvFlavourName = $csvFlavour;
$this->csv2assoc->setCsvFlavour($csvFlavour);
}
function pp_setMultiSchema(bool $multiSchema): void {
$this->csv2assoc->setMultiSchema($multiSchema);
}
function pp_setParseHeaders(?bool $parseHeaders): void {
$this->csv2assoc->setParseHeaders($parseHeaders);
}
function pp_setHeaders(?array $headers): void {
$this->csv2assoc->setHeaders($headers);
}
function pp_setSkipLines(int $skipLines): void {
$this->csv2assoc->setSkipLines($skipLines);
}
function pp_setMapEmpty($mapEmpty=null): void {
$this->csv2assoc->setMapEmpty($mapEmpty);
}
/** @var IReader */
protected $reader;
protected function setup(): void {
if ($this->reader === null) {
$this->reader = reader::with($this->ppInput);
$this->_rwAppendFilters($this->reader);
}
$this->csv2assoc->ensureSetup();
}
protected function teardown(): void {
$this->csv2assoc->close();
if ($this->reader !== null) {
$this->reader->close();
$this->reader = null;
}
}
function producer() {
$parser = $this->csv2assoc;
[$separator, $enclosure, $escape] = $parser->getCsvFlavour();
$reader = $this->reader;
$resource = $reader->getResource();
if ($resource !== null) {
while (!$parser->_parseLine()) {
if (fgets($resource) === false) break;
}
while (($values = fgetcsv($resource, 0, $separator, $enclosure, $escape)) !== false) {
if (!$parser->_checkHeader($values)) {
yield $parser->_mapRow($values);
}
}
} else {
while (true) {
try {
$line = $reader->readLine();
} catch (EOFException $e) {
break;
}
if (!$parser->_parseLine()) continue;
$values = str_getcsv($line, $separator, $enclosure, $escape);
if (!$parser->_checkHeader($values)) {
yield $parser->_mapRow($values);
}
}
}
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts", true],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[self::class, 'self::class'],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getInput' => 'input',
'getInputEncoding' => 'input_encoding',
'getOutputEncoding' => 'output_encoding',
'getCsvFlavour' => 'csv_flavour',
'isMultiSchema' => 'multi_schema',
'getParseHeaders' => 'parse_headers',
'getHeaders' => 'headers',
'getSkipLines' => 'skip_lines',
'getMapEmpty' => 'map_empty',
];
const _AUTO_SETTERS = /*autogen*/[
'setInput' => 'input',
'setInputEncoding' => 'input_encoding',
'setOutputEncoding' => 'output_encoding',
'setCsvFlavour' => 'csv_flavour',
'setMultiSchema' => 'multi_schema',
'setParseHeaders' => 'parse_headers',
'setHeaders' => 'headers',
'setSkipLines' => 'skip_lines',
'setMapEmpty' => 'map_empty',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,69 @@
<?php
namespace nur\mapper\csv;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class CsvReader_command: classe de support pour construire une instance de
* {@link CsvReader} pour une application recevant des commandes utilisateurs
*/
class CsvReader_command {
use Tparams_command;
const NAME = "incsv";
const DEF = [self::NAME, "inc",
"help" => "configurer le fichier en entrée au format CSV",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-e", "--input-encoding", "args" => "value",
"action" => [self::class, "set_input_encoding"],
],
["-s", "--skip-lines", "args" => "value", "type" => "int",
"action" => [self::class, "set_skip_lines"],
],
["-f", "--csv-flavour", "args" => "value",
"action" => [self::class, "set_csv_flavour"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const F_OPTION = ["-f", "--input", "args" => "file",
"action" => [self::class, "set_file"],
];
const E_OPTION = ["-e", "--input-encoding", "args" => "value",
"action" => [self::class, "set_input_encoding"],
];
/** @var CsvReader */
protected static $command;
static function create_command() {
self::$command = new CsvReader();
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setInput($file);
}
static function set_input_encoding(string $input_encoding) {
self::get()->setEncodingFilter($input_encoding);
}
static function set_skip_lines(int $skip_lines) {
self::get()->setSkipLines($skip_lines);
}
static function set_csv_flavour(string $csv_flavour) {
self::get()->setCsvFlavour($csv_flavour);
}
}

View File

@ -0,0 +1,195 @@
<?php
namespace nur\mapper\csv;
use nur\A;
use nur\b\io\Tfilter;
use nur\b\params\Tparametrable;
use nur\data\types\Metadata;
use nur\mapper\base\Consumer;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Tencoding;
use nur\mapper\base\Ttmpwriter;
/**
* Class CsvWriter: écrire un flux de données au format CSV dans un fichier
* destination
*
* --autogen-properties-and-methods--
* @method string|null getInputEncoding()
* @method string|null getOutputEncoding()
* @method setOutput($value)
* @method string|null setInputEncoding(?string $value)
* @method string|null setOutputEncoding(?string $value)
* @method setCsvFlavour($value)
* @method bool setMultiSchema(bool $value)
* @method array setHeaders(array $value)
* @method bool setOutputHeaders(bool $value)
*/
class CsvWriter extends Consumer {
use Tparametrable, Ttmpwriter, Tencoding, Tfilter;
const SEPARATOR = csv_defaults::OO_SEPARATOR;
const ENCLOSURE = csv_defaults::OO_ENCLOSURE;
const ESCAPE = csv_defaults::OO_ESCAPE;
function __construct($output=null) {
$this->assoc2csv = new Assoc2CsvMapper();
$this->assoc2csv->setCsvFlavour([static::SEPARATOR, static::ENCLOSURE, static::ESCAPE]);
parent::__construct();
$this->pp_setOutput($output);
}
function setEncodingFilter(string $to, string $from="utf-8"): void {
$this->_setEncodingFilter($from, $to);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"output" => [null, null, "fichier en sortie"],
];
static function _get_parametrable_params_schema(): array {
return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA
, encoding_utils::PARAMETRABLE_PARAMS_SCHEMA
, AbstractCsvMapper::PARAMETRABLE_PARAMS_SCHEMA
, Assoc2CsvMapper::PARAMETRABLE_PARAMS_SCHEMA);
}
protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void {
if (!in_array("output_encoding", $modifiedKeys)
&& in_array("csv_flavour", $modifiedKeys)) {
$flavourName = $this->csvFlavourName;
if ($flavourName !== null) {
$flavourName = csv_defaults::verifix_name($flavourName);
$this->ppOutputEncoding = csv_defaults::get_encoding($flavourName);
$modifiedKeys[] = "output_encoding";
}
}
$this->encodingOutput__afterSetParametrableParams($modifiedKeys);
}
/** @var Assoc2CsvMapper */
protected $assoc2csv;
private $csvFlavourName;
function pp_setCsvFlavour($csvFlavour): void {
if (is_string($csvFlavour)) $this->csvFlavourName = $csvFlavour;
$this->assoc2csv->setCsvFlavour($csvFlavour);
}
function pp_setMultiSchema(bool $multiSchema=true): void {
$this->assoc2csv->setMultiSchema($multiSchema);
}
function pp_setHeaders(array $headers): void {
$this->assoc2csv->setHeaders($headers);
}
function pp_setOutputHeaders(bool $outputHeaders=true): void {
$this->assoc2csv->setOutputHeaders($outputHeaders);
}
protected function setup(): void {
parent::setup();
$this->setupWriter(true);
$this->assoc2csv->ensureSetup();
}
protected function teardown(): void {
$this->assoc2csv->close();
$this->teardownWriter(true);
}
function _consume(iterable $items): void {
$mapper = $this->assoc2csv;
[$separator, $enclosure, $escape] = $mapper->getCsvFlavour();
$writer = $this->getWriter();
$resource = $writer->getResource();
if ($resource !== null) {
[$skipLine, $outputHeaders, $headers] = $mapper->_checkHeaders(null);
if ($skipLine) fwrite($resource, "\n");
if ($outputHeaders) {
$cols = $mapper->_cookHeaders($headers);
fputcsv($resource, $cols, $separator, $enclosure, $escape);
$mapper->_setOutputHeaders($headers);
}
foreach ($items as $value) {
$row = A::with($value);
[$skipLine, $outputHeaders, $headers] = $mapper->_checkHeaders($row);
if ($skipLine) fwrite($resource, "\n");
if ($outputHeaders) {
$cols = $mapper->_cookHeaders($headers);
fputcsv($resource, $cols, $separator, $enclosure, $escape);
$mapper->_setOutputHeaders($headers);
}
$cols = $mapper->_cookValues($headers, $row);
fputcsv($resource, $cols, $separator, $enclosure, $escape);
}
fflush($resource);
} else {
[$skipLine, $outputHeaders, $headers] = $mapper->_checkHeaders(null);
if ($skipLine) $writer->wnl();
if ($outputHeaders) {
$cols = $mapper->_cookHeaders($headers);
$writer->write($mapper->getLine($cols, false));
$mapper->_setOutputHeaders($headers);
}
foreach ($items as $value) {
$row = A::with($value);
[$skipLine, $outputHeaders, $headers] = $mapper->_checkHeaders($row);
if ($skipLine) $writer->wnl();
if ($outputHeaders) {
$cols = $mapper->_cookHeaders($headers);
$writer->write($mapper->getLine($cols, false));
$mapper->_setOutputHeaders($headers);
}
$cols = $mapper->_cookValues($headers, $row);
$writer->write($mapper->getLine($cols, false));
}
}
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts", true],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[self::class, 'self::class'],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getOutput' => 'output',
'getInputEncoding' => 'input_encoding',
'getOutputEncoding' => 'output_encoding',
'getCsvFlavour' => 'csv_flavour',
'isMultiSchema' => 'multi_schema',
'getHeaders' => 'headers',
'isOutputHeaders' => 'output_headers',
];
const _AUTO_SETTERS = /*autogen*/[
'setOutput' => 'output',
'setInputEncoding' => 'input_encoding',
'setOutputEncoding' => 'output_encoding',
'setCsvFlavour' => 'csv_flavour',
'setMultiSchema' => 'multi_schema',
'setHeaders' => 'headers',
'setOutputHeaders' => 'output_headers',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,62 @@
<?php
namespace nur\mapper\csv;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class CsvWriter_command: classe de support pour construire une instance de
* {@link CsvWriter} pour une application recevant des commandes utilisateurs
*/
class CsvWriter_command {
use Tparams_command;
const NAME = "outcsv";
const DEF = [self::NAME, "outc",
"help" => "configurer le fichier en sortie au format CSV",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-t", "--output-encoding", "args" => "value",
"action" => [self::class, "set_output_encoding"],
],
["-f", "--csv-flavour", "args" => "value",
"action" => [self::class, "set_csv_flavour"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const O_OPTION = ["-o", "--output", "args" => "file",
"action" => [self::class, "set_file"],
];
const T_OPTION = ["-t", "--output-encoding", "args" => "value",
"action" => [self::class, "set_output_encoding"],
];
/** @var CsvWriter */
protected static $command;
static function create_command() {
self::$command = new CsvWriter();
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setOutput($file);
}
static function set_output_encoding(string $output_encoding) {
self::get()->setEncodingFilter($output_encoding);
}
static function set_csv_flavour(string $csv_flavour) {
self::get()->setCsvFlavour($csv_flavour);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace nur\mapper\csv;
use nur\b\ValueException;
class csv_defaults {
const OO_NAME = "ooffice";
const OO_SEPARATOR = ",";
const OO_ENCLOSURE = "\"";
const OO_ESCAPE = "\\";
const OO_FLAVOUR = [self::OO_SEPARATOR, self::OO_ENCLOSURE, self::OO_ESCAPE];
const OO_ENCODING = "utf-8";
const XL_NAME = "excel";
const XL_SEPARATOR = ";";
const XL_ENCLOSURE = "\"";
const XL_ESCAPE = "\\";
const XL_FLAVOUR = [self::XL_SEPARATOR, self::XL_ENCLOSURE, self::XL_ESCAPE];
const XL_ENCODING = "cp1252";
const NAME_MAP = [
"oo" => self::OO_NAME,
"xl" => self::XL_NAME,
];
static final function verifix_name(string $name): string {
if (array_key_exists(strtolower($name), self::NAME_MAP)) {
$name = self::NAME_MAP[strtolower($name)];
}
return $name;
}
private static function invalid_name(string $name): ValueException {
return new ValueException("$name: format CSV invalide");
}
static final function get_flavour(string $name): array {
switch ($name) {
case self::OO_NAME: return self::OO_FLAVOUR;
case self::XL_NAME: return self::XL_FLAVOUR;
default: throw self::invalid_name($name);
}
}
static final function get_encoding(string $name): string {
switch ($name) {
case self::OO_NAME: return self::OO_ENCODING;
case self::XL_NAME: return self::XL_ENCODING;
default: throw self::invalid_name($name);
}
}
}

View File

@ -0,0 +1,118 @@
<?php
namespace nur\mapper\fsv;
use nur\A;
use nur\b\params\Tparametrable;
use nur\b\ValueException;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Mapper;
use nur\mapper\base\Tencoding;
/**
* Class Assoc2FsvMapper: mapper qui convertir un flux de tableaux associatifs
* en flux de lignes au format FSV
*
* --autogen-properties-and-methods--
* @method setFsvSchema($value)
* @method string|null setInputEncoding(?string $value)
* @method string|null setOutputEncoding(?string $value)
*/
class Assoc2FsvMapper extends Mapper {
use Tparametrable;
protected function FSV_SCHEMA(): ?array {
return self::FSV_SCHEMA;
} const FSV_SCHEMA = null;
function __construct(?array $fsvSchema=null, ?iterable $source=null) {
parent::__construct($source);
if ($fsvSchema === null) $fsvSchema = $this->FSV_SCHEMA();
$fsvSchema = new FsvSchema($fsvSchema);
$this->setFsvSchema($fsvSchema);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"fsv_schema" => [null, null, "schéma des données en sortie"],
];
static function _get_parametrable_params_schema(): array {
return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA
, encoding_utils::PARAMETRABLE_PARAMS_SCHEMA);
}
/** @var FsvSchema */
protected $fsvSchema;
function pp_setFsvSchema($fsvSchema): void {
if ($fsvSchema instanceof FsvSchema) $this->fsvSchema = $fsvSchema;
elseif (is_array($fsvSchema)) $this->fsvSchema->setFsvSchema($fsvSchema);
elseif ($fsvSchema !== null) throw ValueException::invalid_value($fsvSchema, "fsvSchema");
}
function pp_setInputEncoding(?string $inputEncoding): void {
$this->fsvSchema->setDataEncoding($inputEncoding);
}
function pp_setOutputEncoding(?string $outputEncoding): void {
$this->fsvSchema->setOutputEncoding($outputEncoding);
}
function getFsvSchema(): array {
return $this->fsvSchema->getFsvSchema();
}
function getFsvColumns(): array {
return $this->fsvSchema->getFsvColumns();
}
function _formatLine(array $row): string {
$fsvSchema = $this->fsvSchema;
if (!$fsvSchema->checkFsvSchema(false)) {
$fsvSchema->setFsvSchema($this->getOvalue(fsv_defaults::OKEY_FSV_SCHEMA));
$fsvSchema->checkFsvSchema();
}
return $this->fsvSchema->formatLine($row);
}
function mapper($item) {
return $this->_formatLine(A::with($item));
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts", true],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[self::class, 'self::class'],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getFsvSchema' => 'fsv_schema',
'getInputEncoding' => 'input_encoding',
'getOutputEncoding' => 'output_encoding',
];
const _AUTO_SETTERS = /*autogen*/[
'setFsvSchema' => 'fsv_schema',
'setInputEncoding' => 'input_encoding',
'setOutputEncoding' => 'output_encoding',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,51 @@
<?php
namespace nur\mapper\fsv;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class Assoc2FsvMapper_command: classe de support pour construire une instance
* de mapper pour une application recevant des commandes utilisateurs
*/
class Assoc2FsvMapper_command {
use Tparams_command;
const NAME = "assoc2fsv";
const DEF = [self::NAME,
"help" => "convertir en FSV",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-r", "--run-code", "args" => "value",
"action" => [self::class, "set_schema_from_code"],
"help" => "exécuter le code et utiliser sa valeur de retour comme schéma",
],
["-f", "--run-file", "args" => "file",
"action" => [self::class, "set_schema_from_file"],
"help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma",
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var Assoc2FsvMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new Assoc2FsvMapper());
}
static function set_schema_from_code(string $code) {
$schema = eval($code);
self::get()->setFsvSchema($schema);
}
static function set_schema_from_file(string $file) {
$schema = require($file);
self::get()->setFsvSchema($schema);
}
}

View File

@ -0,0 +1,164 @@
<?php
namespace nur\mapper\fsv;
use nur\b\params\Tparametrable;
use nur\b\ValueException;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Mapper;
/**
* Class Fsv2AssocMapper: mapper qui analyse un flux de lignes au format FSV et
* qui produit un flux de tableaux associatifs
*
* --autogen-properties-and-methods--
* @method int getSkipLines()
* @method setFsvSchema($value)
* @method string|null setMapEmpty(?string $value)
* @method bool setOutputSeq(bool $value)
* @method int setSkipLines(int $value)
* @method string|null setInputEncoding(?string $value)
* @method string|null setOutputEncoding(?string $value)
*/
class Fsv2AssocMapper extends Mapper {
use Tparametrable;
protected function FSV_SCHEMA(): ?array {
return self::FSV_SCHEMA;
} const FSV_SCHEMA = null;
const MAP_EOF = true;
function __construct(?array $fsvSchema=null, ?iterable $source=null) {
parent::__construct($source);
if ($fsvSchema === null) $fsvSchema = $this->FSV_SCHEMA();
$fsvSchema = new FsvSchema($fsvSchema);
$this->pp_setFsvSchema($fsvSchema);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"fsv_schema" => [null, null, "schéma des données en entrée"],
"map_empty" => ["?string", null, "valeur adoptée pour les chaines vides"],
"output_seq" => ["bool", null, "faut-il générer en-têtes et données en tableaux séquentiels?"],
"skip_lines" => ["int", null, "nombre de lignes à sauter en entrée"],
];
static function _get_parametrable_params_schema(): array {
return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA
, encoding_utils::PARAMETRABLE_PARAMS_SCHEMA);
}
/** @var FsvSchema */
protected $fsvSchema;
function pp_setFsvSchema($fsvSchema): void {
if ($fsvSchema instanceof FsvSchema) $this->fsvSchema = $fsvSchema;
elseif (is_array($fsvSchema)) $this->fsvSchema->setFsvSchema($fsvSchema);
elseif ($fsvSchema !== null) throw ValueException::invalid_value($fsvSchema, "fsvSchema");
}
function pp_setInputEncoding(?string $inputEncoding): void {
$this->fsvSchema->setInputEncoding($inputEncoding);
}
function pp_setOutputEncoding(?string $outputEncoding): void {
$this->fsvSchema->setDataEncoding($outputEncoding);
}
function pp_setMapEmpty($mapEmpty=null): void {
$this->fsvSchema->setMapEmpty($mapEmpty);
}
function pp_setOutputSeq(bool $outputSeq=true): void {
$this->fsvSchema->setOutputSeq($outputSeq);
}
function getFsvSchema(): array {
return $this->fsvSchema->getFsvSchema();
}
function getFsvColumns(): array {
return $this->fsvSchema->getFsvColumns();
}
/** @var int */
protected $ppSkipLines = 0;
protected function setup(): void {
parent::setup();
$fsvSchema = $this->fsvSchema;
$fsvSchema->checkFsvSchema();
$this->setOvalue(fsv_defaults::OKEY_FSV_SCHEMA, $fsvSchema->getFsvSchema());
$this->setOvalue(fsv_defaults::OKEY_FSV_COLUMNS, $fsvSchema->getFsvColumns());
}
function _parseLine(): bool {
if ($this->ppSkipLines > 0) {
$this->ppSkipLines--;
return false;
}
return true;
}
function _outputKeys(bool $reset=true): bool {
return $this->fsvSchema->_outputKeys($reset);
}
function _getKeys(): array {
return $this->fsvSchema->_getKeys();
}
function _parseColumns(string $line): array {
return $this->fsvSchema->parseRow($line);
}
function mapper($item) {
if (!$this->_parseLine()) return $this->mapToNone();
$arrays = [];
if ($this->_outputKeys()) $arrays[] = $this->_getKeys();
if (!$this->eof) $arrays[] = $this->_parseColumns($item);
return $this->mapTo($arrays);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts", true],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[self::class, 'self::class'],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getFsvSchema' => 'fsv_schema',
'getMapEmpty' => 'map_empty',
'isOutputSeq' => 'output_seq',
'getSkipLines' => 'skip_lines',
'getInputEncoding' => 'input_encoding',
'getOutputEncoding' => 'output_encoding',
];
const _AUTO_SETTERS = /*autogen*/[
'setFsvSchema' => 'fsv_schema',
'setMapEmpty' => 'map_empty',
'setOutputSeq' => 'output_seq',
'setSkipLines' => 'skip_lines',
'setInputEncoding' => 'input_encoding',
'setOutputEncoding' => 'output_encoding',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,51 @@
<?php
namespace nur\mapper\fsv;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class Fsv2AssocMapper_command: classe de support pour construire une instance
* de mapper pour une application recevant des commandes utilisateurs
*/
class Fsv2AssocMapper_command {
use Tparams_command;
const NAME = "fsv2assoc";
const DEF = [self::NAME,
"help" => "analyser un flux FSV",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-r", "--run-code", "args" => "value",
"action" => [self::class, "set_schema_from_code"],
"help" => "exécuter le code et utiliser sa valeur de retour comme schéma",
],
["-f", "--run-file", "args" => "file",
"action" => [self::class, "set_schema_from_file"],
"help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma",
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var Fsv2AssocMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new Fsv2AssocMapper());
}
static function set_schema_from_code(string $code) {
$schema = eval($code);
self::get()->setFsvSchema($schema);
}
static function set_schema_from_file(string $file) {
$schema = require($file);
self::get()->setFsvSchema($schema);
}
}

View File

@ -0,0 +1,174 @@
<?php
namespace nur\mapper\fsv;
use nur\b\io\EOFException;
use nur\b\io\IReader;
use nur\b\io\Tfilter;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Producer;
use nur\mapper\csv\AbstractCsvMapper;
use nur\mapper\csv\Csv2AssocMapper;
use nur\reader;
use nur\str;
/**
* Class FsvReader: produit un flux de données à partir d'une source au format
* FSV.
*
* --autogen-properties-and-methods--
* @method setInput($value)
* @method setFsvSchema($value)
* @method string|null setMapEmpty(?string $value)
* @method bool setOutputSeq(bool $value)
* @method int setSkipLines(int $value)
* @method string|null setInputEncoding(?string $value)
* @method string|null setOutputEncoding(?string $value)
*/
class FsvReader extends Producer {
use Tparametrable, Tfilter;
function __construct($input=null, ?array $fsvSchema=null) {
parent::__construct();
$this->fsv2assoc = new Fsv2AssocMapper();
$this->setFsvSchema($fsvSchema);
$this->setInput($input);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"input" => [null, null, "fichier en entrée"],
];
static function _get_parametrable_params_schema(): array {
return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA
, Fsv2AssocMapper::PARAMETRABLE_PARAMS_SCHEMA
, encoding_utils::PARAMETRABLE_PARAMS_SCHEMA);
}
protected $ppInput;
function pp_setInput($input): void {
if ($input instanceof IReader) $this->reader = $input;
else $this->ppInput = $input;
}
/** @var Fsv2AssocMapper */
protected $fsv2assoc;
function pp_setFsvSchema($fsvSchema): void {
$this->fsv2assoc->setFsvSchema($fsvSchema);
}
function pp_setMapEmpty($emptyIs=null, bool $mapEmpty=true): void {
$this->fsv2assoc->setMapEmpty($emptyIs, $mapEmpty);
}
function pp_setOutputSeq(bool $outputSeq=true): void {
$this->fsv2assoc->setOutputSeq($outputSeq);
}
function pp_setSkipLines(int $skipLines): void {
$this->fsv2assoc->setSkipLines($skipLines);
}
function pp_setInputEncoding(?string $inputEncoding): void {
$this->fsv2assoc->setInputEncoding($inputEncoding);
}
function pp_setOutputEncoding(?string $outputEncoding): void {
$this->fsv2assoc->setOutputEncoding($outputEncoding);
}
/** @var IReader */
protected $reader;
protected function setup(): void {
if ($this->reader === null) {
$this->reader = reader::with($this->ppInput);
$this->_rwAppendFilters($this->reader);
}
if ($this->sharedOobdManager !== null) {
$this->fsv2assoc->setSharedOobdManager($this->sharedOobdManager);
}
$this->fsv2assoc->ensureSetup();
}
protected function teardown(): void {
$this->fsv2assoc->close();
if ($this->reader !== null) {
$this->reader->close();
$this->reader = null;
}
}
function producer() {
$parser = $this->fsv2assoc;
$reader = $this->reader;
$resource = $reader->getResource();
if ($resource !== null) {
while (!$parser->_parseLine()) {
if (fgets($resource) === false) break;
}
if ($parser->_outputKeys()) yield $parser->_getKeys();
while (($line = fgets($resource)) !== false) {
$line = str::strip_nl($line);
yield $parser->_parseColumns($line);
}
} else {
while (true) {
try {
$line = $reader->readLine();
} catch (EOFException $e) {
break;
}
if (!$parser->_parseLine()) continue;
if ($parser->_outputKeys()) yield $parser->_getKeys();
yield $parser->_parseColumns($line);
}
}
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts", true],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[self::class, 'self::class'],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getInput' => 'input',
'getFsvSchema' => 'fsv_schema',
'getMapEmpty' => 'map_empty',
'isOutputSeq' => 'output_seq',
'getSkipLines' => 'skip_lines',
'getInputEncoding' => 'input_encoding',
'getOutputEncoding' => 'output_encoding',
];
const _AUTO_SETTERS = /*autogen*/[
'setInput' => 'input',
'setFsvSchema' => 'fsv_schema',
'setMapEmpty' => 'map_empty',
'setOutputSeq' => 'output_seq',
'setSkipLines' => 'skip_lines',
'setInputEncoding' => 'input_encoding',
'setOutputEncoding' => 'output_encoding',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,80 @@
<?php
namespace nur\mapper\fsv;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class FsvReader_command: classe de support pour construire une instance de
* {@link FsvReader} pour une application recevant des commandes utilisateurs
*/
class FsvReader_command {
use Tparams_command;
const NAME = "infsv";
const DEF = [self::NAME, "inv",
"help" => "configurer le fichier en entrée au format FSV",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-r", "--run-code", "args" => "value",
"action" => [self::class, "set_schema_from_code"],
"help" => "exécuter le code et utiliser sa valeur de retour comme schéma",
],
["-f", "--run-file", "args" => "file",
"action" => [self::class, "set_schema_from_file"],
"help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma",
],
["-e", "--input-encoding", "args" => "value",
"action" => [self::class, "set_input_encoding"],
],
["-s", "--skip-lines", "args" => "value", "type" => "int",
"action" => [self::class, "set_skip_lines"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const F_OPTION = ["-f", "--input", "args" => "file",
"action" => [self::class, "set_file"],
];
const E_OPTION = ["-e", "--input-encoding", "args" => "value",
"action" => [self::class, "set_input_encoding"],
];
/** @var FsvReader */
protected static $command;
static function create_command() {
self::$command = new FsvReader();
}
static function set_schema_from_code(string $code) {
$schema = eval($code);
self::get()->setFsvSchema($schema);
}
static function set_schema_from_file(string $file) {
$schema = require($file);
self::get()->setFsvSchema($schema);
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setInput($file);
}
static function set_input_encoding(string $input_encoding) {
self::get()->setInputEncoding($input_encoding);
}
static function set_skip_lines(int $skip_lines) {
self::get()->setSkipLines($skip_lines);
}
}

View File

@ -0,0 +1,385 @@
<?php
namespace nur\mapper\fsv;
use nur\A;
use nur\b\date\Date;
use nur\b\IllegalAccessException;
use nur\b\ValueException;
use nur\data\types\md_utils;
use nur\data\types\Metadata;
use nur\func;
class FsvSchema {
const COLUMN_SCHEMA = [
"name" => ["string", null, "nom du champ", "required" => true],
"size" => ["int", null, "taille du champ", "required" => true],
"type" => ["?string", null, "type du champ: string, number, date"],
"precision" => ["?int", null, "précision pour un champ de type number"],
"format" => ["?string", null, "format à appliquer avant écriture"],
# ces champs sont en principe calculés automatiquement:
"position" => ["?int", null, "position du premier caractère du champ (commence à 1)"],
"index" => ["?int", null, "index du premier caractère du champ (commence à 1)"],
];
/** @var Metadata */
private static $column_md;
private static function column_md(): Metadata {
return md_utils::ensure_md(self::$column_md, self::COLUMN_SCHEMA);
}
protected function INPUT_ENCODING(): ?string {
return static::INPUT_ENCODING;
} const INPUT_ENCODING = "latin1";
protected function DATA_ENCODING(): ?string {
return static::DATA_ENCODING;
} const DATA_ENCODING = "utf-8";
protected function OUTPUT_ENCODING(): ?string {
return static::OUTPUT_ENCODING;
} const OUTPUT_ENCODING = "latin1//TRANSLIT//IGNORE";
protected function FSV_SCHEMA(): ?array {
return self::FSV_SCHEMA;
} const FSV_SCHEMA = null;
function __construct(?array $fsvSchema=null) {
$this->setFsvSchema($fsvSchema);
$this->setInputEncoding(null);
$this->setDataEncoding(null);
$this->setOutputEncoding(null);
}
protected $inputEncoding;
function setInputEncoding(?string $inputEncoding): self {
if ($inputEncoding === null) $inputEncoding = $this->INPUT_ENCODING();
$this->inputEncoding = $inputEncoding;
return $this;
}
protected $dataEncoding;
function setDataEncoding(?string $dataEncoding): self {
if ($dataEncoding === null) $dataEncoding = $this->DATA_ENCODING();
$this->dataEncoding = $dataEncoding;
return $this;
}
protected $outputEncoding;
function setOutputEncoding(?string $outputEncoding): self {
if ($outputEncoding === null) $outputEncoding = $this->OUTPUT_ENCODING();
$this->outputEncoding = $outputEncoding;
return $this;
}
protected function iconvInput(array $row): array {
$inputEncoding = $this->inputEncoding;
$dataEncoding = $this->dataEncoding;
if ($inputEncoding !== null && $dataEncoding !== null) {
foreach ($row as &$col) {
if (is_string($col)) $col = iconv($inputEncoding, $dataEncoding, $col);
}; unset($col);
}
return $row;
}
protected function iconvOutput(string $line): string {
$dataEncoding = $this->dataEncoding;
$outputEncoding = $this->outputEncoding;
if ($outputEncoding !== null && $dataEncoding !== null) {
$line = iconv($dataEncoding, $outputEncoding, $line);
}
return $line;
}
/** @var bool */
protected $shouldMapEmpty = false;
protected $mapEmpty = null;
function setMapEmpty($mapEmpty=null): void {
$this->shouldMapEmpty = true;
$this->mapEmpty = $mapEmpty;
}
/**
* @var bool faut-il générer les en-têtes et les données sous forme de
* tableaux séquentiels?
*/
protected $outputSeq;
/** @var bool faut-il afficher les en-têtes en sortie? */
protected $outputKeys;
function setOutputSeq(bool $outputSeq=true): void {
$this->outputSeq = $outputSeq;
$this->outputKeys = true;
}
/** @var array */
protected $fsvSchema;
function getFsvSchema(): array {
return $this->fsvSchema;
}
/** @var array liste des noms des champs */
protected $fsvColumns;
function getFsvColumns(): array {
return $this->fsvColumns;
}
function setFsvSchema(?array $fsvSchema): self {
if ($fsvSchema === null) $fsvSchema = $this->FSV_SCHEMA();
if ($fsvSchema === null) return $this;
$column_md = self::column_md();
$index = 0;
$position = 1;
$columns = [];
foreach ($fsvSchema as $key => &$sfield) {
if ($key === $index) {
# séquentiel
$index++;
$column_md->ensureSchema($sfield);
} else {
# associatif
$column_md->ensureSchema($sfield, $key);
}
A::replace_n($sfield, "type", "string");
A::replace_n($sfield, "precision", 0);
A::replace_n($sfield, "position", $position);
A::replace_n($sfield, "index", $position - 1);
$this->validateSfield($sfield);
$position += $sfield["size"];
$columns[] = $sfield["name"];
}; unset($sfield);
$this->fsvSchema = $fsvSchema;
$this->fsvColumns = $columns;
return $this;
}
protected function validateSfield($sfield): void {
$type = $sfield["type"];
switch ($type) {
case "string":
case "number":
break;
case "date":
$size = $sfield["size"];
#XXX tenir compte du format
if ($size != 6 && $size != 8) {
throw new ValueException("date type require size=6 or size=8");
}
break;
default:
throw ValueException::invalid_value($type, "type");
}
}
function checkFsvSchema(bool $throw=true): bool {
if ($this->fsvSchema !== null) return true;
elseif ($throw) throw new ValueException("a schema is required");
else return false;
}
#############################################################################
function _outputKeys(bool $reset=true): bool {
$outputKeys = $this->outputSeq && $this->outputKeys;
if ($reset) $this->outputKeys = false;
return $outputKeys;
}
function _getKeys(): array {
return $this->fsvColumns;
}
function parseRow(string $line, bool $iconv=true): array {
$this->checkFsvSchema();
$outputSeq = $this->outputSeq;
$row = [];
$length = strlen($line);
foreach ($this->fsvSchema as $sfield) {
[
"name" => $name,
"index" => $index,
"size" => $size,
"type" => $type,
] = $sfield;
if ($index >= $length) {
if ($outputSeq) $row[] = false;
else $row[$name] = false;
} else {
$value = substr($line, $index, $size);
$blank = str_pad("", $size);
switch ($type) {
case "string":
$value = $this->parseString($value, $sfield);
break;
case "number":
if ($value === $blank) $value = false;
else $value = $this->parseNumber($value, $sfield);
break;
case "date":
if ($value === $blank) $value = false;
else $value = $this->parseDate($value, $sfield);
break;
default:
throw IllegalAccessException::unexpected_state();
}
if ($value === "" && $this->shouldMapEmpty) $value = $this->mapEmpty;
if ($outputSeq) $row[] = $value;
else $row[$name] = $value;
}
}
if ($iconv) $row = $this->iconvInput($row);
return $row;
}
protected function parseString(string $value, array $sfield): string {
return rtrim($value);
}
protected function parseNumber(string $value, array $sfield) {
$precision = $sfield["precision"];
if ($precision == 0) {
$value = intval($value);
} else {
$value = doubleval($value) / (10**$precision);
}
return $value;
}
protected function parseDate(string $value, array $sfield) {
$size = $sfield["size"];
$dd = substr($value, 0, 2);
$mm = substr($value, 2, 2);
if ($size == 8) {
$yyyy = substr($value, 4, 4);
} elseif ($size == 6) {
$yy = substr($value, 4, 2);
$yyyy = Date::fix_any_year($yy);
} else {
throw IllegalAccessException::unexpected_state();
}
return "$dd/$mm/$yyyy";
}
#############################################################################
protected static function ensure_size(string $value, int $size): string {
$length = mb_strlen($value);
if ($length < $size) {
while ($length < $size) {
$value .= " ";
$length = mb_strlen($value);
}
} elseif ($length > $size) {
$value = mb_substr($value, 0, $size);
}
return $value;
}
protected static function invalid_size(string $value, int $actual_size, array $sfield): ValueException {
["name" => $name, "size" => $size, "format" => $format] = $sfield;
return new ValueException("field=$name with format $format, value=|$value|, expected size=$size, actual size=$actual_size");
}
function formatLine(array $row, bool $iconv=true): string {
$this->checkFsvSchema();
$line = [];
foreach ($this->fsvSchema as $sfield) {
[
"name" => $name,
"size" => $size,
"type" => $type,
] = $sfield;
$value = A::get($row, $name, "");
if ($value === false) {
$value = self::ensure_size("", $size);
} else {
switch ($type) {
case "string":
$value = $this->formatString($value, $sfield);
break;
case "number":
$value = $this->formatNumber($value, $sfield);
break;
case "date":
$value = $this->formatDate($value, $sfield);
break;
default:
throw IllegalAccessException::unexpected_state();
}
}
$line[] = $value;
}
$line = implode("", $line);
if ($iconv) $line = $this->iconvOutput($line);
return $line;
}
function formatString($value, $sfield): string {
["size" => $size, "format" => $format] = $sfield;
$value = strval($value);
if ($format !== null) {
$func = [$this, "stringFormat_$format"];
$value = func::call($func, $value, $sfield);
$actualSize = mb_strlen($value);
if ($actualSize != $size) {
throw self::invalid_size($value, $actualSize, $sfield);
}
} else {
$value = self::ensure_size($value, $size);
}
return $value;
}
function stringFormat_upper(string $value): string {
return mb_strtoupper($value);
}
function stringFormat_lower(string $value): string {
return mb_strtolower($value);
}
function formatNumber($value, $sfield): string {
["size" => $size, "precision" => $precision, "format" => $format] = $sfield;
if ($format !== null) {
$value = sprintf($format, $value);
$actualSize = strlen($value);
if ($actualSize != $size) {
throw self::invalid_size($value, $actualSize, $sfield);
}
} elseif ($precision == 0) {
$value = sprintf("%0${size}u", $value);
} else {
$size++;
$value = sprintf("%0${size}.${precision}F", $value);
$value = str_replace(".", "", $value);
}
return $value;
}
function formatDate($value, $sfield): string {
$date = new Date($value);
["size" => $size, "format" => $format] = $sfield;
if ($format !== null) {
$value = $date->format($format);
$actualSize = strlen($value);
if ($actualSize != $size) {
throw self::invalid_size($value, $actualSize, $sfield);
}
} elseif ($size == 6) {
$value = $date->format("dmy");
} elseif ($size == 8) {
$value = $date->format("dmY");
} else {
throw IllegalAccessException::unexpected_state();
}
return $value;
}
}

View File

@ -0,0 +1,114 @@
<?php
namespace nur\mapper\fsv;
use nur\b\io\Tfilter;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\mapper\base\Consumer;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Ttmpwriter;
/**
* Class FsvWriter: écrire un flux de données au format FSV dans un fichier
* destination
*
* --autogen-properties-and-methods--
* @method setOutput($value)
* @method setFsvSchema($value)
* @method string|null setInputEncoding(?string $value)
* @method string|null setOutputEncoding(?string $value)
*/
class FsvWriter extends Consumer {
use Tparametrable, Ttmpwriter, Tfilter;
function __construct($output=null) {
parent::__construct();
$this->assoc2fsv = new Assoc2FsvMapper();
$this->pp_setOutput($output);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"output" => [null, null, "fichier en sortie"],
];
static function _get_parametrable_params_schema(): array {
return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA
, Assoc2FsvMapper::PARAMETRABLE_PARAMS_SCHEMA
, encoding_utils::PARAMETRABLE_PARAMS_SCHEMA);
}
/** @var Assoc2FsvMapper */
protected $assoc2fsv;
function pp_setFsvSchema($fsvSchema): void {
$this->assoc2fsv->setFsvSchema($fsvSchema);
}
function pp_setInputEncoding(?string $inputEncoding): void {
$this->assoc2fsv->setInputEncoding($inputEncoding);
}
function pp_setOutputEncoding(?string $outputEncoding): void {
$this->assoc2fsv->setOutputEncoding($outputEncoding);
}
protected function setup(): void {
parent::setup();
$this->setupWriter(false);
$this->assoc2fsv->ensureSetup();
}
protected function teardown(): void {
$this->assoc2fsv->close();
$this->teardownWriter(false);
}
function _consume(iterable $items): void {
$mapper = $this->assoc2fsv;
$mapper->setSharedOobdManager($this->sharedOobdManager);
$writer = $this->getWriter();
foreach ($items as $value) {
$writer->wnl($mapper->_formatLine($value));
}
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts", true],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[self::class, 'self::class'],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
self::class,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getOutput' => 'output',
'getFsvSchema' => 'fsv_schema',
'getInputEncoding' => 'input_encoding',
'getOutputEncoding' => 'output_encoding',
];
const _AUTO_SETTERS = /*autogen*/[
'setOutput' => 'output',
'setFsvSchema' => 'fsv_schema',
'setInputEncoding' => 'input_encoding',
'setOutputEncoding' => 'output_encoding',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,73 @@
<?php
namespace nur\mapper\fsv;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class FsvWriter_command: classe de support pour construire une instance de
* {@link FsvWriter} pour une application recevant des commandes utilisateurs
*/
class FsvWriter_command {
use Tparams_command;
const NAME = "outfsv";
const DEF = [self::NAME, "outv",
"help" => "configurer le fichier en sortie au format FSV",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-r", "--run-code", "args" => "value",
"action" => [self::class, "set_schema_from_code"],
"help" => "exécuter le code et utiliser sa valeur de retour comme schéma",
],
["-f", "--run-file", "args" => "file",
"action" => [self::class, "set_schema_from_file"],
"help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma",
],
["-t", "--output-encoding", "args" => "value",
"action" => [self::class, "set_output_encoding"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const O_OPTION = ["-o", "--output", "args" => "file",
"action" => [self::class, "set_file"],
];
const T_OPTION = ["-t", "--output-encoding", "args" => "value",
"action" => [self::class, "set_output_encoding"],
];
/** @var FsvWriter */
protected static $command;
static function create_command() {
self::$command = new FsvWriter();
}
static function set_schema_from_code(string $code) {
$schema = eval($code);
self::get()->setFsvSchema($schema);
}
static function set_schema_from_file(string $file) {
$schema = require($file);
self::get()->setFsvSchema($schema);
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setOutput($file);
}
static function set_output_encoding(string $output_encoding) {
self::get()->setEncodingFilter($output_encoding);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace nur\mapper\fsv;
class fsv_defaults {
public const OKEY_FSV_COLUMNS = "fsv_columns";
public const OKEY_FSV_SCHEMA = "fsv_schema";
}

View File

@ -0,0 +1,410 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\b\IllegalAccessException;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\base;
use nur\mapper\base\Mapper;
use nur\mapper\base\mapper_utils;
use nur\mapper\base\mark_utils;
use nur\str;
/**
* Class AbstractStringMapper: un mapper qui fait des opérations sur des chaines
* (cette classe factorise les méthodes communes entre {@link StringMapper} et
* {@link TextMapper})
*/
abstract class AbstractStringMapper extends Mapper {
use Tparametrable;
# si l'élément est un tableau, ces actions sont faites sur chaque valeur
const ACTION_SET_VALUE = 1;
const ACTION_ADD_PREFIX = 2;
const ACTION_ADD_SUFFIX = 3;
const ACTION_DEL_PREFIX = 4;
const ACTION_DEL_SUFFIX = 5;
const ACTION_LEFT = 6;
const ACTION_RIGHT = 7;
const ACTION_SUBSTR = 8;
const ACTION_REPLACE = 9;
const ACTION_SPLIT = 10;
const ACTION_JOIN = 11;
const ACTION_TRUNC = 12;
const ACTION_TRIM = 13;
const ACTION_LTRIM = 14;
const ACTION_RTRIM = 15;
const ACTION_LOWER = 16;
const ACTION_LOWER1 = 17;
const ACTION_UPPER = 18;
const ACTION_UPPER1 = 19;
const ACTION_UPPERW = 20;
# si l'élément est un tableau, ces actions sont faites sur l'élément en entier
const ACTION_WHOLE_ITEM = 100;
const ACTION_SPLIT_ALL = 101;
const ACTION_JOIN_ALL = 102;
/** @var array */
protected $actions = [];
function addAction(?string $action): self {
if (preg_match('/^([A-Za-z0-9_]*)=(.*)$/', $action, $vs)) {
$key = A::get($vs, 1);
$action = A::get($vs, 2);
} else {
$key = null;
}
if (mapper_utils::check_prefix($action, $value, "set:", "=")) {
$this->actions[] = [self::ACTION_SET_VALUE, $key, $value];
} elseif (mapper_utils::check_prefix($action, $value, "addp:", "+#")) {
$this->actions[] = [self::ACTION_ADD_PREFIX, $key, $value];
} elseif (mapper_utils::check_prefix($action, $value, "adds:", "+%", "+")) {
$this->actions[] = [self::ACTION_ADD_SUFFIX, $key, $value];
} elseif (mapper_utils::check_prefix($action, $value, "delp:", "-#", "#")) {
$this->actions[] = [self::ACTION_DEL_PREFIX, $key, $value];
} elseif (mapper_utils::check_prefix($action, $value, "dels:", "-%", "%", "-")) {
$this->actions[] = [self::ACTION_DEL_SUFFIX, $key, $value];
} elseif (mapper_utils::check_prefix($action, $value, "left:", "l:")) {
$length = intval($value);
$this->actions[] = [self::ACTION_LEFT, $key, $length];
} elseif (mapper_utils::check_prefix($action, $value, "right:", "r:")) {
$length = intval($value);
$this->actions[] = [self::ACTION_RIGHT, $key, $length];
} elseif (mapper_utils::check_prefix($action, $value, "substr:", "mid:", "m:")) {
preg_match('/^([^:]*)(?::(.*))?$/', $value, $vs);
$offset = $vs[1];
$offset = $offset? intval($offset): 0;
$length = A::get($vs, 2);
if ($length !== null) $length = intval($length);
$this->actions[] = [self::ACTION_SUBSTR, $key, $offset, $length];
} elseif (mapper_utils::check_prefix($action, $value, "replace:", "repl:", "r//", "r/")) {
if (str::_starts_with("r//", $action)) {
$sep = "/";
$limit = -1;
} elseif (str::_starts_with("r/", $action)) {
$sep = "/";
$limit = 1;
} else {
$sep = ":";
$limit = -1;
}
[$from, $to] = str::split_pair($value, $sep);
if ($sep == "/") str::del_suffix($to, "/");
$this->actions[] = [self::ACTION_REPLACE, $key, false, $from, $to, $limit];
} elseif (mapper_utils::check_prefix($action, $value, "split:", "|", "split")) {
if ($action === "split") $value = ",";
$this->actions[] = [self::ACTION_SPLIT, $key, $value];
} elseif (mapper_utils::check_prefix($action, $value, "join:", "§", "join")) {
if ($action === "join") $value = ",";
$this->actions[] = [self::ACTION_JOIN, $key, $value];
} elseif (mapper_utils::check_prefix($action, $value, "trunc:")) {
preg_match('/^([^:]*)(?::([^:]*)(?::([^:]*))?)?$/', $value, $vs);
$length = $vs[1];
$length = $length? intval($length): 0;
$ellips = base::t2(A::get($vs, 2));
$suffix = A::get($vs, 3);
$this->actions[] = [self::ACTION_TRUNC, $key, $length, $ellips, $suffix];
} elseif (mapper_utils::check_prefix($action, $value, "trim", "t")) {
$this->actions[] = [self::ACTION_TRIM, $key];
} elseif (mapper_utils::check_prefix($action, $value, "ltrim", "lt")) {
$this->actions[] = [self::ACTION_LTRIM, $key];
} elseif (mapper_utils::check_prefix($action, $value, "rtrim", "rt")) {
$this->actions[] = [self::ACTION_RTRIM, $key];
} elseif (mapper_utils::check_prefix($action, $value, "lower", "l")) {
$this->actions[] = [self::ACTION_LOWER, $key];
} elseif (mapper_utils::check_prefix($action, $value, "lower1", "l1")) {
$this->actions[] = [self::ACTION_LOWER1, $key];
} elseif (mapper_utils::check_prefix($action, $value, "upper", "u")) {
$this->actions[] = [self::ACTION_UPPER, $key];
} elseif (mapper_utils::check_prefix($action, $value, "upper1", "u1")) {
$this->actions[] = [self::ACTION_UPPER1, $key];
} elseif (mapper_utils::check_prefix($action, $value, "upperw", "uw")) {
$this->actions[] = [self::ACTION_UPPERW, $key];
} elseif (mapper_utils::check_prefix($action, $value, "split_all:", "split_all")) {
if ($action === "split_all") $value = ",";
$this->actions[] = [self::ACTION_SPLIT_ALL, $key, $value];
} elseif (mapper_utils::check_prefix($action, $value, "join_all:", "join_all")) {
if ($action === "join_all") $value = ",";
$this->actions[] = [self::ACTION_JOIN_ALL, $key, $value];
} else {
# par défaut, spécifier la valeur
$this->actions[] = [self::ACTION_SET_VALUE, $key, $action];
}
return $this;
}
function addActionSetValue(?string $value, ?string $key=null): self {
$this->actions[] = [self::ACTION_SET_VALUE, $key, $value];
return $this;
}
function addActionAddPrefix(?string $value, ?string $key=null): self {
$this->actions[] = [self::ACTION_ADD_PREFIX, $key, $value];
return $this;
}
function addActionAddSuffix(?string $value, ?string $key=null): self {
$this->actions[] = [self::ACTION_ADD_SUFFIX, $key, $value];
return $this;
}
function addActionDelPrefix(?string $value, ?string $key=null): self {
$this->actions[] = [self::ACTION_DEL_PREFIX, $key, $value];
return $this;
}
function addActionDelSuffix(?string $value, ?string $key=null): self {
$this->actions[] = [self::ACTION_DEL_SUFFIX, $key, $value];
return $this;
}
function addActionLeft(int $length, ?string $key=null): self {
$this->actions[] = [self::ACTION_LEFT, $key, $length];
return $this;
}
function addActionRight(int $length, ?string $key=null): self {
$this->actions[] = [self::ACTION_RIGHT, $key, $length];
return $this;
}
function addActionSubstr(int $offset, ?int $length, ?string $key=null): self {
$this->actions[] = [self::ACTION_SUBSTR, $key, $offset, $length];
return $this;
}
function addActionReplace(string $from, ?string $to, int $limit=-1, ?string $key=null): self {
$this->actions[] = [self::ACTION_REPLACE, $key, false, $from, $to, $limit];
return $this;
}
function addActionSplit(?string $sep, ?string $key=null): self {
$this->actions[] = [self::ACTION_SPLIT, $key, $sep];
return $this;
}
function addActionJoin(?string $sep, ?string $key=null): self {
$this->actions[] = [self::ACTION_JOIN, $key, $sep];
return $this;
}
function addActionTrunc(int $length, bool $ellips=false, ?string $suffix=null, ?string $key=null): self {
$this->actions[] = [self::ACTION_TRUNC, $key, $length, $ellips, $suffix];
return $this;
}
function addActionTrim(?string $key=null): self {
$this->actions[] = [self::ACTION_TRIM, $key];
return $this;
}
function addActionLtrim(?string $key=null): self {
$this->actions[] = [self::ACTION_LTRIM, $key];
return $this;
}
function addActionRtrim(?string $key=null): self {
$this->actions[] = [self::ACTION_RTRIM, $key];
return $this;
}
function addActionLower(?string $key=null): self {
$this->actions[] = [self::ACTION_LOWER, $key];
return $this;
}
function addActionLower1(?string $key=null): self {
$this->actions[] = [self::ACTION_LOWER1, $key];
return $this;
}
function addActionUpper(?string $key=null): self {
$this->actions[] = [self::ACTION_UPPER, $key];
return $this;
}
function addActionUpper1(?string $key=null): self {
$this->actions[] = [self::ACTION_UPPER1, $key];
return $this;
}
function addActionUpperw(?string $key=null): self {
$this->actions[] = [self::ACTION_UPPERW, $key];
return $this;
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"actions" => ["?array", null, "actions à effectuer sur les chaines du flux"],
"marked_only" => ["bool", null, "ne faire les actions que sur les éléments marqués"],
];
function pp_setActions(?array $actions): void {
if ($actions !== null) {
foreach ($actions as $action) {
$this->addAction($action);
}
}
}
/** @var bool */
protected $markedOnly;
#############################################################################
protected static function apply(&$item, ?string $forKey, callable $func, array $args): void {
if (is_array($item)) {
foreach ($item as $key => &$value) {
if ($forKey !== null & $key != $forKey) continue;
self::apply($value, null, $func, $args);
}
} else {
$item = $func($item, ...$args);
}
}
protected static function set_value($value) {
if (base::z($value)) return $value;
return strval($value);
}
protected static function add_prefix($value, ?string $prefix) {
return "$prefix$value";
}
protected static function add_suffix($value, ?string $suffix) {
return "$value$suffix";
}
protected static function split($value, string $sep): array {
if ($value === null) return [];
return explode($sep, strval($value));
}
protected function doActionItem(&$item, int $action, ?string $forKey, array $params): void {
switch ($action) {
case self::ACTION_SET_VALUE:
if (is_array($item)) {
foreach ($item as $key => &$value) {
if ($forKey !== null & $key != $forKey) continue;
$value = self::set_value(...$params);
}
} else {
$item = self::set_value(...$params);
}
break;
case self::ACTION_ADD_PREFIX:
self::apply($item, $forKey, [static::class, "add_prefix"], $params);
break;
case self::ACTION_ADD_SUFFIX:
self::apply($item, $forKey, [static::class, "add_suffix"], $params);
break;
case self::ACTION_DEL_PREFIX:
self::apply($item, $forKey, [static::class, "del_prefix"], $params);
break;
case self::ACTION_DEL_SUFFIX:
self::apply($item, $forKey, [static::class, "del_suffix"], $params);
break;
case self::ACTION_LEFT:
self::apply($item, $forKey, [static::class, "left"], $params);
break;
case self::ACTION_RIGHT:
self::apply($item, $forKey, [static::class, "right"], $params);
break;
case self::ACTION_SUBSTR:
self::apply($item, $forKey, [static::class, "substr"], $params);
break;
case self::ACTION_REPLACE:
$regexp = $params[0];
$params = array_slice($params, 1);
if ($regexp) {
#XXX à implémenter
#self::apply($item, $forKey, [static::class, "preg_replace"], $params);
} else {
self::apply($item, $forKey, [static::class, "str_replace"], $params);
}
break;
case self::ACTION_SPLIT:
self::apply($item, $forKey, [static::class, "split"], $params);
break;
case self::ACTION_JOIN:
self::apply($item, $forKey, [static::class, "join"], $params);
break;
case self::ACTION_TRUNC:
self::apply($item, $forKey, [static::class, "trunc"], $params);
break;
case self::ACTION_TRIM:
self::apply($item, $forKey, [static::class, "trim"], $params);
break;
case self::ACTION_LTRIM:
self::apply($item, $forKey, [static::class, "ltrim"], $params);
break;
case self::ACTION_RTRIM:
self::apply($item, $forKey, [static::class, "rtrim"], $params);
break;
case self::ACTION_LOWER:
self::apply($item, $forKey, [static::class, "lower"], $params);
break;
case self::ACTION_LOWER1:
self::apply($item, $forKey, [static::class, "lower1"], $params);
break;
case self::ACTION_UPPER:
self::apply($item, $forKey, [static::class, "upper"], $params);
break;
case self::ACTION_UPPER1:
self::apply($item, $forKey, [static::class, "upper1"], $params);
break;
case self::ACTION_UPPERW:
self::apply($item, $forKey, [static::class, "upperw"], $params);
break;
default:
throw IllegalAccessException::not_implemented("action $action");
}
}
protected static function split_all($item, string $sep): array {
if (base::z($item)) return $item;
if (is_array($item)) $item = self::join_all($item, "");
return explode($sep, strval($item));
}
protected static function join_all($item, string $sep): string {
if (base::z($item)) return $item;
return implode($sep, A::with($item));
}
protected function doActionWhole(&$item, int $action, ?string $forKey, array $params): void {
switch ($action) {
case self::ACTION_SPLIT_ALL:
$item = static::split_all($item, ...$params);
break;
case self::ACTION_JOIN_ALL:
$item = static::join_all($item, ...$params);
break;
default:
throw IllegalAccessException::not_implemented("action $action");
}
}
protected function setup(): void {
parent::setup();
if ($this->markedOnly === null) {
$this->markedOnly = mark_utils::is_use_marks($this);
}
}
function mapper($item) {
if (!$this->markedOnly || $this->isItemMarked($item)) {
foreach ($this->actions as $actionParams) {
[$action, $forKey] = array_slice($actionParams, 0, 2);
$params = array_slice($actionParams, 2);
if ($action < self::ACTION_WHOLE_ITEM) {
$this->doActionItem($item, $action, $forKey, $params);
} else {
$this->doActionWhole($item, $action, $forKey, $params);
}
}
}
return $item;
}
}

View File

@ -0,0 +1,147 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\b\params\Tparametrable;
use nur\mapper\base\Mapper;
/**
* Class Assoc2SeqMapper: transformer un flux de tableaux associatifs en
* tableaux séquentiels
*
* --autogen-properties-and-methods--
* @method bool isMultiSchema()
* @method array getKeys()
* @method bool isOutputKeys()
* @method bool setMultiSchema(bool $value)
* @method array setKeys(array $value)
* @method bool setOutputKeys(bool $value)
*/
class Assoc2SeqMapper extends Mapper {
use Tparametrable;
const MAP_EOF = true;
const PARAMETRABLE_PARAMS_SCHEMA = [
"multi_schema" => ["bool", false, "les flux multi-schémas sont-ils supportés?"],
"keys" => ["array", null, "liste et ordre des champs en sortie"],
"output_keys" => ["bool", true, "faut-il afficher les en-têtes en sortie?"],
];
/** @var bool les flux avec plusieurs schémas sont-ils supportés? */
protected $ppMultiSchema;
/**
* @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 $ppKeys;
/** @var bool faut-il afficher les en-têtes en sortie? */
protected $ppOutputKeys;
private static final function is_different(array $h1, array $h2): bool {
sort($h1);
sort($h2);
return $h1 != $h2;
}
private function computeKeys(array $item) {
return array_keys($item);
}
function _checkKeys(?array $item): array {
$skipLine = false;
if ($item === null) {
$outputKeys = $this->ppOutputKeys && $this->ppKeys !== null;
return [$skipLine, $outputKeys, $this->ppKeys];
}
$prevKeys = $this->ppKeys;
if ($this->ppMultiSchema) {
# vérifier si le schéma a changé
$keys = $this->computeKeys($item);
if ($prevKeys === null) $prevKeys = $keys;
if (self::is_different($prevKeys, $keys)) {
$skipLine = true;
$this->ppOutputKeys = true;
} else {
$keys = $prevKeys;
}
} else {
$keys = $prevKeys;
if ($keys === null) $keys = $this->computeKeys($item);
}
return [$skipLine, $this->ppOutputKeys, $keys];
}
function _setOutputKeys(array $keys): void {
$this->ppKeys = $keys;
$this->ppOutputKeys = false;
}
function _cookValues(array $keys, array $item): array {
$values = [];
foreach ($keys as $header) {
$values[] = A::get($item, $header, false);
}
return $values;
}
function mapper($item) {
if ($this->eof) $item = null;
else $item = A::with($item);
$arrays = [];
[$skipLine, $outputKeys, $keys] = $this->_checkKeys($item);
if ($skipLine) $arrays[] = [];
if ($outputKeys) {
$arrays[] = $keys;
$this->_setOutputKeys($keys);
}
if ($item !== null) {
$arrays[] = $this->_cookValues($keys, $item);
}
return $this->mapTo($arrays);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'isMultiSchema' => 'multi_schema',
'getKeys' => 'keys',
'isOutputKeys' => 'output_keys',
];
const _AUTO_SETTERS = /*autogen*/[
'setMultiSchema' => 'multi_schema',
'setKeys' => 'keys',
'setOutputKeys' => 'output_keys',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,33 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class Assoc2SeqMapper_command: classe de support pour construire une instance de
* mapper pour une application recevant des commandes utilisateurs
*/
class Assoc2SeqMapper_command {
use Tparams_command;
const NAME = "assoc2seq";
const DEF = [self::NAME,
"help" => "convertir en tableaux séquentiels",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var Assoc2SeqMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new Assoc2SeqMapper());
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\b\params\Tparametrable;
use nur\mapper\base\Mapper;
/**
* Class AttributeFilterMapper: sélection de certains attributs d'un flux de
* tableaux associatifs
*
* --autogen-properties-and-methods--
* @method array|null setFields(?array $value)
* @method array|null setAddFields(?array $value)
* @method array|null setRemoveFields(?array $value)
*/
class AttributeFilterMapper extends Mapper {
use Tparametrable;
function __construct(?array $fields=null, ?iterable $source=null) {
parent::__construct($source);
$this->pp_setFields($fields);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"fields" => ["?array", null, "liste des champs à sélectionner"],
"add_fields" => ["?array", null, "liste des champs à ajouter"],
"remove_fields" => ["?array", null, "liste des champs à supprimer"],
];
/** @var array */
protected $ppFields;
function pp_setFields(?array $fields): void {
if ($fields !== null) $fields = A::flattened($fields);
$this->ppFields = $fields;
}
/** @var array */
protected $ppAddFields;
function pp_setAddFields(?array $addFields): void {
if ($addFields !== null) $addFields = A::flattened($addFields);
$this->ppAddFields = $addFields;
}
/** @var array */
protected $ppRemoveFields;
function pp_setRemoveFields(?array $removeFields): void {
if ($removeFields !== null) $removeFields = A::flattened($removeFields);
$this->ppRemoveFields = $removeFields;
}
function mapper($item) {
$item = A::with($item);
$fields = $this->ppFields;
$addFields = $this->ppAddFields;
$removeFields = $this->ppRemoveFields;
if ($fields === null && ($addFields !== null || $removeFields !== null)) {
$fields = array_keys($item);
}
if ($addFields !== null) A::merge($fields, $addFields);
$item = A::select_default($item, $fields);
if ($removeFields !== null) {
foreach ($removeFields as $field) {
unset($item[$field]);
}
}
return $item;
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getFields' => 'fields',
'getAddFields' => 'add_fields',
'getRemoveFields' => 'remove_fields',
];
const _AUTO_SETTERS = /*autogen*/[
'setFields' => 'fields',
'setAddFields' => 'add_fields',
'setRemoveFields' => 'remove_fields',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,79 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\mapper_utils;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
use nur\types;
/**
* Class AttributeFilterMapper_command: classe de support pour construire une
* instance de mapper pour une application recevant des commandes utilisateurs
*/
class AttributeFilterMapper_command {
use Tparams_command;
const NAME = "sela";
const DEF = [self::NAME,
"help" => "filtrer et mapper des attributs",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [name[=default]...]",
["-a", "--add-fields", "args" => ["value", [null]],
"action" => [self::class, "set_add_fields"],
],
["-r", "--remove-fields", "args" => ["value", [null]],
"action" => [self::class, "set_remove_fields"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["value", null]],
"action" => [self::class, "set_fields"],
],
],
];
/** @var AttributeFilterMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new AttributeFilterMapper());
}
static function set_fields(array $names) {
$fields = [];
foreach ($names as $name) {
if (mapper_utils::split_param($name, $name, $type, $value)) {
if ($type !== null) $value = types::with($type, $value);
$fields[$name] = $value;
} else {
$fields[] = $name;
}
}
if ($fields) self::$command->setFields($fields);
}
static function set_add_fields(array $names) {
$fields = [];
foreach ($names as $name) {
if (mapper_utils::split_param($name, $name, $type, $value)) {
if ($type !== null) $value = types::with($type, $value);
$fields[$name] = $value;
} else {
$fields[] = $name;
}
}
self::$command->setAddFields($fields);
}
static function set_remove_fields(array $names) {
$fields = [];
foreach ($names as $name) {
mapper_utils::split_param($name, $name, $type, $value);
$fields[] = $name;
}
self::$command->setRemoveFields($fields);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\Mapper;
/**
* Class EnsureLatin1Mapper: s'assurer que toutes les valeurs chaines peuvent
* être converties en latin1 sans problème. les chaines restent en utf-8, mais
* elles sont translitérées si nécessaire
*/
class EnsureLatin1Mapper extends Mapper {
static function ensure_latin1(&$values): void {
if (is_array($values)) {
foreach ($values as &$value) {
self::ensure_latin1($value);
}; unset($value);
} elseif (is_string($values)) {
$latin1 = iconv("utf-8", "latin1//TRANSLIT//IGNORE", $values);
$values = iconv("latin1", "utf-8", $latin1);
}
}
function mapper($item) {
self::ensure_latin1($item);
return $item;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\Mapper;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class GenericMapper_command: classe de support pour construire une instance
* de mapper pour une application recevant des commandes utilisateurs
*/
class GenericMapper_command {
use Tparams_command;
static function get_def(): array {
return [null, [
"package\\Mapper",
"help" => "appliquer un mapper générique",
], function(string $command): ?array {
if (!is_subclass_of($command, Mapper::class)) return null;
return [$command, "help" => "appliquer le mapper $command",
"action" => [static::class, "create_command"],
"cmd_args" => [
"usage" => $command,
["-o", "--param", "args" => "value",
"action" => [static::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
}];
}
/** @var Mapper */
protected static $command;
static function create_command($value, $name, $arg) {
self::$command = mappers_command::add(new $arg());
}
}

View File

@ -0,0 +1,400 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\b\IllegalAccessException;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\b\ValueException;
use nur\mapper\base\Mapper;
use nur\mapper\base\mapper_utils;
use nur\mapper\base\mark_utils;
use nur\str;
/**
* Class ItemFilterMapper: filtrage des élements d'un flux
*
* --autogen-properties-and-methods--
* @method int getSkipItems()
* @method int getMaxCount()
* @method setAction($value)
* @method int setSkipItems(int $value)
* @method int setMaxCount(int $value)
* @method array|null setConditions(?array $value)
*/
class ItemFilterMapper extends Mapper {
use Tparametrable;
const ACTION_FILTER = 1;
const ACTION_KEEP = 2;
const ACTION_MARK = 3;
const CONDITION_EQ = 1;
const CONDITION_NE = 2;
const CONDITION_LT = 3;
const CONDITION_LE = 4;
const CONDITION_GT = 5;
const CONDITION_GE = 6;
const CONDITION_Z = 7;
const CONDITION_NZ = 8;
const CONDITION_N = 9;
const CONDITION_NN = 10;
const CONDITION_TRUE = 11;
const CONDITION_FALSE = 12;
const PARAMETRABLE_PARAMS_SCHEMA = [
"action" => ["key", self::ACTION_KEEP, "action à effectuer si l'élément correspondant"],
"skip_items" => ["int", null, "nombre d'éléments à ignorer au début du flux"],
"max_count" => ["int", null, "nombre d'éléments maximum à retourner"],
"conditions" => ["?array", null, "conditions que doivent remplir l'élément pour être sélectionné"],
];
/** @var int */
protected $ppAction;
function pp_setAction($action): void {
switch ($action) {
case self::ACTION_FILTER:
case self::ACTION_KEEP:
case self::ACTION_MARK:
break;
case "filter":
case "x":
$action = self::ACTION_FILTER;
break;
case "keep":
case "k":
$action = self::ACTION_KEEP;
break;
case "mark":
case "m":
$action = self::ACTION_MARK;
break;
default:
throw ValueException::invalid_value($action, "action");
}
if ($action == self::ACTION_MARK) mark_utils::set_use_marks($this);
$this->ppAction = $action;
}
function setActionFilter(): self {
$this->pp_setAction(self::ACTION_FILTER);
return $this;
}
function setActionKeep(): self {
$this->pp_setAction(self::ACTION_KEEP);
return $this;
}
function setActionMark(): self {
$this->pp_setAction(self::ACTION_MARK);
return $this;
}
/** @var int nombre d'éléments à ignorer au début du flux */
protected $ppSkipItems = 0;
/** @var int nombre d'éléments maximum à retourner */
protected $ppMaxCount = -1;
/** @var array */
protected $ppConditions = [];
function pp_setConditions(?array $conditions): void {
if ($conditions !== null) {
foreach ($conditions as $condition) {
$this->addCondition($condition);
}
}
}
function addCondition(?string $condition): self {
if (preg_match('/^([A-Za-z0-9_]*)=(.*)$/', $condition, $vs)) {
$key = A::get($vs, 1);
$condition = A::get($vs, 2);
} else {
$key = null;
}
if (mapper_utils::check_prefix($condition, $value, "eq:", "=")) {
$this->ppConditions[] = [self::CONDITION_EQ, $key, $value];
} elseif (mapper_utils::check_prefix($condition, $value, "ne:", "!=")) {
$this->ppConditions[] = [self::CONDITION_NE, $key, $value];
} elseif (mapper_utils::check_prefix($condition, $value, "lt:", "<")) {
$this->ppConditions[] = [self::CONDITION_LT, $key, $value];
} elseif (mapper_utils::check_prefix($condition, $value, "le:", "<=")) {
$this->ppConditions[] = [self::CONDITION_LE, $key, $value];
} elseif (mapper_utils::check_prefix($condition, $value, "gt:", ">")) {
$this->ppConditions[] = [self::CONDITION_GT, $key, $value];
} elseif (mapper_utils::check_prefix($condition, $value, "ge:", ">=")) {
$this->ppConditions[] = [self::CONDITION_GE, $key, $value];
} elseif (mapper_utils::check_prefix($condition, $value, "nz")) {
$this->ppConditions[] = [self::CONDITION_NZ, $key];
} elseif (mapper_utils::check_prefix($condition, $value, "z")) {
$this->ppConditions[] = [self::CONDITION_Z, $key];
} elseif (mapper_utils::check_prefix($condition, $value, "notnull", "nn")) {
$this->ppConditions[] = [self::CONDITION_NN, $key];
} elseif (mapper_utils::check_prefix($condition, $value, "null", "n")) {
$this->ppConditions[] = [self::CONDITION_N, $key];
} elseif (mapper_utils::check_prefix($condition, $value, "true", "t")) {
$this->ppConditions[] = [self::CONDITION_TRUE, $key];
} elseif (mapper_utils::check_prefix($condition, $value, "false", "f")) {
$this->ppConditions[] = [self::CONDITION_FALSE, $key];
} else {
# par défaut, tester l'égalité
$this->ppConditions[] = [self::CONDITION_EQ, $key, $condition];
}
return $this;
}
function addConditionEq($value, ?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_EQ, $key, $value];
return $this;
}
function addConditionNe($value, ?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_NE, $key, $value];
return $this;
}
function addConditionLt($value, ?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_LT, $key, $value];
return $this;
}
function addConditionLe($value, ?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_LE, $key, $value];
return $this;
}
function addConditionGt($value, ?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_GT, $key, $value];
return $this;
}
function addConditionGe($value, ?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_GE, $key, $value];
return $this;
}
function addConditionZ(?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_Z, $key];
return $this;
}
function addConditionNz(?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_NZ, $key];
return $this;
}
function addConditionN(?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_N, $key];
return $this;
}
function addConditionNn(?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_NN, $key];
return $this;
}
function addConditionTrue(?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_TRUE, $key];
return $this;
}
function addConditionFalse(?string $key=null): self {
$this->ppConditions[] = [self::CONDITION_FALSE, $key];
return $this;
}
#############################################################################
protected static function check(&$item, ?string $forKey, callable $func, array $args): bool {
if (is_array($item)) {
[$forKey, $subKey] = str::split_pair($forKey, ".");
$result = false;
foreach ($item as $key => &$value) {
if ($forKey !== null & $key != $forKey) continue;
$result = $result || self::check($value, $subKey, $func, $args);
}
return $result;
} else {
return boolval($func($item, ...$args));
}
}
protected static function check_eq($value, $other): bool {
return $value == $other;
}
protected static function check_ne($value, $other): bool {
return $value != $other;
}
protected static function check_lt($value, $other): bool {
return $value < $other;
}
protected static function check_le($value, $other): bool {
return $value <= $other;
}
protected static function check_gt($value, $other): bool {
return $value > $other;
}
protected static function check_ge($value, $other): bool {
return $value >= $other;
}
protected static function check_z($value): bool {
return $value === null || $value === false;
}
protected static function check_nz($value): bool {
return $value !== null && $value !== false;
}
protected static function check_n($value): bool {
return $value === null;
}
protected static function check_nn($value): bool {
return $value !== null;
}
protected static function check_true($value): bool {
return $value === true;
}
protected static function check_false($value): bool {
return $value === false;
}
/** @var int */
private $skipped;
/** @var int */
private $count;
protected function setup(): void {
parent::setup();
$this->skipped = 0;
$this->count = 0;
}
protected function select($item, bool $selected, ?int $action=null) {
if ($action === null) $action = $this->ppAction;
switch ($action) {
case self::ACTION_FILTER:
if ($selected) return $this->mapToNone();
else return $item;
case self::ACTION_KEEP:
if ($selected) return $item;
else return $this->mapToNone();
case self::ACTION_MARK:
if ($selected) $this->setItemMarked($item);
return $item;
}
throw IllegalAccessException::not_implemented("action $this->ppAction");
}
function mapper($item) {
if ($this->skipped < $this->ppSkipItems) {
$this->skipped++;
return $this->select($item, false, self::ACTION_KEEP);
}
if ($this->ppMaxCount < 0 || $this->count < $this->ppMaxCount) {
$this->count++;
$selected = true;
foreach ($this->ppConditions as $conditionParams) {
[$condition, $forKey] = array_slice($conditionParams, 0, 2);
$params = array_slice($conditionParams, 2);
switch ($condition) {
case self::CONDITION_EQ:
$selected = $selected && self::check($item, $forKey, [self::class, "check_eq"], $params);
break;
case self::CONDITION_NE:
$selected = $selected && self::check($item, $forKey, [self::class, "check_ne"], $params);
break;
case self::CONDITION_LT:
$selected = $selected && self::check($item, $forKey, [self::class, "check_lt"], $params);
break;
case self::CONDITION_LE:
$selected = $selected && self::check($item, $forKey, [self::class, "check_le"], $params);
break;
case self::CONDITION_GT:
$selected = $selected && self::check($item, $forKey, [self::class, "check_gt"], $params);
break;
case self::CONDITION_GE:
$selected = $selected && self::check($item, $forKey, [self::class, "check_ge"], $params);
break;
case self::CONDITION_Z:
$selected = $selected && self::check($item, $forKey, [self::class, "check_z"], $params);
break;
case self::CONDITION_NZ:
$selected = $selected && self::check($item, $forKey, [self::class, "check_nz"], $params);
break;
case self::CONDITION_N:
$selected = $selected && self::check($item, $forKey, [self::class, "check_n"], $params);
break;
case self::CONDITION_NN:
$selected = $selected && self::check($item, $forKey, [self::class, "check_nn"], $params);
break;
case self::CONDITION_TRUE:
$selected = $selected && self::check($item, $forKey, [self::class, "check_true"], $params);
break;
case self::CONDITION_FALSE:
$selected = $selected && self::check($item, $forKey, [self::class, "check_false"], $params);
break;
default:
throw IllegalAccessException::not_implemented("condition $condition");
}
}
return $this->select($item, $selected);
}
return $this->select($item, false, self::ACTION_KEEP);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getAction' => 'action',
'getSkipItems' => 'skip_items',
'getMaxCount' => 'max_count',
'getConditions' => 'conditions',
];
const _AUTO_SETTERS = /*autogen*/[
'setAction' => 'action',
'setSkipItems' => 'skip_items',
'setMaxCount' => 'max_count',
'setConditions' => 'conditions',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,75 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class ItemFilterMapper_command: classe de support pour construire une
* instance de mapper pour une application recevant des commandes utilisateurs
*/
class ItemFilterMapper_command {
use Tparams_command;
const NAME = "seli";
const DEF = [self::NAME,
"help" => "filtrer des éléments",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["group",
["-a", "--action", "args" => "value",
"action" => [self::class, "set_action"],
],
["-X", "--action-filter", "value" => "filter",
"action" => [self::class, "set_action"],
],
["-K", "--action-keep", "value" => "keep",
"action" => [self::class, "set_action"],
],
["-M", "--action-mark", "value" => "mark",
"action" => [self::class, "set_action"],
],
],
["-s", "--skip-items", "args" => "value", "type" => "int",
"action" => [self::class, "set_skip_items"],
],
["-m", "--max-count", "args" => "value", "type" => "int",
"action" => [self::class, "set_max_count"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["value", null]],
"action" => [self::class, "add_conditions"],
],
],
];
/** @var ItemFilterMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new ItemFilterMapper());
}
static function set_action(string $action) {
self::get()->setAction($action);
}
static function set_skip_items(int $skip_items) {
self::get()->setSkipItems($skip_items);
}
static function set_max_count(int $max_count) {
self::get()->setMaxCount($max_count);
}
static function add_conditions(array $conditions) {
$command = self::get();
foreach ($conditions as $condition) {
$command->addCondition($condition);
}
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\mapper\base\Consumer;
use nur\msg;
class LoggerConsumer extends Consumer {
function section(string $title) {
msg::section($title);
}
static function to_string($item): string {
if (A::is_assoc($item)) {
$parts = [];
foreach ($item as $key => $part) {
$parts[] = "$key => ".self::to_string($part);
}
return "{ ".implode(", ", $parts)." }";
} elseif (A::is_seq($item)) {
$parts = [];
foreach ($item as $key => $part) {
$parts[] = self::to_string($part);
}
return implode(", ", $parts);
} elseif (is_string($item)) {
return $item;
} else {
return var_export($item, true);
}
}
function cook($item) {
msg::info(self::to_string($item));
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\Mapper;
/**
* Class NumberMapper: un mapper qui fait des opérations sur des nombres
*/
class NumberMapper extends Mapper {
}

View File

@ -0,0 +1,33 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class NumberMapper_command: classe de support pour construire une instance de
* mapper pour une application recevant des commandes utilisateurs
*/
class NumberMapper_command {
use Tparams_command;
const NAME = "number";
const DEF = [self::NAME, "num",
"help" => "faire des opérations sur des nombres",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var NumberMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new NumberMapper());
}
}

View File

@ -0,0 +1,260 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\b\IllegalAccessException;
use nur\b\params\Tparametrable;
use nur\b\ValueException;
use nur\data\types\Metadata;
use nur\mapper\base\Mapper;
/**
* Class SchemaMapper
*
* --autogen-properties-and-methods--
* @method bool isUseKey()
* @method bool isEnsureKnownTypes()
* @method bool isRecursive()
* @method bool isEnsureTypes()
* @method bool isThrow()
* @method bool isKeepResults()
* @method bool isCheckRequired()
* @method array|null setSchema(?array $value)
* @method bool setUseKey(bool $value)
* @method bool setEnsureKnownTypes(bool $value)
* @method bool setRecursive(bool $value)
* @method bool setEnsureTypes(bool $value)
* @method bool setThrow(bool $value)
* @method bool setKeepResults(bool $value)
* @method bool setCheckRequired(bool $value)
*/
class SchemaMapper extends Mapper {
use Tparametrable;
protected function SCHEMA(): ?array {
return self::SCHEMA;
} const SCHEMA = null;
function __construct(?array $schema=null, ?iterable $source=null) {
parent::__construct($source);
$this->pp_setSchema($schema);
}
const MULTIPLEX_RESULTS_COLS = "cols";
const MULTIPLEX_RESULTS_ROWS = "rows";
const PARAMETRABLE_PARAMS_SCHEMA = [
"schema" => ["?array", null, "schéma des données"],
"use_key" => ["bool", false, "faut-il tenir compte des clés fournies par la source"],
"ensure_known_types" => ["bool", false, "faut-il vérifier et convertir les types connus?"],
"recursive" => ["bool", true, "faut-il appliquer les schémas de façon récursive"],
"ensure_types" => ["bool", true, "faut-il vérifier et convertir les types connus?"],
"throw" => ["bool", false, "faut-il lancer une exception si certains champs sont invalides?"],
"keep_results" => ["bool", false, "faut-il garder le résultat des analyses et l'insérer dans le flux?"],
"multiplex_results" => [["string"], null, "faut-il multiplexer champs et résultats d'analyse",
"allowed_values" => [self::MULTIPLEX_RESULTS_COLS, self::MULTIPLEX_RESULTS_ROWS],
],
"check_required" => ["bool", false, "faut-il vérifier que les champs requis soient présents"],
];
/** @var Metadata */
protected $md;
function pp_setSchema(?array $schema): self {
if ($schema === null) $schema = $this->SCHEMA();
if ($schema !== null) $this->md = new Metadata($schema);
return $this;
}
/** @var bool */
protected $ppUseKey;
/** @var bool */
protected $ppEnsureKnownTypes;
/** @var bool */
protected $ppRecursive;
/** @var bool */
protected $ppEnsureTypes;
/** @var bool */
protected $ppThrow;
/** @var bool */
protected $ppKeepResults;
/** @var string|null */
protected $ppMultiplexResults;
function pp_setMultiplexResults(string $multiplexResults): self {
switch ($multiplexResults) {
case "cols":
case "col":
case "c":
$multiplexResults = self::MULTIPLEX_RESULTS_COLS;
break;
case "rows":
case "row":
case "r":
$multiplexResults = self::MULTIPLEX_RESULTS_ROWS;
break;
default:
throw ValueException::unexpected_value($multiplexResults, [
self::MULTIPLEX_RESULTS_COLS,
self::MULTIPLEX_RESULTS_ROWS,
]);
}
$this->ppMultiplexResults = $multiplexResults;
return $this;
}
function setMultiplexResults(string $multiplexResults=self::MULTIPLEX_RESULTS_COLS): void {
$this->pp_setMultiplexResults($multiplexResults);
}
/** @var bool */
protected $ppCheckRequired;
const OKEY_NB_ERRORS_TOTAL = "nb_errors_total";
function getNbErrorsTotal(): int {
return $this->getOvalue(self::OKEY_NB_ERRORS_TOTAL);
}
protected function setup(): void {
$this->setOvalue(self::OKEY_NB_ERRORS_TOTAL, 0);
}
function mapper($item, $key=null) {
if (!$this->ppUseKey) $key = null;
$md = $this->md;
$md->ensureSchema($item, $key, ["recursive" => $this->ppRecursive]);
$itemResults = null;
if ($this->ppEnsureTypes) {
$md->verifix($item, $results, $this->ppThrow, $this->ppRecursive);
$nbErrors = 0;
foreach ($results as $result) {
if (!$result["valid"]) {
$nbErrors++;
$this->incOvalue(self::OKEY_NB_ERRORS_TOTAL);
}
}
if ($this->ppKeepResults) {
$item = [
"item" => $item,
"results" => $results,
"nb_errors" => $nbErrors,
];
} elseif ($this->ppMultiplexResults !== null) {
switch ($this->ppMultiplexResults) {
case "cols":
$mitem = ["[nb_errors]" => $nbErrors];
foreach ($item as $key => $value) {
$mitem[$key] = $value;
$result = A::get($results, $key);
if ($result !== null) {
$result = $result["valid"]? null: $result["error"];
}
$mitem["[${key}_error]"] = $result;
}
foreach ($md->getCikeys() as $key) {
$result = A::get($results, $key);
if ($result !== null) {
$result = $result["valid"]? null: $result["error"];
}
$mitem["[${key}_error]"] = $result;
}
$item = $mitem;
break;
case "rows":
# construire la ligne avec les résultats
$mitem = ["[nb_errors]" => $nbErrors];
foreach ($item as $key => $value) {
$result = A::get($results, $key);
if ($result !== null) {
$result = $result["valid"]? null: $result["error"];
}
$mitem[$key] = $result;
}
foreach ($md->getCikeys() as $key) {
$result = A::get($results, $key);
if ($result !== null) {
$result = $result["valid"]? null: $result["error"];
}
$mitem[$key] = $result;
}
$itemResults = $mitem;
# puis ajouter la colonne manquante dans l'objet original
$mitem = ["[nb_errors]" => $nbErrors];
foreach ($item as $key => $value) {
$mitem[$key] = $value;
}
foreach ($md->getCikeys() as $key) {
$mitem[$key] = null;
}
$item = $mitem;
break;
default:
throw IllegalAccessException::unexpected_state();
}
}
} elseif ($this->ppCheckRequired) {
$md->checkRequired($item);
}
if ($itemResults === null) return $item;
else return $this->mapTo([$item, $itemResults]);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getSchema' => 'schema',
'isUseKey' => 'use_key',
'isEnsureKnownTypes' => 'ensure_known_types',
'isRecursive' => 'recursive',
'isEnsureTypes' => 'ensure_types',
'isThrow' => 'throw',
'isKeepResults' => 'keep_results',
'getMultiplexResults' => 'multiplex_results',
'isCheckRequired' => 'check_required',
];
const _AUTO_SETTERS = /*autogen*/[
'setSchema' => 'schema',
'setUseKey' => 'use_key',
'setEnsureKnownTypes' => 'ensure_known_types',
'setRecursive' => 'recursive',
'setEnsureTypes' => 'ensure_types',
'setThrow' => 'throw',
'setKeepResults' => 'keep_results',
'setMultiplexResults' => 'multiplex_results',
'setCheckRequired' => 'check_required',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,51 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class SchemaMapper_command: classe de support pour construire une instance de
* mapper pour une application recevant des commandes utilisateurs
*/
class SchemaMapper_command {
use Tparams_command;
const NAME = "schema";
const DEF = [self::NAME,
"help" => "appliquer un schéma",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-r", "--run-code", "args" => "value",
"action" => [self::class, "set_schema_from_code"],
"help" => "exécuter le code et utiliser sa valeur de retour comme schéma",
],
["-f", "--run-file", "args" => "file",
"action" => [self::class, "set_schema_from_file"],
"help" => "exécuter le fichier et utiliser sa valeur de retour comme schéma",
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var SchemaMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new SchemaMapper());
}
static function set_schema_from_code(string $code) {
$schema = eval($code);
self::get()->setFsvSchema($schema);
}
static function set_schema_from_file(string $file) {
$schema = require($file);
self::get()->setFsvSchema($schema);
}
}

View File

@ -0,0 +1,109 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\b\params\Tparametrable;
use nur\mapper\base\Mapper;
/**
* Class Seq2AssocMapper: transformer un flux de tableaux séquentiels en
* tableaux associatifs
*
* --autogen-properties-and-methods--
* @method bool|null getParseKeys()
* @method array|null getKeys()
* @method bool|null setParseKeys(?bool $value)
* @method array|null setKeys(?array $value)
*/
class Seq2AssocMapper extends Mapper {
use Tparametrable;
const PARAMETRABLE_PARAMS_SCHEMA = [
"parse_keys" => ["?bool", null, "faut-il analyser le premier élément du flux pour calculer la liste des clés en entrée?"],
"keys" => ["?array", null, "liste et ordre des clés en entrée"],
];
/**
* @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 ($keys === 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 setKeys()}
*/
protected $ppParseKeys;
/**
* @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 $ppKeys;
function _checkKeys(array $keys): bool {
$parseKeys = $this->ppParseKeys;
$setParseKeys = $parseKeys !== null;
if ($parseKeys === null) $parseKeys = $this->ppKeys === null;
if ($parseKeys) {
if ($this->ppKeys === null) $this->ppKeys = $keys;
if ($setParseKeys) $this->ppParseKeys = false;
return true;
}
return false;
}
function _mapValues(array $values): array {
$keys = $this->ppKeys;
if ($keys === null) return $values;
$row = [];
$index = 0;
foreach ($keys as $key) {
$row[$key] = A::get($values, $index++, false);
}
return $row;
}
function mapper($item) {
$values = A::with($item);
if ($this->_checkKeys($values)) return $this->mapToNone();
return $this->_mapValues($values);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getParseKeys' => 'parse_keys',
'getKeys' => 'keys',
];
const _AUTO_SETTERS = /*autogen*/[
'setParseKeys' => 'parse_keys',
'setKeys' => 'keys',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,49 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class Seq2AssocMapper_command: classe de support pour construire une instance
* de mapper pour une application recevant des commandes utilisateurs
*/
class Seq2AssocMapper_command {
use Tparams_command;
const NAME = "seq2assoc";
const DEF = [self::NAME,
"help" => "convertir en tableaux associatifs",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-k", "--parse-keys", "type" => "bool",
"action" => [self::class, "set_parse_keys", true],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["value", null]], "type" => "array",
"action" => [self::class, "set_keys"],
],
],
];
/** @var Seq2AssocMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new Seq2AssocMapper());
}
static function set_parse_keys(bool $parseKeys) {
self::get()->setParseKeys($parseKeys);
}
static function set_keys(array $args) {
A::merge($keys, ...$args);
if ($keys) self::get()->setKeys($keys);
}
}

View File

@ -0,0 +1,145 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\b\params\Tparametrable;
use nur\b\ValueException;
use nur\mapper\base\capacitor\Capacitor;
use nur\mapper\base\Mapper;
use Traversable;
/**
* Class StreamMapper: un mapper qui fait des opérations sur le flux
*
* --autogen-properties-and-methods--
* @method string setAction(string $value)
* @method int setMaxCount(int $value)
*/
class StreamMapper extends Mapper {
use Tparametrable;
const MAP_EOF = true;
/** @var Capacitor */
protected $capacitor;
function setCapacitor(Capacitor $capacitor): self {
$this->capacitor = $capacitor;
return $this;
}
protected function capacitor(): Capacitor {
if ($this->capacitor === null) $this->capacitor = new Capacitor();
return $this->capacitor;
}
const ACTION_SPLIT = "split";
const ACTION_JOIN = "join";
const PARAMETRABLE_PARAMS_SCHEMA = [
"action" => ["string", null, "action à effectuer sur le flux"],
"max_count" => ["int", null, "nombre d'élément par batch"],
];
/** @var string */
protected $ppAction;
/** @var int */
protected $ppMaxCount = 0;
function pp_setAction(?string $action, ?int $maxCount=null): void {
switch ($action) {
case "split":
case "s":
$this->ppAction = self::ACTION_SPLIT;
break;
case "join":
case "j":
$this->ppAction = self::ACTION_JOIN;
$this->count = 0;
break;
default:
if ($action !== null) {
throw ValueException::invalid_value($action, "action");
}
}
if ($maxCount !== null) $this->ppMaxCount = $maxCount;
}
function setActionSplit(): self {
$this->pp_setAction("split");
return $this;
}
function setActionJoin(?int $maxCount=null): self {
$this->pp_setAction("join", $maxCount);
return $this;
}
function pp_setMaxCount(int $maxCount): self {
$this->pp_setAction(null, $maxCount);
return $this;
}
protected $count;
function mapper($item) {
switch ($this->ppAction) {
case self::ACTION_SPLIT:
if (!is_iterable($item)) $item = A::with($item);
return $this->mapTo($item);
case self::ACTION_JOIN:
if (!$this->eof) $this->capacitor()->charge($item);
$this->count++;
$discharge = $this->ppMaxCount > 0 && $this->count >= $this->ppMaxCount;
if ($discharge || $this->eof) {
$values = $this->capacitor()->discharge();
$this->count = 0;
if ($values instanceof Traversable) $values = iterator_to_array($values);
$item = $values? [$values]: null;
} else {
$item = null;
}
return $this->mapTo($item);
}
return $item;
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getAction' => 'action',
'getMaxCount' => 'max_count',
];
const _AUTO_SETTERS = /*autogen*/[
'setAction' => 'action',
'setMaxCount' => 'max_count',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,47 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class StreamMapper_command: classe de support pour construire une instance de
* mapper pour une application recevant des commandes utilisateurs
*/
class StreamMapper_command {
use Tparams_command;
const NAME = "stream";
const DEF = [self::NAME,
"help" => "faire des opérations sur le flux",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-s", "--split",
"action" => [self::class, "set_action_split"],
],
["-j", "--join", "args" => [["value"]], "type" => "?int",
"action" => [self::class, "set_action_join"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var StreamMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new StreamMapper());
}
static function set_action_split() {
self::get()->setActionSplit();
}
static function set_action_join(?int $maxCount) {
self::get()->setActionJoin($maxCount);
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\base;
use nur\str;
/**
* Class StringMapper: un mapper qui fait des opérations sur des chaines
*
* --autogen-properties-and-methods--
* @method array|null setActions(?array $value)
* @method bool setMarkedOnly(bool $value)
*/
class StringMapper extends AbstractStringMapper {
protected static function del_prefix($value, ?string $prefix) {
return str::without_prefix($prefix, $value);
}
protected static function del_suffix($value, ?string $suffix) {
return str::without_suffix($suffix, $value);
}
protected static function left($value, int $length) {
if (base::z($value)) return $value;
return substr(strval($value), 0, $length);
}
protected static function right($value, int $length) {
if (base::z($value)) return $value;
return substr(strval($value), -$length);
}
protected static function substr($value, int $offset, ?int $length) {
if (base::z($value)) return $value;
$args = [strval($value), $offset];
if ($length !== null) $args[] = $length;
return substr(...$args);
}
protected static function str_replace($value, ?string $from, ?string $to, int $limit) {
if (base::z($value)) return $value;
if ($limit == -1) return str_replace($from, $to, strval($value));
$value = strval($value);
$index = 0;
$flength = strlen($from);
while ($limit-- > 0) {
$pos = strpos($value, $from, $index);
if ($pos === false) break;
$value = substr($value, 0, $pos).$to.substr($value, $pos + $flength);
$index = $pos + $flength + 1;
}
return $value;
}
protected static function join($value, string $sep): string {
if (base::z($value)) return $value;
return str::join($sep, A::with($value));
}
protected static function trunc($value, int $length, bool $ellips=false, ?string $suffix=null): string {
if (base::z($value)) return $value;
return str::trunc(strval($value), $length, $ellips, $suffix);
}
protected static function trim($value): string {
if (base::z($value)) return $value;
return str::trim(strval($value));
}
protected static function ltrim($value): string {
if (base::z($value)) return $value;
return str::ltrim(strval($value));
}
protected static function rtrim($value): string {
if (base::z($value)) return $value;
return str::rtrim(strval($value));
}
protected static function lower($value): string {
if (base::z($value)) return $value;
return str::lower(strval($value));
}
protected static function lower1($value): string {
if (base::z($value)) return $value;
return str::lower1(strval($value));
}
protected static function upper($value): string {
if (base::z($value)) return $value;
return str::upper(strval($value));
}
protected static function upper1($value): string {
if (base::z($value)) return $value;
return str::upper1(strval($value));
}
protected static function upperw($value): string {
if (base::z($value)) return $value;
return str::upperw(strval($value));
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getActions' => 'actions',
'isMarkedOnly' => 'marked_only',
];
const _AUTO_SETTERS = /*autogen*/[
'setActions' => 'actions',
'setMarkedOnly' => 'marked_only',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,51 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class StringMapper_command: classe de support pour construire une instance de
* mapper pour une application recevant des commandes utilisateurs
*/
class StringMapper_command {
use Tparams_command;
const NAME = "string";
const DEF = [self::NAME, "str",
"help" => "faire des opérations sur des chaines",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["--mo", "--marked-only", "args" => "value", "type" => "bool",
"action" => [self::class, "set_marked_only"],
"help" => "indiquer si les opérations ne doivent être effectuées que sur les éléments marqués",
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["value", null]],
"action" => [self::class, "add_actions"],
],
],
];
/** @var StringMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new StringMapper());
}
static function set_marked_only(?bool $marked_only) {
self::get()->setMarkedOnly($marked_only);
}
static function add_actions(array $actions) {
$command = self::get();
foreach ($actions as $action) {
$command->addAction($action);
}
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace nur\mapper\item;
use nur\A;
use nur\base;
use nur\str;
use nur\txt;
/**
* Class TextMapper: un mapper qui fait des opérations sur des chaines
*
* --autogen-properties-and-methods--
* @method array|null setActions(?array $value)
* @method bool setMarkedOnly(bool $value)
*/
class TextMapper extends AbstractStringMapper {
protected static function del_prefix($value, ?string $prefix) {
return txt::without_prefix($prefix, $value);
}
protected static function del_suffix($value, ?string $suffix) {
return txt::without_suffix($suffix, $value);
}
protected static function left($value, int $length) {
if (base::z($value)) return $value;
return mb_substr(strval($value), 0, $length);
}
protected static function right($value, int $length) {
if (base::z($value)) return $value;
return mb_substr(strval($value), -$length);
}
protected static function substr($value, int $offset, ?int $length) {
if (base::z($value)) return $value;
$args = [strval($value), $offset];
if ($length !== null) $args[] = $length;
return mb_substr(...$args);
}
protected static function str_replace($value, ?string $from, ?string $to, int $limit) {
if (base::z($value)) return $value;
if ($limit == -1) return str_replace($from, $to, strval($value));
$value = strval($value);
$index = 0;
$flength = mb_strlen($from);
while ($limit-- > 0) {
$pos = mb_strpos($value, $from, $index);
if ($pos === false) break;
$value = mb_substr($value, 0, $pos).$to.mb_substr($value, $pos + $flength);
$index = $pos + $flength + 1;
}
return $value;
}
protected static function join($value, string $sep): string {
if (base::z($value)) return $value;
return str::join($sep, A::with($value));
}
protected static function trunc($value, int $length, bool $ellips=false, ?string $suffix=null): string {
if (base::z($value)) return $value;
return txt::trunc(strval($value), $length, $ellips, $suffix);
}
protected static function trim($value): string {
if (base::z($value)) return $value;
return txt::trim(strval($value));
}
protected static function ltrim($value): string {
if (base::z($value)) return $value;
return txt::ltrim(strval($value));
}
protected static function rtrim($value): string {
if (base::z($value)) return $value;
return txt::rtrim(strval($value));
}
protected static function lower($value): string {
if (base::z($value)) return $value;
return txt::lower(strval($value));
}
protected static function lower1($value): string {
if (base::z($value)) return $value;
return txt::lower1(strval($value));
}
protected static function upper($value): string {
if (base::z($value)) return $value;
return txt::upper(strval($value));
}
protected static function upper1($value): string {
if (base::z($value)) return $value;
return txt::upper1(strval($value));
}
protected static function upperw($value): string {
if (base::z($value)) return $value;
return txt::upperw(strval($value));
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getActions' => 'actions',
'isMarkedOnly' => 'marked_only',
];
const _AUTO_SETTERS = /*autogen*/[
'setActions' => 'actions',
'setMarkedOnly' => 'marked_only',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,51 @@
<?php
namespace nur\mapper\item;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class TextMapper_command: classe de support pour construire une instance de
* mapper pour une application recevant des commandes utilisateurs
*/
class TextMapper_command {
use Tparams_command;
const NAME = "text";
const DEF = [self::NAME, "txt",
"help" => "faire des opérations sur des chaines",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["--mo", "--marked-only", "args" => "value", "type" => "bool",
"action" => [self::class, "set_marked_only"],
"help" => "indiquer si les opérations ne doivent être effectuées que sur les éléments marqués",
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["value", null]],
"action" => [self::class, "add_actions"],
],
],
];
/** @var TextMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new TextMapper());
}
static function set_marked_only(?bool $marked_only) {
self::get()->setMarkedOnly($marked_only);
}
static function add_actions(array $actions) {
$command = self::get();
foreach ($actions as $action) {
$command->addAction($action);
}
}
}

View File

@ -0,0 +1,112 @@
<?php
namespace nur\mapper\json;
use nur\b\io\EOFException;
use nur\b\io\IReader;
use nur\b\params\Tparametrable;
use nur\mapper\base\Producer;
use nur\reader;
/**
* Class JsonReader
*
* --autogen-properties-and-methods--
* @method bool isParseWhole()
* @method setInput($value)
* @method bool setParseWhole(bool $value)
*/
class JsonReader extends Producer {
use Tparametrable;
function __construct($input=null) {
parent::__construct();
$this->pp_setInput($input);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"input" => [null, null, "fichier en entrée"],
"parse_whole" => ["bool", false, "faut-il analyser un objet par ligne"],
];
protected $ppInput;
function pp_setInput($input): void {
if ($input instanceof IReader) $this->reader = $input;
else $this->ppInput = $input;
}
/** @var bool */
protected $ppParseWhole = false;
/** @var IReader */
protected $reader;
protected function setup(): void {
if ($this->reader === null) {
$this->reader = reader::with($this->ppInput);
}
}
function producer() {
$reader = $this->reader;
$flags = JSON_THROW_ON_ERROR;
if ($this->ppParseWhole) {
$contents = $reader->readContents();
yield json_decode($contents, true, 512, $flags);
} else {
while (true) {
try {
$line = $reader->readLine();
yield json_decode($line, true, 512, $flags);
} catch (EOFException $e) {
break;
}
}
}
}
protected function teardown(): void {
if ($this->reader !== null) {
$this->reader->close();
$this->reader = null;
}
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getInput' => 'input',
'isParseWhole' => 'parse_whole',
];
const _AUTO_SETTERS = /*autogen*/[
'setInput' => 'input',
'setParseWhole' => 'parse_whole',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,45 @@
<?php
namespace nur\mapper\json;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class JsonReader_command: classe de support pour construire une instance de
* {@link JsonReader} pour une application recevant des commandes utilisateurs
*/
class JsonReader_command {
use Tparams_command;
const NAME = "injson";
const DEF = [self::NAME, "inj",
"help" => "configurer le fichier en entrée au format JSON",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const F_OPTION = ["-f", "--input", "args" => "file",
"action" => [self::class, "set_file"],
];
/** @var JsonReader */
private static $command;
static function create_command() {
self::$command = new JsonReader();
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setInput($file);
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace nur\mapper\json;
use nur\b\params\Tparametrable;
use nur\mapper\base\Consumer;
use nur\mapper\base\Ttmpwriter;
/**
* Class JsonWriter
*
* --autogen-properties-and-methods--
* @method bool isPrettyPrint()
* @method bool isForceObject()
* @method setOutput($value)
* @method bool setPrettyPrint(bool $value)
* @method bool setForceObject(bool $value)
*/
class JsonWriter extends Consumer {
use Tparametrable, Ttmpwriter;
function __construct($output=null) {
parent::__construct();
$this->pp_setOutput($output);
}
protected $ppPrettyPrint = false;
protected $ppForceObject = false;
const PARAMETRABLE_PARAMS_SCHEMA = [
"output" => [null, null, "fichier en sortie"],
"pretty_print" => ["bool", true, "type d'affichage"],
"force_object" => ["bool", true, "forcer la sortie en objet"],
];
protected function setup(): void {
parent::setup();
$this->setupWriter(false);
}
function cook($item) {
$options = JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE;
if ($this->ppPrettyPrint) $options += JSON_PRETTY_PRINT;
if ($this->ppForceObject) $options += JSON_FORCE_OBJECT;
$this->getWriter()->wnl(json_encode($item, $options));
}
protected function teardown(): void {
$this->teardownWriter(false);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getOutput' => 'output',
'isPrettyPrint' => 'pretty_print',
'isForceObject' => 'force_object',
];
const _AUTO_SETTERS = /*autogen*/[
'setOutput' => 'output',
'setPrettyPrint' => 'pretty_print',
'setForceObject' => 'force_object',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,58 @@
<?php
namespace nur\mapper\json;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class JsonWriter_command: classe de support pour construire une instance de
* {@link JsonWriter} pour une application recevant des commandes utilisateurs
*/
class JsonWriter_command {
use Tparams_command;
const NAME = "outjson";
const DEF = [self::NAME, "outj",
"help" => "configurer le fichier en sortie au format JSON",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-p", "--pretty-print",
"action" => [self::class, "set_pretty_print", true],
],
["-j", "--force-object",
"action" => [self::class, "set_force_object", true],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const O_OPTION = ["-o", "--output", "args" => "file",
"action" => [self::class, "set_file"],
];
/** @var JsonWriter */
private static $command;
static function create_command() {
self::$command = new JsonWriter();
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setOutput($file);
}
static function set_pretty_print(bool $pretty_print) {
self::get()->setPrettyPrint($pretty_print);
}
static function set_force_object(bool $force_object) {
self::get()->setForceObject($force_object);
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace nur\mapper\json;
use nur\b\io\IReader;
use nur\b\params\Tparametrable;
use nur\mapper\base\Producer;
use nur\reader;
use Symfony\Component\Yaml\Yaml;
/**
* Class YamlReader
*
* --autogen-properties-and-methods--
* @method setInput($value)
*/
class YamlReader extends Producer {
use Tparametrable;
function __construct($input=null) {
parent::__construct();
$this->pp_setInput($input);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"input" => [null, null, "fichier en entrée"],
];
protected $ppInput;
function pp_setInput($input): void {
if ($input instanceof IReader) $this->reader = $input;
else $this->ppInput = $input;
}
/** @var IReader */
protected $reader;
protected function setup(): void {
if ($this->reader === null) {
$this->reader = reader::with($this->ppInput);
}
}
function producer() {
$flags = Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE;
yield Yaml::parse($this->reader->readContents(), $flags);
}
protected function teardown(): void {
if ($this->reader !== null) {
$this->reader->close();
$this->reader = null;
}
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getInput' => 'input',
];
const _AUTO_SETTERS = /*autogen*/[
'setInput' => 'input',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,45 @@
<?php
namespace nur\mapper\json;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class YamlReader_command: classe de support pour construire une instance de
* {@link YamlReader} pour une application recevant des commandes utilisateurs
*/
class YamlReader_command {
use Tparams_command;
const NAME = "inyaml";
const DEF = [self::NAME, "inl",
"help" => "configurer le fichier en entrée au format YAML",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const F_OPTION = ["-f", "--input", "args" => "file",
"action" => [self::class, "set_file"],
];
/** @var YamlReader */
private static $command;
static function create_command() {
self::$command = new YamlReader();
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setInput($file);
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace nur\mapper\json;
use nur\b\params\Tparametrable;
use nur\mapper\base\Consumer;
use nur\mapper\base\Ttmpwriter;
use Symfony\Component\Yaml\Yaml;
/**
* Class YamlWriter
*
* --autogen-properties-and-methods--
* @method setOutput($value)
*/
class YamlWriter extends Consumer {
use Tparametrable, Ttmpwriter;
function __construct($output=null) {
parent::__construct();
$this->pp_setOutput($output);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"output" => [null, null, "fichier en sortie"],
];
protected function setup(): void {
parent::setup();
$this->setupWriter(false);
$this->firstObject = true;
}
private $firstObject;
function cook($item) {
$writer = $this->getWriter();
if ($this->firstObject) $this->firstObject = false;
else $writer->wnl("---");
$flags = Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK;
$writer->write(Yaml::dump($item, 20, 4, $flags));
}
protected function teardown(): void {
$this->teardownWriter(false);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getOutput' => 'output',
];
const _AUTO_SETTERS = /*autogen*/[
'setOutput' => 'output',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,45 @@
<?php
namespace nur\mapper\json;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class YamlWriter_command: classe de support pour construire une instance de
* {@link YamlWriter} pour une application recevant des commandes utilisateurs
*/
class YamlWriter_command {
use Tparams_command;
const O_OPTION = ["-o", "--output", "args" => "file",
"action" => [self::class, "set_file"],
];
const NAME = "outyaml";
const DEF = [self::NAME, "outl",
"help" => "configurer le fichier en sortie au format YAML",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
/** @var YamlWriter */
private static $command;
static function create_command() {
self::$command = new YamlWriter();
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setOutput($file);
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace nur\mapper\line;
use nur\b\params\Tparametrable;
use nur\mapper\base\Mapper;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Tencoding;
/**
* Class IconvMapper
*
* --autogen-properties-and-methods--
* @method string|null setInputEncoding(?string $value)
* @method string|null setOutputEncoding(?string $value)
*/
class IconvMapper extends Mapper {
use Tparametrable, Tencoding;
protected function INPUT_ENCODING(): ?string {
return static::INPUT_ENCODING;
} const INPUT_ENCODING = null;
protected function OUTPUT_ENCODING(): ?string {
return static::OUTPUT_ENCODING;
} const OUTPUT_ENCODING = "utf-8";
function __construct(?string $inputEncoding=null, ?iterable $source=null) {
parent::__construct($source);
$this->pp_setInputEncoding($inputEncoding);
$this->pp_setOutputEncoding(null);
}
const PARAMETRABLE_PARAMS_SCHEMA = encoding_utils::PARAMETRABLE_PARAMS_SCHEMA;
protected $ppInputEncoding;
function pp_setInputEncoding(?string $inputEncoding): self {
if ($inputEncoding === null) $inputEncoding = $this->INPUT_ENCODING();
$this->ppInputEncoding = $inputEncoding;
return $this;
}
protected $ppOutputEncoding;
function pp_setOutputEncoding(?string $outputEncoding): self {
if ($outputEncoding === null) $outputEncoding = $this->OUTPUT_ENCODING();
$this->ppOutputEncoding = $outputEncoding;
return $this;
}
function _cook(&$values, string $fromEncoding, string $toEncoding): void {
if (is_array($values)) {
foreach ($values as &$value) {
$this->_cook($value, $fromEncoding, $toEncoding);
}; unset($value);
} else {
$values = iconv($fromEncoding, $toEncoding, strval($values));
}
}
function mapper($item) {
$inputEncoding = $this->ppInputEncoding;
$outputEncoding = $this->ppOutputEncoding;
if ($inputEncoding !== null && $outputEncoding !== null) {
$this->_cook($item, $inputEncoding, $outputEncoding);
}
return $item;
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts"],
];
const _AUTOGEN_LITERALS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'\\nur\\b\\params\\parametrable_utils::class',
],
[
self::PARAMETRABLE_PARAMS_SCHEMA,
'self::PARAMETRABLE_PARAMS_SCHEMA',
],
];
const _AUTOGEN_METHODS = /*autogen*/[
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_getters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
[
\nur\b\params\parametrable_utils::class,
'_autogen_methods_setters',
self::PARAMETRABLE_PARAMS_SCHEMA,
null,
],
];
const _AUTO_GETTERS = /*autogen*/[
'getInputEncoding' => 'input_encoding',
'getOutputEncoding' => 'output_encoding',
];
const _AUTO_SETTERS = /*autogen*/[
'setInputEncoding' => 'input_encoding',
'setOutputEncoding' => 'output_encoding',
];
#--autogen-dynamic--
}

View File

@ -0,0 +1,47 @@
<?php
namespace nur\mapper\line;
use nur\mapper\base\mappers_command;
use nur\mapper\base\Tparams_command;
/**
* Class IconvMapper_command: classe de support pour construire une instance de
* mapper pour une application recevant des commandes utilisateurs
*/
class IconvMapper_command {
use Tparams_command;
const NAME = "iconv";
const DEF = [self::NAME,
"help" => "convertir l'encoding",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME,
["-f", "--from", "args" => "value",
"action" => [self::class, "set_input_encoding"],
],
["-t", "--to", "args" => "value",
"action" => [self::class, "set_output_encoding"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
],
];
/** @var IconvMapper */
protected static $command;
static function create_command() {
self::$command = mappers_command::add(new IconvMapper());
}
static function set_input_encoding(string $input_encoding) {
self::get()->setInputEncoding($input_encoding);
}
static function set_output_encoding(string $output_encoding) {
self::get()->setOutputEncoding($output_encoding);
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace nur\mapper\line;
use nur\A;
use nur\b\io\EOFException;
use nur\b\io\IReader;
use nur\b\io\Tfilter;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\data\types\Metadata;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Producer;
use nur\mapper\base\Tencoding;
use nur\mapper\csv\AbstractCsvMapper;
use nur\mapper\csv\Csv2AssocMapper;
use nur\mapper\csv\csv_defaults;
use nur\reader;
/**
* Class LineReader
*
* --autogen-methods--
*/
class LineReader extends Producer {
use Tparametrable, Tencoding, Tfilter;
function __construct($input=null) {
parent::__construct();
$this->pp_setInput($input);
}
function setEncodingFilter(string $from, string $to="utf-8"): void {
$this->_setEncodingFilter($from, $to);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"input" => [null, null, "fichier en entrée"],
];
static function _get_parametrable_params_schema(): array {
return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA
, encoding_utils::PARAMETRABLE_PARAMS_SCHEMA);
}
protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void {
$this->encodingInput__afterSetParametrableParams($modifiedKeys);
}
protected $ppInput;
function pp_setInput($input): void {
if ($input instanceof IReader) $this->reader = $input;
else $this->ppInput = $input;
}
/** @var IReader */
protected $reader;
protected function setup(): void {
if ($this->reader === null) {
$this->reader = reader::with($this->ppInput);
$this->_rwAppendFilters($this->reader);
}
}
function producer() {
$reader = $this->reader;
while (true) {
try {
yield $reader->readLine();
} catch (EOFException $e) {
break;
}
}
}
protected function teardown(): void {
if ($this->reader !== null) {
$this->reader->close();
$this->reader = null;
}
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts", true],
];
const _AUTOGEN_LITERALS = null;
const _AUTOGEN_METHODS = null;
const _AUTO_GETTERS = null;
const _AUTO_SETTERS = null;
#--autogen-dynamic--
}

View File

@ -0,0 +1,55 @@
<?php
namespace nur\mapper\line;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class LineReader_command: classe de support pour construire une instance de
* {@link LineReader} pour une application recevant des commandes utilisateurs
*/
class LineReader_command {
use Tparams_command;
const NAME = "infile";
const DEF = [self::NAME, "inf",
"help" => "configurer le fichier en entrée pour lecture ligne par ligne",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-e", "--input-encoding", "args" => "value",
"action" => [self::class, "set_input_encoding"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const F_OPTION = ["-f", "--input", "args" => "file",
"action" => [self::class, "set_file"],
];
const E_OPTION = ["-e", "--input-encoding", "args" => "value",
"action" => [self::class, "set_input_encoding"],
];
/** @var LineReader */
protected static $command;
static function create_command() {
self::$command = new LineReader();
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setInput($file);
}
static function set_input_encoding(string $input_encoding) {
self::get()->setEncodingFilter($input_encoding);
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace nur\mapper\line;
use nur\A;
use nur\b\io\Tfilter;
use nur\b\params\parametrable_utils;
use nur\b\params\Tparametrable;
use nur\data\types\Metadata;
use nur\mapper\base\Consumer;
use nur\mapper\base\encoding_utils;
use nur\mapper\base\Tencoding;
use nur\mapper\base\Ttmpwriter;
use nur\mapper\csv\csv_defaults;
/**
* Class LineWriter
*
* --autogen-methods--
*/
class LineWriter extends Consumer {
use Tparametrable, Ttmpwriter, Tencoding, Tfilter;
function __construct($output=null) {
parent::__construct();
$this->pp_setOutput($output);
}
function setEncodingFilter(string $to, string $from="utf-8"): void {
$this->_setEncodingFilter($from, $to);
}
const PARAMETRABLE_PARAMS_SCHEMA = [
"output" => [null, null, "fichier en sortie"],
];
static function _get_parametrable_params_schema(): array {
return array_merge(self::PARAMETRABLE_PARAMS_SCHEMA
, encoding_utils::PARAMETRABLE_PARAMS_SCHEMA);
}
protected function afterSetParametrableParams(array $modifiedKeys, ?Metadata $md=null): void {
$this->encodingOutput__afterSetParametrableParams($modifiedKeys);
}
protected function setup(): void {
parent::setup();
$this->setupWriter(true);
}
function cook($item) {
$this->getWriter()->wnl($item);
}
protected function teardown(): void {
$this->teardownWriter(true);
}
#############################################################################
const _AUTOGEN_CONSTS = [
"" => [self::class, "_autogen_consts", true],
];
const _AUTOGEN_LITERALS = null;
const _AUTOGEN_METHODS = null;
const _AUTO_GETTERS = null;
const _AUTO_SETTERS = null;
#--autogen-dynamic--
}

View File

@ -0,0 +1,55 @@
<?php
namespace nur\mapper\line;
use nur\A;
use nur\mapper\base\Tparams_command;
/**
* Class LineWriter_command: classe de support pour construire une instance de
* {@link LineWriter} pour une application recevant des commandes utilisateurs
*/
class LineWriter_command {
use Tparams_command;
const NAME = "outfile";
const DEF = [self::NAME, "outf",
"help" => "configurer le fichier en sortie pour écriture ligne par ligne",
"action" => [self::class, "create_command"],
"cmd_args" => [
"usage" => self::NAME." [file]",
["-t", "--output-encoding", "args" => "value",
"action" => [self::class, "set_output_encoding"],
],
["-o", "--param", "args" => "value",
"action" => [self::class, "add_params"],
"help" => "spécifier une option générique",
],
["args" => [["file"]],
"action" => [self::class, "set_file"],
],
],
];
const O_OPTION = ["-o", "--output", "args" => "file",
"action" => [self::class, "set_file"],
];
const T_OPTION = ["-t", "--output-encoding", "args" => "value",
"action" => [self::class, "set_output_encoding"],
];
/** @var LineWriter */
protected static $command;
static function create_command() {
self::$command = new LineWriter();
}
static function set_file($file) {
$file = A::last(A::with($file));
self::get()->setOutput($file);
}
static function set_output_encoding(string $output_encoding) {
self::get()->setEncodingFilter($output_encoding);
}
}

17
nur_src/mapper/pl.php Normal file
View File

@ -0,0 +1,17 @@
<?php
namespace nur\mapper;
use nur\mapper\base\Consumer;
use nur\mapper\base\Mapper;
class pl {
/**
* appliquer un ensemble de mappers, instances de {@link Mapper}, sur un
* iterable, typiquement une instance de {@link Producer}
*/
static final function consume($producer, ...$mappers): Consumer {
$consumer = new Consumer($producer, ...$mappers);
$consumer->consume();
return $consumer;
}
}

53
nur_src/t/TestCase.php Normal file
View File

@ -0,0 +1,53 @@
<?php
namespace nur\t;
use Exception;
use PHPUnit\Framework\Constraint\Exception as ConstraintException;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase {
const UNCHANGED_ARRAY = ["unchanged"];
static function assertException($class, callable $func, ...$args) {
try {
call_user_func_array($func, $args);
$e = null;
} catch (Exception $e) {
}
self::assertThat($e, new ConstraintException($class));
}
static function assertNotException(callable $func, ...$args) {
try {
call_user_func_array($func, $args);
$e = null;
$message = "";
} catch (Exception $e) {
$message = "L'exception ".get_class($e)." a été lancée";
}
self::assertTrue($e === null, $message);
}
static function assertSameKeys(array $expected, array $actual) {
self::assertSame($expected, array_keys($actual));
}
static function assertSameValues(array $expected, array $actual) {
self::assertSame($expected, array_values($actual));
}
protected $origTz;
/**
* forcer systématiquement le timezone dans Indian/Reunion pour que les tests
* de date soient corrects
*/
protected function setUp(): void {
$this->origTz = date_default_timezone_get();
date_default_timezone_set("Indian/Reunion");
}
protected function tearDown(): void {
date_default_timezone_set($this->origTz);
}
}

7
nur_tbin/datareader.php Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/php
<?php
require(__DIR__.'/../vendor/autoload.php');
use nur\mapper\app\DatareaderApp;
DatareaderApp::run();

View File

@ -0,0 +1,3 @@
nom,prenom,age
clain,jephté,45
clain,françoise,53
1 nom prenom age
2 clain jephté 45
3 clain françoise 53

View File

@ -0,0 +1,2 @@
clain jephté 045
clain françoise 053

View File

@ -0,0 +1,6 @@
<?php
return [
"nom" => [10, "string"],
"prenom" => [10, "string"],
"age" => [3, "number"],
];

View File

@ -0,0 +1,2 @@
{"nom":"clain","prenom":"jephté","age":45}
{"nom":"clain","prenom":"françoise","age":53}

View File

@ -0,0 +1,9 @@
nom
prenom
age
clain
jephté
45
clain
françoise
53

View File

@ -0,0 +1,8 @@
-
nom: clain
prenom: jephté
age: 45
-
nom: clain
prenom: françoise
age: 53

View File

@ -0,0 +1,135 @@
<?php
namespace nur\mapper\base;
use nur\mapper\base\impl\Add2Mapper;
use nur\mapper\base\impl\AddMapper;
use nur\mapper\base\impl\NumberProducer;
use nur\mapper\base\impl\PlusOneMapper;
use nur\mapper\base\impl\ResultConsumer;
use nur\mapper\base\impl\TwiceMapper;
use nur\t\TestCase;
class ConsumerTest extends TestCase {
function testConsumer1() {
$numberProducer = new class() extends Producer {
function producer() {
yield from [1, 2, 3, 4];
}
};
$plusOne = new class(null) extends Mapper {
function mapper($item) {
return $item + 1;
}
};
$twice = new class(null) extends Mapper {
function mapper($item) {
return $item * 2;
}
};
$consumer = new class($numberProducer, $plusOne, $twice) extends Consumer {
public $result = [];
function cook($item) {
$this->result[] = $item;
}
};
$consumer->consume();
self::assertSame([4, 6, 8, 10], $consumer->result);
}
function testConsumer2() {
$consumer = new ResultConsumer(NumberProducer::class);
$consumer->add(PlusOneMapper::class);
$consumer->add(TwiceMapper::class);
$consumer->consume();
self::assertSame([4, 6, 8, 10], $consumer->result);
}
function testConsumer3() {
$consumer = new ResultConsumer([1, 2, 3, 4], PlusOneMapper::class, TwiceMapper::class);
$consumer->consume();
self::assertSame([4, 6, 8, 10], $consumer->result);
}
function testConsumer4() {
$consumer = new class extends Consumer {
private $count;
function getCount(): int {
return $this->count;
}
private $sum;
function getSum(): int {
return $this->sum;
}
function cook($item) {
$this->count++;
$this->sum += $item;
}
};
$consumer->consume([1, 2, 3, 4], function ($value) {
return $value + 1;
});
self::assertSame(4, $consumer->getCount());
self::assertSame(14, $consumer->getSum());
}
function testConsumer5() {
$consumer = new ResultConsumer([1, 2, 3, 4], [AddMapper::class, 1]);
$consumer->consume();
self::assertSame([2, 3, 4, 5], $consumer->result);
}
function testConsumer6() {
$consumer = new ResultConsumer(null, [AddMapper::class, 1]);
$consumer->push(1);
self::assertSame([2], $consumer->result);
$consumer->pushAll([2, 3]);
self::assertSame([2, 3, 4], $consumer->result);
$consumer->push(4);
self::assertSame([2, 3, 4, 5], $consumer->result);
$consumer->close();
}
function testPushMix() {
$consumer = new ResultConsumer(NumberProducer::class, [AddMapper::class, 1]);
$consumer->push(10);
self::assertSame([11], $consumer->result);
$consumer->consume();
self::assertSame([11], $consumer->result);
$consumer->close();
$consumer->consume();
self::assertSame([11, 2, 3, 4, 5], $consumer->result);
$consumer = new ResultConsumer(NumberProducer::class, [AddMapper::class, 1]);
$consumer->consume();
self::assertSame([2, 3, 4, 5], $consumer->result);
$consumer->push(10);
self::assertSame([2, 3, 4, 5, 11], $consumer->result);
$consumer->close();
$consumer = new ResultConsumer(NumberProducer::class, [AddMapper::class, 1]);
$consumer->consume();
self::assertSame([2, 3, 4, 5], $consumer->result);
$consumer->consume();
self::assertSame([2, 3, 4, 5, 2, 3, 4, 5], $consumer->result);
$consumer->push(10);
self::assertSame([2, 3, 4, 5, 2, 3, 4, 5, 11], $consumer->result);
$consumer->consume();
self::assertSame([2, 3, 4, 5, 2, 3, 4, 5, 11], $consumer->result);
$consumer->close();
$consumer->consume();
self::assertSame([2, 3, 4, 5, 2, 3, 4, 5, 11, 2, 3, 4, 5], $consumer->result);
}
function testDefaultArgs() {
$consumer = new ResultConsumer(NumberProducer::class
, [Add2Mapper::class, 1] # 1
, [Add2Mapper::class, 1, 2] # 3
, [Add2Mapper::class, 1, 2, 3] # 6
);
$consumer->consume();
self::assertSame([11, 12, 13, 14], $consumer->result);
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace nur\mapper\base;
use nur\mapper\base\impl\MultMapper;
use nur\mapper\base\impl\PlusOneMapper;
use nur\mapper\base\impl\ResultConsumer;
use nur\t\TestCase;
class MapperAggregateTest extends TestCase {
function testMapper() {
$mapper = new MapperAggregate();
$mapper->add(PlusOneMapper::class);
$mapper->add(MultMapper::class);
$consumer = new ResultConsumer([1, 2, 3], $mapper);
$consumer->consume();
self::assertSame([2, 4, 3, 6, 4, 8], $consumer->getResult());
}
}

View File

@ -0,0 +1,183 @@
<?php
namespace nur\mapper\base;
use nur\b\ValueException;
use nur\mapper\base\impl\IdentMapToMapper;
use nur\mapper\base\impl\IdentReturnMapper;
use nur\mapper\base\impl\SofEofMapper;
use nur\t\TestCase;
class MapperTest extends TestCase {
const NUMBERS = [1, 2, 3, 4];
const DOUBLE = [2, 4, 6, 8];
const FIRST = [1, 2];
const LAST = [3, 4];
const LAST_DOUBLE = [6, 8];
const ASSOC = [1, "a" => "b", 2, "c" => "d"];
const STRINGS = ["a", "bb", "ccc", "dddd"];
const SPLIT = ["b", "b", "c", "cc", "dd", "dd"];
const SPLIT2 = ["<a>", "<bb>", "b", "-", "b", "<ccc>", "c", "-", "cc", "<dddd>", "dd", "-", "dd"];
function testReturn() {
$mapper = new IdentReturnMapper(self::NUMBERS);
self::assertSame(self::NUMBERS, iterator_to_array($mapper));
$mapper = new IdentReturnMapper(self::ASSOC);
self::assertSame(self::ASSOC, iterator_to_array($mapper));
$mapper = new class(self::NUMBERS) extends Mapper {
function mapper($item) {
return $item * 2;
}
};
self::assertSame(self::DOUBLE, iterator_to_array($mapper));
}
function testFilter() {
$mapper = new class(self::NUMBERS) extends Mapper {
function mapper($item) {
return $item <= 2 ? $item : null;
}
};
self::assertSame(self::FIRST, iterator_to_array($mapper));
$mapper = new class(self::NUMBERS) extends Mapper {
function mapper($item) {
return $item > 2 ? $item : null;
}
};
self::assertSame(self::LAST, iterator_to_array($mapper));
}
static function split($value): ?array {
$length = strlen($value);
if ($length < 2) return null;
$middle = intdiv($length, 2);
$first = substr($value, 0, $middle);
$last = substr($value, $middle);
return [$first, $last];
}
function testMapTo() {
$mapper = new IdentMapToMapper(self::NUMBERS);
self::assertSame(self::NUMBERS, iterator_to_array($mapper));
$mapper = new IdentMapToMapper(self::ASSOC);
self::assertSame(self::ASSOC, iterator_to_array($mapper));
$mapper = new class(self::NUMBERS) extends Mapper {
function mapper($item) {
$this->mapToValue($item * 2);
}
};
self::assertSame(self::DOUBLE, iterator_to_array($mapper));
$mapper = new class(self::NUMBERS) extends Mapper {
function mapper($item) {
// possible d'utiliser return (la valeur est ignorée)
return $item > 2 ? $this->mapToValue($item * 2) : $this->mapToNone();
}
};
self::assertSame(self::LAST_DOUBLE, iterator_to_array($mapper));
$mapper = new class(self::STRINGS) extends Mapper {
function mapper($item) {
return $this->mapTo(MapperTest::split($item));
}
};
self::assertSame(self::SPLIT, iterator_to_array($mapper));
$mapper = new class(self::STRINGS) extends Mapper {
function splitemup($value) {
$parts = MapperTest::split($value);
if ($parts !== null) {
yield $parts[0];
yield "-";
yield $parts[1];
}
}
function mapper($item) {
$this->mapToValue("<$item>");
$this->mapTo($this->splitemup($item));
}
};
self::assertSame(self::SPLIT2, iterator_to_array($mapper));
}
function testSofEof() {
self::assertSame(["a", "b"], iterator_to_array(new SofEofMapper(["a", "b"])));
self::assertSame(["<", "a", "b"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper {
const MAP_SOF = true;
}));
self::assertSame(["a", "b", ">"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper {
const MAP_EOF = true;
}));
self::assertSame(["<", "a", "b", ">"], iterator_to_array(new class(["a", "b"]) extends SofEofMapper {
const MAP_SOF = true;
const MAP_EOF = true;
}));
}
function testClose() {
$mapper1 = new class extends Mapper {
public $complete = false;
public function getIterator(): iterable {
yield from parent::getIterator();
$this->complete = true;
}
public $closed = false;
protected function teardown(): void {
parent::teardown();
$this->closed = true;
}
public function mapper($item) {
return $item;
}
};
$mapper2 = new class extends Mapper {
public $complete = false;
public function getIterator(): iterable {
yield from parent::getIterator();
$this->complete = true;
}
public $closed = false;
protected function teardown(): void {
parent::teardown();
$this->closed = true;
}
public function mapper($item) {
return $item;
}
};
$consumer = new class extends Consumer {
public $throw = true;
public function cook($item) {
if ($this->throw && $item >= 5) throw new ValueException("cinq");
return $item + 1;
}
};
$mapper1->complete = $mapper1->closed = false;
$mapper2->complete = $mapper2->closed = false;
$consumer->throw = true;
self::assertException(ValueException::class, [$consumer, "consume"], [1, 2, 3, 5, 6, 7], $mapper1, $mapper2);
self::assertFalse($mapper1->complete);
self::assertTrue($mapper1->closed);
self::assertFalse($mapper2->complete);
self::assertTrue($mapper2->closed);
$mapper1->complete = $mapper1->closed = false;
$mapper2->complete = $mapper2->closed = false;
$consumer->throw = false;
self::assertNotException([$consumer, "consume"], [1, 2, 3, 5, 6, 7], $mapper1, $mapper2);
self::assertTrue($mapper1->complete);
self::assertTrue($mapper1->closed);
self::assertTrue($mapper2->complete);
self::assertTrue($mapper2->closed);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace nur\mapper\base;
use nur\b\io\StringReader;
use nur\mapper\line\LineReader;
use nur\t\TestCase;
class ProducerTest extends TestCase {
function testProducer() {
$p = new LineReader(new StringReader("first\nsecond\nthird\nfourth\nfifth"));
$i = 0;
$lines = [];
foreach ($p as $line) {
if ($i++ < 2) {
$lines[] = $line;
} else {
break;
}
}
$p->close();
self::assertSame(["first", "second"], $lines);
$p = new LineReader(new StringReader("first\nsecond\n"));
$lines = [];
foreach ($p as $line) {
$lines[] = $line;
}
$p->close();
self::assertSame(["first", "second"], $lines);
}
}

View File

@ -0,0 +1,107 @@
<?php
namespace nur\mapper\base\capacitor;
use PHPUnit\Framework\TestCase;
class CapacitorTest extends TestCase {
function testPk() {
$c = new Capacitor();
$c->setKeys(["id" => true]);
$c->charge(["id" => 1, "title" => "first"]);
$c->charge(["id" => 1, "title" => "first (doublon)"]);
self::assertSame([
"1" => ["id" => 1, "title" => "first (doublon)"],
], $c->discharge());
}
function testSortSeq() {
$items = [
["second", 4, 2, 1],
["hello", 1, 1, 4],
["first", 3, 1, 2],
["world", 2, 2, 3],
];
$c = new Capacitor();
foreach ($items as $item) {
$c->charge($item);
}
$c->sort([1]);
self::assertSame([
["hello", 1, 1, 4],
["world", 2, 2, 3],
["first", 3, 1, 2],
["second", 4, 2, 1],
], $c->discharge(null, false));
$c->sort([2]);
self::assertSame([
["hello", 1, 1, 4],
["first", 3, 1, 2],
["world", 2, 2, 3],
["second", 4, 2, 1],
], $c->discharge(null, false));
$c->sort([3]);
self::assertSame([
["second", 4, 2, 1],
["first", 3, 1, 2],
["world", 2, 2, 3],
["hello", 1, 1, 4],
], $c->discharge(null, false));
$c->sort([2, 3]);
self::assertSame([
["first", 3, 1, 2],
["hello", 1, 1, 4],
["second", 4, 2, 1],
["world", 2, 2, 3],
], $c->discharge(null, false));
}
function testSortAssoc() {
$items = [
["hello", 1, 1, 4],
["world", 2, 2, 3],
["first", 3, 1, 2],
["second", 4, 2, 1],
];
$c = new Capacitor();
$c->setKeys([[0, true]]);
foreach ($items as $item) {
$c->charge($item);
}
$c->sort([1]);
self::assertSame([
"hello" => ["hello", 1, 1, 4],
"world" => ["world", 2, 2, 3],
"first" => ["first", 3, 1, 2],
"second" => ["second", 4, 2, 1],
], $c->discharge(null, false));
$c->sort([2]);
self::assertSame([
"hello" => ["hello", 1, 1, 4],
"first" => ["first", 3, 1, 2],
"world" => ["world", 2, 2, 3],
"second" => ["second", 4, 2, 1],
], $c->discharge(null, false));
$c->sort([3]);
self::assertSame([
"second" => ["second", 4, 2, 1],
"first" => ["first", 3, 1, 2],
"world" => ["world", 2, 2, 3],
"hello" => ["hello", 1, 1, 4],
], $c->discharge(null, false));
$c->sort([2, 3]);
self::assertSame([
"first" => ["first", 3, 1, 2],
"hello" => ["hello", 1, 1, 4],
"second" => ["second", 4, 2, 1],
"world" => ["world", 2, 2, 3],
], $c->discharge(null, false));
}
}

Some files were not shown because too many files have changed in this diff Show More