Skip to content

Instantly share code, notes, and snippets.

@WinterSilence
Last active January 14, 2022 15:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WinterSilence/fe4022567888ea29f5ba65cee7a8b9a0 to your computer and use it in GitHub Desktop.
Save WinterSilence/fe4022567888ea29f5ba65cee7a8b9a0 to your computer and use it in GitHub Desktop.
Extended DOMElement for generate HTML tags
<?php
/**
* HTML tag.
*/
class Tag extends DOMElement implements TagInterface
{
public function __construct(string $name, string $value = '')
{
parent::__construct($name, $value);
}
/**
* @param iterable $attributes the attributes, attribute name/value pairs.
*/
public function setAttributes(iterable $attributes): self
{
if ($this->hasAttributes()) {
$this->resetAttributes();
}
foreach ($attributes as $name => $value) {
$this->setAttribute($name, $value);
}
return $this;
}
/**
*
*/
public function setAttribute($name, $value): DOMAttr
{
// @todo support passing arrays in $value to set `data-*` and `aria-*` attributes
return parent::setAttribute($name, $this->normalizeAttribute($name, $value));
}
/**
* Returns encoded value.
*
* @param mixed $value the value to encode
*/
protected function encodeValue($value): string
{
return (string) json_encode(
$value,
JSON_FORCE_OBJECT
| JSON_NUMERIC_CHECK
| JSON_PARTIAL_OUTPUT_ON_ERROR
| JSON_PRESERVE_ZERO_FRACTION
| JSON_UNESCAPED_SLASHES
| JSON_UNESCAPED_UNICODE
| JSON_THROW_ON_ERROR
);
}
/**
* Returns normalized value of attribute.
*
* @param string $name the attribute name
* @param mixed $value the attribute value
*/
protected function normalizeAttribute(string $name, $value): string
{
if (is_object($value)) {
if (method_exists($value, '__toString')) {
$value = (string) $value;
} else {
$value = (array) $value;
}
}
if (is_array($value)) {
if (empty($value)) {
$value = '';
} elseif ($name === 'style') {
if (!isset($value[0])) {
foreach ($value as $key => $val) {
$value[$key] = $key . ': ' . str_replace('"', "'", $this->encodeValue($val));
}
}
$value = implode('; ', $value);
} else {
$value = implode(' ', array_filter($value));
}
}
return is_string($value) ? $value : $this->encodeValue($value);
}
/**
* Removes all attributes of tag.
*/
public function resetAttributes(): self
{
foreach ($this->attributes as $attribute) {
$this->removeAttributeNode($attribute);
}
return $this;
}
/**
* Sets inner content(text or HTML) of tag.
*/
public function setContent(...$values): self
{
$this->removeContent();
foreach ($values as $value) {
if (is_callable($value)) {
$value = call_user_func($value, $this, $this->ownerDocument);
}
if ($value instanceof DOMElement) {
$this->appendChild($value);
} elseif ($value instanceof SimpleXMLElement) {
$this->appendChild(dom_import_simplexml($value));
} else {
$value = (string) $value;
// Dirty, but fast solution to detect HTML tags
if (strpos($value, '<') !== false && strpos($value, '>') !== false) {
// HTML:
$fragment = $this->ownerDocument->createDocumentFragment();
$fragment->appendXML($value);
$this->appendChild($fragment);
} else {
// Text:
$this->appendChild($this->ownerDocument->createTextNode($value));
}
}
}
return $this;
}
public function removeContent(): self
{
foreach ($this->childNodes as $child) {
$this->removeChild($child);
}
return $this;
}
public function __toString(): string
{
return (string) $this->ownerDocument->saveHTML($this);
}
}
<?php
/**
* HTML tag factory.
*/
class TagFactory
{
/**
* @var DOMDocument the HTML document
*/
protected $document;
/**
* @param bool $html5 if `TRUE`, then generate HTML 5 document, else, HTML 4 document
*/
public function __construct(bool $html5 = true)
{
$dom = new DOMImplementation();
if ($html5) {
$docType = $dom->createDocumentType('html');
} else {
$docType = $dom->createDocumentType(
'HTML',
'-//W3C//DTD HTML 4.01 Transitional//EN',
'http://www.w3.org/TR/html4/loose.dtd'
);
}
$this->document = $dom->createDocument('', 'html', $docType);
$this->document->registerNodeClass(DOMElement::class, Tag::class);
// @todo
// $this->document->registerNodeClass(DOMAttr::class, TagAttribute::class);
}
/**
* Creates new `TagInterface` instance.
*
* @param string $name the tag name
* @param iterable $attributes the tag attributes
* @param string|DOMElement|SimpleXMLElement|TagInterface|callable|null $content the inner text/HTML, callback syntax:
* `function (TagInterface $tag, DOMDocument $document): TagInterface|string`
* @return TagInterface
*/
public function createTag(string $name, iterable $attributes = [], $content = null): TagInterface
{
// @todo remove tag from document instead cloning
$document = $this->document->cloneNode();
$tag = $document->documentElement->appendChild($document->createElement($name));
$tag->setAttributes($attributes);
if ($content !== null) {
$tag->setContent($content);
}
return $tag;
}
}
<?php
/**
* Interface of HTML tags.
*/
interface TagInterface
{
public function setAttributes(iterable $attributes);
/**
* @sstring|DOMElement|SimpleXMLElement|TagInterface|callable|null
*/
public function setContent($content);
public function __toString(): string;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment