PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Nette/Web/Html.php

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