href($link)->setText('Nette'); * $el->class = 'myclass'; * echo $el; * * echo $el->startTag(), $el->endTag(); * * Requirements: * - PHP 5.0.0 * - NetteException * - optionally NApplication * * @property mixed element's attributes */ class NHtml { /** @var string element's name */ private $name; /** @var bool is element empty? */ private $isEmpty; /** * @var mixed element's content * array of NHtml - child nodes * string - content as string (text-node) */ public $children; /** @var array element's attributes */ public $attrs = array(); /** @var bool use XHTML syntax? */ public static $xhtml = TRUE; /** @var array empty elements */ public static $emptyTags = array('img'=>1,'hr'=>1,'br'=>1,'input'=>1,'meta'=>1,'area'=>1, 'base'=>1,'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'embed'=>1); /** * Static factory * @param string element name (or NULL) * @param array element's attributes * @throws NHtmlException * @return NHtml */ public static function el($name = NULL, $attrs = NULL) { $el = new self; if ($name !== NULL) $el->setName($name); if ($attrs !== NULL) { if (!is_array($attrs)) throw new NHtmlException('Attributes must be array'); $el->attrs = $attrs; } return $el; } /** * Static factory for textual element * @param string * @return NHtml */ public static function text($text) { $el = new self; $el->setText($text); return $el; } /** * Changes element's name * @param string * @throws NHtmlException * @return NHtml provides a fluent interface */ public function setName($name) { if ($name !== NULL && !is_string($name)) throw new NHtmlException("Element's name must string or NULL"); $this->name = $name; $this->isEmpty = isset(self::$emptyTags[$name]); return $this; } /** * Returns element's name * @return string */ public function getName() { return $this->name; } /** * Is element empty? * @param optional setter * @return bool */ public function isEmpty($value = NULL) { if (is_bool($value)) $this->isEmpty = $value; return $this->isEmpty; } /** * Sets element's textual content * @param string * @param bool is the string HTML encoded yet? * @throws NHtmlException * @return NHtml provides a fluent interface */ public function setText($text, $isHtml = FALSE) { if ($text === NULL) $text = ''; elseif (!is_scalar($text)) throw new NHtmlException("Textual content must be a scalar"); if (!$isHtml) $text = str_replace(array('&', '<', '>'), array('&', '<', '>'), $text); $this->children = $text; return $this; } /** * Gets element's textual content * @return string */ public function getText() { if (is_array($this->children)) return FALSE; return $this->children; } /** * Adds new element's child * @param NHtml object * @return NHtml provides a fluent interface */ public function addChild(NHtml $child) { $this->children[] = $child; return $this; } /** * Returns child node * @param mixed index * @return NHtml */ public function getChild($index) { if (isset($this->children[$index])) return $this->children[$index]; return NULL; } /** * Adds and creates new NHtml child * @param string elements's name * @param string optional textual content * @return NHtml */ public function add($name, $text = NULL) { $child = new self; $child->setName($name); if ($text !== NULL) $child->setText($text); return $this->children[] = $child; } /** * Overloaded setter for element's attribute * @param string property name * @param mixed property value * @return void */ public function __set($name, $value) { $this->attrs[$name] = $value; } /** * Overloaded getter for element's attribute * @param string property name * @return mixed property value */ public function &__get($name) { return $this->attrs[$name]; } /** * Overloaded setter for element's attribute * @param string attribute name * @param array value * @return NHtml provides a fluent interface */ public function __call($m, $args) { $this->attrs[$m] = $args[0]; return $this; } /** * Special setter for element's attribute * @param string path * @param array query * @return NHtml provides a fluent interface */ public function href($path, $query = NULL) { // for Nette framework if (class_exists('NApplication', FALSE)) { if (substr($path, 0, 6) === 'event:') { if ($query === NULL) $query = array(); $this->attrs['href'] = NApplication::getPage()->constructEvent(substr($path, 6), $query)->constructUrl(); return $this; } elseif (substr($path, 0, 5) === 'page:') { if ($query === NULL) $query = array(); $this->attrs['href'] = NApplication::getPage()->forwardArgs(substr($path, 5), $query)->constructUrl(); return $this; } } if ($query) { $query = http_build_query($query, NULL, '&'); if ($query !== '') $path .= '?' . $query; } $this->attrs['href'] = $path; return $this; } /** * Renders element's start tag, content and end tag * @return string */ public function render() { $s = $this->startTag(); // empty elements are finished now if ($this->isEmpty) return $s; // add content if (is_array($this->children)) { foreach ($this->children as $value) $s .= $value->__toString(); } else { $s .= $this->children; } // add end tag return $s . $this->endTag(); } /** * Returns element's start tag * @return string */ public function startTag() { if (!$this->name) return ''; $s = '<' . $this->name; if (is_array($this->attrs)) { foreach ($this->attrs as $key => $value) { // skip NULLs and false boolean attributes if ($value === NULL || $value === FALSE) continue; // true boolean attribute if ($value === TRUE) { // in XHTML must use unminimized form if (self::$xhtml) $s .= ' ' . $key . '="' . $key . '"'; // in HTML should use minimized form else $s .= ' ' . $key; continue; } elseif (is_array($value)) { // prepare into temporary array $tmp = NULL; foreach ($value as $k => $v) { // skip NULLs & empty string; composite 'style' vs. 'others' if ($v == NULL) continue; if (is_string($k)) $tmp[] = $k . ':' . $v; else $tmp[] = $v; } if (!$tmp) continue; $value = implode($key === 'style' ? ';' : ' ', $tmp); } elseif ($key === 'href' && substr($value, 0, 7) === 'mailto:') { // email-obfuscate hack $tmp = ''; for ($i = 0; $i'), array('&', '"', '<', '>'), $value) . '"'; } } // finish start tag if (self::$xhtml && $this->isEmpty) return $s . ' />'; return $s . '>'; } public function __toString() { return $this->render(); } /** * Returns element's end tag * @return string */ public function endTag() { if ($this->name && !$this->isEmpty) return 'name . '>'; return ''; } /** * Is element textual node? * @return bool */ public function isTextual() { return !$this->isEmpty && is_scalar($this->children); } /** * Clones all children too */ public function __clone() { if (is_array($this->children)) { foreach ($this->children as $key => $value) $this->children[$key] = clone $value; } } }