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: 30: 31: 32: 33: 34: 35: 36: 37:
38: final class Jyxo_HtmlTag
39: {
40: 41: 42: 43: 44:
45: private $xhtml = true;
46:
47: 48: 49: 50: 51:
52: private $tag = '';
53:
54: 55: 56: 57: 58:
59: private $isEmptyElement = false;
60:
61: 62: 63: 64: 65:
66: private $attributes = array();
67:
68: 69: 70: 71: 72:
73: private $children = array();
74:
75: 76: 77: 78: 79:
80: private $noEncode = array();
81:
82: 83: 84: 85: 86:
87: private $contentOnly = FALSE;
88:
89: 90: 91: 92: 93:
94: private static $attrs = array(
95: 'accesskey' => true, 'action' => true, 'alt' => true, 'cellpadding' => true, 'cellspacing' => true, 'checked' => true, 'class' => true,
96: 'cols' => true, 'disabled' => true, 'for' => true, 'href' => true, 'id' => true, 'label' => true, 'method' => true, 'name' => true, 'onblur' => true,
97: 'onchange' => true, 'onclick' => true, 'onfocus' => true, 'onkeyup' => true, 'onsubmit' => true, 'readonly' => true, 'rel' => true,
98: 'rows' => true, 'selected' => true, 'size' => true, 'src' => true, 'style' => true, 'tabindex' => true, 'title' => true, 'type' => true,
99: 'value' => true, 'width' => true,
100: );
101:
102: 103: 104: 105: 106:
107: private $emptyElements = array(
108: 'br' => true, 'hr' => true, 'img' => true, 'input' => true, 'meta' => true, 'link' => true
109: );
110:
111: 112: 113: 114: 115:
116: private $requiredAttrs = array(
117: 'option' => 'value',
118: 'optgroup' => 'label'
119: );
120:
121: 122: 123: 124: 125: 126: 127:
128: private function __construct($tag)
129: {
130: $this->tag = (string) $tag;
131: }
132:
133: 134: 135: 136: 137: 138:
139: public static function create($tag)
140: {
141: return new self($tag);
142: }
143:
144: 145: 146: 147: 148: 149: 150: 151: 152:
153: public static function createFromSource($html)
154: {
155: if (preg_match('~<(\w+)(\s[^>]*)+>(.*)((<[^>]+>)?[^>]*)$~', $html, $matches)) {
156: $tag = new self($matches[1]);
157: if ('' !== $matches[3]) {
158:
159: $tag->setText($matches[3]);
160: }
161: if (preg_match_all('/(\w+)\s*=\s*"([^"]+)"/', $matches[2], $submatches, PREG_PATTERN_ORDER)) {
162: $attrs = array_combine($submatches[1], $submatches[2]);
163: $tag->setAttributes($attrs);
164: }
165: return $tag;
166: }
167: throw new InvalidArgumentException('Zadaný text neobsahuje validní html');
168: }
169:
170: 171: 172: 173: 174:
175: public function open()
176: {
177: if (TRUE === $this->contentOnly) {
178: return '';
179: }
180: $this->isEmptyElement = isset($this->emptyElements[$this->tag]);
181: $buff = '';
182: foreach ($this->attributes as $name => $value) {
183: if (isset(self::$attrs[$name])) {
184: if (($name === 'selected' || $name === 'checked' || $name === 'readonly' || $name === 'disabled') && $value) {
185: $value = $name;
186: }
187: $notEmpty = $value !== null && $value !== '' && $value !== false;
188: if ($this->isRequiredAttr($this->tag, $name) || $notEmpty) {
189:
190: if (!isset($this->noEncode[$name])) {
191: $value = Jyxo_String::escape($value);
192: }
193: $attrString = sprintf(' %s="%s"', $name, $value);
194: if ($name === 'value') {
195: if ($this->tag === 'textarea') {
196: $buff .= '';
197: } else {
198: $buff .= $attrString;
199: }
200: } else {
201: $buff .= $attrString;
202: }
203: }
204: }
205: }
206: $buff = '<' . $this->tag . $buff . ($this->xhtml ? $this->isEmptyElement ? ' />' : '>' : '>');
207: return $buff;
208: }
209:
210: 211: 212: 213: 214:
215: public function content()
216: {
217: $buff = '';
218: if (!$this->isEmptyElement) {
219:
220: $hasValue = isset($this->attributes['value']);
221: $hasText = isset($this->attributes['text']);
222: if ($hasValue || $hasText) {
223: $text = $hasText ? $this->attributes['text'] : $this->attributes['value'];
224: $noEncode = isset($this->noEncode['value']) || isset($this->noEncode['text']);
225:
226: $noEncode = 'script' === $this->tag ? true : $noEncode;
227: $buff .= $noEncode ? $text : Jyxo_String::escape($text);
228: }
229: }
230: if (!$this->isEmptyElement && !empty($this->children)) {
231: foreach ($this->children as $element) {
232: $buff .= $element->render();
233: }
234: }
235: return $buff;
236: }
237:
238: 239: 240: 241: 242:
243: public function close()
244: {
245: if (true === $this->contentOnly) {
246: return '';
247: }
248: $close = '</' . $this->tag . '>';
249: if ($this->xhtml) {
250: $buff = !$this->isEmptyElement ? $close : '';
251: } else {
252: $buff = $close;
253: }
254: $buff .= "\n";
255: return $buff;
256: }
257:
258: 259: 260: 261: 262:
263: public function render()
264: {
265: return $this->open() . $this->content() . $this->close();
266: }
267:
268: 269: 270: 271: 272: 273:
274: public function addChild(Jyxo_HtmlTag $element)
275: {
276: $this->children[] = $element;
277: return $this;
278: }
279:
280: 281: 282: 283: 284: 285:
286: public function addChildren(array $elements)
287: {
288: foreach ($elements as $element) {
289: $this->addChild($element);
290: }
291: return $this;
292: }
293:
294: 295: 296: 297: 298: 299:
300: public function setAttributes(array $attributes)
301: {
302: foreach ($attributes as $name => $value) {
303: $this->attributes[strtolower($name)] = $value;
304: }
305: return $this;
306: }
307:
308: 309: 310: 311: 312: 313:
314: public function setNoEncode($attribute)
315: {
316: $this->noEncode[$attribute] = true;
317:
318: return $this;
319: }
320:
321: 322: 323: 324: 325: 326:
327: public function setContentOnly($contentOnly)
328: {
329: $this->contentOnly = (bool) $contentOnly;
330: return $this;
331: }
332:
333: 334: 335: 336: 337:
338: public function __toString()
339: {
340: return $this->render();
341: }
342:
343: 344: 345: 346: 347: 348: 349:
350: public function __call($method, $args)
351: {
352: $type = $method[0] === 's' ? 'set' : 'get';
353: if ($type === 'set') {
354: $this->attributes[strtolower(substr($method, 3))] = $args[0];
355: return $this;
356: } else {
357: if (isset($this->attributes[strtolower(substr($method, 3))])) {
358: return $this->attributes[strtolower(substr($method, 3))];
359: }
360: return '';
361: }
362: }
363:
364: 365: 366: 367: 368: 369:
370: public function __get($name)
371: {
372:
373: return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
374: }
375:
376: 377: 378: 379: 380: 381: 382:
383: private function isRequiredAttr($tag, $attr)
384: {
385: if (isset($this->requiredAttrs[$tag])) {
386: if (is_array($this->requiredAttrs[$tag])) {
387: return in_array($attr, $this->requiredAttrs[$tag]);
388: }
389: return $attr == $this->requiredAttrs[$tag];
390: }
391: return false;
392: }
393: }
394: