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