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