PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/www/libs/nette-dev/Web/Html.php

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