Passed
Push — master ( bc0996...c35245 )
by Bruno
05:47
created

HTMLElement::getRenderHTML()   F

Complexity

Conditions 20
Paths 145

Size

Total Lines 64
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 22.1993

Importance

Changes 0
Metric Value
cc 20
eloc 33
c 0
b 0
f 0
nc 145
nop 2
dl 0
loc 64
ccs 28
cts 34
cp 0.8235
crap 22.1993
rs 3.7916

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Formularium;
4
5
use PHP_CodeSniffer\Generators\HTML;
6
7
/**
8
 * Class that encapsule DOM elements. Similar to PHP DOMElement but more flexible.
9
 * This is not used for parsing, but to build HTML.
10
 */
11
class HTMLElement
12
{
13
    const STANDALONE_TAGS = ['img', 'hr', 'br', 'input', 'meta', 'col', 'command', 'link', 'param', 'source', 'embed'];
14
15
    /**
16
     * The HTML attributes and respectives values
17
     * This is an associative array wich:
18
     *     key is the attribute and
19
     *     value is the array with attributes values
20
     * @var array
21
     */
22
    protected $attributes = [];
23
24
    /**
25
     * Tag name
26
     * @var string
27
     */
28
    protected $tag;
29
30
    /**
31
     * The content of Element
32
     * @var array
33
     */
34
    protected $content = [];
35
36
    /**
37
     * If this tag has no children, still render it?
38
     * @var boolean
39
     */
40
    protected $renderIfEmpty = true;
41
42
    /**
43
     * Create a HTML Element
44
     * @param string $tag The tag name of Element
45
     * @param array $attributes The attribute with values
46
     * @param mixed $content The content of element, can be:
47
     *                - string (with text content)
48
     *                - HTMLElement
49
     *                - array with others elements or text
50 9
     * @param boolean $raw If true, do not escape content.
51
     */
52 9
    public function __construct(string $tag, array $attributes = [], $content = '', $raw = false)
53
    {
54 9
        $this->tag = $tag;
55
56 9
        $this->setAttributes($attributes);
57 5
58
        if (!empty($content)) {
59 9
            $this->setContent($content, true, $raw);
60
        }
61
    }
62
63
    /**
64
     * Create a HTML Element
65
     * @param string $tag The tag name of Element
66
     * @param array $attributes The attribute with values
67
     * @param mixed $content The content of element, can be:
68
     *                - string (with text content)
69
     *                - HTMLElement
70
     *                - array with others elements or text
71
     * @param boolean $raw If true, do not escape content.
72
     * @return HTMLElement
73
     */
74
    public static function factory(string $tag, array $attributes = [], $content = '', $raw = false): HTMLElement
75
    {
76
        return new self($tag, $attributes, $content, $raw);
77
    }
78
79
    /**
80
     * Sets whether this tag will render if it is empty (that is,
81
     * no contents or children)
82
     *
83
     * @param boolean $val
84
     * @return HTMLElement Itself
85
     */
86
    public function setRenderIfEmpty(bool $val): HTMLElement
87
    {
88
        $this->renderIfEmpty = $val;
89
        return $this;
90
    }
91
92
    /**
93
     * Return a tag
94 4
     * @return string tag name
95
     */
96 4
    public function getTag()
97
    {
98
        return $this->tag;
99
    }
100
101
    /**
102
     * Modifies our tag
103
     *
104
     * @param string $tag
105 1
     * @return HTMLElement Itself
106
     */
107 1
    public function setTag($tag)
108 1
    {
109
        $this->tag = $tag;
110
        return $this;
111
    }
112
113
    /**
114
     * Return a attribute
115
     * @param string $name The name of attribute
116 1
     * @return array Return the list of values, if attribute don't exist return empty array
117
     */
118 1
    public function getAttribute($name): array
119
    {
120
        return $this->attributes[$name] ?? [];
121
    }
122
123
    /**
124
     * Return the content of element
125 3
     * @return mixed array of HTMLElement and string (text)
126
     */
127 3
    public function getContent()
128
    {
129
        return $this->content;
130
    }
131
132
    public function isEmpty(): bool
133
    {
134
        return count($this->content) > 0;
135
    }
136
137
    /**
138
     *
139
     * @return int
140
     */
141
    public function getCountChildren(): int
142
    {
143
        return count($this->content);
144
    }
145
146
    /**
147
     * Set a attribute value, if attribute exist overwrite it
148
     * @param string $name
149
     * @param mixed $value Can be a string or array of string
150 1
     * @return HTMLElement Itself
151
     */
152 1
    public function setAttribute(string $name, $value): HTMLElement
153 1
    {
154
        $this->setAttributes([$name => $value], true);
155
        return $this;
156
    }
157
158
    /**
159
     * Add an attribute value, if attribute exist append value
160
     * @param string $name
161
     * @param mixed $value Can be a string or array of string
162 1
     * @return HTMLElement Itself
163
     */
164 1
    public function addAttribute(string $name, $value): HTMLElement
165 1
    {
166
        $this->setAttributes([$name => $value], false);
167
        return $this;
168
    }
169
170
    /**
171
     * Set a set of attributes
172
     * @param array $attributes Associative array wich
173
     * 				key is attributes names
174
     *              value is string or array values
175
     * @param bool $overwrite if true and attribute name exist overwrite them
176
     * @return HTMLElement Itself
177 9
     *
178
     */
179 9
    public function setAttributes(array $attributes, $overwrite = true): HTMLElement
180 8
    {
181 2
        foreach ($attributes as $atrib => $value) {
182
            if (is_array($value)) {
183 8
                $values = $value;
184
            } else {
185
                $values = [$value];
186 8
            }
187 8
188
            if ($overwrite) {
189 1
                $this->attributes[$atrib] = $values;
190 1
            } else {
191
                if (empty($this->attributes[$atrib])) {
192
                    $this->attributes[$atrib] = [];
193 8
                }
194
195
                $this->attributes[$atrib] = array_merge($this->attributes[$atrib], $values);
196 9
            }
197
        }
198
        return $this;
199
    }
200
201
    public function removeAttribute(string $attribute): HTMLElement
202
    {
203
        if (array_key_exists($attribute, $this->attributes)) {
204
            unset($this->attributes[$attribute]);
205
        }
206
        return $this;
207
    }
208
209
    /**
210
     * Aliases to HTMLElement::setAttributes($content, false);
211
     * @see setAttributes
212 1
     * @return HTMLElement Itself
213
     */
214 1
    public function addAttributes(array $attributes): HTMLElement
215 1
    {
216
        $this->setAttributes($attributes, false);
217
        return $this;
218
    }
219
220
    /**
221
     * Set content (dom objects or texts) to element
222
     * @param string|HTMLElement|string[]|HTMLElement[] $content The content of element, can be:
223
     *                - string (with text content)
224
     *                - HTMLElement
225
     *                - array with others elements or text
226
     * @param bool $overwrite if true overwrite content otherwise append the content
227
     * @param bool $raw If true, this is raw content (html) and should not be escaped.
228
     * @param bool $prepend If true prepend instead of appending
229 7
     * @return HTMLElement Itself
230
     */
231
    public function setContent($content, $overwrite = true, $raw = false, $prepend = false): HTMLElement
232 7
    {
233 4
        // TODO Don't work with reference objects, change it
234
        if (!is_array($content)) {
235
            $content = [$content];
236 7
        }
237 5
238 5
        if ($raw === false) {
239 5
            foreach ($content as &$item) {
240
                if (is_string($item)) {
241
                    $item = htmlspecialchars($item);
242
                }
243
            }
244 7
        }
245 5
246
        if ($overwrite) {
247 4
            $this->content = $content;
248
        } else {
249
            if ($prepend) {
250 4
                $this->content = array_merge($content, $this->content);
251
            } else {
252
                $this->content = array_merge($this->content, $content);
253 7
            }
254
        }
255
        return $this;
256
    }
257
258
    /**
259
     * Aliases to HTMLElement::setContent($content, false);
260
     * @see setContent
261
     * @param string|HTMLElement|string[]|HTMLElement[] $content
262 3
     * @return HTMLElement Itself
263
     */
264 3
    public function addContent($content, bool $raw = false): HTMLElement
265 3
    {
266
        $this->setContent($content, false, $raw);
267
        return $this;
268
    }
269
270
    /**
271
     * Appends content nodes to the bottom of this element.
272
     *
273
     * @see setContent
274
     * @param string|HTMLElement|string[]|HTMLElement[] $content
275
     * @param boolean $raw
276
     * @return HTMLElement
277
     */
278
    public function appendContent($content, bool $raw = false): HTMLElement
279
    {
280
        $this->setContent($content, false, $raw);
281
        return $this;
282
    }
283
284
    /**
285
     * Prepends content nodes to the beginning of this element.
286
     *
287
     * @see setContent
288
     * @param string|HTMLElement|string[]|HTMLElement[] $content
289
     * @param boolean $raw
290
     * @return HTMLElement
291
     */
292
    public function prependContent($content, bool $raw = false): HTMLElement
293
    {
294
        $this->setContent($content, false, $raw, true);
295
        return $this;
296
    }
297
298
    /**
299
     * Find and return elements using selector
300
     * @param string $selector A selector of elements based in jQuery
301
     *						'eee' - Select elements 'eee' (with tag)
302
     * 						'#ii' - Select a element with id attribute 'ii'
303
     * 						'.cc' - Select elements with class attribute 'cc'
304
     * 						'[a=v]' - Select elements with 'a' attribute with 'v' value
305
     * 						'e#i' - Select elements 'e' with id attribute 'i'
306
     * 						'e.c' - Select elements 'e' with class attribute 'c'
307 1
     * @return HTMLElement[]
308
     */
309 1
    public function get(string $selector)
310 1
    {
311 1
        $tag = null;
312
        $attr = null;
313
        $val = null;
314 1
315 1
        // Define what could be found
316 1
        $selector = trim($selector);
317 1
        if ($selector[0] == "#") {
318 1
            $attr = "id";
319 1
            $val = mb_substr($selector, 1);
320 1
        } elseif ($selector[0] == ".") {
321 1
            $attr = "class";
322 1
            $val = mb_substr($selector, 1);
323 1
        } elseif (mb_strpos($selector, "=") !== false) {
324 1
            list($attr, $val) = explode("=", substr($selector, 1, -1));
325 1
        } elseif (mb_strpos($selector, "#") !== false) {
326 1
            $attr = "id";
327 1
            list($tag, $val) = explode("#", $selector);
328 1
        } elseif (mb_strpos($selector, ".") !== false) {
329
            $attr = "class";
330 1
            list($tag, $val) = explode(".", $selector);
331
        } else {
332
            $tag = $selector;
333 1
        }
334
335
        return $this->getInternal($this, $tag, $attr, $val);
336
    }
337
338
    /**
339
     * Find and return elements based in $tag, $attr, $val
340
     * The $tag or $attr must be a value
341
     *
342
     * @see HTMLElement::get
343
     * @param string $tag tag of search
344
     * @param string $attr attribute of search
345
     * @param string $val value of attribute search
346 1
     * @return HTMLElement[]
347
     */
348 1
    public function getElements(string $tag, string $attr, string $val)
349
    {
350
        return $this->getInternal($this, $tag, $attr, $val);
351
    }
352
353
    /**
354
     * Recursive function to found elements
355
     * @param HTMLElement $element Element that will be available
356
     * @param string $tag Tag or null value to compare
357
     * @param string $attr Attribute name or null value to compare
358
     * @param string $val Value of attribute or null value to compare
359 1
     * @return HTMLElement[]
360
     */
361 1
    protected function getInternal(HTMLElement $element, string $tag = null, string $attr = null, string $val = null)
362 1
    {
363
        if ($this->match($element, $tag, $attr, $val)) {
364 1
            $return = [$element];
365
        } else {
366
            $return = [];
367 1
        }
368 1
369 1
        foreach ($element->getContent() as $content) {
370
            if ($content instanceof HTMLElement) {
371
                $return = array_merge($return, $this->getInternal($content, $tag, $attr, $val));
372
            }
373 1
        }
374
375
        return $return;
376
    }
377
378
    /**
379
     * Return a boolean based on match of the element with $tag, $attr or $val
380
     * @param HTMLElement $element Element that will be available
381
     * @param string $tag Tag or null value to compare
382
     * @param string $attr Attribute name or null value to compare
383
     * @param string $val Value of attribute or null value to compare
384 1
     * @return boolean - true when satisfy and false otherwise
385
     */
386 1
    protected function match(HTMLElement $element, $tag, $attr, $val): bool
387 1
    {
388 1
        if (!empty($tag)) {
389
            if ($element->getTag() != $tag) {
390
                return false;
391
            }
392 1
        } elseif (empty($attr)) {
393
            // Only when $tag and $attr is empty
394
            return false;
395 1
        }
396 1
397 1
        if (!empty($attr)) {
398 1
            $values = $element->getAttribute($attr);
399
            if (count($values) == 0) {
400
                return false;
401 1
            }
402 1
403
            if (!empty($val)) {
404
                return in_array($val, $values);
405
            }
406 1
        }
407
408
        return true;
409
    }
410
411
    public function __toString()
412
    {
413
        return $this->getRenderHTML();
414
    }
415
    
416
    /**
417
     * Return the html element code including all children
418 5
     * @return string (html code)
419
     */
420
    public function getRenderHTML($indentString = '  ', $level = 0): string
421 5
    {
422
423
        // skip empty non renderable
424
        if ($this->renderIfEmpty === false) {
425
            if (!count($this->content)) {
426
                return '';
427
            }
428 5
        }
429
430
        // start
431 5
        $data = [];
432 5
433 5
        // if this is not empty, the tag
434 5
        if (!empty($this->tag)) {
435
            $open = [];
436
            $open[] = ($level > 0 ? $indentString : '') . // initial indentation
437 5
                '<' . htmlspecialchars($this->tag);
438 4
439
            // render tag attributes
440 5
            foreach ($this->attributes as $atrib => $value) {
441
                $open[] = $atrib . '="' . htmlspecialchars(implode(' ', $value)) . '"';
442
            }
443
            $data[] = join(' ', $open) . (in_array($this->tag, self::STANDALONE_TAGS) ? '/>' : '>');
444 5
        }
445 5
446 5
        // recurse
447 2
        $contentdata = [];
448 1
        $emptyfieldset = ($this->tag == 'fieldset'); // avoid rendering fieldset with only a "legend"
449 1
        foreach ($this->content as $content) {
450
            if ($content instanceof HTMLElement) {
451
                $c = $content->getRenderHTML($indentString, $level + 1);
452 1
                if ($this->tag == 'fieldset' and $content->getTag() != 'legend' and $c) {
453
                    $emptyfieldset = false;
454 2
                }
455 2
                $contentdata[] = $c;
456
            } else {
457
                $emptyfieldset = false;
458
                $contentdata[] = $indentString . $content;
459
            }
460 5
        }
461
462 5
        // handle special empty cases
463
        if ($emptyfieldset) {
464
            return '';
465
        } elseif ($contentdata == [] && $this->renderIfEmpty === false) {
466
            return '';
467 5
        }
468
469
        // join content
470 5
        $data = array_merge($data, $contentdata);
471 5
472 5
        // handle closing
473 5
        if (!empty($this->tag)
474
            && !in_array($this->tag, self::STANDALONE_TAGS)
475
        ) {
476 5
            $data[] = '</' . htmlspecialchars($this->tag) . '>';
477
        }
478
479 5
        if ($indentString && $level === 0) {
480
            $data[] = "\n";
481
        }
482
483 5
        return implode(($indentString ? "\n" : '') . str_repeat($indentString, $level), $data);
484
    }
485
486
    /**
487
     * Clone HTMLElement object and its child
488
     * @return Object HTMLElement
489
     */
490
    public function __clone()
491
    {
492
        $obj = new HTMLElement($this->tag, $this->attributes);
493
        foreach ($this->content as $content) {
494
            if ($content instanceof HTMLElement) {
495
                $obj->addContent(clone $content);
496
            } else {
497
                $obj->addContent($content);
498
            }
499
        }
500
    }
501
502
    /**
503
     * Clear All Attributes
504
     */
505
    public function clearAttributes(): HTMLElement
506
    {
507
        $this->attributes = [];
508
        return $this;
509
    }
510
511
512
    /**
513
     * Clear All Content
514
     */
515 1
    public function clearContent(): HTMLElement
516
    {
517 1
        $this->content = [];
518 1
        return $this;
519
    }
520
521
    /**
522
     * Similar to array_walk(). Applied to this HTMLElement and all its children.
523
     * Does not call text content.
524
     *
525
     * @param callable $f
526
     * @return HTMLElement self
527
     */
528 1
    public function walk(callable $f): HTMLElement
529
    {
530 1
        $f($this);
531 1
        foreach ($this->content as $content) {
532 1
            if ($content instanceof HTMLElement) {
533 1
                $content->walk($f);
534
            }
535
        }
536 1
        return $this;
537
    }
538
539
    /**
540
     * Similar to array_map(). Calls for text content too.
541
     *
542
     * @param callable $f
543
     * @return HTMLElement
544
     */
545 1
    public function map(callable $f): array
546
    {
547 1
        $data = [$f($this)];
548 1
        foreach ($this->content as $content) {
549 1
            if ($content instanceof HTMLElement) {
550 1
                $data = array_merge($data, $content->map($f));
551
            } else {
552 1
                $data[] = $f($content);
553
            }
554
        }
555 1
        return $data;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $data returns the type array which is incompatible with the documented return type Formularium\HTMLElement.
Loading history...
556
    }
557
558
    /**
559
     * Similar to array_filter().
560
     *
561
     * @param callable $f
562
     * @return HTMLElement
563
     */
564 1
    public function filter(callable $f): HTMLElement
565
    {
566 1
        foreach ($this->content as $key => $content) {
567 1
            if ($content instanceof HTMLElement) {
568 1
                if (!$f($content)) {
569 1
                    unset($this->content[$key]);
570
                } else {
571 1
                    $content->filter($f);
572
                }
573
            }
574
        }
575 1
        return $this;
576
    }
577
}
578