~ubuntu-branches/debian/experimental/php-nette/experimental

« back to all changes in this revision

Viewing changes to Nette-2.1.0RC/Nette/Utils/Html.php

  • Committer: Package Import Robot
  • Author(s): David Prévot
  • Date: 2013-11-30 08:47:54 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20131130084754-4udf1xsu9085tnfc
Tags: 2.1.0~rc-1
* New upstream branch
* Update copyright

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
/**
 
4
 * This file is part of the Nette Framework (http://nette.org)
 
5
 *
 
6
 * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
 
7
 *
 
8
 * For the full copyright and license information, please view
 
9
 * the file license.txt that was distributed with this source code.
 
10
 */
 
11
 
 
12
namespace Nette\Utils;
 
13
 
 
14
use Nette;
 
15
 
 
16
 
 
17
/**
 
18
 * HTML helper.
 
19
 *
 
20
 * <code>
 
21
 * $el = Html::el('a')->href($link)->setText('Nette');
 
22
 * $el->class = 'myclass';
 
23
 * echo $el;
 
24
 *
 
25
 * echo $el->startTag(), $el->endTag();
 
26
 * </code>
 
27
 *
 
28
 * @author     David Grudl
 
29
 */
 
30
class Html extends Nette\Object implements \ArrayAccess, \Countable, \IteratorAggregate
 
31
{
 
32
        /** @var string  element's name */
 
33
        private $name;
 
34
 
 
35
        /** @var bool  is element empty? */
 
36
        private $isEmpty;
 
37
 
 
38
        /** @var array  element's attributes */
 
39
        public $attrs = array();
 
40
 
 
41
        /** @var array  of Html | string nodes */
 
42
        protected $children = array();
 
43
 
 
44
        /** @var bool  use XHTML syntax? */
 
45
        public static $xhtml = FALSE;
 
46
 
 
47
        /** @var array  empty (void) elements */
 
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
         * Static factory.
 
54
         * @param  string element name (or NULL)
 
55
         * @param  array|string element's attributes (or textual content)
 
56
         * @return Html
 
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
         * Changes element's name.
 
83
         * @param  string
 
84
         * @param  bool  Is element empty?
 
85
         * @return self
 
86
         * @throws Nette\InvalidArgumentException
 
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
         * Returns element's name.
 
102
         * @return string
 
103
         */
 
104
        final public function getName()
 
105
        {
 
106
                return $this->name;
 
107
        }
 
108
 
 
109
 
 
110
        /**
 
111
         * Is element empty?
 
112
         * @return bool
 
113
         */
 
114
        final public function isEmpty()
 
115
        {
 
116
                return $this->isEmpty;
 
117
        }
 
118
 
 
119
 
 
120
        /**
 
121
         * Sets multiple attributes.
 
122
         * @param  array
 
123
         * @return self
 
124
         */
 
125
        public function addAttributes(array $attrs)
 
126
        {
 
127
                $this->attrs = array_merge($this->attrs, $attrs);
 
128
                return $this;
 
129
        }
 
130
 
 
131
 
 
132
        /**
 
133
         * Overloaded setter for element's attribute.
 
134
         * @param  string    HTML attribute name
 
135
         * @param  mixed     HTML attribute value
 
136
         * @return void
 
137
         */
 
138
        final public function __set($name, $value)
 
139
        {
 
140
                $this->attrs[$name] = $value;
 
141
        }
 
142
 
 
143
 
 
144
        /**
 
145
         * Overloaded getter for element's attribute.
 
146
         * @param  string    HTML attribute name
 
147
         * @return mixed     HTML attribute value
 
148
         */
 
149
        final public function &__get($name)
 
150
        {
 
151
                return $this->attrs[$name];
 
152
        }
 
153
 
 
154
 
 
155
        /**
 
156
         * Overloaded tester for element's attribute.
 
157
         * @param  string    HTML attribute name
 
158
         * @return bool
 
159
         */
 
160
        final public function __isset($name)
 
161
        {
 
162
                return isset($this->attrs[$name]);
 
163
        }
 
164
 
 
165
 
 
166
        /**
 
167
         * Overloaded unsetter for element's attribute.
 
168
         * @param  string    HTML attribute name
 
169
         * @return void
 
170
         */
 
171
        final public function __unset($name)
 
172
        {
 
173
                unset($this->attrs[$name]);
 
174
        }
 
175
 
 
176
 
 
177
        /**
 
178
         * Overloaded setter for element's attribute.
 
179
         * @param  string  HTML attribute name
 
180
         * @param  array   (string) HTML attribute value or pair?
 
181
         * @return self
 
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) { // invalid
 
198
 
 
199
                } elseif (count($args) === 1) { // set
 
200
                        $this->attrs[$m] = $args[0];
 
201
 
 
202
                } elseif ((string) $args[0] === '') {
 
203
                        $tmp = & $this->attrs[$m]; // appending empty value? -> ignore, but ensure it exists
 
204
 
 
205
                } elseif (!isset($this->attrs[$m]) || is_array($this->attrs[$m])) { // needs array
 
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
         * Special setter for element's attribute.
 
218
         * @param  string path
 
219
         * @param  array query
 
220
         * @return self
 
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
         * Sets element's HTML content.
 
237
         * @param  string
 
238
         * @return self
 
239
         * @throws Nette\InvalidArgumentException
 
240
         */
 
241
        final public function setHtml($html)
 
242
        {
 
243
                if (is_array($html)) {
 
244
                        throw new Nette\InvalidArgumentException("Textual content must be a scalar, " . gettype($html) ." given.");
 
245
                }
 
246
                $this->removeChildren();
 
247
                $this->children[] = (string) $html;
 
248
                return $this;
 
249
        }
 
250
 
 
251
 
 
252
        /**
 
253
         * Returns element's HTML content.
 
254
         * @return string
 
255
         */
 
256
        final public function getHtml()
 
257
        {
 
258
                $s = '';
 
259
                foreach ($this->children as $child) {
 
260
                        if (is_object($child)) {
 
261
                                $s .= $child->render();
 
262
                        } else {
 
263
                                $s .= $child;
 
264
                        }
 
265
                }
 
266
                return $s;
 
267
        }
 
268
 
 
269
 
 
270
        /**
 
271
         * Sets element's textual content.
 
272
         * @param  string
 
273
         * @return self
 
274
         * @throws Nette\InvalidArgumentException
 
275
         */
 
276
        final public function setText($text)
 
277
        {
 
278
                if (!is_array($text) && !$text instanceof self) {
 
279
                        $text = htmlspecialchars((string) $text, ENT_NOQUOTES);
 
280
                }
 
281
                return $this->setHtml($text);
 
282
        }
 
283
 
 
284
 
 
285
        /**
 
286
         * Returns element's textual content.
 
287
         * @return string
 
288
         */
 
289
        final public function getText()
 
290
        {
 
291
                return html_entity_decode(strip_tags($this->getHtml()), ENT_QUOTES, 'UTF-8');
 
292
        }
 
293
 
 
294
 
 
295
        /**
 
296
         * Adds new element's child.
 
297
         * @param  Html|string child node
 
298
         * @return self
 
299
         */
 
300
        final public function add($child)
 
301
        {
 
302
                return $this->insert(NULL, $child);
 
303
        }
 
304
 
 
305
 
 
306
        /**
 
307
         * Creates and adds a new Html child.
 
308
         * @param  string  elements's name
 
309
         * @param  array|string element's attributes (or textual content)
 
310
         * @return Html  created element
 
311
         */
 
312
        final public function create($name, $attrs = NULL)
 
313
        {
 
314
                $this->insert(NULL, $child = static::el($name, $attrs));
 
315
                return $child;
 
316
        }
 
317
 
 
318
 
 
319
        /**
 
320
         * Inserts child node.
 
321
         * @param  int
 
322
         * @param  Html node
 
323
         * @param  bool
 
324
         * @return self
 
325
         * @throws \Exception
 
326
         */
 
327
        public function insert($index, $child, $replace = FALSE)
 
328
        {
 
329
                if ($child instanceof Html || is_scalar($child)) {
 
330
                        if ($index === NULL) { // append
 
331
                                $this->children[] = $child;
 
332
 
 
333
                        } else { // insert or replace
 
334
                                array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child));
 
335
                        }
 
336
 
 
337
                } else {
 
338
                        throw new Nette\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
         * Inserts (replaces) child node (\ArrayAccess implementation).
 
347
         * @param  int
 
348
         * @param  Html node
 
349
         * @return void
 
350
         */
 
351
        final public function offsetSet($index, $child)
 
352
        {
 
353
                $this->insert($index, $child, TRUE);
 
354
        }
 
355
 
 
356
 
 
357
        /**
 
358
         * Returns child node (\ArrayAccess implementation).
 
359
         * @param  int index
 
360
         * @return mixed
 
361
         */
 
362
        final public function offsetGet($index)
 
363
        {
 
364
                return $this->children[$index];
 
365
        }
 
366
 
 
367
 
 
368
        /**
 
369
         * Exists child node? (\ArrayAccess implementation).
 
370
         * @param  int index
 
371
         * @return bool
 
372
         */
 
373
        final public function offsetExists($index)
 
374
        {
 
375
                return isset($this->children[$index]);
 
376
        }
 
377
 
 
378
 
 
379
        /**
 
380
         * Removes child node (\ArrayAccess implementation).
 
381
         * @param  int index
 
382
         * @return void
 
383
         */
 
384
        public function offsetUnset($index)
 
385
        {
 
386
                if (isset($this->children[$index])) {
 
387
                        array_splice($this->children, (int) $index, 1);
 
388
                }
 
389
        }
 
390
 
 
391
 
 
392
        /**
 
393
         * Required by the \Countable interface.
 
394
         * @return int
 
395
         */
 
396
        final public function count()
 
397
        {
 
398
                return count($this->children);
 
399
        }
 
400
 
 
401
 
 
402
        /**
 
403
         * Removed all children.
 
404
         * @return void
 
405
         */
 
406
        public function removeChildren()
 
407
        {
 
408
                $this->children = array();
 
409
        }
 
410
 
 
411
 
 
412
        /**
 
413
         * Iterates over a elements.
 
414
         * @param  bool    recursive?
 
415
         * @param  string  class types filter
 
416
         * @return \RecursiveIterator
 
417
         */
 
418
        final public function getIterator($deep = FALSE)
 
419
        {
 
420
                if ($deep) {
 
421
                        $deep = $deep > 0 ? \RecursiveIteratorIterator::SELF_FIRST : \RecursiveIteratorIterator::CHILD_FIRST;
 
422
                        return new \RecursiveIteratorIterator(new Nette\Iterators\Recursor(new \ArrayIterator($this->children)), $deep);
 
423
 
 
424
                } else {
 
425
                        return new Nette\Iterators\Recursor(new \ArrayIterator($this->children));
 
426
                }
 
427
        }
 
428
 
 
429
 
 
430
        /**
 
431
         * Returns all of children.
 
432
         * @return array
 
433
         */
 
434
        final public function getChildren()
 
435
        {
 
436
                return $this->children;
 
437
        }
 
438
 
 
439
 
 
440
        /**
 
441
         * Renders element's start tag, content and end tag.
 
442
         * @param  int indent
 
443
         * @return string
 
444
         */
 
445
        final public function render($indent = NULL)
 
446
        {
 
447
                $s = $this->startTag();
 
448
 
 
449
                if (!$this->isEmpty) {
 
450
                        // add content
 
451
                        if ($indent !== NULL) {
 
452
                                $indent++;
 
453
                        }
 
454
                        foreach ($this->children as $child) {
 
455
                                if (is_object($child)) {
 
456
                                        $s .= $child->render($indent);
 
457
                                } else {
 
458
                                        $s .= $child;
 
459
                                }
 
460
                        }
 
461
 
 
462
                        // add end tag
 
463
                        $s .= $this->endTag();
 
464
                }
 
465
 
 
466
                if ($indent !== NULL) {
 
467
                        return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2));
 
468
                }
 
469
                return $s;
 
470
        }
 
471
 
 
472
 
 
473
        final public function __toString()
 
474
        {
 
475
                return $this->render();
 
476
        }
 
477
 
 
478
 
 
479
        /**
 
480
         * Returns element's start tag.
 
481
         * @return string
 
482
         */
 
483
        final public function startTag()
 
484
        {
 
485
                if ($this->name) {
 
486
                        return '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>');
 
487
 
 
488
                } else {
 
489
                        return '';
 
490
                }
 
491
        }
 
492
 
 
493
 
 
494
        /**
 
495
         * Returns element's end tag.
 
496
         * @return string
 
497
         */
 
498
        final public function endTag()
 
499
        {
 
500
                return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : '';
 
501
        }
 
502
 
 
503
 
 
504
        /**
 
505
         * Returns element's attributes.
 
506
         * @return string
 
507
         */
 
508
        final public function attributes()
 
509
        {
 
510
                if (!is_array($this->attrs)) {
 
511
                        return '';
 
512
                }
 
513
 
 
514
                $s = '';
 
515
                foreach ($this->attrs as $key => $value) {
 
516
                        if ($value === NULL || $value === FALSE) {
 
517
                                continue;
 
518
 
 
519
                        } elseif ($value === TRUE) {
 
520
                                if (static::$xhtml) {
 
521
                                        $s .= ' ' . $key . '="' . $key . '"';
 
522
                                } else {
 
523
                                        $s .= ' ' . $key;
 
524
                                }
 
525
                                continue;
 
526
 
 
527
                        } elseif (is_array($value)) {
 
528
                                if ($key === 'data') {
 
529
                                        foreach ($value as $k => $v) {
 
530
                                                if ($v !== NULL && $v !== FALSE) {
 
531
                                                        $q = strpos($v, '"') === FALSE ? '"' : "'";
 
532
                                                        $s .= ' data-' . $k . '=' . $q . str_replace(array('&', $q), array('&amp;', $q === '"' ? '&quot;' : '&#39;'), $v) . $q;
 
533
                                                }
 
534
                                        }
 
535
                                        continue;
 
536
                                }
 
537
 
 
538
                                $tmp = NULL;
 
539
                                foreach ($value as $k => $v) {
 
540
                                        if ($v != NULL) { // intentionally ==, skip NULLs & empty string
 
541
                                                //  composite 'style' vs. 'others'
 
542
                                                $tmp[] = $v === TRUE ? $k : (is_string($k) ? $k . ':' . $v : $v);
 
543
                                        }
 
544
                                }
 
545
                                if ($tmp === NULL) {
 
546
                                        continue;
 
547
                                }
 
548
 
 
549
                                $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp);
 
550
 
 
551
                        } else {
 
552
                                $value = (string) $value;
 
553
                        }
 
554
 
 
555
                        $q = strpos($value, '"') === FALSE ? '"' : "'";
 
556
                        $s .= ' ' . $key . '=' . $q . str_replace(array('&', $q), array('&amp;', $q === '"' ? '&quot;' : '&#39;'), $value) . $q;
 
557
                }
 
558
 
 
559
                $s = str_replace('@', '&#64;', $s);
 
560
                return $s;
 
561
        }
 
562
 
 
563
 
 
564
        /**
 
565
         * Clones all children too.
 
566
         */
 
567
        public function __clone()
 
568
        {
 
569
                foreach ($this->children as $key => $value) {
 
570
                        if (is_object($value)) {
 
571
                                $this->children[$key] = clone $value;
 
572
                        }
 
573
                }
 
574
        }
 
575
 
 
576
}