Packages

  • Nette
    • Application
      • Diagnostics
      • Responses
      • Routers
      • UI
    • Caching
      • Storages
    • ComponentModel
    • Config
      • Adapters
      • Extensions
    • Database
      • Diagnostics
      • Drivers
      • Reflection
      • Table
    • DI
      • Diagnostics
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
      • Macros
    • Loaders
    • Localization
    • Mail
    • Reflection
    • Security
      • Diagnostics
    • Templating
    • Utils
      • PhpGenerator
  • NetteModule
  • none

Classes

Exceptions

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