more comments, improved boolean attributes

This commit is contained in:
Joby 2023-09-08 23:03:13 +00:00
parent 074b41167e
commit 51de2d6f64
31 changed files with 944 additions and 266 deletions

View file

@ -5,6 +5,7 @@ namespace ByJoby\HTML;
use ByJoby\HTML\Containers\Fragment;
use ByJoby\HTML\Containers\FragmentInterface;
use ByJoby\HTML\Containers\HtmlDocumentInterface;
use ByJoby\HTML\Html5\Enums\BooleanAttribute;
use ByJoby\HTML\Nodes\CData;
use ByJoby\HTML\Nodes\CDataInterface;
use ByJoby\HTML\Nodes\Comment;
@ -66,33 +67,34 @@ abstract class AbstractParser
public function parseFragment(string $html): FragmentInterface
{
$fragment = new ($this->fragment_class);
$fragment = new($this->fragment_class);
$dom = new DOMDocument();
$dom->loadHTML(
'<div>' . $html . '</div>', // wrap in DIV otherwise it will wrap root-level text in P tags
LIBXML_BIGLINES
| LIBXML_COMPACT
| LIBXML_HTML_NOIMPLIED
| LIBXML_HTML_NODEFDTD
| LIBXML_PARSEHUGE
| LIBXML_NOERROR
| LIBXML_COMPACT
| LIBXML_HTML_NOIMPLIED
| LIBXML_HTML_NODEFDTD
| LIBXML_PARSEHUGE
| LIBXML_NOERROR
);
$this->walkDom($dom->childNodes[0], $fragment);
// @phpstan-ignore-next-line we actually do know there's an item zero
$this->walkDom($dom->childNodes->item(0), $fragment);
return $fragment;
}
public function parseDocument(string $html): HtmlDocumentInterface
{
/** @var HtmlDocumentInterface */
$document = new ($this->document_class);
$document = new($this->document_class);
$dom = new DOMDocument();
$dom->loadHTML(
$html,
LIBXML_BIGLINES
| LIBXML_COMPACT
| LIBXML_HTML_NODEFDTD
| LIBXML_PARSEHUGE
| LIBXML_NOERROR
| LIBXML_COMPACT
| LIBXML_HTML_NODEFDTD
| LIBXML_PARSEHUGE
| LIBXML_NOERROR
);
$this->walkDom($dom, $document);
return $document;
@ -117,11 +119,11 @@ abstract class AbstractParser
if ($node instanceof DOMElement) {
return $this->convertNodeToTag($node);
} elseif ($node instanceof DOMComment) {
return new ($this->comment_class)($node->textContent);
return new($this->comment_class)($node->textContent);
} elseif ($node instanceof DOMText) {
$content = trim($node->textContent);
if ($content) {
return new ($this->text_class)($content);
return new($this->text_class)($content);
}
}
// It's philosophically consistent to simply ignore unknown node types
@ -149,18 +151,17 @@ abstract class AbstractParser
protected function processAttributes(DOMElement $node, TagInterface $tag): void
{
/** @var array<string,string|bool> */
$attributes = [];
// absorb attributes
// absorb attributes from DOMNode
/** @var DOMNode $attribute */
foreach ($node->attributes ?? [] as $attribute) {
if ($attribute->nodeValue) {
$attributes[$attribute->nodeName] = $attribute->nodeValue;
} else {
$attributes[$attribute->nodeName] = true;
$attributes[$attribute->nodeName] = BooleanAttribute::true;
}
}
// set attributes
// set attributes internally
foreach ($attributes as $k => $v) {
if ($k == 'id' && is_string($v)) {
$tag->setID($v);
@ -205,4 +206,4 @@ abstract class AbstractParser
// return null if nothing found
return null;
}
}
}

View file

@ -5,6 +5,7 @@ namespace ByJoby\HTML\Helpers;
use ArrayAccess;
use ArrayIterator;
use BackedEnum;
use ByJoby\HTML\Html5\Enums\BooleanAttribute;
use Exception;
use IteratorAggregate;
use Stringable;
@ -13,12 +14,12 @@ use Traversable;
/**
* Holds and validates a set of HTML attribute name/value pairs for use in tags.
*
* @implements ArrayAccess<string,bool|string|number|Stringable>
* @implements IteratorAggregate<string,bool|string|number|Stringable>
* @implements ArrayAccess<string,string|number|Stringable|BooleanAttribute>
* @implements IteratorAggregate<string,string|number|Stringable|BooleanAttribute>
*/
class Attributes implements IteratorAggregate, ArrayAccess
{
/** @var array<string,bool|string|number|Stringable> */
/** @var array<string,string|number|Stringable|BooleanAttribute> */
protected $array = [];
/** @var bool */
protected $sorted = true;
@ -26,7 +27,7 @@ class Attributes implements IteratorAggregate, ArrayAccess
protected $disallowed = [];
/**
* @param null|array<string,bool|string|number|Stringable> $array
* @param null|array<string,string|number|Stringable|BooleanAttribute|BooleanAttribute> $array
* @param array<mixed,string> $disallowed
* @return void
*/
@ -69,16 +70,18 @@ class Attributes implements IteratorAggregate, ArrayAccess
}
/**
* Set a value as a stringable enum array, automatically converting from a single enum or normal array of enums.
* Set a value as an array of enums, which will be internally saved as a
* string separated by $separator. An array of Enum values can also be
* retrieved using asEnumArray().
*
* @template T of BackedEnum
* @param string $offset
* @param null|BackedEnum|StringableEnumArray<T>|array<string|int,T> $value
* @param null|BackedEnum|array<string|int,T> $value
* @param class-string<T> $enum_class
* @param string $separator
* @return static
*/
public function setEnumArray(string $offset, null|BackedEnum|StringableEnumArray|array $value, string $enum_class, string $separator): static
public function setEnumArray(string $offset, null|BackedEnum|array $value, string $enum_class, string $separator): static
{
if (is_null($value)) {
$value = [];
@ -94,7 +97,9 @@ class Attributes implements IteratorAggregate, ArrayAccess
}
/**
* Returns a given offset's value as an array of enums.
* Returns a given offset's value as an array of enums. Note that this
* method always returns an array, it will simply be empty for empty
* attributes, unset attributes, or attributes with no valid values in them.
*
* @template T of BackedEnum
* @param string $offset
@ -104,12 +109,31 @@ class Attributes implements IteratorAggregate, ArrayAccess
*/
public function asEnumArray(string $offset, string $enum_class, string $separator): array
{
$value = strval($this->offsetGet($offset));
$value = $this->offsetGet($offset);
// short circuit if value is a boolean attribute
if ($value instanceof BooleanAttribute) {
return [];
}
// process as string
$value = strval($value);
$value = explode($separator, $value);
$value = array_map(
$enum_class::tryFrom(...),
$value
);
if (!$enum_class::cases()) {
// short-circuit if there are no cases in the enum
return [];
} elseif (is_string($enum_class::cases()[0]->value)) {
// look at string values only
$value = array_map(
fn(string|int $e) => $enum_class::tryFrom(strval($e)),
$value
);
} else {
// look at int values only
$value = array_map(
fn(string|int $e) => $enum_class::tryFrom(intval($e)),
$value
);
}
// filter and return
$value = array_filter(
$value,
fn($e) => !empty($e)
@ -126,6 +150,9 @@ class Attributes implements IteratorAggregate, ArrayAccess
public function asString(string $offset): null|string|Stringable
{
$value = $this->offsetGet($offset);
if (is_numeric($value)) {
$value = strval($value);
}
if ($value instanceof Stringable || is_string($value)) {
return $value;
} else {
@ -142,8 +169,8 @@ class Attributes implements IteratorAggregate, ArrayAccess
public function asInt(string $offset): null|int
{
$value = $this->asNumber($offset);
if (is_int($value)) {
return $value;
if (is_numeric($value)) {
return intval($value);
} else {
return null;
}
@ -216,7 +243,7 @@ class Attributes implements IteratorAggregate, ArrayAccess
}
/**
* @return array<string,bool|string|number|Stringable>
* @return array<string,string|number|Stringable|BooleanAttribute>
*/
public function getArray(): array
{

View file

@ -0,0 +1,16 @@
<?php
namespace ByJoby\HTML\Html5\Enums;
/**
* This special enum is used to specify that an attribute is an HTML5 boolean
* value. Any attribute set to BooleanAttribute::true will render as a lone tag,
* with no value like <tag attribute>. Any attribute set to
* BooleanAttribute::false will not render.
*/
enum BooleanAttribute {
/** Render an attribute with no value */
case true;
/** Do not render this attribute */
case false;
}

View file

@ -0,0 +1,53 @@
<?php
namespace ByJoby\HTML\Html5\Enums;
/**
* A string indicating which referrer to use when fetching the resource. These
* values are valid in <script> elements.
*
* Description by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
*/
enum ReferrerPolicy_script: string
{
/**
* (default): Send a full URL when performing a same-origin request, only
* send the origin when the protocol security level stays the same
* (HTTPS→HTTPS), and send no header to a less secure destination
* (HTTPS→HTTP).
*/
case strictOriginWhenCrossOrigin = "strict-origin-when-cross-origin";
/**
* means that the Referer header will not be sent.
*/
case noReferrer = "no-referrer";
/**
* The sent referrer will be limited to the origin of the referring page:
* its scheme, host, and port.
*/
case origin = "origin";
/**
* The referrer sent to other origins will be limited to the scheme, the
* host, and the port. Navigations on the same origin will still include the
* path.
*/
case originWhenCrossOrigin = "origin-when-cross-origin";
/**
* A referrer will be sent for same origin, but cross-origin requests will
* contain no referrer information.
*/
case sameOrigin = "same-origin";
/**
* Only send the origin of the document as the referrer when the protocol
* security level stays the same (HTTPS→HTTPS), but don't send it to a less
* secure destination (HTTPS→HTTP).
*/
case strictOrigin = "strict-origin";
/**
* The referrer will include the origin and the path (but not the fragment,
* password, or username). This value is unsafe, because it leaks origins
* and paths from TLS-protected resources to insecure origins.
*/
case unsafeUrl = "unsafe-url";
}

View file

@ -0,0 +1,38 @@
<?php
namespace ByJoby\HTML\Html5\Enums;
/**
* The type attribute of the <script> element indicates the type of script
* represented by the element: a classic script, a JavaScript module, an import
* map, or a data block.
*
* Descriptions by Mozilla Contributors licensed under CC-BY-SA 2.5
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type
*/
enum Type_script: string
{
/**
* Indicates that the script is a "classic script", containing JavaScript
* code. Authors are encouraged to omit the attribute if the script refers
* to JavaScript code rather than specify a MIME type. JavaScript MIME types
* are listed in the IANA media types specification.
*
* Equivalent to the attribute being unset.
*/
case default = "text/javascript";
/**
* This value causes the code to be treated as a JavaScript module. The
* processing of the script contents is deferred. The charset and defer
* attributes have no effect. For information on using module, see our
* JavaScript modules guide. Unlike classic scripts, module scripts require
* the use of the CORS protocol for cross-origin fetching.
*/
case module = "module";
/**
* This value indicates that the body of the element contains an import map.
* The import map is a JSON object that developers can use to control how
* the browser resolves module specifiers when importing JavaScript modules
*/
case importMap = "importmap";
}

View file

@ -26,9 +26,9 @@ class BaseTag extends AbstractTag implements MetadataContent
* Absolute and relative URLs are allowed. data: and javascript: URLs are
* not allowed.
*
* @return null|string
* @return null|string|Stringable
*/
public function href(): null|string
public function href(): null|string|Stringable
{
return $this->attributes()->asString('href');
}
@ -38,16 +38,13 @@ class BaseTag extends AbstractTag implements MetadataContent
* Absolute and relative URLs are allowed. data: and javascript: URLs are
* not allowed.
*
* @param null|string $href
* @param null|string|Stringable $href
* @return static
*/
public function setHref(null|string $href): static
public function setHref(null|string|Stringable $href): static
{
if (!$href) {
$this->attributes()['href'] = false;
} else {
$this->attributes()['href'] = $href;
}
if ($href) $this->attributes()['href'] = $href;
else $this->unsetHref();
return $this;
}
@ -88,7 +85,7 @@ class BaseTag extends AbstractTag implements MetadataContent
public function setTarget(null|string|Stringable|BrowsingContext $target): static
{
if (!$target) {
$this->attributes()['target'] = false;
$this->unsetTarget();
} elseif ($target instanceof BrowsingContext) {
$this->attributes()['target'] = $target->value;
} else {

View file

@ -47,17 +47,17 @@ class LinkTag extends AbstractTag implements MetadataContent
*
* if $as is As_link::fetch then $crossorigin must be specified
*
* @param null|Rel_link|StringableEnumArray<Rel_link>|array<int|string,Rel_link> $rel
* @param null|Rel_link|array<int|string,Rel_link> $rel
* @param null|As_link|null $as
* @param null|CrossOrigin|null $crossorigin
* @return static
*/
public function setRel(null|Rel_link|StringableEnumArray|array $rel, null|As_link $as = null, null|CrossOrigin $crossorigin = null): static
public function setRel(null|Rel_link|array $rel, null|As_link $as = null, null|CrossOrigin $crossorigin = null): static
{
if (!$rel) {
$this->attributes()['rel'] = false;
$this->unsetRel();
} else {
$this->attributes()->setEnumArray('rel',$rel,Rel_link::class,' ');
$this->attributes()->setEnumArray('rel', $rel, Rel_link::class, ' ');
// check if new value includes Rel_link::preload and require $as if so
$rel = $this->rel();
if (in_array(Rel_link::preload, $rel)) {
@ -116,7 +116,7 @@ class LinkTag extends AbstractTag implements MetadataContent
public function setAs(null|As_link $as, null|CrossOrigin $crossorigin = null): static
{
if (!$as) {
$this->attributes()['as'] = false;
$this->unsetAs();
} else {
$this->attributes()['as'] = $as->value;
// check if we just set as to As_link::fetch and require $crossorigin if so
@ -171,7 +171,7 @@ class LinkTag extends AbstractTag implements MetadataContent
public function setCrossorigin(null|CrossOrigin $crossorigin): static
{
if (!$crossorigin) {
$this->attributes()['crossorigin'] = false;
$this->unsetCrossorigin();
} else {
$this->attributes()['crossorigin'] = $crossorigin->value;
}
@ -211,11 +211,8 @@ class LinkTag extends AbstractTag implements MetadataContent
*/
public function setHref(null|string|Stringable $href): static
{
if (!$href) {
$this->attributes()['href'] = false;
} else {
$this->attributes()['href'] = $href;
}
if ($href) $this->attributes()['href'] = $href;
else $this->unsetHref();
return $this;
}
@ -258,11 +255,8 @@ class LinkTag extends AbstractTag implements MetadataContent
*/
public function setHreflang(null|string|Stringable $hreflang): static
{
if (!$hreflang) {
$this->attributes()['hreflang'] = false;
} else {
$this->attributes()['hreflang'] = $hreflang;
}
if ($hreflang) $this->attributes()['hreflang'] = $hreflang;
else $this->unsetHreflang();
return $this;
}
@ -304,11 +298,8 @@ class LinkTag extends AbstractTag implements MetadataContent
*/
public function setImagesizes(null|string|Stringable $imagesizes): static
{
if (!$imagesizes) {
$this->attributes()['imagesizes'] = false;
} else {
$this->attributes()['imagesizes'] = $imagesizes;
}
if ($imagesizes) $this->attributes()['imagesizes'] = $imagesizes;
else $this->unsetImagesizes();
return $this;
}
@ -350,11 +341,8 @@ class LinkTag extends AbstractTag implements MetadataContent
*/
public function setImagesrcset(null|string|Stringable $imagesrcset): static
{
if (!$imagesrcset) {
$this->attributes()['imagesrcset'] = false;
} else {
$this->attributes()['imagesrcset'] = $imagesrcset;
}
if ($imagesrcset) $this->attributes()['imagesrcset'] = $imagesrcset;
else $this->unsetImagesrcset();
return $this;
}
@ -396,11 +384,8 @@ class LinkTag extends AbstractTag implements MetadataContent
*/
public function setIntegrity(null|string|Stringable $integrity): static
{
if (!$integrity) {
$this->attributes()['integrity'] = false;
} else {
$this->attributes()['integrity'] = $integrity;
}
if ($integrity) $this->attributes()['integrity'] = $integrity;
else $this->unsetIntegrity();
return $this;
}
@ -442,11 +427,8 @@ class LinkTag extends AbstractTag implements MetadataContent
*/
public function setMedia(null|string|Stringable $media): static
{
if (!$media) {
$this->attributes()['media'] = false;
} else {
$this->attributes()['media'] = $media;
}
if ($media) $this->attributes()['media'] = $media;
else $this->unsetMedia();
return $this;
}
@ -482,11 +464,8 @@ class LinkTag extends AbstractTag implements MetadataContent
*/
public function setReferrerpolicy(null|ReferrerPolicy_link $referrerpolicy): static
{
if (!$referrerpolicy) {
$this->attributes()['referrerpolicy'] = false;
} else {
$this->attributes()['referrerpolicy'] = $referrerpolicy->value;
}
if ($referrerpolicy) $this->attributes()['referrerpolicy'] = $referrerpolicy->value;
else $this->unsetReferrerpolicy();
return $this;
}
@ -533,11 +512,8 @@ class LinkTag extends AbstractTag implements MetadataContent
*/
public function setType(null|string|Stringable $type): static
{
if (!$type) {
$this->attributes()['type'] = false;
} else {
$this->attributes()['type'] = $type;
}
if ($type) $this->attributes()['type'] = $type;
else $this->unsetType();
return $this;
}

View file

@ -8,10 +8,12 @@ use ByJoby\HTML\DisplayTypes\DisplayContents;
use ByJoby\HTML\Tags\AbstractContentTag;
/**
*
*
* The <noscript> HTML element defines a section of HTML to be inserted if a
* script type on the page is unsupported or if scripting is currently turned
* off in the browser.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/noscript
*/
class NoscriptTag extends AbstractContentTag implements MetadataContent, PhrasingContent, DisplayContents
{

View file

@ -5,174 +5,379 @@ namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\ContentCategories\MetadataContent;
use ByJoby\HTML\ContentCategories\PhrasingContent;
use ByJoby\HTML\DisplayTypes\DisplayNone;
use ByJoby\HTML\Html5\Enums\BooleanAttribute;
use ByJoby\HTML\Html5\Enums\CrossOrigin;
use ByJoby\HTML\Html5\Enums\ReferrerPolicy_script;
use ByJoby\HTML\Html5\Enums\Type_script;
use ByJoby\HTML\Tags\AbstractContentTag;
use Stringable;
/**
*
*
* The <script> HTML element is used to embed executable code or data; this is
* typically used to embed or refer to JavaScript code. The <script> element can
* also be used with other languages, such as WebGL's GLSL shader programming
* language and JSON.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
*/
class ScriptTag extends AbstractContentTag implements MetadataContent, PhrasingContent, DisplayNone
{
const TAG = 'script';
/**
* For classic scripts, if the async attribute is present, then the classic
* script will be fetched in parallel to parsing and evaluated as soon as it
* is available.
*
* For module scripts, if the async attribute is present then the scripts
* and all their dependencies will be executed in the defer queue, therefore
* they will get fetched in parallel to parsing and evaluated as soon as
* they are available.
*
* This attribute allows the elimination of parser-blocking JavaScript where
* the browser would have to load and evaluate scripts before continuing to
* parse. defer has a similar effect in this case.
*
* @param boolean $async
* @return static
*/
public function setAsync(bool $async): static
{
$this->attributes()['async'] = $async;
if ($async) $this->attributes()['async'] = BooleanAttribute::true;
else unset($this->attributes()['async']);
return $this;
}
/**
* For classic scripts, if the async attribute is present, then the classic
* script will be fetched in parallel to parsing and evaluated as soon as it
* is available.
*
* For module scripts, if the async attribute is present then the scripts
* and all their dependencies will be executed in the defer queue, therefore
* they will get fetched in parallel to parsing and evaluated as soon as
* they are available.
*
* This attribute allows the elimination of parser-blocking JavaScript where
* the browser would have to load and evaluate scripts before continuing to
* parse. defer has a similar effect in this case.
*
* @return boolean
*/
public function async(): bool
{
return !!$this->attributes()['async'];
return $this->attributes()['async'] === BooleanAttribute::true;
}
/**
* This Boolean attribute is set to indicate to a browser that the script is
* meant to be executed after the document has been parsed, but before
* firing DOMContentLoaded.
*
* Scripts with the defer attribute will prevent the DOMContentLoaded event
* from firing until the script has loaded and finished evaluating.
*
* @param boolean $defer
* @return static
*/
public function setDefer(bool $defer): static
{
$this->attributes()['defer'] = $defer;
if ($defer) $this->attributes()['defer'] = BooleanAttribute::true;
else unset($this->attributes()['defer']);
return $this;
}
/**
* This Boolean attribute is set to indicate to a browser that the script is
* meant to be executed after the document has been parsed, but before
* firing DOMContentLoaded.
*
* Scripts with the defer attribute will prevent the DOMContentLoaded event
* from firing until the script has loaded and finished evaluating.
*
* @return boolean
*/
public function defer(): bool
{
return !!$this->attributes()['defer'];
return $this->attributes()['defer'] === BooleanAttribute::true;
}
public function crossorigin(): null|string
/**
* Normal script elements pass minimal information to the window.onerror for
* scripts which do not pass the standard CORS checks. To allow error
* logging for sites which use a separate domain for static media, use this
* attribute. See CORS settings attributes for a more descriptive
* explanation of its valid arguments.
*
* @return null|CrossOrigin
*/
public function crossorigin(): null|CrossOrigin
{
return $this->attributes()->asString('crossorigin');
return $this->attributes()->asEnum('crossorigin', CrossOrigin::class);
}
public function setCrossorigin(null|string $crossorigin): static
/**
* Normal script elements pass minimal information to the window.onerror for
* scripts which do not pass the standard CORS checks. To allow error
* logging for sites which use a separate domain for static media, use this
* attribute. See CORS settings attributes for a more descriptive
* explanation of its valid arguments.
*
* @param null|CrossOrigin $crossorigin
* @return static
*/
public function setCrossorigin(null|CrossOrigin $crossorigin): static
{
if (!$crossorigin) {
$this->attributes()['crossorigin'] = false;
} else {
$this->attributes()['crossorigin'] = $crossorigin;
}
if ($crossorigin) $this->attributes()['crossorigin'] = $crossorigin->value;
else unset($this->attributes()['crossorigin']);
return $this;
}
/**
* Normal script elements pass minimal information to the window.onerror for
* scripts which do not pass the standard CORS checks. To allow error
* logging for sites which use a separate domain for static media, use this
* attribute. See CORS settings attributes for a more descriptive
* explanation of its valid arguments.
*
* @return static
*/
public function unsetCrossorigin(): static
{
unset($this->attributes()['crossorigin']);
return $this;
}
public function integrity(): null|string
/**
* This attribute contains inline metadata that a user agent can use to
* verify that a fetched resource has been delivered free of unexpected
* manipulation. See Subresource Integrity.
*
* https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
*
* @return null|string|Stringable
*/
public function integrity(): null|string|Stringable
{
return $this->attributes()->asString('integrity');
}
public function setIntegrity(null|string $integrity): static
/**
* This attribute contains inline metadata that a user agent can use to
* verify that a fetched resource has been delivered free of unexpected
* manipulation. See Subresource Integrity.
*
* https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
*
* @param null|string|Stringable $integrity
* @return static
*/
public function setIntegrity(null|string|Stringable $integrity): static
{
if (!$integrity) {
$this->attributes()['integrity'] = false;
} else {
$this->attributes()['integrity'] = $integrity;
}
if ($integrity) $this->attributes()['integrity'] = $integrity;
else $this->unsetIntegrity();
return $this;
}
/**
* This attribute contains inline metadata that a user agent can use to
* verify that a fetched resource has been delivered free of unexpected
* manipulation. See Subresource Integrity.
*
* https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
*
* @return static
*/
public function unsetIntegrity(): static
{
unset($this->attributes()['integrity']);
return $this;
}
/**
* This Boolean attribute is set to indicate that the script should not be
* executed in browsers that support ES modules in effect, this can be
* used to serve fallback scripts to older browsers that do not support
* modular JavaScript code.
*
* @param boolean $nomodule
* @return static
*/
public function setNomodule(bool $nomodule): static
{
$this->attributes()['nomodule'] = $nomodule;
if ($nomodule) $this->attributes()['nomodule'] = BooleanAttribute::true;
else unset($this->attributes()['nomodule']);
return $this;
}
/**
* This Boolean attribute is set to indicate that the script should not be
* executed in browsers that support ES modules in effect, this can be
* used to serve fallback scripts to older browsers that do not support
* modular JavaScript code.
*
* @return boolean
*/
public function nomodule(): bool
{
return !!$this->attributes()['nomodule'];
return $this->attributes()['nomodule'] === BooleanAttribute::true;
}
public function nonce(): null|string
/**
* A cryptographic nonce (number used once) to allow scripts in a script-src
* Content-Security-Policy. The server must generate a unique nonce value
* each time it transmits a policy. It is critical to provide a nonce that
* cannot be guessed as bypassing a resource's policy is otherwise trivial.
*
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
*
* @return null|string
*/
public function nonce(): null|string|Stringable
{
return $this->attributes()->asString('nonce');
}
public function setNonce(null|string $nonce): static
/**
* A cryptographic nonce (number used once) to allow scripts in a script-src
* Content-Security-Policy. The server must generate a unique nonce value
* each time it transmits a policy. It is critical to provide a nonce that
* cannot be guessed as bypassing a resource's policy is otherwise trivial.
*
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
*
* @param null|string|Stringable $nonce
* @return static
*/
public function setNonce(null|string|Stringable $nonce): static
{
if (!$nonce) {
$this->attributes()['nonce'] = false;
} else {
$this->attributes()['nonce'] = $nonce;
}
if ($nonce) $this->attributes()['nonce'] = $nonce;
else $this->unsetNonce();
return $this;
}
/**
* A cryptographic nonce (number used once) to allow scripts in a script-src
* Content-Security-Policy. The server must generate a unique nonce value
* each time it transmits a policy. It is critical to provide a nonce that
* cannot be guessed as bypassing a resource's policy is otherwise trivial.
*
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
*
* @return static
*/
public function unsetNonce(): static
{
unset($this->attributes()['nonce']);
return $this;
}
public function referrerpolicy(): null|string
/**
* Indicates which referrer to send when fetching the script, or resources
* fetched by the script.
*
* @return null|ReferrerPolicy_script
*/
public function referrerpolicy(): null|ReferrerPolicy_script
{
return $this->attributes()->asString('referrerpolicy');
return $this->attributes()->asEnum('referrerpolicy', ReferrerPolicy_script::class);
}
public function setReferrerpolicy(null|string $referrerpolicy): static
/**
* Indicates which referrer to send when fetching the script, or resources
* fetched by the script.
*
* @param null|ReferrerPolicy_script $referrerpolicy
* @return static
*/
public function setReferrerpolicy(null|ReferrerPolicy_script $referrerpolicy): static
{
if (!$referrerpolicy) {
$this->attributes()['referrerpolicy'] = false;
} else {
$this->attributes()['referrerpolicy'] = $referrerpolicy;
}
if ($referrerpolicy) $this->attributes()['referrerpolicy'] = $referrerpolicy->value;
else $this->unsetReferrerpolicy();
return $this;
}
/**
* Indicates which referrer to send when fetching the script, or resources
* fetched by the script.
*
* @return static
*/
public function unsetReferrerpolicy(): static
{
unset($this->attributes()['referrerpolicy']);
return $this;
}
public function src(): null|string
/**
* This attribute specifies the URI of an external script; this can be used
* as an alternative to embedding a script directly within a document.
*
* @return null|string|Stringable
*/
public function src(): null|string|Stringable
{
return $this->attributes()->asString('src');
}
public function setSrc(null|string $src): static
/**
* This attribute specifies the URI of an external script; this can be used
* as an alternative to embedding a script directly within a document.
*
* @param null|string|Stringable $src
* @return static
*/
public function setSrc(null|string|Stringable $src): static
{
if (!$src) {
$this->attributes()['src'] = false;
} else {
$this->attributes()['src'] = $src;
}
if ($src) $this->attributes()['src'] = $src;
else $this->unsetSrc();
return $this;
}
/**
* This attribute specifies the URI of an external script; this can be used
* as an alternative to embedding a script directly within a document.
*
* @return static
*/
public function unsetSrc(): static
{
unset($this->attributes()['src']);
return $this;
}
public function type(): null|string
/**
* This attribute indicates the type of script represented.
*
* @return null|Type_script
*/
public function type(): null|Type_script
{
return $this->attributes()->asString('type');
return $this->attributes()->asEnum('type', Type_script::class);
}
public function setType(null|string $type): static
/**
* This attribute indicates the type of script represented.
*
* @param null|Type_script $type
* @return static
*/
public function setType(null|Type_script $type): static
{
if (!$type) {
$this->attributes()['type'] = false;
} else {
$this->attributes()['type'] = $type;
}
if ($type) $this->attributes()['type'] = $type->value;
else $this->unsetType();
return $this;
}
/**
* This attribute indicates the type of script represented.
*
* @return static
*/
public function unsetType(): static
{
unset($this->attributes()['type']);
return $this;
}
}
}

View file

@ -4,56 +4,110 @@ namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\ContentCategories\MetadataContent;
use ByJoby\HTML\Tags\AbstractContentTag;
use Stringable;
/**
*
*
* The <style> HTML element contains style information for a document, or part
* of a document. It contains CSS, which is applied to the contents of the
* document containing the <style> element.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style
*/
class StyleTag extends AbstractContentTag implements MetadataContent
{
const TAG = 'style';
public function media(): null|string
/**
* This attribute defines which media the style should be applied to. Its
* value is a media query, which defaults to all if the attribute is
* missing.
*
* @return null|string|Stringable
*/
public function media(): null|string|Stringable
{
return $this->attributes()->asString('media');
}
public function setMedia(null|string $media): static
/**
* This attribute defines which media the style should be applied to. Its
* value is a media query, which defaults to all if the attribute is
* missing.
*
* @param null|string|Stringable $media
* @return static
*/
public function setMedia(null|string|Stringable $media): static
{
if (!$media) {
$this->attributes()['media'] = false;
} else {
$this->attributes()['media'] = $media;
}
if ($media) $this->attributes()['media'] = $media;
else $this->unsetMedia();
return $this;
}
/**
* This attribute defines which media the style should be applied to. Its
* value is a media query, which defaults to all if the attribute is
* missing.
*
* @return static
*/
public function unsetMedia(): static
{
unset($this->attributes()['media']);
return $this;
}
public function nonce(): null|string
/**
* A cryptographic nonce (number used once) used to allow inline styles in a
* style-src Content-Security-Policy. The server must generate a unique
* nonce value each time it transmits a policy. It is critical to provide a
* nonce that cannot be guessed as bypassing a resource's policy is
* otherwise trivial.
*
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src
*
* @return null|string|Stringable
*/
public function nonce(): null|string|Stringable
{
return $this->attributes()->asString('nonce');
}
public function setNonce(null|string $nonce): static
/**
* A cryptographic nonce (number used once) used to allow inline styles in a
* style-src Content-Security-Policy. The server must generate a unique
* nonce value each time it transmits a policy. It is critical to provide a
* nonce that cannot be guessed as bypassing a resource's policy is
* otherwise trivial.
*
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src
*
* @param null|string|Stringable $nonce
* @return static
*/
public function setNonce(null|string|Stringable $nonce): static
{
if (!$nonce) {
$this->attributes()['nonce'] = false;
} else {
$this->attributes()['nonce'] = $nonce;
}
if ($nonce) $this->attributes()['nonce'] = $nonce;
else $this->unsetNonce();
return $this;
}
/**
*
* A cryptographic nonce (number used once) used to allow inline styles in a
* style-src Content-Security-Policy. The server must generate a unique
* nonce value each time it transmits a policy. It is critical to provide a
* nonce that cannot be guessed as bypassing a resource's policy is
* otherwise trivial.
*
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src
*
* @return static
*/
public function unsetNonce(): static
{
unset($this->attributes()['nonce']);
return $this;
}
}
}

View file

@ -6,35 +6,59 @@ use ByJoby\HTML\ContentCategories\FlowContent;
use ByJoby\HTML\ContentCategories\SectioningRoot;
use ByJoby\HTML\DisplayTypes\DisplayBlock;
use ByJoby\HTML\Tags\AbstractContainerTag;
use Stringable;
/**
*
*
* The <blockquote> HTML element indicates that the enclosed text is an extended
* quotation. Usually, this is rendered visually by indentation (see Notes for
* how to change it). A URL for the source of the quotation may be given using
* the cite attribute, while a text representation of the source can be given
* using the <cite> element.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote
*/
class BlockquoteTag extends AbstractContainerTag implements FlowContent, SectioningRoot, DisplayBlock
{
const TAG = 'blockquote';
public function cite(): null|string
/**
* A URL that designates a source document or message for the information
* quoted. This attribute is intended to point to information explaining the
* context or the reference for the quote.
*
* @return null|string|Stringable
*/
public function cite(): null|string|Stringable
{
return $this->attributes()->asString('cite');
}
public function setCite(null|string $cite): static
/**
* A URL that designates a source document or message for the information
* quoted. This attribute is intended to point to information explaining the
* context or the reference for the quote.
*
* @param null|string|Stringable $cite
* @return static
*/
public function setCite(null|string|Stringable $cite): static
{
if (!$cite) {
$this->attributes()['cite'] = false;
} else {
$this->attributes()['cite'] = $cite;
}
if ($cite) $this->attributes()['cite'] = $cite;
else $this->unsetCite();
return $this;
}
/**
* A URL that designates a source document or message for the information
* quoted. This attribute is intended to point to information explaining the
* context or the reference for the quote.
*
* @return static
*/
public function unsetCite(): static
{
unset($this->attributes()['cite']);
return $this;
}
}
}

View file

@ -5,10 +5,11 @@ namespace ByJoby\HTML\Html5\TextContentTags;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
*
*
* The <dd> HTML element provides the description, definition, or value for the
* preceding term (<dt>) in a description list (<dl>).
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd
*/
class DdTag extends AbstractContainerTag
{

View file

@ -7,10 +7,13 @@ use ByJoby\HTML\DisplayTypes\DisplayBlock;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
*
*
* The <div> HTML element is the generic container for flow content. It has no
* effect on the content or layout until styled in some way using CSS (e.g.
* styling is directly applied to it, or some kind of layout model like Flexbox
* is applied to its parent element).
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
*/
class DivTag extends AbstractContainerTag implements DisplayBlock, SectioningContent
{

View file

@ -7,10 +7,13 @@ use ByJoby\HTML\DisplayTypes\DisplayBlock;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
*
*
* The <dl> HTML element represents a description list. The element encloses a
* list of groups of terms (specified using the <dt> element) and descriptions
* (provided by <dd> elements). Common uses for this element are to implement a
* glossary or to display metadata (a list of key-value pairs).
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl
*/
class DlTag extends AbstractContainerTag implements FlowContent, DisplayBlock
{

View file

@ -5,10 +5,16 @@ namespace ByJoby\HTML\Html5\TextContentTags;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
*
*
* The <dt> HTML element specifies a term in a description or definition list,
* and as such must be used inside a <dl> element. It is usually followed by a
* <dd> element; however, multiple <dt> elements in a row indicate several terms
* that are all defined by the immediate next <dd> element.
*
* The subsequent <dd> (Description Details) element provides the definition or
* other related text associated with the term specified using <dt>.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt
*/
class DtTag extends AbstractContainerTag
{

View file

@ -7,10 +7,11 @@ use ByJoby\HTML\DisplayTypes\DisplayBlock;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
*
*
* The <figcaption> HTML element represents a caption or legend describing the
* rest of the contents of its parent <figure> element.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption
*/
class FigcaptionTag extends AbstractContainerTag implements FlowContent, DisplayBlock
{

View file

@ -10,10 +10,12 @@ use ByJoby\HTML\Tags\AbstractGroupedTag;
use ByJoby\HTML\Tags\TagInterface;
/**
*
*
* The <figure> HTML element represents self-contained content, potentially with
* an optional caption, which is specified using the <figcaption> element. The
* figure, its caption, and its contents are referenced as a single unit.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure
*/
class FigureTag extends AbstractGroupedTag implements FlowContent, DisplayBlock
{
@ -34,7 +36,13 @@ class FigureTag extends AbstractGroupedTag implements FlowContent, DisplayBlock
$this->addGroup(ContainerGroup::ofTag('figcaption', 1));
}
public function reverseCaptionOrder(): static
/**
* Flip the caption and content order from its current state. The default
* state is to have the content first, then the caption.
*
* @return static
*/
public function flipCaptionOrder(): static
{
$this->children = array_reverse($this->children);
return $this;

View file

@ -7,10 +7,12 @@ use ByJoby\HTML\DisplayTypes\DisplayBlock;
use ByJoby\HTML\Tags\AbstractTag;
/**
*
*
* The <hr> HTML element represents a thematic break between paragraph-level
* elements: for example, a change of scene in a story, or a shift of topic
* within a section.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr
*/
class HrTag extends AbstractTag implements FlowContent, DisplayBlock
{

View file

@ -8,10 +8,14 @@ use ByJoby\HTML\Html5\Enums\Type_list;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
*
*
* The <li> HTML element is used to represent an item in a list. It must be
* contained in a parent element: an ordered list (<ol>), an unordered list
* (<ul>), or a menu (<menu>). In menus and unordered lists, list items are
* usually displayed using bullet points. In ordered lists, they are usually
* displayed with an ascending counter on the left, such as a number or letter.
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li
*/
class LiTag extends AbstractContainerTag implements FlowContent, DisplayBlock
{
@ -35,11 +39,8 @@ class LiTag extends AbstractContainerTag implements FlowContent, DisplayBlock
*/
public function setType(null|Type_list $type): static
{
if (!$type) {
$this->attributes()['type'] = false;
} else {
$this->attributes()['type'] = $type->value;
}
if ($type) $this->attributes()['type'] = $type->value;
else $this->unsetType();
return $this;
}

View file

@ -7,10 +7,13 @@ use ByJoby\HTML\DisplayTypes\DisplayBlock;
use ByJoby\HTML\Tags\AbstractContainerTag;
/**
*
*
* The <menu> HTML element is described in the HTML specification as a semantic
* alternative to <ul>, but treated by browsers (and exposed through the
* accessibility tree) as no different than <ul>. It represents an unordered
* list of items (which are represented by <li> elements).
*
* Tag description by Mozilla Contributors licensed under CC-BY-SA 2.5
*
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu
*/
class MenuTag extends AbstractContainerTag implements FlowContent, DisplayBlock
{

View file

@ -4,6 +4,7 @@ namespace ByJoby\HTML\Html5\TextContentTags;
use ByJoby\HTML\ContentCategories\FlowContent;
use ByJoby\HTML\DisplayTypes\DisplayBlock;
use ByJoby\HTML\Html5\Enums\BooleanAttribute;
use ByJoby\HTML\Html5\Enums\Type_list;
use ByJoby\HTML\Tags\AbstractContainerTag;
@ -27,7 +28,8 @@ class OlTag extends AbstractContainerTag implements FlowContent, DisplayBlock
*/
public function setReversed(bool $reversed): static
{
$this->attributes()['reversed'] = $reversed;
if ($reversed) $this->attributes()['reversed'] = BooleanAttribute::true;
else unset($this->attributes()['reversed']);
return $this;
}
@ -39,7 +41,7 @@ class OlTag extends AbstractContainerTag implements FlowContent, DisplayBlock
*/
public function reversed(): bool
{
return !!$this->attributes()['reversed'];
return $this->attributes()['reversed'] === BooleanAttribute::true;
}
/**
@ -52,11 +54,7 @@ class OlTag extends AbstractContainerTag implements FlowContent, DisplayBlock
*/
public function start(): null|int
{
if ($this->attributes()['start']) {
return intval($this->attributes()->asString('start'));
} else {
return null;
}
return $this->attributes()->asInt('start');
}
/**
@ -70,11 +68,8 @@ class OlTag extends AbstractContainerTag implements FlowContent, DisplayBlock
*/
public function setStart(null|int $start): static
{
if (!$start) {
$this->attributes()['start'] = false;
} else {
$this->attributes()['start'] = strval($start);
}
if (is_null($start)) $this->unsetStart();
else $this->attributes()['start'] = $start;
return $this;
}
@ -116,11 +111,8 @@ class OlTag extends AbstractContainerTag implements FlowContent, DisplayBlock
*/
public function setType(null|Type_list $type): static
{
if (!$type) {
$this->attributes()['type'] = false;
} else {
$this->attributes()['type'] = $type->value;
}
if ($type) $this->attributes()['type'] = $type->value;
else $this->unsetType();
return $this;
}

View file

@ -5,6 +5,7 @@ namespace ByJoby\HTML\Traits;
use ByJoby\HTML\Helpers\Attributes;
use ByJoby\HTML\Helpers\Classes;
use ByJoby\HTML\Helpers\Styles;
use ByJoby\HTML\Html5\Enums\BooleanAttribute;
use Exception;
use Stringable;
@ -88,10 +89,14 @@ trait TagTrait
$strings[] = sprintf('style="%s"', $this->styles());
}
foreach ($this->attributes() as $name => $value) {
if (is_string($value)) {
$strings[] = sprintf('%s="%s"', $name, static::sanitizeAttribute($value));
} elseif ($value) {
if ($value == BooleanAttribute::false) {
// skip over false boolean attributes
continue;
}elseif ($value == BooleanAttribute::true) {
// true boolean attributes render as null
$strings[] = $name;
}elseif (is_string($value) || is_numeric($value) || $value instanceof Stringable) {
$strings[] = sprintf('%s="%s"', $name, static::sanitizeAttribute(strval($value)));
}
}
return $strings;

View file

@ -9,7 +9,14 @@ class FragmentTest extends TestCase
{
public function tag(string $name): AbstractContainerTag
{
$tag = $this->getMockForAbstractClass(AbstractContainerTag::class, [], '', true, true, true, ['tag']);
$tag = $this->getMockForAbstractClass(
AbstractContainerTag::class,
[], 'Mock_Tag_' . $name,
true,
true,
true,
['tag']
);
$tag->method('tag')->willReturn($name);
return $tag;
}
@ -62,4 +69,4 @@ class FragmentTest extends TestCase
$div2->addChildAfter('c', 'a');
$this->assertEquals($fragment, $div2->children()[2]->parentDocument());
}
}
}

View file

@ -4,12 +4,6 @@ namespace ByJoby\HTML\Helpers;
use PHPUnit\Framework\TestCase;
// TODO test setEnumArray
// TODO test asEnumArray
// TODO test asNumber
// TODO test asInt
// TODO test asFloat
class AttributesTest extends TestCase
{
public function testConstruction(): Attributes
@ -79,4 +73,165 @@ class AttributesTest extends TestCase
$this->expectExceptionMessage('Invalid character in attribute name');
$attributes['>'] = 'b';
}
public function testAsString(): void
{
$a = new Attributes();
$this->assertNull($a->asString('non-existent'));
$a['empty-string'] = '';
$this->assertEquals('', $a->asString('empty-string'));
$a['string'] = 's';
$this->assertEquals('s', $a->asString('string'));
$a['int'] = 1;
$this->assertEquals('1', $a->asString('int'));
$a['int_string'] = '1';
$this->assertEquals('1', $a->asString('int_string'));
$a['float'] = 1.5;
$this->assertEquals('1.5', $a->asString('float'));
$a['float_string'] = '1.5';
$this->assertEquals('1.5', $a->asString('float_string'));
$a['zero'] = 0;
$this->assertEquals('0', $a->asString('zero'));
$a['zero_string'] = '0';
$this->assertEquals('0', $a->asString('zero_string'));
}
public function testAsNumber(): void
{
$a = new Attributes();
$this->assertNull($a->asNumber('non-existent'));
$a['empty-string'] = '';
$this->assertNull($a->asNumber('empty-string'));
$a['string'] = 's';
$this->assertNull($a->asNumber('string'));
$a['int'] = 1;
$this->assertEquals(1, $a->asNumber('int'));
$a['int_string'] = '1';
$this->assertEquals(1, $a->asNumber('int_string'));
$a['float'] = 1.5;
$this->assertEquals(1.5, $a->asNumber('float'));
$a['float_string'] = '1.5';
$this->assertEquals(1.5, $a->asNumber('float_string'));
$a['zero'] = 0;
$this->assertEquals(0, $a->asNumber('zero'));
$a['zero_string'] = '0';
$this->assertEquals(0, $a->asNumber('zero_string'));
}
public function testAsFloat(): void
{
$a = new Attributes();
$this->assertNull($a->asFloat('non-existent'));
$a['empty-string'] = '';
$this->assertNull($a->asFloat('empty-string'));
$a['string'] = 's';
$this->assertNull($a->asFloat('string'));
$a['int'] = 1;
$this->assertEquals(1, $a->asFloat('int'));
$a['int_string'] = '1';
$this->assertEquals(1, $a->asFloat('int_string'));
$a['float'] = 1.5;
$this->assertEquals(1.5, $a->asFloat('float'));
$a['float_string'] = '1.5';
$this->assertEquals(1.5, $a->asFloat('float_string'));
$a['zero'] = 0;
$this->assertEquals(0, $a->asFloat('zero'));
$a['zero_string'] = '0';
$this->assertEquals(0, $a->asFloat('zero_string'));
}
public function testAsInt(): void
{
$a = new Attributes();
$this->assertNull($a->asInt('non-existent'));
$a['empty-string'] = '';
$this->assertNull($a->asInt('empty-string'));
$a['string'] = 's';
$this->assertNull($a->asInt('string'));
$a['int'] = 1;
$this->assertEquals(1, $a->asInt('int'));
$a['int_string'] = '1';
$this->assertEquals(1, $a->asInt('int_string'));
$a['float'] = 1.5;
$this->assertEquals(1, $a->asInt('float'));
$a['float_string'] = '1.5';
$this->assertEquals(1., $a->asInt('float_string'));
$a['zero'] = 0;
$this->assertEquals(0, $a->asInt('zero'));
$a['zero_string'] = '0';
$this->assertEquals(0, $a->asInt('zero_string'));
}
public function testAsEnum(): void
{
$a = new Attributes();
$a['string-a'] = TestStringEnum::a->value;
$this->assertEquals(TestStringEnum::a, $a->asEnum('string-a', TestStringEnum::class));
$a['string-b'] = TestStringEnum::b->value;
$this->assertEquals(TestStringEnum::b, $a->asEnum('string-b', TestStringEnum::class));
$a['int-one'] = TestIntEnum::one->value;
$this->assertEquals(TestIntEnum::one, $a->asEnum('int-one', TestIntEnum::class));
$a['int-two'] = TestIntEnum::two->value;
$this->assertEquals(TestIntEnum::two, $a->asEnum('int-two', TestIntEnum::class));
}
public function testAsEnumInvalidCases(): void
{
$a = new Attributes();
$a['string-c'] = 'c';
$this->assertNull($a->asEnum('string-a', TestStringEnum::class));
$this->assertNull($a->asEnum('string-a', TestIntEnum::class));
$a['int-three'] = 'three';
$this->assertNull($a->asEnum('int-two', TestIntEnum::class));
$this->assertNull($a->asEnum('int-two', TestStringEnum::class));
}
public function testEnumArraySingleValue(): void
{
$a = new Attributes();
$a->setEnumArray('v', TestStringEnum::a, TestStringEnum::class, ' ');
$this->assertEquals('a', $a['v']);
$this->assertEquals(
[TestStringEnum::a],
$a->asEnumArray('v', TestStringEnum::class, ' ')
);
// should return an empty array for a different enum class
$this->assertEquals(
[],
$a->asEnumArray('v', TestIntEnum::class, ' ')
);
}
public function testEnumArrayValue(): void
{
$a = new Attributes();
$a->setEnumArray('v', [TestStringEnum::a, TestStringEnum::b], TestStringEnum::class, ' ');
$this->assertEquals('a b', $a['v']);
$this->assertEquals(
[TestStringEnum::a, TestStringEnum::b],
$a->asEnumArray('v', TestStringEnum::class, ' ')
);
// should return an empty array for a different enum class
$this->assertEquals(
[],
$a->asEnumArray('v', TestIntEnum::class, ' ')
);
// should return an empty array for a different separator
$this->assertEquals(
[],
$a->asEnumArray('v', TestStringEnum::class, ', ')
);
}
}
enum TestStringEnum: string
{
case a = 'a';
case b = 'b';
}
enum TestIntEnum: int
{
case one = 1;
case two = 2;
}

View file

@ -2,17 +2,17 @@
namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\Html5\Enums\As_link;
use ByJoby\HTML\Html5\Enums\CrossOrigin;
use ByJoby\HTML\Html5\Enums\ReferrerPolicy_link;
use ByJoby\HTML\Html5\Enums\Rel_link;
use ByJoby\HTML\Html5\Exceptions\InvalidArgumentsException;
class LinkTagTest extends TagTestCase
{
public function testAttributeHelpers(): void
{
// TODO update tests that now use enums, add tests for their exeption cases
// $this->assertAttributeHelperMethods('rel', LinkTag::class, Rel_link::class, 'class');
// $this->assertAttributeHelperMethods('as', LinkTag::class);
// $this->assertAttributeHelperMethods('referrerpolicy', LinkTag::class);
$this->assertAttributeHelperMethods('referrerpolicy', LinkTag::class, ReferrerPolicy_link::origin, 'origin');
$this->assertAttributeHelperMethods('crossorigin', LinkTag::class, CrossOrigin::anonymous, 'anonymous');
$this->assertAttributeHelperMethods('crossorigin', LinkTag::class, CrossOrigin::useCredentials, 'use-credentials');
$this->assertAttributeHelperMethods('href', LinkTag::class);
@ -23,4 +23,46 @@ class LinkTagTest extends TagTestCase
$this->assertAttributeHelperMethods('media', LinkTag::class);
$this->assertAttributeHelperMethods('type', LinkTag::class);
}
public function testAsBasic(): void
{
$tag = new LinkTag();
// set as to something that can be set alone
$tag->setAs(As_link::document);
$this->assertEquals('document', $tag->attributes()->asString('as'));
// set as to fetch so crossorigin is required
$tag->setAs(As_link::fetch, CrossOrigin::anonymous);
$this->assertEquals('fetch', $tag->attributes()->asString('as'));
$this->assertEquals('anonymous', $tag->attributes()->asString('crossorigin'));
}
public function testRelBasic(): void
{
$tag = new LinkTag();
// set rel as a single attribute
$tag->setRel(Rel_link::prefetch);
$this->assertEquals('prefetch', $tag->attributes()->asString('rel'));
// set rel as an array of attributes
$tag->setRel([Rel_link::prefetch, Rel_link::next]);
$this->assertEquals('prefetch next', $tag->attributes()->asString('rel'));
// set deeper values
$tag->setRel(Rel_link::preload, As_link::fetch, CrossOrigin::anonymous);
$this->assertEquals('preload', $tag->attributes()->asString('rel'));
$this->assertEquals('fetch', $tag->attributes()->asString('as'));
$this->assertEquals('anonymous', $tag->attributes()->asString('crossorigin'));
}
public function testRelMIssingAs(): void
{
$this->expectException(InvalidArgumentsException::class);
$tag = new LinkTag();
$tag->setRel(Rel_link::preload);
}
public function testRelMIssingCrossorigin(): void
{
$this->expectException(InvalidArgumentsException::class);
$tag = new LinkTag();
$tag->setRel(Rel_link::preload, As_link::fetch);
}
}

View file

@ -2,14 +2,64 @@
namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\Html5\Enums\HttpEquiv_meta;
use ByJoby\HTML\Html5\Enums\Name_meta;
class MetaTagTest extends TagTestCase
{
public function testAttributeHelpers(): void
public function testCharset(): void
{
// TODO test all of these with their new format
// $this->assertAttributeHelperMethods('name', MetaTag::class);
// $this->assertAttributeHelperMethods('content', MetaTag::class);
// $this->assertAttributeHelperMethods('http-equiv', MetaTag::class);
// $this->assertAttributeHelperMethods('charset', MetaTag::class);
$tag = new MetaTag();
$tag->setCharset(true);
$this->assertEquals("utf-8", $tag->attributes()['charset']);
$tag->setCharset(false);
$this->assertNull($tag->attributes()['charset']);
}
}
public function testName(): void
{
$tag = new MetaTag();
$tag->setNameAndContent(Name_meta::application, 'test');
$this->assertEquals(Name_meta::application->value, $tag->attributes()['name']);
$this->assertEquals('test', $tag->attributes()['content']);
}
public function testHttpEquiv(): void
{
$tag = new MetaTag();
$tag->setHttpEquivAndContent(HttpEquiv_meta::mime, 'text/plain');
$this->assertEquals(HttpEquiv_meta::mime->value, $tag->attributes()['http-equiv']);
$this->assertEquals('text/plain', $tag->attributes()['content']);
}
public function testNameOverrides(): void
{
$tag = new MetaTag();
$tag->setCharset(true);
$tag->setHttpEquivAndContent(HttpEquiv_meta::mime, 'text/plain');
$tag->setNameAndContent(Name_meta::application, 'test');
$this->assertNull($tag->attributes()['charset']);
$this->assertNull($tag->attributes()['http-equiv']);
}
public function testHttpEquivOverrides(): void
{
$tag = new MetaTag();
$tag->setCharset(true);
$tag->setNameAndContent(Name_meta::application, 'test');
$tag->setHttpEquivAndContent(HttpEquiv_meta::mime, 'text/plain');
$this->assertNull($tag->attributes()['charset']);
$this->assertNull($tag->attributes()['name']);
}
public function testCharsetOverrides(): void
{
$tag = new MetaTag();
$tag->setHttpEquivAndContent(HttpEquiv_meta::mime, 'text/plain');
$tag->setNameAndContent(Name_meta::application, 'test');
$tag->setCharset(true);
$this->assertNull($tag->attributes()['name']);
$this->assertNull($tag->attributes()['http-equiv']);
$this->assertNull($tag->attributes()['content']);
}
}

View file

@ -2,19 +2,23 @@
namespace ByJoby\HTML\Html5\Tags;
use ByJoby\HTML\Html5\Enums\CrossOrigin;
use ByJoby\HTML\Html5\Enums\ReferrerPolicy_script;
use ByJoby\HTML\Html5\Enums\Type_script;
class ScriptTagTest extends TagTestCase
{
public function testAttributeHelpers(): void
{
$this->assertAttributeHelperMethods('crossorigin', ScriptTag::class);
$this->assertAttributeHelperMethods('crossorigin', ScriptTag::class, CrossOrigin::useCredentials, CrossOrigin::useCredentials->value);
$this->assertAttributeHelperMethods('integrity', ScriptTag::class);
$this->assertAttributeHelperMethods('integrity', ScriptTag::class);
$this->assertAttributeHelperMethods('nonce', ScriptTag::class);
$this->assertAttributeHelperMethods('referrerpolicy', ScriptTag::class);
$this->assertAttributeHelperMethods('referrerpolicy', ScriptTag::class, ReferrerPolicy_script::noReferrer, ReferrerPolicy_script::noReferrer->value);
$this->assertAttributeHelperMethods('src', ScriptTag::class);
$this->assertAttributeHelperMethods('type', ScriptTag::class);
$this->assertAttributeHelperMethods('type', ScriptTag::class, Type_script::module, Type_script::module->value);
$this->assertBooleanAttributeHelperMethods('async', ScriptTag::class);
$this->assertBooleanAttributeHelperMethods('defer', ScriptTag::class);
$this->assertBooleanAttributeHelperMethods('nomodule', ScriptTag::class);
}
}
}

View file

@ -8,14 +8,14 @@ use ByJoby\HTML\Nodes\TextInterface;
class FigureTagTest extends BaseTagTest
{
public function testCaptionOrder()
public function testCaptionOrder(): void
{
$figure = new FigureTag();
$figure->addChild(new FigcaptionTag);
$figure->addChild('Some content');
$this->assertInstanceOf(TextInterface::class, $figure->children()[0]);
$this->assertInstanceOf(FigcaptionTag::class, $figure->children()[1]);
$figure->reverseCaptionOrder();
$figure->flipCaptionOrder();
$this->assertInstanceOf(TextInterface::class, $figure->children()[1]);
$this->assertInstanceOf(FigcaptionTag::class, $figure->children()[0]);
}

View file

@ -11,7 +11,7 @@ class OlTagTest extends BaseTagTest
{
$this->assertBooleanAttributeHelperMethods('reversed', OlTag::class);
$this->assertAttributeHelperMethods('start', OlTag::class, 1, '1');
$this->assertAttributeHelperMethods('start', OlTag::class, 0, null);
$this->assertAttributeHelperMethods('start', OlTag::class, 0, '0');
$this->assertAttributeHelperMethods('type', OlTag::class, Type_list::letterLower, 'a');
$this->assertAttributeHelperMethods('type', OlTag::class, Type_list::letterUpper, 'A');
$this->assertAttributeHelperMethods('type', OlTag::class, Type_list::romanLower, 'i');

View file

@ -2,6 +2,7 @@
namespace ByJoby\HTML\Tags;
use ByJoby\HTML\Html5\Enums\BooleanAttribute;
use ByJoby\HTML\Nodes\Text;
use ByJoby\HTML\Nodes\UnsanitizedText;
use PHPUnit\Framework\TestCase;
@ -47,10 +48,10 @@ class AbstractContainerTagTest extends TestCase
public function testBooleanAttributes(): void
{
$tag = $this->tag('div');
$tag->attributes()['a'] = true;
$tag->attributes()['b'] = false;
$tag->attributes()['c'] = null;
$this->assertEquals('<div a></div>', $tag->__toString());
$tag->attributes()['a'] = BooleanAttribute::true;
$tag->attributes()['b'] = BooleanAttribute::false;
$tag->attributes()['c'] = "";
$this->assertEquals('<div a c=""></div>', $tag->__toString());
}
/** @depends clone testDIV */

View file

@ -4,6 +4,7 @@ namespace ByJoby\HTML\Tags;
use ByJoby\HTML\Helpers\Attributes;
use ByJoby\HTML\Helpers\Classes;
use ByJoby\HTML\Html5\Enums\BooleanAttribute;
use PHPUnit\Framework\TestCase;
class AbstractTagTest extends TestCase
@ -67,10 +68,10 @@ class AbstractTagTest extends TestCase
public function testBooleanAttributes(): void
{
$tag = $this->tag('br');
$tag->attributes()['a'] = true;
$tag->attributes()['b'] = false;
$tag->attributes()['c'] = null;
$this->assertEquals('<br a>', $tag->__toString());
$tag->attributes()['a'] = BooleanAttribute::true;
$tag->attributes()['b'] = BooleanAttribute::false;
$tag->attributes()['c'] = "";
$this->assertEquals('<br a c="">', $tag->__toString());
}
/**
@ -99,4 +100,4 @@ class AbstractTagTest extends TestCase
$this->expectExceptionMessage('Setting attribute is disallowed');
$tag->attributes()['style'] = 'foo';
}
}
}