PageRenderTime 26ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/Nette/Utils/Html.php

https://github.com/premiumcombination/nts
PHP | 602 lines | 284 code | 136 blank | 182 comment | 49 complexity | 7953b8b6be50077c9ad9316b10129cc2 MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (http://nette.org)
  4. *
  5. * Copyright (c) 2004, 2011 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. */
  10. namespace Nette\Utils;
  11. use Nette;
  12. /**
  13. * HTML helper.
  14. *
  15. * <code>
  16. * $anchor = Html::el('a')->href($link)->setText('Nette');
  17. * $el->class = 'myclass';
  18. * echo $el;
  19. *
  20. * echo $el->startTag(), $el->endTag();
  21. * </code>
  22. *
  23. * @author David Grudl
  24. */
  25. class Html extends Nette\Object implements \ArrayAccess, \Countable, \IteratorAggregate
  26. {
  27. /** @var string element's name */
  28. private $name;
  29. /** @var bool is element empty? */
  30. private $isEmpty;
  31. /** @var array element's attributes */
  32. public $attrs = array();
  33. /** @var array of Html | string nodes */
  34. protected $children = array();
  35. /** @var bool use XHTML syntax? */
  36. public static $xhtml = TRUE;
  37. /** @var array empty elements */
  38. public static $emptyElements = array('img'=>1,'hr'=>1,'br'=>1,'input'=>1,'meta'=>1,'area'=>1,'embed'=>1,'keygen'=>1,
  39. 'source'=>1,'base'=>1,'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'command'=>1);
  40. /**
  41. * Static factory.
  42. * @param string element name (or NULL)
  43. * @param array|string element's attributes (or textual content)
  44. * @return Html
  45. */
  46. public static function el($name = NULL, $attrs = NULL)
  47. {
  48. $el = new static;
  49. $parts = explode(' ', $name, 2);
  50. $el->setName($parts[0]);
  51. if (is_array($attrs)) {
  52. $el->attrs = $attrs;
  53. } elseif ($attrs !== NULL) {
  54. $el->setText($attrs);
  55. }
  56. if (isset($parts[1])) {
  57. foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\\2|\s))?#i') as $m) {
  58. $el->attrs[$m[1]] = isset($m[3]) ? $m[3] : TRUE;
  59. }
  60. }
  61. return $el;
  62. }
  63. /**
  64. * Changes element's name.
  65. * @param string
  66. * @param bool Is element empty?
  67. * @return Html provides a fluent interface
  68. * @throws Nette\InvalidArgumentException
  69. */
  70. final public function setName($name, $isEmpty = NULL)
  71. {
  72. if ($name !== NULL && !is_string($name)) {
  73. throw new Nette\InvalidArgumentException("Name must be string or NULL, " . gettype($name) ." given.");
  74. }
  75. $this->name = $name;
  76. $this->isEmpty = $isEmpty === NULL ? isset(self::$emptyElements[$name]) : (bool) $isEmpty;
  77. return $this;
  78. }
  79. /**
  80. * Returns element's name.
  81. * @return string
  82. */
  83. final public function getName()
  84. {
  85. return $this->name;
  86. }
  87. /**
  88. * Is element empty?
  89. * @return bool
  90. */
  91. final public function isEmpty()
  92. {
  93. return $this->isEmpty;
  94. }
  95. /**
  96. * Sets multiple attributes.
  97. * @param array
  98. * @return Html provides a fluent interface
  99. */
  100. public function addAttributes(array $attrs)
  101. {
  102. $this->attrs = $attrs + $this->attrs;
  103. return $this;
  104. }
  105. /**
  106. * Overloaded setter for element's attribute.
  107. * @param string HTML attribute name
  108. * @param mixed HTML attribute value
  109. * @return void
  110. */
  111. final public function __set($name, $value)
  112. {
  113. $this->attrs[$name] = $value;
  114. }
  115. /**
  116. * Overloaded getter for element's attribute.
  117. * @param string HTML attribute name
  118. * @return mixed HTML attribute value
  119. */
  120. final public function &__get($name)
  121. {
  122. return $this->attrs[$name];
  123. }
  124. /**
  125. * Overloaded unsetter for element's attribute.
  126. * @param string HTML attribute name
  127. * @return void
  128. */
  129. final public function __unset($name)
  130. {
  131. unset($this->attrs[$name]);
  132. }
  133. /**
  134. * Overloaded setter for element's attribute.
  135. * @param string HTML attribute name
  136. * @param array (string) HTML attribute value or pair?
  137. * @return Html provides a fluent interface
  138. */
  139. final public function __call($m, $args)
  140. {
  141. $p = substr($m, 0, 3);
  142. if ($p === 'get' || $p === 'set' || $p === 'add') {
  143. $m = substr($m, 3);
  144. $m[0] = $m[0] | "\x20";
  145. if ($p === 'get') {
  146. return isset($this->attrs[$m]) ? $this->attrs[$m] : NULL;
  147. } elseif ($p === 'add') {
  148. $args[] = TRUE;
  149. }
  150. }
  151. if (count($args) === 0) { // invalid
  152. } elseif (count($args) === 1) { // set
  153. $this->attrs[$m] = $args[0];
  154. } elseif ((string) $args[0] === '') {
  155. $tmp = & $this->attrs[$m]; // appending empty value? -> ignore, but ensure it exists
  156. } elseif (!isset($this->attrs[$m]) || is_array($this->attrs[$m])) { // needs array
  157. $this->attrs[$m][$args[0]] = $args[1];
  158. } else {
  159. $this->attrs[$m] = array($this->attrs[$m], $args[0] => $args[1]);
  160. }
  161. return $this;
  162. }
  163. /**
  164. * Special setter for element's attribute.
  165. * @param string path
  166. * @param array query
  167. * @return Html provides a fluent interface
  168. */
  169. final public function href($path, $query = NULL)
  170. {
  171. if ($query) {
  172. $query = http_build_query($query, NULL, '&');
  173. if ($query !== '') {
  174. $path .= '?' . $query;
  175. }
  176. }
  177. $this->attrs['href'] = $path;
  178. return $this;
  179. }
  180. /**
  181. * Sets element's HTML content.
  182. * @param string
  183. * @return Html provides a fluent interface
  184. * @throws Nette\InvalidArgumentException
  185. */
  186. final public function setHtml($html)
  187. {
  188. if ($html === NULL) {
  189. $html = '';
  190. } elseif (is_array($html)) {
  191. throw new Nette\InvalidArgumentException("Textual content must be a scalar, " . gettype($html) ." given.");
  192. } else {
  193. $html = (string) $html;
  194. }
  195. $this->removeChildren();
  196. $this->children[] = $html;
  197. return $this;
  198. }
  199. /**
  200. * Returns element's HTML content.
  201. * @return string
  202. */
  203. final public function getHtml()
  204. {
  205. $s = '';
  206. foreach ($this->children as $child) {
  207. if (is_object($child)) {
  208. $s .= $child->render();
  209. } else {
  210. $s .= $child;
  211. }
  212. }
  213. return $s;
  214. }
  215. /**
  216. * Sets element's textual content.
  217. * @param string
  218. * @return Html provides a fluent interface
  219. * @throws Nette\InvalidArgumentException
  220. */
  221. final public function setText($text)
  222. {
  223. if (!is_array($text)) {
  224. $text = htmlspecialchars((string) $text, ENT_NOQUOTES);
  225. }
  226. return $this->setHtml($text);
  227. }
  228. /**
  229. * Returns element's textual content.
  230. * @return string
  231. */
  232. final public function getText()
  233. {
  234. return html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8');
  235. }
  236. /**
  237. * Adds new element's child.
  238. * @param Html|string child node
  239. * @return Html provides a fluent interface
  240. */
  241. final public function add($child)
  242. {
  243. return $this->insert(NULL, $child);
  244. }
  245. /**
  246. * Creates and adds a new Html child.
  247. * @param string elements's name
  248. * @param array|string element's attributes (or textual content)
  249. * @return Html created element
  250. */
  251. final public function create($name, $attrs = NULL)
  252. {
  253. $this->insert(NULL, $child = static::el($name, $attrs));
  254. return $child;
  255. }
  256. /**
  257. * Inserts child node.
  258. * @param int
  259. * @param Html node
  260. * @param bool
  261. * @return Html provides a fluent interface
  262. * @throws \Exception
  263. */
  264. public function insert($index, $child, $replace = FALSE)
  265. {
  266. if ($child instanceof Html || is_scalar($child)) {
  267. if ($index === NULL) { // append
  268. $this->children[] = $child;
  269. } else { // insert or replace
  270. array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child));
  271. }
  272. } else {
  273. throw new Nette\InvalidArgumentException("Child node must be scalar or Html object, " . (is_object($child) ? get_class($child) : gettype($child)) ." given.");
  274. }
  275. return $this;
  276. }
  277. /**
  278. * Inserts (replaces) child node (\ArrayAccess implementation).
  279. * @param int
  280. * @param Html node
  281. * @return void
  282. */
  283. final public function offsetSet($index, $child)
  284. {
  285. $this->insert($index, $child, TRUE);
  286. }
  287. /**
  288. * Returns child node (\ArrayAccess implementation).
  289. * @param int index
  290. * @return mixed
  291. */
  292. final public function offsetGet($index)
  293. {
  294. return $this->children[$index];
  295. }
  296. /**
  297. * Exists child node? (\ArrayAccess implementation).
  298. * @param int index
  299. * @return bool
  300. */
  301. final public function offsetExists($index)
  302. {
  303. return isset($this->children[$index]);
  304. }
  305. /**
  306. * Removes child node (\ArrayAccess implementation).
  307. * @param int index
  308. * @return void
  309. */
  310. public function offsetUnset($index)
  311. {
  312. if (isset($this->children[$index])) {
  313. array_splice($this->children, (int) $index, 1);
  314. }
  315. }
  316. /**
  317. * Required by the \Countable interface.
  318. * @return int
  319. */
  320. final public function count()
  321. {
  322. return count($this->children);
  323. }
  324. /**
  325. * Removed all children.
  326. * @return void
  327. */
  328. public function removeChildren()
  329. {
  330. $this->children = array();
  331. }
  332. /**
  333. * Iterates over a elements.
  334. * @param bool recursive?
  335. * @param string class types filter
  336. * @return \RecursiveIterator
  337. */
  338. final public function getIterator($deep = FALSE)
  339. {
  340. if ($deep) {
  341. $deep = $deep > 0 ? \RecursiveIteratorIterator::SELF_FIRST : \RecursiveIteratorIterator::CHILD_FIRST;
  342. return new \RecursiveIteratorIterator(new Nette\Iterators\Recursor(new \ArrayIterator($this->children)), $deep);
  343. } else {
  344. return new Nette\Iterators\Recursor(new \ArrayIterator($this->children));
  345. }
  346. }
  347. /**
  348. * Returns all of children.
  349. * return array
  350. */
  351. final public function getChildren()
  352. {
  353. return $this->children;
  354. }
  355. /**
  356. * Renders element's start tag, content and end tag.
  357. * @param int indent
  358. * @return string
  359. */
  360. final public function render($indent = NULL)
  361. {
  362. $s = $this->startTag();
  363. if (!$this->isEmpty) {
  364. // add content
  365. if ($indent !== NULL) {
  366. $indent++;
  367. }
  368. foreach ($this->children as $child) {
  369. if (is_object($child)) {
  370. $s .= $child->render($indent);
  371. } else {
  372. $s .= $child;
  373. }
  374. }
  375. // add end tag
  376. $s .= $this->endTag();
  377. }
  378. if ($indent !== NULL) {
  379. return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
  380. }
  381. return $s;
  382. }
  383. final public function __toString()
  384. {
  385. return $this->render();
  386. }
  387. /**
  388. * Returns element's start tag.
  389. * @return string
  390. */
  391. final public function startTag()
  392. {
  393. if ($this->name) {
  394. return '<' . $this->name . $this->attributes() . (self::$xhtml && $this->isEmpty ? ' />' : '>');
  395. } else {
  396. return '';
  397. }
  398. }
  399. /**
  400. * Returns element's end tag.
  401. * @return string
  402. */
  403. final public function endTag()
  404. {
  405. return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
  406. }
  407. /**
  408. * Returns element's attributes.
  409. * @return string
  410. */
  411. final public function attributes()
  412. {
  413. if (!is_array($this->attrs)) {
  414. return '';
  415. }
  416. $s = '';
  417. foreach ($this->attrs as $key => $value) {
  418. if ($value === NULL || $value === FALSE) {
  419. continue;
  420. } elseif ($value === TRUE) {
  421. if (self::$xhtml) {
  422. $s .= ' ' . $key . '="' . $key . '"';
  423. } else {
  424. $s .= ' ' . $key;
  425. }
  426. continue;
  427. } elseif (is_array($value)) {
  428. if ($key === 'data') {
  429. foreach ($value as $k => $v) {
  430. if ($v !== NULL && $v !== FALSE) {
  431. $s .= ' data-' . $k . '="' . htmlspecialchars((string) $v) . '"';
  432. }
  433. }
  434. continue;
  435. }
  436. $tmp = NULL;
  437. foreach ($value as $k => $v) {
  438. if ($v != NULL) { // intentionally ==, skip NULLs & empty string
  439. // composite 'style' vs. 'others'
  440. $tmp[] = $v === TRUE ? $k : (is_string($k) ? $k . ':' . $v : $v);
  441. }
  442. }
  443. if ($tmp === NULL) {
  444. continue;
  445. }
  446. $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
  447. } else {
  448. $value = (string) $value;
  449. }
  450. $s .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
  451. }
  452. $s = str_replace('@', '&#64;', $s);
  453. return $s;
  454. }
  455. /**
  456. * Clones all children too.
  457. */
  458. public function __clone()
  459. {
  460. foreach ($this->children as $key => $value) {
  461. if (is_object($value)) {
  462. $this->children[$key] = clone $value;
  463. }
  464. }
  465. }
  466. }