modifs.mineures sans commentaires
This commit is contained in:
parent
18b05b7ed7
commit
b82a74418d
|
@ -3,6 +3,7 @@ namespace nur;
|
||||||
|
|
||||||
use nur\b\ui\IContent;
|
use nur\b\ui\IContent;
|
||||||
use nur\b\ui\IPrintable;
|
use nur\b\ui\IPrintable;
|
||||||
|
use nur\sery\php\content\c;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class co: affichage générique de données formatées avec {@link c}
|
* Class co: affichage générique de données formatées avec {@link c}
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace nur\ref;
|
||||||
use nur\data\types\Metadata;
|
use nur\data\types\Metadata;
|
||||||
use nur\md;
|
use nur\md;
|
||||||
use nur\sery\os\file\file;
|
use nur\sery\os\file\file;
|
||||||
|
use nur\sery\php\content\c;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ref_type: référence des types utilisables dans les schémas
|
* Class ref_type: référence des types utilisables dans les schémas
|
||||||
|
@ -102,7 +103,7 @@ class ref_type {
|
||||||
/** comme {@link KEY} mais nullable */
|
/** comme {@link KEY} mais nullable */
|
||||||
const NKEY = "?".self::KEY;
|
const NKEY = "?".self::KEY;
|
||||||
|
|
||||||
/** comme {@link CONTENT} mais nullable */
|
/** comme {@link c} mais nullable */
|
||||||
const NCONTENT = "?".self::CONTENT;
|
const NCONTENT = "?".self::CONTENT;
|
||||||
|
|
||||||
/** comme {@link \nur\sery\FILE} mais nullable */
|
/** comme {@link \nur\sery\FILE} mais nullable */
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
# nulib\php\content
|
||||||
|
|
||||||
|
un contenu est une liste de valeurs, avec une syntaxe pour que certains éléments
|
||||||
|
soient dynamiquement calculés.
|
||||||
|
|
||||||
|
le contenu final est résolu selon les règles suivantes:
|
||||||
|
- Si le contenu n'est pas un tableau:
|
||||||
|
- une chaine est quotée avec `htmlspecialchars()`
|
||||||
|
- un scalaire ou une instance d'objet sont pris tels quels
|
||||||
|
- Sinon, le contenu doit être un tableau, séquentiel ou associatif, ça n'a pas
|
||||||
|
d'incidence
|
||||||
|
- les éléments scalaires ou instance d'objets sont pris tels quels
|
||||||
|
- les tableaux représentent un traitement dynamique: appel de fonction,
|
||||||
|
instanciation, etc.
|
||||||
|
|
||||||
|
Les syntaxes possibles sont:
|
||||||
|
|
||||||
|
`[[], $args...]`
|
||||||
|
: merge littéral: les valeurs $args... sont insérées dans le
|
||||||
|
flux du contenu sans modification. c'est la seule façon d'insérer un tableau
|
||||||
|
dans la liste des valeurs
|
||||||
|
|
||||||
|
`["class_or_function", $args...]`
|
||||||
|
`[["class_or_function"], $args...]`
|
||||||
|
: instantiation ou appel de fonction
|
||||||
|
|
||||||
|
`["->method", $args...]`
|
||||||
|
`[["->method"], $args...]`
|
||||||
|
`[[null, "method"], $args...]`
|
||||||
|
: appel de méthode sur l'objet contexte spécifié lors de la résolution du contenu
|
||||||
|
|
||||||
|
`[[$object, "method"], $args...]`
|
||||||
|
: appel de méthode sur l'objet spécifié
|
||||||
|
|
||||||
|
`[["class", "method"], $args...]`
|
||||||
|
: appel de méthode statique de la classe spécifiée
|
||||||
|
|
||||||
|
Le fait de transformer un contenu en une liste de valeurs statiques s'appelle
|
||||||
|
la résolution. la résolution se fait par rapport à un objet contexte, qui est
|
||||||
|
utilisé lors des appels de méthodes.
|
||||||
|
|
||||||
|
## Affichage d'un contenu
|
||||||
|
|
||||||
|
Deux interfaces sont utilisées pour modéliser un élément de contenu à afficher:
|
||||||
|
- IContent: objet capable de produire du contenu
|
||||||
|
- IPrintable: objet capable d'afficher un contenu
|
||||||
|
|
||||||
|
Tous les autres éléments de contenus sont transformés en string avant affichage.
|
||||||
|
Un système de formatters permet de définir des fonctions ou méthodes à utiliser
|
||||||
|
pour formatter des objets de certains types.
|
||||||
|
|
||||||
|
Lors de l'affichage du contenu, deux éléments contigûs $a et $b sont affichés
|
||||||
|
séparés par un espace sauf si:
|
||||||
|
- $a se termine par `[> ]` OU
|
||||||
|
- $b commence par `[< ]`
|
||||||
|
- $a et $b sont dans une section littérale e.g `[[], $a, $b]`
|
||||||
|
|
||||||
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
|
@ -0,0 +1,163 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\php\content;
|
||||||
|
|
||||||
|
use nur\sery\cl;
|
||||||
|
use nur\sery\php\func;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class c: classe outil pour gérer du contenu
|
||||||
|
*/
|
||||||
|
class c {
|
||||||
|
/** s'assure que $c est un contenu. quoter $c si c'est une chaine */
|
||||||
|
static final function q($content): iterable {
|
||||||
|
if (is_string($content)) return [htmlspecialchars($content)];
|
||||||
|
elseif (is_iterable($content)) return $content;
|
||||||
|
elseif ($content === null || $content === false) return [];
|
||||||
|
else return [$content];
|
||||||
|
}
|
||||||
|
const q = [self::class, "q"];
|
||||||
|
|
||||||
|
/** s'assure que $c est un contenu. ne jamais quoter la valeur */
|
||||||
|
static final function nq($content): iterable {
|
||||||
|
if (is_iterable($content)) return $content;
|
||||||
|
elseif ($content === null || $content === false) return [];
|
||||||
|
else return [$content];
|
||||||
|
}
|
||||||
|
const nq = [self::class, "nq"];
|
||||||
|
|
||||||
|
/** résoudre le contenu, et retourner la liste des valeurs */
|
||||||
|
static final function resolve($content, $object_or_class=null, bool $quote=true, ?array &$dest=null): array {
|
||||||
|
if ($dest === null) $dest = [];
|
||||||
|
$content = $quote? self::q($content): self::nq($content);
|
||||||
|
$index = 0;
|
||||||
|
foreach ($content as $key => $value) {
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
$seq = true;
|
||||||
|
} else {
|
||||||
|
$seq = false;
|
||||||
|
}
|
||||||
|
if (is_array($value)) {
|
||||||
|
# contenu dynamique
|
||||||
|
if (count($value) == 0) continue;
|
||||||
|
$func = cl::first($value);
|
||||||
|
$args = array_slice($value, 1);
|
||||||
|
if ($func === []) {
|
||||||
|
# merge statique
|
||||||
|
$sindex = 0;
|
||||||
|
foreach ($args as $skey => $arg) {
|
||||||
|
if ($skey === $sindex) {
|
||||||
|
$sindex++;
|
||||||
|
if ($seq) {
|
||||||
|
$dest[] = $arg;
|
||||||
|
} else {
|
||||||
|
# la première sous-clé séquentielle est ajoutée avec la clé du
|
||||||
|
# merge statique
|
||||||
|
$dest[$key] = $arg;
|
||||||
|
$seq = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$dest[$skey] = $arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
# chaque argument est aussi un contenu
|
||||||
|
foreach ($args as &$arg) {
|
||||||
|
$array = is_array($arg);
|
||||||
|
$arg = self::resolve($arg, $object_or_class, false);
|
||||||
|
if (!$array) $arg = $arg[0];
|
||||||
|
}; unset($arg);
|
||||||
|
if (func::is_static($func)) {
|
||||||
|
func::ensure_func($func, $object_or_class, $args);
|
||||||
|
$value = func::call($func, ...$args);
|
||||||
|
} elseif (func::is_class($func)) {
|
||||||
|
func::fix_class_args($func, $args);
|
||||||
|
$value = func::cons($func, ...$args);
|
||||||
|
} else {
|
||||||
|
func::ensure_func($func, $object_or_class, $args);
|
||||||
|
$value = func::call($func, ...$args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($seq) $dest[] = $value;
|
||||||
|
else $dest[$key] = $value;
|
||||||
|
}
|
||||||
|
return $dest;
|
||||||
|
}
|
||||||
|
const resolve = [self::class, "resolve"];
|
||||||
|
|
||||||
|
private static final function wend(?string $value): bool {
|
||||||
|
return $value !== null && preg_match('/(\w|\w\.)$/', $value);
|
||||||
|
}
|
||||||
|
private static final function startw(?string $value): bool {
|
||||||
|
return $value !== null && preg_match('/^\w/', $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final function to_values($content, ?array &$values=null): void {
|
||||||
|
$pvalue = cl::last($values);
|
||||||
|
$wend = self::wend($pvalue);
|
||||||
|
foreach ($content as $value) {
|
||||||
|
if ($value === null || $value === false) {
|
||||||
|
continue;
|
||||||
|
} elseif ($value instanceof IContent) {
|
||||||
|
self::to_values($value->getContent(), $values);
|
||||||
|
continue;
|
||||||
|
} elseif ($value instanceof IPrintable) {
|
||||||
|
ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
|
||||||
|
$value->print();
|
||||||
|
$value = ob_get_clean();
|
||||||
|
} else {
|
||||||
|
$value = strval($value);
|
||||||
|
#XXX rendre paramétrable le formatage de $value
|
||||||
|
}
|
||||||
|
if ($value !== "") {
|
||||||
|
$startw = self::startw($value);
|
||||||
|
if ($wend && $startw) $values[] = " ";
|
||||||
|
$values[] = $value;
|
||||||
|
$wend = self::wend($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** écrire le contenu sur la resource spécifiée, qui vaut STDOUT par défaut */
|
||||||
|
static final function write($content, $fd=null, bool $resolve=true): void {
|
||||||
|
if ($resolve) $content = self::resolve($content);
|
||||||
|
$wend = false;
|
||||||
|
foreach ($content as $value) {
|
||||||
|
if ($value === null || $value === false) {
|
||||||
|
continue;
|
||||||
|
} elseif ($value instanceof IPrintable) {
|
||||||
|
if ($fd === null) {
|
||||||
|
$value->print();
|
||||||
|
} else {
|
||||||
|
ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
|
||||||
|
$value->print();
|
||||||
|
fwrite($fd, ob_get_clean());
|
||||||
|
}
|
||||||
|
$wend = false;
|
||||||
|
continue;
|
||||||
|
} elseif ($value instanceof IContent) {
|
||||||
|
$values = [];
|
||||||
|
self::to_values($content, $values);
|
||||||
|
$value = implode("", $values);
|
||||||
|
} else {
|
||||||
|
$value = strval($value);
|
||||||
|
#XXX rendre paramétrable le formattage de $value
|
||||||
|
}
|
||||||
|
$startw = self::startw($value);
|
||||||
|
if (!$wend && !$startw) $value = " $value";
|
||||||
|
if ($fd === null) echo $value;
|
||||||
|
else fwrite($fd, $value);
|
||||||
|
$wend = self::wend($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** retourner le contenu sous forme de chaine */
|
||||||
|
static final function to_string($content, bool $resolve=true): string {
|
||||||
|
if ($resolve) $content = self::resolve($content);
|
||||||
|
$values = [];
|
||||||
|
self::to_values($content, $values);
|
||||||
|
return implode("", $values);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ namespace nur\sery\php;
|
||||||
use Closure;
|
use Closure;
|
||||||
use nur\sery\cl;
|
use nur\sery\cl;
|
||||||
use nur\sery\ref\php\ref_func;
|
use nur\sery\ref\php\ref_func;
|
||||||
use nur\sery\schema\Schema;
|
|
||||||
use nur\sery\ValueException;
|
use nur\sery\ValueException;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionFunction;
|
use ReflectionFunction;
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
# nulib\web\content
|
||||||
|
|
||||||
|
un contenu web est un type spécial de contenu permettant de gérer aussi les
|
||||||
|
attributs avec une syntaxe particulière.
|
||||||
|
|
||||||
|
les règles diffèrent légèrement:
|
||||||
|
- Si le contenu n'est pas un tableau:
|
||||||
|
- une chaine est quotée avec `htmlspecialchars()`
|
||||||
|
- un scalaire ou une instance d'objet sont pris tels quels
|
||||||
|
- Sinon, le contenu doit être un tableau, séquentiel ou associatif
|
||||||
|
- les éléments séquentiels scalaires ou instance d'objets sont pris tels quels
|
||||||
|
- les éléments associatifs sont des attributs, et sont fusionnés le cas échéant
|
||||||
|
- les éléments séquentiels de type tableau séqentiel sont du contenu dynamique
|
||||||
|
- les éléments séquentiels de type tableau associatif sont évalues
|
||||||
|
récursivement avec les mêmes règles (sauf la partie chaine quotée avec
|
||||||
|
`htmlspecialchars()`)
|
||||||
|
|
||||||
|
Par exemple, les deux contenus web suivants sont équivalents:
|
||||||
|
~~~php
|
||||||
|
# ce contenu:
|
||||||
|
$content1 = [
|
||||||
|
"before",
|
||||||
|
"class" => "first",
|
||||||
|
["class" => "second"],
|
||||||
|
["func", "arg"],
|
||||||
|
"after",
|
||||||
|
];
|
||||||
|
# donne le même résultat que:
|
||||||
|
$content2 = [
|
||||||
|
"class" => "first second",
|
||||||
|
"before",
|
||||||
|
func("arg"),
|
||||||
|
"after",
|
||||||
|
];
|
||||||
|
~~~
|
||||||
|
|
||||||
|
lors de la définition d'attributs,
|
||||||
|
- un tableau séquentiel est un appel de fonction
|
||||||
|
- un tableau associatif permet de faire des définitions conditionnelles
|
||||||
|
|
||||||
|
Par exemple, les deux contenus web suivants sont équivalents:
|
||||||
|
~~~php
|
||||||
|
# ce contenu:
|
||||||
|
$content1 = [
|
||||||
|
["class" => ["func", "arg"]],
|
||||||
|
];
|
||||||
|
# donne le même résultat que:
|
||||||
|
$content2 = [
|
||||||
|
"class" => func("arg"),
|
||||||
|
];
|
||||||
|
~~~
|
||||||
|
De même, les deux contenus web suivants sont équivalents:
|
||||||
|
~~~php
|
||||||
|
# si $is_primary && !$is_danger
|
||||||
|
# alors ce contenu:
|
||||||
|
$content1 = [
|
||||||
|
["class" => [
|
||||||
|
"btn",
|
||||||
|
"btn-primary" => $is_primary,
|
||||||
|
"btn-danger" => $is_danger,
|
||||||
|
"btn-default" => !$is_primary && !$is_danger,
|
||||||
|
]],
|
||||||
|
];
|
||||||
|
# donne le même résultat que:
|
||||||
|
$content2 = [
|
||||||
|
"class" => "btn btn-primary",
|
||||||
|
];
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\wip\web\content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class v: classe outil pour gérer du contenu pour le web
|
||||||
|
*/
|
||||||
|
class v {
|
||||||
|
}
|
|
@ -5,27 +5,23 @@ use nur\sery\php\content\impl\html;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class contentTest extends TestCase {
|
class contentTest extends TestCase {
|
||||||
function testFlattern() {
|
function testTo_string() {
|
||||||
self::assertSame([], content::flatten(null));
|
self::assertSame("", c::to_string(null));
|
||||||
self::assertSame([], content::flatten([null]));
|
self::assertSame("", c::to_string(false));
|
||||||
self::assertSame([""], content::flatten(""));
|
self::assertSame("pouet<q/>", c::to_string("pouet<q/>"));
|
||||||
self::assertSame([""], content::flatten([""]));
|
self::assertSame("pouet<q/>", c::to_string(["pouet<q/>"]));
|
||||||
|
self::assertSame("hello world", c::to_string(["hello", "world"]));
|
||||||
self::assertSame(["a", "b", "c", "d"], content::flatten(["a", ["b", ["c"], "d"]]));
|
self::assertSame("hello1 world", c::to_string(["hello1", "world"]));
|
||||||
}
|
self::assertSame("hello<world>", c::to_string(["hello", "<world>"]));
|
||||||
|
self::assertSame("<hello>world", c::to_string(["<hello>", "world"]));
|
||||||
function testResolve_static() {
|
self::assertSame("hello,world", c::to_string(["hello,", "world"]));
|
||||||
self::assertSame([], content::resolve_static(null, null));
|
self::assertSame("hello world", c::to_string(["hello ", "world"]));
|
||||||
self::assertSame([], content::resolve_static([null], [null]));
|
self::assertSame("hello. world", c::to_string(["hello.", "world"]));
|
||||||
self::assertSame([""], content::resolve_static("", ""));
|
self::assertSame("hello.<world>", c::to_string(["hello.", "<world>"]));
|
||||||
self::assertSame([""], content::resolve_static([""], [""]));
|
|
||||||
|
|
||||||
self::assertSame(["<quoted>"], content::resolve_static("<quoted>", "<quoted>"));
|
|
||||||
self::assertSame(["<non-quoted>"], content::resolve_static(["<non-quoted>"], ["<non-quoted>"]));
|
|
||||||
|
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
"<h1>title<q/></h1><p>hello<nq/><span>brave<q/></span><span>world<nq/></span></p>",
|
"<h1>title<q/></h1><p>hello<nq/><span>brave<q/></span><span>world<nq/></span></p>",
|
||||||
content::to_string([
|
c::to_string([
|
||||||
[html::H1, "title<q/>"],
|
[html::H1, "title<q/>"],
|
||||||
[html::P, [
|
[html::P, [
|
||||||
"hello<nq/>",
|
"hello<nq/>",
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace nur\sery\php\content\impl;
|
|
||||||
|
|
||||||
use nur\sery\php\content\IStaticContent;
|
|
||||||
|
|
||||||
class AStaticContent implements IStaticContent {
|
|
||||||
function getContent(): iterable {
|
|
||||||
return [
|
|
||||||
[html::P, "static content"],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,22 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\php\content\impl;
|
namespace nur\sery\php\content\impl;
|
||||||
|
|
||||||
use nur\sery\php\content\content;
|
use nur\sery\php\content\c;
|
||||||
use nur\sery\php\content\IStaticContent;
|
use nur\sery\php\content\IContent;
|
||||||
|
|
||||||
class ATag implements IStaticContent {
|
class ATag implements IContent {
|
||||||
function __construct(string $tag, $contents=null) {
|
function __construct(string $tag, $content=null) {
|
||||||
$this->tag = $tag;
|
$this->tag = $tag;
|
||||||
$this->contents = $contents;
|
$this->content = $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $tag;
|
protected $tag;
|
||||||
protected $contents;
|
protected $content;
|
||||||
|
|
||||||
function getContent(): iterable {
|
function getContent(): iterable {
|
||||||
return [
|
return [
|
||||||
"<$this->tag>",
|
"<$this->tag>",
|
||||||
...content::quote($this->contents),
|
...c::q($this->content),
|
||||||
"</$this->tag>",
|
"</$this->tag>",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ class html {
|
||||||
const P = [self::class, "p"];
|
const P = [self::class, "p"];
|
||||||
const SPAN = [self::class, "span"];
|
const SPAN = [self::class, "span"];
|
||||||
|
|
||||||
static function h1($contents) { return new ATag("h1", $contents); }
|
static function h1($content) { return new ATag("h1", $content); }
|
||||||
static function div($contents) { return new ATag("div", $contents); }
|
static function div($content) { return new ATag("div", $content); }
|
||||||
static function p($contents) { return new ATag("p", $contents); }
|
static function p($content) { return new ATag("p", $content); }
|
||||||
static function span($contents) { return new ATag("span", $contents); }
|
static function span($content) { return new ATag("span", $content); }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue