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