diff --git a/src/Html/Component.php b/src/Html/Component.php new file mode 100644 index 0000000..ff9a8b9 --- /dev/null +++ b/src/Html/Component.php @@ -0,0 +1,8 @@ +*/ + private $attributes; + + /** @var Component[]|null */ + private $Contents; + + /** + * @param string $name + * @param array $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 $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 + */ + 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 .= "\n"; + } else { + $html .= ' />'; + } + + return $html; + } +} diff --git a/src/Html/Components/Text.php b/src/Html/Components/Text.php new file mode 100644 index 0000000..1737cf2 --- /dev/null +++ b/src/Html/Components/Text.php @@ -0,0 +1,23 @@ +text = $text; + } + + public function getHtml(): string + { + return Escaper::htmlElementValue($text); + } +} diff --git a/src/Html/Sanitisation/CharacterFilter.php b/src/Html/Sanitisation/CharacterFilter.php new file mode 100644 index 0000000..441bbb7 --- /dev/null +++ b/src/Html/Sanitisation/CharacterFilter.php @@ -0,0 +1,36 @@ +), + * 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); + } +} diff --git a/src/Html/Sanitisation/Escaper.php b/src/Html/Sanitisation/Escaper.php new file mode 100644 index 0000000..3f19a5d --- /dev/null +++ b/src/Html/Sanitisation/Escaper.php @@ -0,0 +1,27 @@ +