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: class TexyHtml extends TexyObject implements ArrayAccess, IteratorAggregate
28: {
29:
30: private $name;
31:
32:
33: private $isEmpty;
34:
35:
36: public $attrs = array();
37:
38:
39: protected $children = array();
40:
41:
42: public static $xhtml = TRUE;
43:
44:
45: public static $emptyElements = array('img'=>1,'hr'=>1,'br'=>1,'input'=>1,'meta'=>1,'area'=>1,
46: 'base'=>1,'col'=>1,'link'=>1,'param'=>1,'basefont'=>1,'frame'=>1,'isindex'=>1,'wbr'=>1,'embed'=>1);
47:
48:
49: public static $inlineElements = array('ins'=>0,'del'=>0,'tt'=>0,'i'=>0,'b'=>0,'big'=>0,'small'=>0,'em'=>0,
50: 'strong'=>0,'dfn'=>0,'code'=>0,'samp'=>0,'kbd'=>0,'var'=>0,'cite'=>0,'abbr'=>0,'acronym'=>0,
51: 'sub'=>0,'sup'=>0,'q'=>0,'span'=>0,'bdo'=>0,'a'=>0,'object'=>1,'img'=>1,'br'=>1,'script'=>1,
52: 'map'=>0,'input'=>1,'select'=>1,'textarea'=>1,'label'=>0,'button'=>1,
53: 'u'=>0,'s'=>0,'strike'=>0,'font'=>0,'applet'=>1,'basefont'=>0, 54: 'embed'=>1,'wbr'=>0,'nobr'=>0,'canvas'=>1, 55: );
56:
57:
58: public static $optionalEnds = array('body'=>1,'head'=>1,'html'=>1,'colgroup'=>1,'dd'=>1,
59: 'dt'=>1,'li'=>1,'option'=>1,'p'=>1,'tbody'=>1,'td'=>1,'tfoot'=>1,'th'=>1,'thead'=>1,'tr'=>1);
60:
61:
62: public static $prohibits = array(
63: 'a' => array('a','button'),
64: 'img' => array('pre'),
65: 'object' => array('pre'),
66: 'big' => array('pre'),
67: 'small' => array('pre'),
68: 'sub' => array('pre'),
69: 'sup' => array('pre'),
70: 'input' => array('button'),
71: 'select' => array('button'),
72: 'textarea' => array('button'),
73: 'label' => array('button', 'label'),
74: 'button' => array('button'),
75: 'form' => array('button', 'form'),
76: 'fieldset' => array('button'),
77: 'iframe' => array('button'),
78: 'isindex' => array('button'),
79: );
80:
81:
82:
83: 84: 85: 86: 87: 88:
89: public static function el($name = NULL, $attrs = NULL)
90: {
91: $el = new self;
92: $el->setName($name);
93: if (is_array($attrs)) {
94: $el->attrs = $attrs;
95: } elseif ($attrs !== NULL) {
96: $el->setText($attrs);
97: }
98: return $el;
99: }
100:
101:
102:
103: 104: 105: 106: 107: 108: 109:
110: final public function setName($name, $empty = NULL)
111: {
112: if ($name !== NULL && !is_string($name)) {
113: throw new InvalidArgumentException("Name must be string or NULL.");
114: }
115:
116: $this->name = $name;
117: $this->isEmpty = $empty === NULL ? isset(self::$emptyElements[$name]) : (bool) $empty;
118: return $this;
119: }
120:
121:
122:
123: 124: 125: 126:
127: final public function getName()
128: {
129: return $this->name;
130: }
131:
132:
133:
134: 135: 136: 137:
138: final public function isEmpty()
139: {
140: return $this->isEmpty;
141: }
142:
143:
144:
145: 146: 147: 148: 149: 150:
151: final public function __set($name, $value)
152: {
153: $this->attrs[$name] = $value;
154: }
155:
156:
157:
158: 159: 160: 161: 162:
163: final public function &__get($name)
164: {
165: return $this->attrs[$name];
166: }
167:
168:
169:
170: 171: 172: 173: 174: 175:
176: 177: 178: 179: 180: 181: 182: 183: 184: 185:
186:
187:
188: 189: 190: 191: 192: 193:
194: final public function href($path, $query = NULL)
195: {
196: if ($query) {
197: $query = http_build_query($query, NULL, '&');
198: if ($query !== '') $path .= '?' . $query;
199: }
200: $this->attrs['href'] = $path;
201: return $this;
202: }
203:
204:
205:
206: 207: 208: 209: 210:
211: final public function setText($text)
212: {
213: if (is_scalar($text)) {
214: $this->removeChildren();
215: $this->children = array($text);
216: } elseif ($text !== NULL) {
217: throw new InvalidArgumentException('Content must be scalar.');
218: }
219: return $this;
220: }
221:
222:
223:
224: 225: 226: 227:
228: final public function getText()
229: {
230: $s = '';
231: foreach ($this->children as $child) {
232: if (is_object($child)) return FALSE;
233: $s .= $child;
234: }
235: return $s;
236: }
237:
238:
239:
240: 241: 242: 243: 244:
245: final public function add($child)
246: {
247: return $this->insert(NULL, $child);
248: }
249:
250:
251:
252: 253: 254: 255: 256: 257:
258: final public function create($name, $attrs = NULL)
259: {
260: $this->insert(NULL, $child = self::el($name, $attrs));
261: return $child;
262: }
263:
264:
265:
266: 267: 268: 269: 270: 271: 272: 273:
274: public function insert($index, $child, $replace = FALSE)
275: {
276: if ($child instanceof TexyHtml || is_string($child)) {
277: if ($index === NULL) { 278: $this->children[] = $child;
279:
280: } else { 281: array_splice($this->children, (int) $index, $replace ? 1 : 0, array($child));
282: }
283:
284: } else {
285: throw new InvalidArgumentException('Child node must be scalar or TexyHtml object.');
286: }
287:
288: return $this;
289: }
290:
291:
292:
293: 294: 295: 296: 297: 298:
299: final public function offsetSet($index, $child)
300: {
301: $this->insert($index, $child, TRUE);
302: }
303:
304:
305:
306: 307: 308: 309: 310:
311: final public function offsetGet($index)
312: {
313: return $this->children[$index];
314: }
315:
316:
317:
318: 319: 320: 321: 322:
323: final public function offsetExists($index)
324: {
325: return isset($this->children[$index]);
326: }
327:
328:
329:
330: 331: 332: 333: 334:
335: public function offsetUnset($index)
336: {
337: if (isset($this->children[$index])) {
338: array_splice($this->children, (int) $index, 1);
339: }
340: }
341:
342:
343:
344: 345: 346: 347:
348: final public function count()
349: {
350: return count($this->children);
351: }
352:
353:
354:
355: 356: 357: 358:
359: public function removeChildren()
360: {
361: $this->children = array();
362: }
363:
364:
365:
366: 367: 368: 369:
370: final public function getIterator()
371: {
372: return new ArrayIterator($this->children);
373: }
374:
375:
376:
377: 378: 379: 380:
381: final public function getChildren()
382: {
383: return $this->children;
384: }
385:
386:
387:
388: 389: 390: 391: 392:
393: final public function toString(Texy $texy)
394: {
395: $ct = $this->getContentType();
396: $s = $texy->protect($this->startTag(), $ct);
397:
398: 399: if ($this->isEmpty) {
400: return $s;
401: }
402:
403: 404: foreach ($this->children as $child) {
405: if (is_object($child)) {
406: $s .= $child->toString($texy);
407: } else {
408: $s .= $child;
409: }
410: }
411:
412: 413: return $s . $texy->protect($this->endTag(), $ct);
414: }
415:
416:
417:
418: 419: 420: 421: 422:
423: final public function toHtml(Texy $texy)
424: {
425: return $texy->stringToHtml($this->toString($texy));
426: }
427:
428:
429:
430: 431: 432: 433: 434:
435: final public function toText(Texy $texy)
436: {
437: return $texy->stringToText($this->toString($texy));
438: }
439:
440:
441:
442: 443: 444: 445:
446: public function startTag()
447: {
448: if (!$this->name) {
449: return '';
450: }
451:
452: $s = '<' . $this->name;
453:
454: if (is_array($this->attrs)) {
455: foreach ($this->attrs as $key => $value)
456: {
457: 458: if ($value === NULL || $value === FALSE) continue;
459:
460: 461: if ($value === TRUE) {
462: 463: if (self::$xhtml) $s .= ' ' . $key . '="' . $key . '"';
464: 465: else $s .= ' ' . $key;
466: continue;
467:
468: } elseif (is_array($value)) {
469:
470: 471: $tmp = NULL;
472: foreach ($value as $k => $v) {
473: 474: if ($v == NULL) continue;
475:
476: if (is_string($k)) $tmp[] = $k . ':' . $v;
477: else $tmp[] = $v;
478: }
479:
480: if (!$tmp) continue;
481: $value = implode($key === 'style' ? ';' : ' ', $tmp);
482:
483: } else {
484: $value = (string) $value;
485: }
486:
487: 488: $value = str_replace(array('&', '"', '<', '>', '@'), array('&', '"', '<', '>', '@'), $value);
489: $s .= ' ' . $key . '="' . Texy::freezeSpaces($value) . '"';
490: }
491: }
492:
493: 494: if (self::$xhtml && $this->isEmpty) {
495: return $s . ' />';
496: }
497: return $s . '>';
498: }
499:
500:
501:
502: 503: 504: 505:
506: public function endTag()
507: {
508: if ($this->name && !$this->isEmpty) {
509: return '</' . $this->name . '>';
510: }
511: return '';
512: }
513:
514:
515:
516: 517: 518:
519: public function __clone()
520: {
521: foreach ($this->children as $key => $value) {
522: if (is_object($value)) {
523: $this->children[$key] = clone $value;
524: }
525: }
526: }
527:
528:
529:
530: 531: 532:
533: final public function getContentType()
534: {
535: if (!isset(self::$inlineElements[$this->name])) return Texy::CONTENT_BLOCK;
536:
537: return self::$inlineElements[$this->name] ? Texy::CONTENT_REPLACED : Texy::CONTENT_MARKUP;
538: }
539:
540:
541:
542: 543: 544: 545:
546: final public function validateAttrs($dtd)
547: {
548: if (isset($dtd[$this->name])) {
549: $allowed = $dtd[$this->name][0];
550: if (is_array($allowed)) {
551: foreach ($this->attrs as $attr => $foo) {
552: if (!isset($allowed[$attr])) unset($this->attrs[$attr]);
553: }
554: }
555: }
556: }
557:
558:
559:
560: public function validateChild($child, $dtd)
561: {
562: if (isset($dtd[$this->name])) {
563: if ($child instanceof TexyHtml) $child = $child->name;
564: return isset($dtd[$this->name][1][$child]);
565: } else {
566: return TRUE; 567: }
568: }
569:
570:
571:
572: 573: 574: 575: 576: 577:
578: final public function parseLine(Texy $texy, $s)
579: {
580: 581: 582: $s = str_replace(array('\)', '\*'), array(')', '*'), $s);
583:
584: $parser = new TexyLineParser($texy, $this);
585: $parser->parse($s);
586: return $parser;
587: }
588:
589:
590:
591: 592: 593: 594: 595: 596: 597:
598: final public function parseBlock(Texy $texy, $s, $indented = FALSE)
599: {
600: $parser = new TexyBlockParser($texy, $this, $indented);
601: $parser->parse($s);
602: }
603:
604: }
605: