diff --git a/src/web/content/BlockTag.php b/src/web/content/BlockTag.php index ad5c04a..cab2cf5 100644 --- a/src/web/content/BlockTag.php +++ b/src/web/content/BlockTag.php @@ -14,8 +14,8 @@ class BlockTag extends Tag { protected ?string $endPrefix; protected ?string $endSuffix; - function reset(string $tag, ?array $params=null, $content=null): self { - parent::reset($tag, $params, $content); + function reset(string $tag, $content=null, ?array $params=null): self { + parent::reset($tag, $content, $params); $this->startPrefix = $params["start_prefix"] ?? self::START_PREFIX; $this->startSuffix = $params["start_suffix"] ?? self::START_SUFFIX; $this->endPrefix = $params["end_prefix"] ?? self::END_PREFIX; diff --git a/src/web/content/EmptyTag.php b/src/web/content/EmptyTag.php index 2e60179..80a5669 100644 --- a/src/web/content/EmptyTag.php +++ b/src/web/content/EmptyTag.php @@ -1,6 +1,23 @@ resolveContents($attrs, $children, $this->contents); + $parts = ["<{$this->tag}"]; + $this->addAttrs($parts, $attrs); + if ($children) { + $parts[] = ">"; + A::merge($parts, $children); + $parts[] = "tag}>"; + } else { + $parts[] = "/>"; + } + return [c::to_string($parts)]; + } } diff --git a/src/web/content/SimpleTag.php b/src/web/content/SimpleTag.php new file mode 100644 index 0000000..bcb73c5 --- /dev/null +++ b/src/web/content/SimpleTag.php @@ -0,0 +1,108 @@ +reset($tag, $content); + } + + protected string $tag; + + protected iterable $contents; + + function reset(string $tag, $content=null, ?array $params=null): self { + $this->tag = $tag; + $this->contents = c::q($content); + return $this; + } + + function add($content): self { + if (!is_array($this->contents)) { + # si c'est un itérable, le mettre en tant qu'élément dans le tableau + $contents = $this->contents; + $this->contents = [static function() use ($contents) { + return $contents; + }]; + } + A::merge($this->contents, c::q($content)); + return $this; + } + + protected function resolveContents(array &$attrs, array &$children, iterable $contents): void { + $index = 0; + foreach ($contents as $key => $content) { + if ($content instanceof Closure) { + # les closure sont appelés dès la résolution + $content = func::with($content)->invoke([$this]); + if ($key === $index) { + $index++; + # le résultat est inséré tels quels dans le flux + if (is_iterable($content)) { + $children[] = $content; + } else { + A::merge($children, cl::with($content)); + } + } else { + # le résultat est la valeur de l'attribut + A::merge($attrs[$key], [$content]); + } + } elseif (is_array($content)) { + if ($key === $index) { + $index++; + $this->resolveContents($attrs, $children, $content); + } else { + foreach ($content as &$value) { + if ($value instanceof Closure) { + $value = func::with($value)->invoke([$this]); + } + }; unset($value); + A::merge($attrs[$key], $content); + } + } elseif ($key === $index) { + $index++; + A::merge($children, cl::with($content)); + } else { + A::merge($attrs[$key], [$content]); + } + } + } + + protected function addAttrs(array &$parts, array $attrs): void { + foreach ($attrs as $key => $value) { + if ($value === null || $value === false) continue; + if ($value === true || $value === [true]) { + $value = $key; + } elseif (is_array($value)) { + $value = str::join3($value); + if (!$value) continue; + } + $parts[] = " "; + $parts[] = $key; + $parts[] = "=\""; + $parts[] = htmlspecialchars($value); + $parts[] = "\""; + } + } + + function getContent($objectOrClass=null): iterable { + $attrs = []; + $children = []; + $this->resolveContents($attrs, $children, $this->contents); + $parts = ["<{$this->tag}"]; + $this->addAttrs($parts, $attrs); + $parts[] = ">"; + A::merge($parts, $children); + $parts[] = "tag}>"; + return [c::to_string($parts)]; + } +} diff --git a/src/web/content/Tag.php b/src/web/content/Tag.php index 6703ff9..802e28d 100644 --- a/src/web/content/Tag.php +++ b/src/web/content/Tag.php @@ -2,133 +2,52 @@ namespace nulib\web\content; -use Closure; -use nulib\A; -use nulib\cl; use nulib\php\content\c; -use nulib\php\content\IContent; use nulib\php\content\IPrintable; -use nulib\php\func; -use nur\str; - -class Tag implements IContent, IPrintable { - const ALLOW_EMPTY = false; +class Tag extends SimpleTag implements IPrintable { const REQUIRE_CONTENT = false; function __construct(string $tag, ?array $params=null, $content=null) { - $this->reset($tag, $params, $content); + $this->reset($tag, $content, $params); } - protected bool $allowEmpty; - protected bool $requireContent; - protected string $tag; - - protected iterable $contents; - protected ?array $attrs = null; protected ?array $children = null; - protected bool $empty = false; - - function reset(string $tag, ?array $params=null, $content=null): self { - $this->allowEmpty = $params["allow_empty"] ?? self::ALLOW_EMPTY; + function reset(string $tag, $content=null, ?array $params=null): self { + parent::reset($tag, $content, $params); $this->requireContent = $params["require_content"] ?? self::REQUIRE_CONTENT; - $this->tag = $tag; - $this->contents = c::q($content); $this->attrs = null; $this->children = null; - $this->empty = false; return $this; } function add($content): self { - if (!is_array($this->contents)) { - # si c'est un itérable, l'inclure avec un merge statique, afin de pouvoir - # rajouter des éléments - $contents = $this->contents; - $this->contents = [static function() use ($contents) { - return $contents; - }]; - } - A::merge($this->contents, c::q($content)); + parent::add($content); $this->attrs = null; $this->children = null; - $this->empty = false; return $this; } - private function add_contents(iterable $source, array &$contents, array &$attrs): void { - $index = 0; - foreach ($source as $key => $content) { - if ($content instanceof Closure) { - # les closure sont appelés dès la résolution - $content = func::with($content)->invoke([$this]); - if ($key === $index) { - $index++; - # le résultat est inséré tels quels dans le flux - if (is_iterable($content)) { - $contents[] = $content; - } else { - A::merge($contents, cl::with($content)); - } - } else { - # le résultat est la valeur de l'attribut - A::merge($attrs[$key], [$content]); - } - } elseif (is_array($content)) { - if ($key === $index) { - $index++; - $this->add_contents($content, $contents, $attrs); - } else { - foreach ($content as &$value) { - if ($value instanceof Closure) { - $value = func::with($value)->invoke([$this]); - } - }; unset($value); - A::merge($attrs[$key], $content); - } - } elseif ($key === $index) { - $index++; - A::merge($contents, cl::with($content)); - } else { - A::merge($attrs[$key], [$content]); - } - } - } - function resolve($objectOrClass=null): self { if ($this->attrs === null) { $attrs = []; - $contents = []; - $this->add_contents($this->contents, $contents, $attrs); + $children = []; + $this->resolveContents($attrs, $children, $this->contents); $this->attrs = $attrs; - $this->children = c::resolve($contents, $objectOrClass); - $this->empty = $this->allowEmpty && !$this->children; + $this->children = c::resolve($children, $objectOrClass); } return $this; } function getStart(): array { $parts = ["<{$this->tag}"]; - foreach ($this->attrs as $key => $value) { - if ($value === null || $value === false) continue; - if ($value === true || $value === [true]) { - $value = $key; - } elseif (is_array($value)) { - $value = str::join3($value); - if (!$value) continue; - } - $parts[] = " "; - $parts[] = $key; - $parts[] = "=\""; - $parts[] = htmlspecialchars($value); - $parts[] = "\""; - } - $parts[] = $this->empty? "/>": ">"; + $this->addAttrs($parts, $this->attrs); + $parts[] = ">"; return $parts; } @@ -137,8 +56,7 @@ class Tag implements IContent, IPrintable { } function getEnd(): array { - if ($this->empty) return []; - else return ["tag}>"]; + return ["tag}>"]; } function getContent($objectOrClass=null): iterable { diff --git a/src/web/content/v.php b/src/web/content/v.php index 1d57970..e054141 100644 --- a/src/web/content/v.php +++ b/src/web/content/v.php @@ -24,10 +24,10 @@ class v { static function h6($content): BlockTag { return self::h("h6", $content); } const H6 = [BlockTag::class, false, "h6", self::require_content]; - static function hr($content): EmptyTag { return (new EmptyTag("hr", null, $content)); } - const HR = [EmptyTag::class, false, "hr", null]; - static function br($content): EmptyTag { return (new EmptyTag("br", null, $content)); } - const BR = [EmptyTag::class, false, "br", null]; + static function hr($content=null): EmptyTag { return (new EmptyTag("hr", $content)); } + const HR = [EmptyTag::class, false, "hr"]; + static function br($content=null): EmptyTag { return (new EmptyTag("br", $content)); } + const BR = [EmptyTag::class, false, "br"]; static function div($content): Tag { return (new Tag("div", null, $content)); } const DIV = [Tag::class, false, "div", null]; @@ -36,16 +36,16 @@ class v { static function pre($content): Tag { return (new Tag("pre", self::require_content, $content)); } const PRE = [Tag::class, false, "pre", self::require_content]; - static function span($content): Tag { return (new Tag("span", null, $content)); } - const SPAN = [Tag::class, false, "span", null]; - static function b($content): Tag { return (new Tag("b", null, $content)); } - const B = [Tag::class, false, "b", null]; - static function i($content): Tag { return (new Tag("i", null, $content)); } - const I = [Tag::class, false, "i", null]; - static function em($content): Tag { return (new Tag("em", null, $content)); } - const EM = [Tag::class, false, "em", null]; - static function strong($content): Tag { return (new Tag("strong", null, $content)); } - const STRONG = [Tag::class, false, "strong", null]; + static function span($content): SimpleTag { return (new SimpleTag("span", $content)); } + const SPAN = [SimpleTag::class, false, "span"]; + static function b($content): SimpleTag { return (new SimpleTag("b", $content)); } + const B = [SimpleTag::class, false, "b"]; + static function i($content): SimpleTag { return (new SimpleTag("i", $content)); } + const I = [SimpleTag::class, false, "i"]; + static function em($content): SimpleTag { return (new SimpleTag("em", $content)); } + const EM = [SimpleTag::class, false, "em"]; + static function strong($content): SimpleTag { return (new SimpleTag("strong", $content)); } + const STRONG = [SimpleTag::class, false, "strong"]; static function ul($content): BlockTag { return (new BlockTag("ul", self::start_nl, $content)); } const UL = [BlockTag::class, false, "ul", self::start_nl]; diff --git a/tests/web/content/TagTest.php b/tests/web/content/TagTest.php index 550fc06..e798137 100644 --- a/tests/web/content/TagTest.php +++ b/tests/web/content/TagTest.php @@ -22,7 +22,6 @@ class TagTest extends TestCase { ]); self::assertSame([ - null, "", - null, ], $tag->getContent()); self::assertSame('before 42 after', c::to_string($tag)); @@ -71,14 +69,12 @@ class TagTest extends TestCase { ]); self::assertSame([ - null, "", "", - null, ], $tag->getContent()); self::assertSame('', c::to_string($tag)); diff --git a/tests/web/content/vTest.php b/tests/web/content/vTest.php index 5d972ae..1cd2fec 100644 --- a/tests/web/content/vTest.php +++ b/tests/web/content/vTest.php @@ -7,53 +7,81 @@ use nulib\tests\TestCase; class vTest extends TestCase { function testStatic() { - $static = [ - [v::H1, "title"], - [v::P, "text"], - ]; self::assertSame(<<title

text

-EOT, c::to_string($static)); +EOT, c::to_string([ + [v::H1, "title"], + [v::P, "text"], + ])); - $static = [ + self::assertSame(<<title +

theboldtext
linefeed


+EOT, c::to_string([ + [v::H1, "title"], + [v::P, [ + "the", + [v::B, "bold"], + "text", + [v::BR], + "linefeed", + ]], + [v::HR], + ])); + + self::assertSame(<<beforespannedblackedafter +EOT, c::to_string([ [v::DIV, [ "before", "class" => "div", [v::SPAN, "spanned"], + [v::SPAN, ["class" => "black", "blacked"]], "disabled" => false, "checked" => true, "after", ]], - ]; - self::assertSame(<<beforespannedafter -EOT, c::to_string($static)); + ])); } function testDynamic() { - $dynamic = [ - v::h1("title"), - v::p("text"), - ]; self::assertSame(<<title

text

-EOT, c::to_string($dynamic)); +EOT, c::to_string([ + v::h1("title"), + v::p("text"), + ])); - $dynamic = [ + self::assertSame(<<title +

theboldtext
linefeed


+EOT, c::to_string([ + v::h1("title"), + v::p([ + "the", + v::b("bold"), + "text", + v::br(), + "linefeed", + ]), + v::hr(), + ])); + + self::assertSame(<<beforespannedblackedafter +EOT, c::to_string([ v::div([ "before", "class" => "div", v::span("spanned"), + v::span(["class" => "black", "blacked"]), "disabled" => false, "checked" => true, "after", ]), - ]; - self::assertSame(<<beforespannedafter -EOT, c::to_string($dynamic)); + ])); } function testDynamic2() { @@ -62,7 +90,19 @@ EOT, c::to_string($dynamic)); ["a" => "un", "b" => "deux", "c" => "trois"], ["a" => "one", "b" => "two", "c" => "three"], ]; - $dynamic = v::table([ + self::assertSame(<< + +abc + + +123 +undeuxtrois +onetwothree + + + +EOT, c::to_string(v::table([ v::thead(v::tr(function() use ($rows) { $headers = array_keys(cl::first($rows)); foreach ($headers as $header) { @@ -78,19 +118,6 @@ EOT, c::to_string($dynamic)); }); } }), - ]); - self::assertSame(<< - -abc - - -123 -undeuxtrois -onetwothree - - - -EOT, c::to_string($dynamic)); + ]))); } }