PageRenderTime 60ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/libs/Nette/Web/Html.php

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