1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Utils;
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,'embed'=>1,'keygen'=>1,
50: 'source'=>1,'base'=>1,'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'command'=>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 (Strings::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 Nette\InvalidArgumentException("Name must be string or NULL, " . gettype($name) ." given.");
95: }
96:
97: $this->name = $name;
98: $this->isEmpty = $isEmpty === NULL ? isset(static::$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: public function addAttributes(array $attrs)
132: {
133: $this->attrs = $attrs + $this->attrs;
134: return $this;
135: }
136:
137:
138:
139: 140: 141: 142: 143: 144:
145: final public function __set($name, $value)
146: {
147: $this->attrs[$name] = $value;
148: }
149:
150:
151:
152: 153: 154: 155: 156:
157: final public function &__get($name)
158: {
159: return $this->attrs[$name];
160: }
161:
162:
163:
164: 165: 166: 167: 168:
169: final public function __isset($name)
170: {
171: return isset($this->attrs[$name]);
172: }
173:
174:
175:
176: 177: 178: 179: 180:
181: final public function __unset($name)
182: {
183: unset($this->attrs[$name]);
184: }
185:
186:
187:
188: 189: 190: 191: 192: 193:
194: final public function __call($m, $args)
195: {
196: $p = substr($m, 0, 3);
197: if ($p === 'get' || $p === 'set' || $p === 'add') {
198: $m = substr($m, 3);
199: $m[0] = $m[0] | "\x20";
200: if ($p === 'get') {
201: return isset($this->attrs[$m]) ? $this->attrs[$m] : NULL;
202:
203: } elseif ($p === 'add') {
204: $args[] = TRUE;
205: }
206: }
207:
208: if (count($args) === 0) {
209:
210: } elseif (count($args) === 1) {
211: $this->attrs[$m] = $args[0];
212:
213: } elseif ((string) $args[0] === '') {
214: $tmp = & $this->attrs[$m];
215:
216: } elseif (!isset($this->attrs[$m]) || is_array($this->attrs[$m])) {
217: $this->attrs[$m][$args[0]] = $args[1];
218:
219: } else {
220: $this->attrs[$m] = array($this->attrs[$m], $args[0] => $args[1]);
221: }
222:
223: return $this;
224: }
225:
226:
227:
228: 229: 230: 231: 232: 233:
234: final public function href($path, $query = NULL)
235: {
236: if ($query) {
237: $query = http_build_query($query, NULL, '&');
238: if ($query !== '') {
239: $path .= '?' . $query;
240: }
241: }
242: $this->attrs['href'] = $path;
243: return $this;
244: }
245:
246:
247:
248: 249: 250: 251: 252: 253:
254: final public function setHtml($html)
255: {
256: if ($html === NULL) {
257: $html = '';
258:
259: } elseif (is_array($html)) {
260: throw new Nette\InvalidArgumentException("Textual content must be a scalar, " . gettype($html) ." given.");
261:
262: } else {
263: $html = (string) $html;
264: }
265:
266: $this->removeChildren();
267: $this->children[] = $html;
268: return $this;
269: }
270:
271:
272:
273: 274: 275: 276:
277: final public function getHtml()
278: {
279: $s = '';
280: foreach ($this->children as $child) {
281: if (is_object($child)) {
282: $s .= $child->render();
283: } else {
284: $s .= $child;
285: }
286: }
287: return $s;
288: }
289:
290:
291:
292: 293: 294: 295: 296: 297:
298: final public function setText($text)
299: {
300: if (!is_array($text)) {
301: $text = htmlspecialchars((string) $text, ENT_NOQUOTES);
302: }
303: return $this->setHtml($text);
304: }
305:
306:
307:
308: 309: 310: 311:
312: final public function getText()
313: {
314: return html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8');
315: }
316:
317:
318:
319: 320: 321: 322: 323:
324: final public function add($child)
325: {
326: return $this->insert(NULL, $child);
327: }
328:
329:
330:
331: 332: 333: 334: 335: 336:
337: final public function create($name, $attrs = NULL)
338: {
339: $this->insert(NULL, $child = static::el($name, $attrs));
340: return $child;
341: }
342:
343:
344:
345: 346: 347: 348: 349: 350: 351: 352:
353: public function insert($index, $child, $replace = FALSE)
354: {
355: if ($child instanceof Html || is_scalar($child)) {
356: if ($index === NULL) {
357: $this->children[] = $child;
358:
359: } else {
360: array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child));
361: }
362:
363: } else {
364: throw new Nette\InvalidArgumentException("Child node must be scalar or Html object, " . (is_object($child) ? get_class($child) : gettype($child)) ." given.");
365: }
366:
367: return $this;
368: }
369:
370:
371:
372: 373: 374: 375: 376: 377:
378: final public function offsetSet($index, $child)
379: {
380: $this->insert($index, $child, TRUE);
381: }
382:
383:
384:
385: 386: 387: 388: 389:
390: final public function offsetGet($index)
391: {
392: return $this->children[$index];
393: }
394:
395:
396:
397: 398: 399: 400: 401:
402: final public function offsetExists($index)
403: {
404: return isset($this->children[$index]);
405: }
406:
407:
408:
409: 410: 411: 412: 413:
414: public function offsetUnset($index)
415: {
416: if (isset($this->children[$index])) {
417: array_splice($this->children, (int) $index, 1);
418: }
419: }
420:
421:
422:
423: 424: 425: 426:
427: final public function count()
428: {
429: return count($this->children);
430: }
431:
432:
433:
434: 435: 436: 437:
438: public function removeChildren()
439: {
440: $this->children = array();
441: }
442:
443:
444:
445: 446: 447: 448: 449: 450:
451: final public function getIterator($deep = FALSE)
452: {
453: if ($deep) {
454: $deep = $deep > 0 ? \RecursiveIteratorIterator::SELF_FIRST : \RecursiveIteratorIterator::CHILD_FIRST;
455: return new \RecursiveIteratorIterator(new Nette\Iterators\Recursor(new \ArrayIterator($this->children)), $deep);
456:
457: } else {
458: return new Nette\Iterators\Recursor(new \ArrayIterator($this->children));
459: }
460: }
461:
462:
463:
464: 465: 466: 467:
468: final public function getChildren()
469: {
470: return $this->children;
471: }
472:
473:
474:
475: 476: 477: 478: 479:
480: final public function render($indent = NULL)
481: {
482: $s = $this->startTag();
483:
484: if (!$this->isEmpty) {
485:
486: if ($indent !== NULL) {
487: $indent++;
488: }
489: foreach ($this->children as $child) {
490: if (is_object($child)) {
491: $s .= $child->render($indent);
492: } else {
493: $s .= $child;
494: }
495: }
496:
497:
498: $s .= $this->endTag();
499: }
500:
501: if ($indent !== NULL) {
502: return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
503: }
504: return $s;
505: }
506:
507:
508:
509: final public function __toString()
510: {
511: return $this->render();
512: }
513:
514:
515:
516: 517: 518: 519:
520: final public function startTag()
521: {
522: if ($this->name) {
523: return '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>');
524:
525: } else {
526: return '';
527: }
528: }
529:
530:
531:
532: 533: 534: 535:
536: final public function endTag()
537: {
538: return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
539: }
540:
541:
542:
543: 544: 545: 546:
547: final public function attributes()
548: {
549: if (!is_array($this->attrs)) {
550: return '';
551: }
552:
553: $s = '';
554: foreach ($this->attrs as $key => $value) {
555: if ($value === NULL || $value === FALSE) {
556: continue;
557:
558: } elseif ($value === TRUE) {
559: if (static::$xhtml) {
560: $s .= ' ' . $key . '="' . $key . '"';
561: } else {
562: $s .= ' ' . $key;
563: }
564: continue;
565:
566: } elseif (is_array($value)) {
567: if ($key === 'data') {
568: foreach ($value as $k => $v) {
569: if ($v !== NULL && $v !== FALSE) {
570: $s .= ' data-' . $k . '="' . htmlspecialchars((string) $v) . '"';
571: }
572: }
573: continue;
574: }
575:
576: $tmp = NULL;
577: foreach ($value as $k => $v) {
578: if ($v != NULL) {
579:
580: $tmp[] = $v === TRUE ? $k : (is_string($k) ? $k . ':' . $v : $v);
581: }
582: }
583: if ($tmp === NULL) {
584: continue;
585: }
586:
587: $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
588:
589: } else {
590: $value = (string) $value;
591: }
592:
593: $s .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
594: }
595:
596: $s = str_replace('@', '@', $s);
597: return $s;
598: }
599:
600:
601:
602: 603: 604:
605: public function __clone()
606: {
607: foreach ($this->children as $key => $value) {
608: if (is_object($value)) {
609: $this->children[$key] = clone $value;
610: }
611: }
612: }
613:
614: }
615: