*/ private array $css = []; /** @var array */ private array $options = []; /** @var array list of elements */ private array $sub = []; /** * create new html element * * @param string $tag html tag (eg div, button, etc) * @param string $id html tag id, used also for name if name * not set in $options * @param string $content content text inside, eg
Content
* if sub elements exist, they are added after content * @param array $css array of css names, put style in $options * @param array $options Additional element options in * key = value format * eg: onClick => 'something();' * id, css are skipped * name only set on input/button * @throws HtmlBuilderExcpetion */ public function __construct( string $tag, string $id = '', string $content = '', array $css = [], array $options = [] ) { // exit if not valid tag try { $this->setTag($tag); } catch (HtmlBuilderExcpetion $e) { throw new HtmlBuilderExcpetion('Could not create Element', 0, $e); } $this->setId($id); $this->setName($options['name'] ?? ''); $this->setContent($content); $this->addCss(...$css); $this->setOptions($options); } /** * set tag * * @param string $tag * @return void * @throws HtmlBuilderExcpetion */ public function setTag(string $tag): void { // tag must be letters only if (!preg_match("/^[A-Za-z]+$/", $tag)) { Error::setError( '201', 'invalid or empty tag', ['tag' => $tag] ); throw new HtmlBuilderExcpetion('Invalid or empty tag: ' . $tag); } $this->tag = $tag; } /** * get the tag name * * @return string HTML element tag */ public function getTag(): string { return $this->tag; } /** * set the element id * * @param string $id * @return void */ public function setId(string $id): void { // invalid id and name check too // be strict: [a-zA-Z0-9], -, _ // cannot start with digit, two hyphens or a hyphen with a digit: // 0abc // __abc // _0abc if ( !empty($id) && !preg_match("/^[A-Za-z][\w-]*$/", $id) ) { Error::setWarning( '202', 'possible invalid id', ['id' => $id, 'tag' => $this->getTag()] ); // TODO: shoud throw error } $this->id = $id; } /** * get the html tag id * * @return string HTML element id */ public function getId(): string { return $this->id; } /** * Set name for elements * only for elements that need it (input/button/form) * * @param string $name * @return void */ public function setName(string $name): void { if ( !empty($name) && !preg_match("/^[A-Za-z][\w-]*$/", $name) ) { Error::setWarning( '203', 'possible invalid name', ['name' => $name, 'id' => $this->getId(), 'tag' => $this->getTag()] ); // TODO: shoud throw error } $this->name = $name; } /** * get the name if set * * @return string Optional HTML name (eg for input) */ public function getName(): string { return $this->name; } /** * Set new content for element * * @param string $content * @return void */ public function setContent(string $content): void { $this->content = $content; } /** * get the elment text content (not sub elements) * * @return string HTML content text as is */ public function getContent(): string { return $this->content; } /** * set or update options * * @param array $options * @return void */ public function setOptions(array $options): void { foreach ($options as $key => $value) { if (empty($key)) { Error::setError( '110', 'Cannot set option with empty key', ['id' => $this->getId(), 'tag' => $this->getTag()] ); // TODO: shoud throw error continue; } // if data is null if ($value === null) { if (isset($this->options[$key])) { unset($this->options[$key]); } else { Error::setError( '210', 'Cannot set option with null value', ['id' => $this->getId(), 'tag' => $this->getTag()] ); } // TODO: shoud throw error continue; } $this->options[$key] = $value; } } /** * get the options array * also holds "name" option * anything like: style, javascript, value or any other html tag option * right side can be empty but not null * * @return array get options as list html option name and value */ public function getOptions(): array { return $this->options; } // Sub Elements /** * get the sub elements (array of Elements) * * @return array Array of Elements (that can have sub elements) */ public function getSub(): array { return $this->sub; } /** * add one or many sub elements (add at the end) * * @param Element $sub One or many elements to add * @return void * @throws HtmlBuilderExcpetion */ public function addSub(Element ...$sub): void { foreach ($sub as $_sub) { // if one of the elements is the same as this class, ignore it // with this we avoid self reference loop if ($_sub == $this) { Error::setError( '100', 'Cannot assign Element to itself, this would create an infinite loop', ['id' => $this->getId(), 'tag' => $this->getTag()] ); throw new HtmlBuilderExcpetion('Cannot assign Element to itself, this would create an infinite loop'); } array_push($this->sub, $_sub); } } /** * Remove an element from the sub array * By pos in array or id set on first level * * @param int|string $id String id name or int pos number in array * @return void */ public function removeSub(int|string $id): void { // find element with id and remove it // or when number find pos in sub and remove it if (is_int($id)) { if (!isset($this->sub[$id])) { return; } unset($this->sub[$id]); return; } // only on first level foreach ($this->sub as $pos => $el) { if ( $el->getId() === $id ) { unset($this->sub[$pos]); return; } } } /** * remove all sub elements * * @return void */ public function resetSub(): void { $this->sub = []; } // CSS Elements /** * get the current set css elements * * @return array list of css element entries */ public function getCss(): array { return $this->css; } /** * add one or many new css elements * Note that we can chain: add/remove/reset * * @param string ...$css one or more css strings to add * @return Element Current element for chaining */ public function addCss(string ...$css): Element { // should do check for empty/invalid css $_set_css = []; foreach ($css as $_css) { if (empty($_css)) { Error::setError( '204', 'cannot have empty css string', ); // TODO: shoud throw error continue; } // -?[_A-Za-z][_A-Za-z0-9-]* if (!preg_match("/^-?[_A-Za-z][_A-Za-z0-9-]*$/", $_css)) { Error::setWarning( '205', 'possible invalid css string', ['css' => $_css, 'id' => $this->id, 'tag' => $this->tag] ); // TODO: shoud throw error } $_set_css[] = $_css; } $this->css = array_unique(array_merge($this->css, $_set_css)); return $this; } /** * remove one or more css elements * Note that we can chain: add/remove/reset * * @param string ...$css one or more css strings to remove * @return Element Current element for chaining */ public function removeCss(string ...$css): Element { $this->css = array_diff($this->css, $css); return $this; } /** * unset all css elements * Note that we can chain: add/remove/reset * * @return Element */ public function resetCss(): Element { $this->css = []; return $this; } // build output from tree /** * build html string from the current element tree (self) * or from the Element tree given as parameter * if $add_nl is set then new lines are added before each sub element added * no indet is done (tab or other) * * @param Element|null $tree Different Element tree to build * if not set (null), self is used * @param bool $add_nl [default=false] Optional output string line breaks * @return string HTML as string */ public function buildHtml(?Element $tree = null, bool $add_nl = false): string { // print "D01: " . microtime(true) . "
"; if ($tree === null) { $tree = $this; } $line = '<' . $tree->getTag(); if ($tree->getId()) { $line .= ' id="' . $tree->getId() . '"'; if (in_array($tree->getTag(), Settings::NAME_ELEMENTS)) { $line .= ' name="' . (!empty($tree->getName()) ? $tree->getName() : $tree->getId()) . '"'; } } if (count($tree->getCss())) { $line .= ' class="' . join(' ', $tree->getCss()) . '"'; } foreach ($tree->getOptions() as $key => $item) { // skip if (in_array($key, Settings::SKIP_OPTIONS)) { continue; } $line .= ' ' . $key . '="' . $item . '"'; } $line .= '>'; if (strlen($tree->getContent()) > 0) { $line .= $tree->getContent(); } // sub nodes foreach ($tree->getSub() as $sub) { if ($add_nl === true) { $line .= "\n"; } $line .= $tree->buildHtml($sub, $add_nl); if ($add_nl === true) { $line .= "\n"; } } // close line if needed if (!in_array($tree->getTag(), Settings::NO_CLOSE)) { $line .= 'getTag() . '>'; } return $line; } // this is static /** * Builds a single string from an array of elements * a new line can be added before each new element if $add_nl is set to true * * @param array $list array of Elements, uses buildHtml internal * @param bool $add_nl [default=false] Optional output string line breaks * @return string HTML as string */ public static function buildHtmlFromList(array $list, bool $add_nl = false): string { $output = ''; foreach ($list as $el) { if (!empty($output) && $add_nl === true) { $output .= "\n"; } $output .= $el->buildHtml(); } return $output; } // so we can call builder statically /** * Search element tree for id and add * if id is empty add at element given in parameter $base * * @param Element $base Element to attach to * @param Element $attach Element to attach (single) * @param string $id Optional id, if empty then attached at the end * If set will loop through ALL sub elements until * matching id found. if not found, not added * @return Element Element with attached sub element */ public static function addElementWithId( Element $base, Element $attach, string $id = '' ): Element { // no id or matching id if ( empty($id) || $base->getId() == $id ) { $base->addSub($attach); return $base; } // find id in 'id' in all 'sub' foreach ($base->getSub() as $el) { self::addElementWithId($el, $attach, $id); } return $base; } /** * add one or more elemens to $base * * @param Element $base Element to attach to * @param Element ...$attach Element or Elements to attach * @return Element Element with attached sub elements */ public static function addElement( Element $base, Element ...$attach ): Element { // we must make sure we do not self attach $base->addSub(...$attach); return $base; } /** * Static call version for building * not recommended to be used, rather use "Element->buildHtml()" * wrapper for buildHtml * * @param ?Element $tree Element tree to build * if not set returns empty string * @param bool $add_nl [default=false] Optional output string line break * @return string build html as string * @deprecated Do not use, use Element->buildHtml() instead */ public static function printHtmlFromObject(?Element $tree = null, bool $add_nl = false): string { // nothing ->bad if ($tree === null) { return ''; } return $tree->buildHtml(add_nl: $add_nl); } /** * Undocumented function * wrapper for buildHtmlFromList * * @param array $list array of Elements to build string from * @param bool $add_nl [default=false] Optional output string line break * @return string build html as string */ public static function printHtmlFromArray(array $list, bool $add_nl = false): string { return self::buildHtmlFromList($list, $add_nl); } } // __END__