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\IPrintable;
|
||||
use nur\sery\php\content\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\md;
|
||||
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
|
||||
|
@ -102,7 +103,7 @@ class ref_type {
|
|||
/** comme {@link KEY} mais nullable */
|
||||
const NKEY = "?".self::KEY;
|
||||
|
||||
/** comme {@link CONTENT} mais nullable */
|
||||
/** comme {@link c} mais nullable */
|
||||
const NCONTENT = "?".self::CONTENT;
|
||||
|
||||
/** 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 nur\sery\cl;
|
||||
use nur\sery\ref\php\ref_func;
|
||||
use nur\sery\schema\Schema;
|
||||
use nur\sery\ValueException;
|
||||
use ReflectionClass;
|
||||
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;
|
||||
|
||||
class contentTest extends TestCase {
|
||||
function testFlattern() {
|
||||
self::assertSame([], content::flatten(null));
|
||||
self::assertSame([], content::flatten([null]));
|
||||
self::assertSame([""], content::flatten(""));
|
||||
self::assertSame([""], content::flatten([""]));
|
||||
|
||||
self::assertSame(["a", "b", "c", "d"], content::flatten(["a", ["b", ["c"], "d"]]));
|
||||
}
|
||||
|
||||
function testResolve_static() {
|
||||
self::assertSame([], content::resolve_static(null, null));
|
||||
self::assertSame([], content::resolve_static([null], [null]));
|
||||
self::assertSame([""], content::resolve_static("", ""));
|
||||
self::assertSame([""], content::resolve_static([""], [""]));
|
||||
|
||||
self::assertSame(["<quoted>"], content::resolve_static("<quoted>", "<quoted>"));
|
||||
self::assertSame(["<non-quoted>"], content::resolve_static(["<non-quoted>"], ["<non-quoted>"]));
|
||||
function testTo_string() {
|
||||
self::assertSame("", c::to_string(null));
|
||||
self::assertSame("", c::to_string(false));
|
||||
self::assertSame("pouet<q/>", c::to_string("pouet<q/>"));
|
||||
self::assertSame("pouet<q/>", c::to_string(["pouet<q/>"]));
|
||||
self::assertSame("hello world", c::to_string(["hello", "world"]));
|
||||
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"]));
|
||||
self::assertSame("hello,world", c::to_string(["hello,", "world"]));
|
||||
self::assertSame("hello world", c::to_string(["hello ", "world"]));
|
||||
self::assertSame("hello. world", c::to_string(["hello.", "world"]));
|
||||
self::assertSame("hello.<world>", c::to_string(["hello.", "<world>"]));
|
||||
|
||||
self::assertSame(
|
||||
"<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::P, [
|
||||
"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
|
||||
namespace nur\sery\php\content\impl;
|
||||
|
||||
use nur\sery\php\content\content;
|
||||
use nur\sery\php\content\IStaticContent;
|
||||
use nur\sery\php\content\c;
|
||||
use nur\sery\php\content\IContent;
|
||||
|
||||
class ATag implements IStaticContent {
|
||||
function __construct(string $tag, $contents=null) {
|
||||
class ATag implements IContent {
|
||||
function __construct(string $tag, $content=null) {
|
||||
$this->tag = $tag;
|
||||
$this->contents = $contents;
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
protected $tag;
|
||||
protected $contents;
|
||||
protected $content;
|
||||
|
||||
function getContent(): iterable {
|
||||
return [
|
||||
"<$this->tag>",
|
||||
...content::quote($this->contents),
|
||||
...c::q($this->content),
|
||||
"</$this->tag>",
|
||||
];
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ class html {
|
|||
const P = [self::class, "p"];
|
||||
const SPAN = [self::class, "span"];
|
||||
|
||||
static function h1($contents) { return new ATag("h1", $contents); }
|
||||
static function div($contents) { return new ATag("div", $contents); }
|
||||
static function p($contents) { return new ATag("p", $contents); }
|
||||
static function span($contents) { return new ATag("span", $contents); }
|
||||
static function h1($content) { return new ATag("h1", $content); }
|
||||
static function div($content) { return new ATag("div", $content); }
|
||||
static function p($content) { return new ATag("p", $content); }
|
||||
static function span($content) { return new ATag("span", $content); }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue