1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Web;
13:
14: use Nette;
15:
16:
17:
18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30:
31: class Html extends Nette\Object implements \ArrayAccess, \Countable, \IteratorAggregate
32: {
33:
34: private $name;
35:
36:
37: private $isEmpty;
38:
39:
40: public $attrs = array();
41:
42:
43: protected $children = array();
44:
45:
46: public static $xhtml = TRUE;
47:
48:
49: public static $emptyElements = array('img'=>1,'hr'=>1,'br'=>1,'input'=>1,'meta'=>1,'area'=>1,'command'=>1,'keygen'=>1,'source'=>1,
50: 'base'=>1,'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'embed'=>1);
51:
52:
53:
54: 55: 56: 57: 58: 59:
60: public static function el($name = NULL, $attrs = NULL)
61: {
62: $el = new static;
63: $parts = explode(' ', $name, 2);
64: $el->setName($parts[0]);
65:
66: if (is_array($attrs)) {
67: $el->attrs = $attrs;
68:
69: } elseif ($attrs !== NULL) {
70: $el->setText($attrs);
71: }
72:
73: if (isset($parts[1])) {
74: foreach (Nette\String::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\\2|\s))?#i') as $m) {
75: $el->attrs[$m[1]] = isset($m[3]) ? $m[3] : TRUE;
76: }
77: }
78:
79: return $el;
80: }
81:
82:
83:
84: 85: 86: 87: 88: 89: 90:
91: final public function setName($name, $isEmpty = NULL)
92: {
93: if ($name !== NULL && !is_string($name)) {
94: throw new \InvalidArgumentException("Name must be string or NULL, " . gettype($name) ." given.");
95: }
96:
97: $this->name = $name;
98: $this->isEmpty = $isEmpty === NULL ? isset(self::$emptyElements[$name]) : (bool) $isEmpty;
99: return $this;
100: }
101:
102:
103:
104: 105: 106: 107:
108: final public function getName()
109: {
110: return $this->name;
111: }
112:
113:
114:
115: 116: 117: 118:
119: final public function isEmpty()
120: {
121: return $this->isEmpty;
122: }
123:
124:
125:
126: 127: 128: 129: 130: 131:
132: final public function __set($name, $value)
133: {
134: $this->attrs[$name] = $value;
135: }
136:
137:
138:
139: 140: 141: 142: 143:
144: final public function &__get($name)
145: {
146: return $this->attrs[$name];
147: }
148:
149:
150:
151: 152: 153: 154: 155:
156: final public function __unset($name)
157: {
158: unset($this->attrs[$name]);
159: }
160:
161:
162:
163: 164: 165: 166: 167: 168:
169: final public function __call($m, $args)
170: {
171: $p = substr($m, 0, 3);
172: if ($p === 'get' || $p === 'set' || $p === 'add') {
173: $m = substr($m, 3);
174: $m[0] = $m[0] | "\x20";
175: if ($p === 'get') {
176: return isset($this->attrs[$m]) ? $this->attrs[$m] : NULL;
177:
178: } elseif ($p === 'add') {
179: $args[] = TRUE;
180: }
181: }
182:
183: if (count($args) === 0) { 184:
185: } elseif (count($args) === 1) { 186: $this->attrs[$m] = $args[0];
187:
188: } elseif ($args[0] == NULL) { 189: $tmp = & $this->attrs[$m]; 190:
191: } elseif (!isset($this->attrs[$m]) || is_array($this->attrs[$m])) { 192: $this->attrs[$m][$args[0]] = $args[1];
193:
194: } else {
195: $this->attrs[$m] = array($this->attrs[$m], $args[0] => $args[1]);
196: }
197:
198: return $this;
199: }
200:
201:
202:
203: 204: 205: 206: 207: 208:
209: final public function href($path, $query = NULL)
210: {
211: if ($query) {
212: $query = http_build_query($query, NULL, '&');
213: if ($query !== '') $path .= '?' . $query;
214: }
215: $this->attrs['href'] = $path;
216: return $this;
217: }
218:
219:
220:
221: 222: 223: 224: 225: 226:
227: final public function setHtml($html)
228: {
229: if ($html === NULL) {
230: $html = '';
231:
232: } elseif (is_array($html)) {
233: throw new \InvalidArgumentException("Textual content must be a scalar, " . gettype($html) ." given.");
234:
235: } else {
236: $html = (string) $html;
237: }
238:
239: $this->removeChildren();
240: $this->children[] = $html;
241: return $this;
242: }
243:
244:
245:
246: 247: 248: 249:
250: final public function getHtml()
251: {
252: $s = '';
253: foreach ($this->children as $child) {
254: if (is_object($child)) {
255: $s .= $child->render();
256: } else {
257: $s .= $child;
258: }
259: }
260: return $s;
261: }
262:
263:
264:
265: 266: 267: 268: 269: 270:
271: final public function setText($text)
272: {
273: if (!is_array($text)) {
274: $text = htmlspecialchars((string) $text, ENT_NOQUOTES);
275: }
276: return $this->setHtml($text);
277: }
278:
279:
280:
281: 282: 283: 284:
285: final public function getText()
286: {
287: return html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8');
288: }
289:
290:
291:
292: 293: 294: 295: 296:
297: final public function add($child)
298: {
299: return $this->insert(NULL, $child);
300: }
301:
302:
303:
304: 305: 306: 307: 308: 309:
310: final public function create($name, $attrs = NULL)
311: {
312: $this->insert(NULL, $child = static::el($name, $attrs));
313: return $child;
314: }
315:
316:
317:
318: 319: 320: 321: 322: 323: 324: 325:
326: public function insert($index, $child, $replace = FALSE)
327: {
328: if ($child instanceof Html || is_scalar($child)) {
329: if ($index === NULL) { 330: $this->children[] = $child;
331:
332: } else { 333: array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child));
334: }
335:
336: } else {
337: throw new \InvalidArgumentException("Child node must be scalar or Html object, " . (is_object($child) ? get_class($child) : gettype($child)) ." given.");
338: }
339:
340: return $this;
341: }
342:
343:
344:
345: 346: 347: 348: 349: 350:
351: final public function offsetSet($index, $child)
352: {
353: $this->insert($index, $child, TRUE);
354: }
355:
356:
357:
358: 359: 360: 361: 362:
363: final public function offsetGet($index)
364: {
365: return $this->children[$index];
366: }
367:
368:
369:
370: 371: 372: 373: 374:
375: final public function offsetExists($index)
376: {
377: return isset($this->children[$index]);
378: }
379:
380:
381:
382: 383: 384: 385: 386:
387: public function offsetUnset($index)
388: {
389: if (isset($this->children[$index])) {
390: array_splice($this->children, (int) $index, 1);
391: }
392: }
393:
394:
395:
396: 397: 398: 399:
400: final public function count()
401: {
402: return count($this->children);
403: }
404:
405:
406:
407: 408: 409: 410:
411: public function removeChildren()
412: {
413: $this->children = array();
414: }
415:
416:
417:
418: 419: 420: 421: 422: 423:
424: final public function getIterator($deep = FALSE)
425: {
426: if ($deep) {
427: $deep = $deep > 0 ? \RecursiveIteratorIterator::SELF_FIRST : \RecursiveIteratorIterator::CHILD_FIRST;
428: return new \RecursiveIteratorIterator(new Nette\GenericRecursiveIterator(new \ArrayIterator($this->children)), $deep);
429:
430: } else {
431: return new Nette\GenericRecursiveIterator(new \ArrayIterator($this->children));
432: }
433: }
434:
435:
436:
437: 438: 439: 440:
441: final public function getChildren()
442: {
443: return $this->children;
444: }
445:
446:
447:
448: 449: 450: 451: 452:
453: final public function render($indent = NULL)
454: {
455: $s = $this->startTag();
456:
457: if (!$this->isEmpty) {
458: 459: if ($indent !== NULL) {
460: $indent++;
461: }
462: foreach ($this->children as $child) {
463: if (is_object($child)) {
464: $s .= $child->render($indent);
465: } else {
466: $s .= $child;
467: }
468: }
469:
470: 471: $s .= $this->endTag();
472: }
473:
474: if ($indent !== NULL) {
475: return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
476: }
477: return $s;
478: }
479:
480:
481:
482: final public function __toString()
483: {
484: return $this->render();
485: }
486:
487:
488:
489: 490: 491: 492:
493: final public function startTag()
494: {
495: if ($this->name) {
496: return '<' . $this->name . $this->attributes() . (self::$xhtml && $this->isEmpty ? ' />' : '>');
497:
498: } else {
499: return '';
500: }
501: }
502:
503:
504:
505: 506: 507: 508:
509: final public function endTag()
510: {
511: return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
512: }
513:
514:
515:
516: 517: 518: 519:
520: final public function attributes()
521: {
522: if (!is_array($this->attrs)) {
523: return '';
524: }
525:
526: $s = '';
527: foreach ($this->attrs as $key => $value)
528: {
529: 530: if ($value === NULL || $value === FALSE) continue;
531:
532: 533: if ($value === TRUE) {
534: 535: if (self::$xhtml) $s .= ' ' . $key . '="' . $key . '"';
536: 537: else $s .= ' ' . $key;
538: continue;
539:
540: } elseif (is_array($value)) {
541: if ($key === 'data') {
542: foreach ($value as $k => $v) {
543: if ($v !== NULL && $v !== FALSE) {
544: $s .= ' data-' . $k . '="' . htmlspecialchars((string) $v) . '"';
545: }
546: }
547: continue;
548: }
549:
550: 551: $tmp = NULL;
552: foreach ($value as $k => $v) {
553: 554: if ($v == NULL) continue; 555:
556: 557: $tmp[] = is_string($k) ? ($v === TRUE ? $k : $k . ':' . $v) : $v;
558: }
559: if ($tmp === NULL) continue;
560:
561: $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
562:
563: } else {
564: $value = (string) $value;
565: }
566:
567: $s .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
568: }
569:
570: $s = str_replace('@', '@', $s);
571: return $s;
572: }
573:
574:
575:
576: 577: 578:
579: public function __clone()
580: {
581: foreach ($this->children as $key => $value) {
582: if (is_object($value)) {
583: $this->children[$key] = clone $value;
584: }
585: }
586: }
587:
588: }
589: