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