ajout t et mapper
This commit is contained in:
parent
93790d7be5
commit
86ce10cf95
|
@ -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,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
@ -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--
|
||||
|
|
|
@ -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--
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 où $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;
|
||||
}
|
|
@ -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 {
|
||||
}
|
||||
}
|
|
@ -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); }
|
||||
}
|
|
@ -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"],
|
||||
];
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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--
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/php
|
||||
<?php
|
||||
require(__DIR__.'/../vendor/autoload.php');
|
||||
|
||||
use nur\mapper\app\DatareaderApp;
|
||||
|
||||
DatareaderApp::run();
|
|
@ -0,0 +1,3 @@
|
|||
nom,prenom,age
|
||||
clain,jephté,45
|
||||
clain,françoise,53
|
|
|
@ -0,0 +1,2 @@
|
|||
clain jephté 045
|
||||
clain françoise 053
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
return [
|
||||
"nom" => [10, "string"],
|
||||
"prenom" => [10, "string"],
|
||||
"age" => [3, "number"],
|
||||
];
|
|
@ -0,0 +1,2 @@
|
|||
{"nom":"clain","prenom":"jephté","age":45}
|
||||
{"nom":"clain","prenom":"françoise","age":53}
|
|
@ -0,0 +1,9 @@
|
|||
nom
|
||||
prenom
|
||||
age
|
||||
clain
|
||||
jephté
|
||||
45
|
||||
clain
|
||||
françoise
|
||||
53
|
|
@ -0,0 +1,8 @@
|
|||
-
|
||||
nom: clain
|
||||
prenom: jephté
|
||||
age: 45
|
||||
-
|
||||
nom: clain
|
||||
prenom: françoise
|
||||
age: 53
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in New Issue