Basic HTML constructs

This commit is contained in:
Aidan Woods 2018-11-03 18:24:36 +00:00
parent 5ab8839d04
commit 04581d0915
No known key found for this signature in database
GPG Key ID: 9A6A8EFAA512BBB9
5 changed files with 196 additions and 0 deletions

8
src/Html/Component.php Normal file
View File

@ -0,0 +1,8 @@
<?php declare(strict_types=1);
namespace Erusev\Parsedown\Html;
interface Component
{
public function getHtml(): string;
}

View File

@ -0,0 +1,102 @@
<?php declare(strict_types=1);
namespace Erusev\Parsedown\Html\Components;
use Erusev\Parsedown\Html\Component;
use Erusev\Parsedown\Html\Sanitisation\CharacterFilter;
use Erusev\Parsedown\Html\Sanitisation\Escaper;
final class Element implements Component
{
/** @var string */
private $name;
/** @var array<string, string>*/
private $attributes;
/** @var Component[]|null */
private $Contents;
/**
* @param string $name
* @param array<string, string> $attributes
* @param Component[]|null $Contents
*/
public function __construct(
string $name,
array $attributes,
?array $Components
) {
$this->name = $name;
$this->attributes = $attributes;
$this->Components = $Components;
}
/**
* @param string $name
* @param array<string, string> $attributes
*/
public static function selfClosing(string $name, array $attributes): self
{
return new self($name, $attributes, null);
}
public function name(): string
{
return $this->name;
}
/**
* @return array<string, string>
*/
public function attributes(): array
{
return $this->attributes;
}
/**
* @return Component[]|null
*/
public function contents(): ?array
{
return $this->Contents;
}
public function getHtml(): string
{
$html = '';
$elementName = CharacterFilter::htmlElementName($this->name);
$html .= '<' . $elementName;
if (! empty($this->attributes)) {
foreach ($this->attributes as $name => $value) {
$html .= ' '
. CharacterFilter::htmlAttributeName($name)
. '="'
. Escaper::htmlAttributeValue($value)
. '"'
;
}
}
if ($this->Contents !== null) {
$html .= '>';
if (! empty($this->Contents)) {
$html .= "\n";
foreach ($this->Contents as $C) {
$html .= $C->getHtml();
}
}
$html .= "</" . $elementName . ">\n";
} else {
$html .= ' />';
}
return $html;
}
}

View File

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace Erusev\Parsedown\Html\Components;
use Erusev\Parsedown\Html\Component;
use Erusev\Parsedown\Html\Sanitisation\CharacterFilter;
use Erusev\Parsedown\Html\Sanitisation\Escaper;
final class Text implements Component
{
/** @var string */
private $text;
public function __construct(string $text)
{
$this->text = $text;
}
public function getHtml(): string
{
return Escaper::htmlElementValue($text);
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace Erusev\Parsedown\Html\Sanisisation;
final class CharacterFilter
{
public static function htmlAttributeName(string $text) : string
{
/**
* https://www.w3.org/TR/html/syntax.html#name
*
* Attribute names must consist of one or more characters other than
* the space characters, U+0000 NULL, U+0022 QUOTATION MARK ("),
* U+0027 APOSTROPHE ('), U+003E GREATER-THAN SIGN (>),
* U+002F SOLIDUS (/), and U+003D EQUALS SIGN (=) characters,
* the control characters, and any characters that are not defined by
* Unicode.
*/
return \preg_replace(
'/(?:[[:space:]\0"\'>\/=[:cntrl:]]|[^\pC\pL\pM\pN\pP\pS\pZ])++/iu',
'',
$text
);
}
public static function htmlElementName(string $text) : string
{
/**
* https://www.w3.org/TR/html/syntax.html#tag-name
*
* HTML elements all have names that only use alphanumeric
* ASCII characters.
*/
return \preg_replace('/[^[:alnum:]]/', '', $text);
}
}

View File

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace Erusev\Parsedown\Sanisisation;
final class Escaper
{
public static function htmlAttributeValue(string $text) : string
{
return self::escape($text);
}
public static function htmlElementValue(string $text) : string
{
return self::escape($text, true);
}
private static function escape(
string $text,
bool $allowQuotes = false
) : string {
return \htmlentities(
$text,
$allowQuotes ? \ENT_NOQUOTES : \ENT_QUOTES,
'UTF-8'
);
}
}