style/test improvements
This commit is contained in:
parent
abea8a6f68
commit
74c8c7079f
34 changed files with 399 additions and 169 deletions
|
@ -3,14 +3,16 @@
|
||||||
"description": "Abstraction layer for constructing arbitrary HTML tags and documents in PHP",
|
"description": "Abstraction layer for constructing arbitrary HTML tags and documents in PHP",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.1",
|
"php": ">=8.1",
|
||||||
"myclabs/deep-copy": "^1"
|
"myclabs/deep-copy": "^1"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [{
|
"authors": [
|
||||||
"name": "Joby Elliott",
|
{
|
||||||
"email": "joby@byjoby.com"
|
"name": "Joby Elliott",
|
||||||
}],
|
"email": "joby@byjoby.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
"minimum-stability": "dev",
|
"minimum-stability": "dev",
|
||||||
"prefer-stable": true,
|
"prefer-stable": true,
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -25,10 +27,12 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "phpunit",
|
"test": "phpunit",
|
||||||
"stan": "phpstan"
|
"stan": "phpstan",
|
||||||
|
"sniff": "phpcs"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.9",
|
"phpstan/phpstan": "^1.9",
|
||||||
"phpunit/phpunit": "^9.5"
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"squizlabs/php_codesniffer": "^3.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
19
phpcs.xml
Normal file
19
phpcs.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer"
|
||||||
|
xsi:noNamespaceSchemaLocation="./vendor/squizlabs/php_codesniffer/phpcs.xsd">
|
||||||
|
<description>Coding Standard</description>
|
||||||
|
|
||||||
|
<file>src</file>
|
||||||
|
|
||||||
|
<arg name="basepath" value="." />
|
||||||
|
<arg name="colors" />
|
||||||
|
<arg name="parallel" value="75" />
|
||||||
|
<arg value="np" />
|
||||||
|
|
||||||
|
<rule ref="PSR12">
|
||||||
|
<exclude name="Generic.Files.LineEndings" />
|
||||||
|
<!-- <exclude name="Generic.NamingConventions.CamelCapsFunctionName" /> -->
|
||||||
|
<!-- <exclude name="PSR1.Methods.CamelCapsMethodName" /> -->
|
||||||
|
</rule>
|
||||||
|
|
||||||
|
</ruleset>
|
|
@ -4,7 +4,6 @@ namespace ByJoby\HTML;
|
||||||
|
|
||||||
use ByJoby\HTML\Containers\Fragment;
|
use ByJoby\HTML\Containers\Fragment;
|
||||||
use ByJoby\HTML\Containers\FragmentInterface;
|
use ByJoby\HTML\Containers\FragmentInterface;
|
||||||
use ByJoby\HTML\Containers\GenericHtmlDocument;
|
|
||||||
use ByJoby\HTML\Containers\HtmlDocumentInterface;
|
use ByJoby\HTML\Containers\HtmlDocumentInterface;
|
||||||
use ByJoby\HTML\Nodes\CData;
|
use ByJoby\HTML\Nodes\CData;
|
||||||
use ByJoby\HTML\Nodes\CDataInterface;
|
use ByJoby\HTML\Nodes\CDataInterface;
|
||||||
|
@ -109,8 +108,10 @@ abstract class AbstractParser
|
||||||
{
|
{
|
||||||
// build object
|
// build object
|
||||||
$class = $this->tagClass($node->tagName);
|
$class = $this->tagClass($node->tagName);
|
||||||
if (!$class) return null;
|
if (!$class) {
|
||||||
$tag = new $class;
|
return null;
|
||||||
|
}
|
||||||
|
$tag = new $class();
|
||||||
// tool for settin gup content tags
|
// tool for settin gup content tags
|
||||||
if ($tag instanceof ContentTagInterface) {
|
if ($tag instanceof ContentTagInterface) {
|
||||||
$tag->setContent($node->textContent);
|
$tag->setContent($node->textContent);
|
||||||
|
@ -124,12 +125,11 @@ abstract class AbstractParser
|
||||||
|
|
||||||
protected function processAttributes(DOMElement $node, TagInterface $tag): void
|
protected function processAttributes(DOMElement $node, TagInterface $tag): void
|
||||||
{
|
{
|
||||||
if (!$node->attributes) return;
|
|
||||||
/** @var array<string,string|bool> */
|
/** @var array<string,string|bool> */
|
||||||
$attributes = [];
|
$attributes = [];
|
||||||
// absorb attributes
|
// absorb attributes
|
||||||
/** @var DOMNode $attribute */
|
/** @var DOMNode $attribute */
|
||||||
foreach ($node->attributes as $attribute) {
|
foreach ($node->attributes ?? [] as $attribute) {
|
||||||
if ($attribute->nodeValue) {
|
if ($attribute->nodeValue) {
|
||||||
$attributes[$attribute->nodeName] = $attribute->nodeValue;
|
$attributes[$attribute->nodeName] = $attribute->nodeValue;
|
||||||
} else {
|
} else {
|
||||||
|
@ -148,10 +148,9 @@ abstract class AbstractParser
|
||||||
// make an effort to set ID
|
// make an effort to set ID
|
||||||
try {
|
try {
|
||||||
$tag->attributes()["$k"] = $v;
|
$tag->attributes()["$k"] = $v;
|
||||||
}
|
} catch (\Throwable $th) { // @codeCoverageIgnore
|
||||||
// it is correct to ignore attributes that are unsettable
|
|
||||||
catch (\Throwable $th) { // @codeCoverageIgnore
|
|
||||||
// does nothing
|
// does nothing
|
||||||
|
// it is correct to ignore attributes that are unsettable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ class ContainerGroup implements ContainerInterface, NodeInterface
|
||||||
protected $limit = 0;
|
protected $limit = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param int $limit
|
* @param int $limit
|
||||||
* @return ContainerGroup<NodeInterface>
|
* @return ContainerGroup<NodeInterface>
|
||||||
*/
|
*/
|
||||||
public static function catchAll(int $limit = 0): ContainerGroup
|
public static function catchAll(int $limit = 0): ContainerGroup
|
||||||
|
@ -44,8 +44,8 @@ class ContainerGroup implements ContainerInterface, NodeInterface
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template C of T
|
* @template C of T
|
||||||
* @param class-string<C> $class
|
* @param class-string<C> $class
|
||||||
* @param int $limit
|
* @param int $limit
|
||||||
* @return ContainerGroup<C>
|
* @return ContainerGroup<C>
|
||||||
*/
|
*/
|
||||||
public static function ofClass(string $class, int $limit = 0): ContainerGroup
|
public static function ofClass(string $class, int $limit = 0): ContainerGroup
|
||||||
|
@ -60,7 +60,7 @@ class ContainerGroup implements ContainerInterface, NodeInterface
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $tag
|
* @param string $tag
|
||||||
* @param int $limit
|
* @param int $limit
|
||||||
* @return ContainerGroup<TagInterface>
|
* @return ContainerGroup<TagInterface>
|
||||||
*/
|
*/
|
||||||
public static function ofTag(string $tag, int $limit = 0): ContainerGroup
|
public static function ofTag(string $tag, int $limit = 0): ContainerGroup
|
||||||
|
|
|
@ -16,7 +16,9 @@ class Fragment implements FragmentInterface
|
||||||
*/
|
*/
|
||||||
public function __construct(null|array|Traversable $children = null)
|
public function __construct(null|array|Traversable $children = null)
|
||||||
{
|
{
|
||||||
if (!$children) return;
|
if (!$children) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach ($children as $child) {
|
foreach ($children as $child) {
|
||||||
$this->addChild($child);
|
$this->addChild($child);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,4 @@ namespace ByJoby\HTML\ContentCategories;
|
||||||
|
|
||||||
interface SectioningRoot
|
interface SectioningRoot
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds and validates a set of HTML attribute name/value pairs for use in tags.
|
* Holds and validates a set of HTML attribute name/value pairs for use in tags.
|
||||||
*
|
*
|
||||||
* @implements ArrayAccess<string,bool|string|Stringable>
|
* @implements ArrayAccess<string,bool|string|Stringable>
|
||||||
* @implements IteratorAggregate<string,bool|string|Stringable>
|
* @implements IteratorAggregate<string,bool|string|Stringable>
|
||||||
*/
|
*/
|
||||||
|
@ -25,48 +25,59 @@ class Attributes implements IteratorAggregate, ArrayAccess
|
||||||
protected $disallowed = [];
|
protected $disallowed = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param null|array<string,bool|string|Stringable> $array
|
* @param null|array<string,bool|string|Stringable> $array
|
||||||
* @param array<mixed,string> $disallowed
|
* @param array<mixed,string> $disallowed
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(null|array $array = null, $disallowed = [])
|
public function __construct(null|array $array = null, $disallowed = [])
|
||||||
{
|
{
|
||||||
$this->disallowed = $disallowed;
|
$this->disallowed = $disallowed;
|
||||||
if (!$array) return;
|
if (!$array) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach ($array as $key => $value) {
|
foreach ($array as $key => $value) {
|
||||||
$this[$key] = $value;
|
$this[$key] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function offsetExists(mixed $offset): bool
|
public function offsetExists(mixed $offset): bool
|
||||||
{
|
{
|
||||||
$offset = static::sanitizeOffset($offset);
|
$offset = static::sanitizeOffset($offset);
|
||||||
return isset($this->array[$offset]);
|
return isset($this->array[$offset]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function offsetGet(mixed $offset): mixed
|
public function offsetGet(mixed $offset): mixed
|
||||||
{
|
{
|
||||||
$offset = static::sanitizeOffset($offset);
|
$offset = static::sanitizeOffset($offset);
|
||||||
return @$this->array[$offset];
|
return @$this->array[$offset];
|
||||||
}
|
}
|
||||||
|
|
||||||
function offsetSet(mixed $offset, mixed $value): void
|
public function offsetSet(mixed $offset, mixed $value): void
|
||||||
{
|
{
|
||||||
if (!$offset || !trim($offset)) throw new Exception('Attribute name must be specified when setting');
|
if (!$offset || !trim($offset)) {
|
||||||
|
throw new Exception('Attribute name must be specified when setting');
|
||||||
|
}
|
||||||
$offset = static::sanitizeOffset($offset);
|
$offset = static::sanitizeOffset($offset);
|
||||||
if (in_array($offset, $this->disallowed)) throw new Exception('Setting attribute is disallowed');
|
if (in_array($offset, $this->disallowed)) {
|
||||||
if (!isset($this->array[$offset])) $this->sorted = false;
|
throw new Exception('Setting attribute is disallowed');
|
||||||
|
}
|
||||||
|
if (!isset($this->array[$offset])) {
|
||||||
|
$this->sorted = false;
|
||||||
|
}
|
||||||
$this->array[$offset] = $value;
|
$this->array[$offset] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function string(string $offset): null|string
|
public function string(string $offset): null|string
|
||||||
{
|
{
|
||||||
$value = $this->offsetGet($offset);
|
$value = $this->offsetGet($offset);
|
||||||
if (is_string($value)) return $value;
|
if (is_string($value)) {
|
||||||
else return null;
|
return $value;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function offsetUnset(mixed $offset): void
|
public function offsetUnset(mixed $offset): void
|
||||||
{
|
{
|
||||||
$offset = static::sanitizeOffset($offset);
|
$offset = static::sanitizeOffset($offset);
|
||||||
unset($this->array[$offset]);
|
unset($this->array[$offset]);
|
||||||
|
@ -75,7 +86,7 @@ class Attributes implements IteratorAggregate, ArrayAccess
|
||||||
/**
|
/**
|
||||||
* @return array<string,bool|string|Stringable>
|
* @return array<string,bool|string|Stringable>
|
||||||
*/
|
*/
|
||||||
function getArray(): array
|
public function getArray(): array
|
||||||
{
|
{
|
||||||
if (!$this->sorted) {
|
if (!$this->sorted) {
|
||||||
ksort($this->array);
|
ksort($this->array);
|
||||||
|
@ -84,7 +95,7 @@ class Attributes implements IteratorAggregate, ArrayAccess
|
||||||
return $this->array;
|
return $this->array;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getIterator(): Traversable
|
public function getIterator(): Traversable
|
||||||
{
|
{
|
||||||
return new ArrayIterator($this->getArray());
|
return new ArrayIterator($this->getArray());
|
||||||
}
|
}
|
||||||
|
@ -93,7 +104,9 @@ class Attributes implements IteratorAggregate, ArrayAccess
|
||||||
{
|
{
|
||||||
$offset = trim($offset);
|
$offset = trim($offset);
|
||||||
$offset = strtolower($offset);
|
$offset = strtolower($offset);
|
||||||
if (preg_match('/[\t\n\f \/>"\'=]/', $offset)) throw new Exception('Invalid character in attribute name');
|
if (preg_match('/[\t\n\f \/>"\'=]/', $offset)) {
|
||||||
|
throw new Exception('Invalid character in attribute name');
|
||||||
|
}
|
||||||
return $offset;
|
return $offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,9 @@ class Classes implements Countable
|
||||||
*/
|
*/
|
||||||
public function __construct(null|array|Traversable $array = null, bool $no_exception = true)
|
public function __construct(null|array|Traversable $array = null, bool $no_exception = true)
|
||||||
{
|
{
|
||||||
if (!$array) return;
|
if (!$array) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach ($array as $class) {
|
foreach ($array as $class) {
|
||||||
$this->add($class, $no_exception);
|
$this->add($class, $no_exception);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +36,9 @@ class Classes implements Countable
|
||||||
{
|
{
|
||||||
foreach (explode(' ', $class_string) as $class) {
|
foreach (explode(' ', $class_string) as $class) {
|
||||||
$class = trim($class);
|
$class = trim($class);
|
||||||
if ($class) $this->add($class);
|
if ($class) {
|
||||||
|
$this->add($class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +50,7 @@ class Classes implements Countable
|
||||||
/**
|
/**
|
||||||
* @return array<int,string|Stringable>
|
* @return array<int,string|Stringable>
|
||||||
*/
|
*/
|
||||||
function getArray(): array
|
public function getArray(): array
|
||||||
{
|
{
|
||||||
if (!$this->sorted) {
|
if (!$this->sorted) {
|
||||||
sort($this->classes);
|
sort($this->classes);
|
||||||
|
@ -60,8 +64,11 @@ class Classes implements Countable
|
||||||
try {
|
try {
|
||||||
$class = static::sanitizeClassName($class, true);
|
$class = static::sanitizeClassName($class, true);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
if ($no_exception) return $this;
|
if ($no_exception) {
|
||||||
else throw $th;
|
return $this;
|
||||||
|
} else {
|
||||||
|
throw $th;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!in_array($class, $this->classes)) {
|
if (!in_array($class, $this->classes)) {
|
||||||
$this->classes[] = $class;
|
$this->classes[] = $class;
|
||||||
|
@ -91,7 +98,9 @@ class Classes implements Countable
|
||||||
protected static function sanitizeClassName(string $class, bool $validate = false): string
|
protected static function sanitizeClassName(string $class, bool $validate = false): string
|
||||||
{
|
{
|
||||||
$class = trim($class);
|
$class = trim($class);
|
||||||
if ($validate && !preg_match('/^[_\-a-z][_\-a-z0-9]*$/i', $class)) throw new Exception('Invalid class name');
|
if ($validate && !preg_match('/^[_\-a-z][_\-a-z0-9]*$/i', $class)) {
|
||||||
|
throw new Exception('Invalid class name');
|
||||||
|
}
|
||||||
return $class;
|
return $class;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,13 @@ use Stringable;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A key difference in strategy between this class and Attributes or Classes is
|
* A key difference in strategy between this class and Attributes or Classes is
|
||||||
* that it does not make significant validation attempts. CSS is an evolving
|
* that it does not make significant validation attempts. CSS is an evolving
|
||||||
* language, and it would be a fool's errand to try and thoroughly validate it.
|
* language, and it would be a fool's errand to try and thoroughly validate it.
|
||||||
*
|
*
|
||||||
* To that end, this class is very accepting of not-obviously-malformed property
|
* To that end, this class is very accepting of not-obviously-malformed property
|
||||||
* names and values.
|
* names and values.
|
||||||
*
|
*
|
||||||
* @implements ArrayAccess<string,null|string|Stringable>
|
* @implements ArrayAccess<string,null|string|Stringable>
|
||||||
*/
|
*/
|
||||||
class Styles implements Countable, ArrayAccess, Stringable
|
class Styles implements Countable, ArrayAccess, Stringable
|
||||||
|
@ -29,7 +29,9 @@ class Styles implements Countable, ArrayAccess, Stringable
|
||||||
*/
|
*/
|
||||||
public function __construct(null|array|Traversable $classes = null)
|
public function __construct(null|array|Traversable $classes = null)
|
||||||
{
|
{
|
||||||
if (!$classes) return;
|
if (!$classes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach ($classes as $name => $value) {
|
foreach ($classes as $name => $value) {
|
||||||
$this[$name] = $value;
|
$this[$name] = $value;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +41,9 @@ class Styles implements Countable, ArrayAccess, Stringable
|
||||||
{
|
{
|
||||||
foreach (explode(';', $css_string) as $rule) {
|
foreach (explode(';', $css_string) as $rule) {
|
||||||
$rule = explode(':', trim($rule));
|
$rule = explode(':', trim($rule));
|
||||||
if (count($rule) == 2) $this[$rule[0]] = $rule[1];
|
if (count($rule) == 2) {
|
||||||
|
$this[$rule[0]] = $rule[1];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +54,7 @@ class Styles implements Countable, ArrayAccess, Stringable
|
||||||
|
|
||||||
public function offsetExists(mixed $offset): bool
|
public function offsetExists(mixed $offset): bool
|
||||||
{
|
{
|
||||||
if (!$offset) return false;
|
return @isset($this->styles[$offset]);
|
||||||
return isset($this->styles[$offset]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function offsetGet(mixed $offset): mixed
|
public function offsetGet(mixed $offset): mixed
|
||||||
|
@ -61,13 +64,16 @@ class Styles implements Countable, ArrayAccess, Stringable
|
||||||
|
|
||||||
public function offsetSet(mixed $offset, mixed $value): void
|
public function offsetSet(mixed $offset, mixed $value): void
|
||||||
{
|
{
|
||||||
if (!$offset) return;
|
if (!$value) {
|
||||||
if ($value) $value = trim($value);
|
unset($this->styles[$offset]);
|
||||||
if (!$value) unset($this->styles[$offset]);
|
} else {
|
||||||
else {
|
if (!static::validate($offset, $value)) {
|
||||||
if (!static::validate($offset, $value)) return;
|
return;
|
||||||
if (!isset($this->styles[$offset])) $this->sorted = false;
|
}
|
||||||
$this->styles[$offset] = $value;
|
if (!isset($this->styles[$offset])) {
|
||||||
|
$this->sorted = false;
|
||||||
|
}
|
||||||
|
$this->styles[$offset] = trim($value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,15 +103,19 @@ class Styles implements Countable, ArrayAccess, Stringable
|
||||||
return implode(';', $styles);
|
return implode(';', $styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function validate(null|string $property, null|string $value): bool
|
protected static function validate(null|string $property, string $value): bool
|
||||||
{
|
{
|
||||||
if (!$property) return false;
|
if (!$property) {
|
||||||
elseif (!preg_match('/[a-z]/', $property)) return false;
|
return false;
|
||||||
|
} elseif (!preg_match('/[a-z]/', $property)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if ($value) $value = trim($value);
|
if (str_contains($value, ';')) {
|
||||||
if (!$value) return false;
|
return false;
|
||||||
elseif (str_contains($value, ';')) return false;
|
} elseif (str_contains($value, ':')) {
|
||||||
elseif (str_contains($value, ':')) return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,10 @@ use ByJoby\HTML\Traits\GroupedContainerTrait;
|
||||||
|
|
||||||
class HeadTag extends AbstractGroupedTag implements HeadTagInterface
|
class HeadTag extends AbstractGroupedTag implements HeadTagInterface
|
||||||
{
|
{
|
||||||
const TAG = 'head';
|
|
||||||
|
|
||||||
use GroupedContainerTrait;
|
use GroupedContainerTrait;
|
||||||
|
|
||||||
|
const TAG = 'head';
|
||||||
|
|
||||||
/** @var ContainerGroup<TitleTagInterface> */
|
/** @var ContainerGroup<TitleTagInterface> */
|
||||||
protected $title;
|
protected $title;
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class HeadTag extends AbstractGroupedTag implements HeadTagInterface
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
$this->title = ContainerGroup::ofClass(TitleTagInterface::class, 1);
|
$this->title = ContainerGroup::ofClass(TitleTagInterface::class, 1);
|
||||||
$this->addGroup($this->title);
|
$this->addGroup($this->title);
|
||||||
$this->addChild(new TitleTag);
|
$this->addChild(new TitleTag());
|
||||||
$this->addGroup(ContainerGroup::ofTag('meta'));
|
$this->addGroup(ContainerGroup::ofTag('meta'));
|
||||||
$this->addGroup(ContainerGroup::ofTag('base', 1));
|
$this->addGroup(ContainerGroup::ofTag('base', 1));
|
||||||
$this->addGroup(ContainerGroup::ofTag('style'));
|
$this->addGroup(ContainerGroup::ofTag('style'));
|
||||||
|
|
|
@ -11,10 +11,10 @@ use ByJoby\HTML\Traits\GroupedContainerTrait;
|
||||||
|
|
||||||
class HtmlTag extends AbstractGroupedTag implements HtmlTagInterface
|
class HtmlTag extends AbstractGroupedTag implements HtmlTagInterface
|
||||||
{
|
{
|
||||||
const TAG = 'html';
|
|
||||||
|
|
||||||
use GroupedContainerTrait;
|
use GroupedContainerTrait;
|
||||||
|
|
||||||
|
const TAG = 'html';
|
||||||
|
|
||||||
/** @var ContainerGroup<HeadTagInterface> */
|
/** @var ContainerGroup<HeadTagInterface> */
|
||||||
protected $head;
|
protected $head;
|
||||||
/** @var ContainerGroup<BodyTagInterface> */
|
/** @var ContainerGroup<BodyTagInterface> */
|
||||||
|
@ -27,8 +27,8 @@ class HtmlTag extends AbstractGroupedTag implements HtmlTagInterface
|
||||||
$this->body = ContainerGroup::ofClass(BodyTagInterface::class, 1);
|
$this->body = ContainerGroup::ofClass(BodyTagInterface::class, 1);
|
||||||
$this->addGroup($this->head);
|
$this->addGroup($this->head);
|
||||||
$this->addGroup($this->body);
|
$this->addGroup($this->body);
|
||||||
$this->addChild(new HeadTag);
|
$this->addChild(new HeadTag());
|
||||||
$this->addChild(new BodyTag);
|
$this->addChild(new BodyTag());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function head(): HeadTagInterface
|
public function head(): HeadTagInterface
|
||||||
|
|
|
@ -27,8 +27,8 @@ class Html5Document implements HtmlDocumentInterface
|
||||||
$this->html = ContainerGroup::ofClass(HtmlTagInterface::class, 1);
|
$this->html = ContainerGroup::ofClass(HtmlTagInterface::class, 1);
|
||||||
$this->addGroup($this->doctype);
|
$this->addGroup($this->doctype);
|
||||||
$this->addGroup($this->html);
|
$this->addGroup($this->html);
|
||||||
$this->addChild(new Doctype);
|
$this->addChild(new Doctype());
|
||||||
$this->addChild(new HtmlTag);
|
$this->addChild(new HtmlTag());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function doctype(): DoctypeInterface
|
public function doctype(): DoctypeInterface
|
||||||
|
|
|
@ -16,8 +16,11 @@ class BaseTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setHref(null|string $href): static
|
public function setHref(null|string $href): static
|
||||||
{
|
{
|
||||||
if (!$href) $this->attributes()['href'] = false;
|
if (!$href) {
|
||||||
else $this->attributes()['href'] = $href;
|
$this->attributes()['href'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['href'] = $href;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +37,11 @@ class BaseTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setTarget(null|string $target): static
|
public function setTarget(null|string $target): static
|
||||||
{
|
{
|
||||||
if (!$target) $this->attributes()['target'] = false;
|
if (!$target) {
|
||||||
else $this->attributes()['target'] = $target;
|
$this->attributes()['target'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['target'] = $target;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setRel(null|string $rel): static
|
public function setRel(null|string $rel): static
|
||||||
{
|
{
|
||||||
if (!$rel) $this->attributes()['rel'] = false;
|
if (!$rel) {
|
||||||
else $this->attributes()['rel'] = $rel;
|
$this->attributes()['rel'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['rel'] = $rel;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +37,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setAs(null|string $as): static
|
public function setAs(null|string $as): static
|
||||||
{
|
{
|
||||||
if (!$as) $this->attributes()['as'] = false;
|
if (!$as) {
|
||||||
else $this->attributes()['as'] = $as;
|
$this->attributes()['as'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['as'] = $as;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +58,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setCrossorigin(null|string $crossorigin): static
|
public function setCrossorigin(null|string $crossorigin): static
|
||||||
{
|
{
|
||||||
if (!$crossorigin) $this->attributes()['crossorigin'] = false;
|
if (!$crossorigin) {
|
||||||
else $this->attributes()['crossorigin'] = $crossorigin;
|
$this->attributes()['crossorigin'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['crossorigin'] = $crossorigin;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,8 +79,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setHref(null|string $href): static
|
public function setHref(null|string $href): static
|
||||||
{
|
{
|
||||||
if (!$href) $this->attributes()['href'] = false;
|
if (!$href) {
|
||||||
else $this->attributes()['href'] = $href;
|
$this->attributes()['href'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['href'] = $href;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,8 +100,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setHreflang(null|string $hreflang): static
|
public function setHreflang(null|string $hreflang): static
|
||||||
{
|
{
|
||||||
if (!$hreflang) $this->attributes()['hreflang'] = false;
|
if (!$hreflang) {
|
||||||
else $this->attributes()['hreflang'] = $hreflang;
|
$this->attributes()['hreflang'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['hreflang'] = $hreflang;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,8 +121,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setImagesizes(null|string $imagesizes): static
|
public function setImagesizes(null|string $imagesizes): static
|
||||||
{
|
{
|
||||||
if (!$imagesizes) $this->attributes()['imagesizes'] = false;
|
if (!$imagesizes) {
|
||||||
else $this->attributes()['imagesizes'] = $imagesizes;
|
$this->attributes()['imagesizes'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['imagesizes'] = $imagesizes;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +142,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setImagesrcset(null|string $imagesrcset): static
|
public function setImagesrcset(null|string $imagesrcset): static
|
||||||
{
|
{
|
||||||
if (!$imagesrcset) $this->attributes()['imagesrcset'] = false;
|
if (!$imagesrcset) {
|
||||||
else $this->attributes()['imagesrcset'] = $imagesrcset;
|
$this->attributes()['imagesrcset'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['imagesrcset'] = $imagesrcset;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,8 +163,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setIntegrity(null|string $integrity): static
|
public function setIntegrity(null|string $integrity): static
|
||||||
{
|
{
|
||||||
if (!$integrity) $this->attributes()['integrity'] = false;
|
if (!$integrity) {
|
||||||
else $this->attributes()['integrity'] = $integrity;
|
$this->attributes()['integrity'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['integrity'] = $integrity;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,8 +184,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setMedia(null|string $media): static
|
public function setMedia(null|string $media): static
|
||||||
{
|
{
|
||||||
if (!$media) $this->attributes()['media'] = false;
|
if (!$media) {
|
||||||
else $this->attributes()['media'] = $media;
|
$this->attributes()['media'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['media'] = $media;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,8 +205,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setReferrerpolicy(null|string $referrerpolicy): static
|
public function setReferrerpolicy(null|string $referrerpolicy): static
|
||||||
{
|
{
|
||||||
if (!$referrerpolicy) $this->attributes()['referrerpolicy'] = false;
|
if (!$referrerpolicy) {
|
||||||
else $this->attributes()['referrerpolicy'] = $referrerpolicy;
|
$this->attributes()['referrerpolicy'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['referrerpolicy'] = $referrerpolicy;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,8 +226,11 @@ class LinkTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setType(null|string $type): static
|
public function setType(null|string $type): static
|
||||||
{
|
{
|
||||||
if (!$type) $this->attributes()['type'] = false;
|
if (!$type) {
|
||||||
else $this->attributes()['type'] = $type;
|
$this->attributes()['type'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['type'] = $type;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,11 @@ class MetaTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setName(null|string $name): static
|
public function setName(null|string $name): static
|
||||||
{
|
{
|
||||||
if (!$name) $this->attributes()['name'] = false;
|
if (!$name) {
|
||||||
else $this->attributes()['name'] = $name;
|
$this->attributes()['name'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['name'] = $name;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +37,11 @@ class MetaTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setContent(null|string $content): static
|
public function setContent(null|string $content): static
|
||||||
{
|
{
|
||||||
if (!$content) $this->attributes()['content'] = false;
|
if (!$content) {
|
||||||
else $this->attributes()['content'] = $content;
|
$this->attributes()['content'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['content'] = $content;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +58,11 @@ class MetaTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setHttpEquiv(null|string $http_equiv): static
|
public function setHttpEquiv(null|string $http_equiv): static
|
||||||
{
|
{
|
||||||
if (!$http_equiv) $this->attributes()['http-equiv'] = false;
|
if (!$http_equiv) {
|
||||||
else $this->attributes()['http-equiv'] = $http_equiv;
|
$this->attributes()['http-equiv'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['http-equiv'] = $http_equiv;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,8 +79,11 @@ class MetaTag extends AbstractTag implements MetadataContent
|
||||||
|
|
||||||
public function setCharset(null|string $charset): static
|
public function setCharset(null|string $charset): static
|
||||||
{
|
{
|
||||||
if (!$charset) $this->attributes()['charset'] = false;
|
if (!$charset) {
|
||||||
else $this->attributes()['charset'] = $charset;
|
$this->attributes()['charset'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['charset'] = $charset;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,11 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
||||||
|
|
||||||
public function setCrossorigin(null|string $crossorigin): static
|
public function setCrossorigin(null|string $crossorigin): static
|
||||||
{
|
{
|
||||||
if (!$crossorigin) $this->attributes()['crossorigin'] = false;
|
if (!$crossorigin) {
|
||||||
else $this->attributes()['crossorigin'] = $crossorigin;
|
$this->attributes()['crossorigin'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['crossorigin'] = $crossorigin;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,8 +61,11 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
||||||
|
|
||||||
public function setIntegrity(null|string $integrity): static
|
public function setIntegrity(null|string $integrity): static
|
||||||
{
|
{
|
||||||
if (!$integrity) $this->attributes()['integrity'] = false;
|
if (!$integrity) {
|
||||||
else $this->attributes()['integrity'] = $integrity;
|
$this->attributes()['integrity'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['integrity'] = $integrity;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,8 +93,11 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
||||||
|
|
||||||
public function setNonce(null|string $nonce): static
|
public function setNonce(null|string $nonce): static
|
||||||
{
|
{
|
||||||
if (!$nonce) $this->attributes()['nonce'] = false;
|
if (!$nonce) {
|
||||||
else $this->attributes()['nonce'] = $nonce;
|
$this->attributes()['nonce'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['nonce'] = $nonce;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,8 +114,11 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
||||||
|
|
||||||
public function setReferrerpolicy(null|string $referrerpolicy): static
|
public function setReferrerpolicy(null|string $referrerpolicy): static
|
||||||
{
|
{
|
||||||
if (!$referrerpolicy) $this->attributes()['referrerpolicy'] = false;
|
if (!$referrerpolicy) {
|
||||||
else $this->attributes()['referrerpolicy'] = $referrerpolicy;
|
$this->attributes()['referrerpolicy'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['referrerpolicy'] = $referrerpolicy;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,8 +135,11 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
||||||
|
|
||||||
public function setSrc(null|string $src): static
|
public function setSrc(null|string $src): static
|
||||||
{
|
{
|
||||||
if (!$src) $this->attributes()['src'] = false;
|
if (!$src) {
|
||||||
else $this->attributes()['src'] = $src;
|
$this->attributes()['src'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['src'] = $src;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +156,11 @@ class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingC
|
||||||
|
|
||||||
public function setType(null|string $type): static
|
public function setType(null|string $type): static
|
||||||
{
|
{
|
||||||
if (!$type) $this->attributes()['type'] = false;
|
if (!$type) {
|
||||||
else $this->attributes()['type'] = $type;
|
$this->attributes()['type'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['type'] = $type;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,11 @@ class StyleTag extends AbstractContentTag implements MetadataContent
|
||||||
|
|
||||||
public function setMedia(null|string $media): static
|
public function setMedia(null|string $media): static
|
||||||
{
|
{
|
||||||
if (!$media) $this->attributes()['media'] = false;
|
if (!$media) {
|
||||||
else $this->attributes()['media'] = $media;
|
$this->attributes()['media'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['media'] = $media;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +37,11 @@ class StyleTag extends AbstractContentTag implements MetadataContent
|
||||||
|
|
||||||
public function setNonce(null|string $nonce): static
|
public function setNonce(null|string $nonce): static
|
||||||
{
|
{
|
||||||
if (!$nonce) $this->attributes()['nonce'] = false;
|
if (!$nonce) {
|
||||||
else $this->attributes()['nonce'] = $nonce;
|
$this->attributes()['nonce'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['nonce'] = $nonce;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,11 @@ class BlockquoteTag extends AbstractContainerTag implements FlowContent, Section
|
||||||
|
|
||||||
public function setCite(null|string $cite): static
|
public function setCite(null|string $cite): static
|
||||||
{
|
{
|
||||||
if (!$cite) $this->attributes()['cite'] = false;
|
if (!$cite) {
|
||||||
else $this->attributes()['cite'] = $cite;
|
$this->attributes()['cite'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['cite'] = $cite;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ class OlTag extends AbstractContainerTag implements FlowContent, DisplayBlock
|
||||||
|
|
||||||
public function start(): null|int
|
public function start(): null|int
|
||||||
{
|
{
|
||||||
if (isset($this->attributes['start'])) {
|
if ($this->attributes()['start']) {
|
||||||
return intval($this->attributes()->string('start'));
|
return intval($this->attributes()->string('start'));
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -37,8 +37,11 @@ class OlTag extends AbstractContainerTag implements FlowContent, DisplayBlock
|
||||||
|
|
||||||
public function setStart(null|int $start): static
|
public function setStart(null|int $start): static
|
||||||
{
|
{
|
||||||
if (!$start) $this->attributes()['start'] = false;
|
if (!$start) {
|
||||||
else $this->attributes()['start'] = strval($start);
|
$this->attributes()['start'] = false;
|
||||||
|
} else {
|
||||||
|
$this->attributes()['start'] = strval($start);
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,21 +8,25 @@ use ByJoby\HTML\Traits\NodeTrait;
|
||||||
|
|
||||||
abstract class AbstractContainerTag extends AbstractTag implements ContainerTagInterface
|
abstract class AbstractContainerTag extends AbstractTag implements ContainerTagInterface
|
||||||
{
|
{
|
||||||
use NodeTrait, TagTrait;
|
use NodeTrait;
|
||||||
|
use TagTrait;
|
||||||
use ContainerTrait;
|
use ContainerTrait;
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
$openingTag = sprintf('<%s>', implode(' ', $this->openingTagStrings()));
|
$openingTag = sprintf('<%s>', implode(' ', $this->openingTagStrings()));
|
||||||
$closingTag = sprintf('</%s>', $this->tag());
|
$closingTag = sprintf('</%s>', $this->tag());
|
||||||
if (!$this->children()) return $openingTag . $closingTag;
|
if (!$this->children()) {
|
||||||
else return implode(
|
return $openingTag . $closingTag;
|
||||||
PHP_EOL,
|
} else {
|
||||||
[
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
[
|
||||||
$openingTag,
|
$openingTag,
|
||||||
implode(PHP_EOL, $this->children()),
|
implode(PHP_EOL, $this->children()),
|
||||||
$closingTag
|
$closingTag
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,14 +25,17 @@ abstract class AbstractContentTag extends AbstractTag implements ContentTagInter
|
||||||
$openingTag = sprintf('<%s>', implode(' ', $this->openingTagStrings()));
|
$openingTag = sprintf('<%s>', implode(' ', $this->openingTagStrings()));
|
||||||
$closingTag = sprintf('</%s>', $this->tag());
|
$closingTag = sprintf('</%s>', $this->tag());
|
||||||
$content = $this->content();
|
$content = $this->content();
|
||||||
if (!$content) return $openingTag . $closingTag;
|
if (!$content) {
|
||||||
else return implode(
|
return $openingTag . $closingTag;
|
||||||
PHP_EOL,
|
} else {
|
||||||
[
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
[
|
||||||
$openingTag,
|
$openingTag,
|
||||||
$content,
|
$content,
|
||||||
$closingTag
|
$closingTag
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,14 +19,17 @@ abstract class AbstractGroupedTag extends AbstractTag implements ContainerTagInt
|
||||||
return !!$group->children();
|
return !!$group->children();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!$groups) return $openingTag . $closingTag;
|
if (!$groups) {
|
||||||
else return implode(
|
return $openingTag . $closingTag;
|
||||||
PHP_EOL,
|
} else {
|
||||||
[
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
[
|
||||||
$openingTag,
|
$openingTag,
|
||||||
implode(PHP_EOL, $groups),
|
implode(PHP_EOL, $groups),
|
||||||
$closingTag
|
$closingTag
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ use ByJoby\HTML\Traits\NodeTrait;
|
||||||
|
|
||||||
abstract class AbstractTag implements TagInterface
|
abstract class AbstractTag implements TagInterface
|
||||||
{
|
{
|
||||||
use NodeTrait, TagTrait;
|
use NodeTrait;
|
||||||
|
use TagTrait;
|
||||||
|
|
||||||
public function tag(): string
|
public function tag(): string
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,7 @@ use ByJoby\HTML\ContainerInterface;
|
||||||
* child tags. They can all have tags added and removed from them as well.
|
* child tags. They can all have tags added and removed from them as well.
|
||||||
* Container Tags always render as a full opening and closing tag, even when
|
* Container Tags always render as a full opening and closing tag, even when
|
||||||
* they are empty.
|
* they are empty.
|
||||||
*
|
*
|
||||||
* @package ByJoby\HTML\Tags
|
* @package ByJoby\HTML\Tags
|
||||||
*/
|
*/
|
||||||
interface ContainerTagInterface extends TagInterface, ContainerInterface
|
interface ContainerTagInterface extends TagInterface, ContainerInterface
|
||||||
|
|
|
@ -8,7 +8,7 @@ use Stringable;
|
||||||
* Content Tags contain a single string or Stringable, which may or may not be
|
* Content Tags contain a single string or Stringable, which may or may not be
|
||||||
* valid HTML. They render as full opening/closing HTML tags which wrap the
|
* valid HTML. They render as full opening/closing HTML tags which wrap the
|
||||||
* content stored in the tag.
|
* content stored in the tag.
|
||||||
*
|
*
|
||||||
* @package ByJoby\HTML\Tags
|
* @package ByJoby\HTML\Tags
|
||||||
*/
|
*/
|
||||||
interface ContentTagInterface extends TagInterface
|
interface ContentTagInterface extends TagInterface
|
||||||
|
|
|
@ -11,7 +11,7 @@ use Stringable;
|
||||||
/**
|
/**
|
||||||
* Simple tags represent self-closing tags that cannot contain anything else
|
* Simple tags represent self-closing tags that cannot contain anything else
|
||||||
* within them.
|
* within them.
|
||||||
*
|
*
|
||||||
* @package ByJoby\HTML\Tags
|
* @package ByJoby\HTML\Tags
|
||||||
*/
|
*/
|
||||||
interface TagInterface extends NodeInterface
|
interface TagInterface extends NodeInterface
|
||||||
|
|
|
@ -35,8 +35,11 @@ trait ContainerTrait
|
||||||
bool $skip_sanitize = false
|
bool $skip_sanitize = false
|
||||||
): static {
|
): static {
|
||||||
$child = $this->prepareChildToAdd($child, $skip_sanitize);
|
$child = $this->prepareChildToAdd($child, $skip_sanitize);
|
||||||
if ($prepend) array_unshift($this->children, $child);
|
if ($prepend) {
|
||||||
else $this->children[] = $child;
|
array_unshift($this->children, $child);
|
||||||
|
} else {
|
||||||
|
$this->children[] = $child;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +49,11 @@ trait ContainerTrait
|
||||||
$this->children = array_filter(
|
$this->children = array_filter(
|
||||||
$this->children,
|
$this->children,
|
||||||
function (NodeInterface $e) use ($child) {
|
function (NodeInterface $e) use ($child) {
|
||||||
if (is_object($child)) $keep = $e !== $child;
|
if (is_object($child)) {
|
||||||
else $keep = $e != $child;
|
$keep = $e !== $child;
|
||||||
|
} else {
|
||||||
|
$keep = $e != $child;
|
||||||
|
}
|
||||||
if (!$keep) {
|
if (!$keep) {
|
||||||
$e->setParent(null);
|
$e->setParent(null);
|
||||||
}
|
}
|
||||||
|
@ -89,8 +95,11 @@ trait ContainerTrait
|
||||||
{
|
{
|
||||||
// turn strings into nodes
|
// turn strings into nodes
|
||||||
if (!($child instanceof NodeInterface)) {
|
if (!($child instanceof NodeInterface)) {
|
||||||
if ($skip_sanitize) $child = new UnsanitizedText($child);
|
if ($skip_sanitize) {
|
||||||
else $child = new Text($child);
|
$child = new UnsanitizedText($child);
|
||||||
|
} else {
|
||||||
|
$child = new Text($child);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// remove from parent, move it here, and return
|
// remove from parent, move it here, and return
|
||||||
if ($parent = $child->parent()) {
|
if ($parent = $child->parent()) {
|
||||||
|
@ -104,11 +113,15 @@ trait ContainerTrait
|
||||||
{
|
{
|
||||||
if ($child instanceof NodeInterface) {
|
if ($child instanceof NodeInterface) {
|
||||||
foreach ($this->children as $i => $v) {
|
foreach ($this->children as $i => $v) {
|
||||||
if ($v === $child) return $i;
|
if ($v === $child) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
foreach ($this->children as $i => $v) {
|
foreach ($this->children as $i => $v) {
|
||||||
if ($v == $child) return $i;
|
if ($v == $child) {
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -21,7 +21,7 @@ trait GroupedContainerTrait
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<int,ContainerGroup<NodeInterface>>
|
* @return array<int,ContainerGroup<NodeInterface>>
|
||||||
*/
|
*/
|
||||||
public function groups(): array
|
public function groups(): array
|
||||||
{
|
{
|
||||||
|
@ -36,7 +36,9 @@ trait GroupedContainerTrait
|
||||||
public function willAccept(NodeInterface|Stringable|string $child): bool
|
public function willAccept(NodeInterface|Stringable|string $child): bool
|
||||||
{
|
{
|
||||||
foreach ($this->groups() as $group) {
|
foreach ($this->groups() as $group) {
|
||||||
if ($group->willAccept($child)) return true;
|
if ($group->willAccept($child)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ trait NodeTrait
|
||||||
/** @var null|ContainerInterface */
|
/** @var null|ContainerInterface */
|
||||||
protected $parent;
|
protected $parent;
|
||||||
|
|
||||||
abstract function __toString();
|
abstract public function __toString();
|
||||||
|
|
||||||
public function parent(): null|ContainerInterface
|
public function parent(): null|ContainerInterface
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ trait TagTrait
|
||||||
/** @var Styles */
|
/** @var Styles */
|
||||||
protected $styles;
|
protected $styles;
|
||||||
|
|
||||||
abstract function tag(): string;
|
abstract public function tag(): string;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
|
@ -35,15 +35,20 @@ trait TagTrait
|
||||||
|
|
||||||
public function setID(null|string|Stringable $id): static
|
public function setID(null|string|Stringable $id): static
|
||||||
{
|
{
|
||||||
if ($id) $this->id = static::sanitizeID($id);
|
if ($id) {
|
||||||
else $this->id = null;
|
$this->id = static::sanitizeID($id);
|
||||||
|
} else {
|
||||||
|
$this->id = null;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static function sanitizeID(string|Stringable $id): string
|
protected static function sanitizeID(string|Stringable $id): string
|
||||||
{
|
{
|
||||||
$id = trim($id);
|
$id = trim($id);
|
||||||
if (!preg_match('/^[_\-a-z][_\-a-z0-9]*$/i', $id)) throw new Exception('Invalid ID name');
|
if (!preg_match('/^[_\-a-z][_\-a-z0-9]*$/i', $id)) {
|
||||||
|
throw new Exception('Invalid tag ID');
|
||||||
|
}
|
||||||
return $id;
|
return $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +78,9 @@ trait TagTrait
|
||||||
protected function openingTagStrings(): array
|
protected function openingTagStrings(): array
|
||||||
{
|
{
|
||||||
$strings = [$this->tag()];
|
$strings = [$this->tag()];
|
||||||
if ($this->id) $strings[] = sprintf('id="%s"', $this->id);
|
if ($this->id) {
|
||||||
|
$strings[] = sprintf('id="%s"', $this->id);
|
||||||
|
}
|
||||||
if ($this->classes()->count()) {
|
if ($this->classes()->count()) {
|
||||||
$strings[] = sprintf('class="%s"', implode(' ', $this->classes()->getArray()));
|
$strings[] = sprintf('class="%s"', implode(' ', $this->classes()->getArray()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,4 +33,29 @@ class StylesTest extends TestCase
|
||||||
$styles = new Styles(['a' => 'b', 'b' => 'c']);
|
$styles = new Styles(['a' => 'b', 'b' => 'c']);
|
||||||
$this->assertEquals('a:b;b:c', $styles->__toString());
|
$this->assertEquals('a:b;b:c', $styles->__toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends clone testConstruction
|
||||||
|
*/
|
||||||
|
public function testInvalidInputs(Styles $styles): void
|
||||||
|
{
|
||||||
|
// null assignments don't work
|
||||||
|
$styles[] = 'b';
|
||||||
|
$this->assertEquals(['foo' => 'bar'], $styles->getArray());
|
||||||
|
// empty attribute doesn't work
|
||||||
|
$styles[''] = 'b';
|
||||||
|
$this->assertEquals(['foo' => 'bar'], $styles->getArray());
|
||||||
|
// attributes that trim to nothing don't work
|
||||||
|
$styles[' '] = 'b';
|
||||||
|
$this->assertEquals(['foo' => 'bar'], $styles->getArray());
|
||||||
|
// empty values don't work
|
||||||
|
$styles['quux'] = '';
|
||||||
|
$this->assertEquals(['foo' => 'bar'], $styles->getArray());
|
||||||
|
// values containing ; don't work
|
||||||
|
$styles['quux'] = 'x;y';
|
||||||
|
$this->assertEquals(['foo' => 'bar'], $styles->getArray());
|
||||||
|
// values containing : don't work
|
||||||
|
$styles['quux'] = 'x:y';
|
||||||
|
$this->assertEquals(['foo' => 'bar'], $styles->getArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,10 @@ abstract class TagTestCase extends TestCase
|
||||||
// test unsetting via unset
|
// test unsetting via unset
|
||||||
call_user_func([$tag, $unsetFn]);
|
call_user_func([$tag, $unsetFn]);
|
||||||
$this->assertNull(call_user_func([$tag, $getFn]));
|
$this->assertNull(call_user_func([$tag, $getFn]));
|
||||||
|
// test setting and unsetting via null value
|
||||||
|
call_user_func([$tag, $setFn], $test_value);
|
||||||
|
call_user_func([$tag, $setFn], null);
|
||||||
|
$this->assertNull(call_user_func([$tag, $getFn]));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assertBooleanAttributeHelperMethods(string $attribute, string $class): void
|
protected function assertBooleanAttributeHelperMethods(string $attribute, string $class): void
|
||||||
|
|
21
tests/Tags/AbstractGroupedTagTest.php
Normal file
21
tests/Tags/AbstractGroupedTagTest.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace ByJoby\HTML\Tags;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class AbstractGroupedTagTest extends TestCase
|
||||||
|
{
|
||||||
|
public function tag(string $name): AbstractGroupedTag
|
||||||
|
{
|
||||||
|
$tag = $this->getMockForAbstractClass(AbstractGroupedTag::class, [], '', true, true, true, ['tag']);
|
||||||
|
$tag->method('tag')->willReturn($name);
|
||||||
|
return $tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyRendering(): void
|
||||||
|
{
|
||||||
|
$tag = $this->tag('div');
|
||||||
|
$this->assertEquals('<div></div>', $tag->__toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ class AbstractTagTest extends TestCase
|
||||||
/**
|
/**
|
||||||
* @depends clone testBR
|
* @depends clone testBR
|
||||||
*/
|
*/
|
||||||
public function testID(AbstractTag $tag): void
|
public function testID(AbstractTag $tag): AbstractTag
|
||||||
{
|
{
|
||||||
$this->assertNull($tag->id());
|
$this->assertNull($tag->id());
|
||||||
$tag->setID('foo');
|
$tag->setID('foo');
|
||||||
|
@ -37,6 +37,16 @@ class AbstractTagTest extends TestCase
|
||||||
$tag->setID(null);
|
$tag->setID(null);
|
||||||
$this->assertNull($tag->id());
|
$this->assertNull($tag->id());
|
||||||
$this->assertEquals('<br>', $tag->__toString());
|
$this->assertEquals('<br>', $tag->__toString());
|
||||||
|
return $tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends clone testID
|
||||||
|
*/
|
||||||
|
public function testIDValidation(AbstractTag $tag): void
|
||||||
|
{
|
||||||
|
$this->expectExceptionMessage('Invalid tag ID');
|
||||||
|
$tag->setID('0abc');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue