Passed
Push — master ( 207f22...1de42a )
by Bruno
12:47 queued 03:59
created

HTMLElement::getRenderHTML()   F

Complexity

Conditions 20
Paths 145

Size

Total Lines 64
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 22.4034

Importance

Changes 0
Metric Value
cc 20
eloc 33
c 0
b 0
f 0
nc 145
nop 2
dl 0
loc 64
ccs 27
cts 33
cp 0.8182
crap 22.4034
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 declare(strict_types=1);
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
     * @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
58 9
        if (!empty($content)) {
59 5
            $this->setContent($content, true, $raw);
60
        }
61 9
    }
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
     * @return string tag name
95
     */
96 4
    public function getTag()
97
    {
98 4
        return $this->tag;
99
    }
100
101
    /**
102
     * Modifies our tag
103
     *
104
     * @param string $tag
105
     * @return HTMLElement Itself
106
     */
107 1
    public function setTag($tag)
108
    {
109 1
        $this->tag = $tag;
110 1
        return $this;
111
    }
112
113
    /**
114
     * Return a attribute
115
     * @param string $name The name of attribute
116
     * @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 1
        return $this->attributes[$name] ?? [];
121
    }
122
123
    /**
124
     * Return the content of element
125
     * @return mixed array of HTMLElement and string (text)
126
     */
127 3
    public function getContent()
128
    {
129 3
        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
     * @return HTMLElement Itself
151
     */
152 1
    public function setAttribute(string $name, $value): HTMLElement
153
    {
154 1
        $this->setAttributes([$name => $value], true);
155 1
        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
     * @return HTMLElement Itself
163
     */
164 1
    public function addAttribute(string $name, $value): HTMLElement
165
    {
166 1
        $this->setAttributes([$name => $value], false);
167 1
        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
     *
178
     */
179 9
    public function setAttributes(array $attributes, $overwrite = true): HTMLElement
180
    {
181 9
        foreach ($attributes as $atrib => $value) {
182 8
            if (is_array($value)) {
183 2
                $values = $value;
184
            } else {
185 8
                $values = [$value];
186
            }
187
188 8
            if ($overwrite) {
189 8
                $this->attributes[$atrib] = $values;
190
            } else {
191 1
                if (empty($this->attributes[$atrib])) {
192 1
                    $this->attributes[$atrib] = [];
193
                }
194
195 8
                $this->attributes[$atrib] = array_merge($this->attributes[$atrib], $values);
196
            }
197
        }
198 9
        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
     * @return HTMLElement Itself
213
     */
214 1
    public function addAttributes(array $attributes): HTMLElement
215
    {
216 1
        $this->setAttributes($attributes, false);
217 1
        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
     * @return HTMLElement Itself
230
     */
231 7
    public function setContent($content, $overwrite = true, $raw = false, $prepend = false): HTMLElement
232
    {
233
        // TODO Don't work with reference objects, change it
234 7
        if (!is_array($content)) {
235 4
            $content = [$content];
236
        }
237
238 7
        if ($raw === false) {
239 5
            foreach ($content as &$item) {
240 5
                if (is_string($item)) {
241 5
                    $item = htmlspecialchars($item);
242
                }
243
            }
244
        }
245
246 7
        if ($overwrite) {
247 5
            $this->content = $content;
248
        } else {
249 4
            if ($prepend) {
250
                $this->content = array_merge($content, $this->content);
251
            } else {
252 4
                $this->content = array_merge($this->content, $content);
253
            }
254
        }
255 7
        return $this;
256
    }
257
258
    /**
259
     * Aliases to HTMLElement::setContent($content, false);
260
     * @see setContent
261
     * @param string|HTMLElement|string[]|HTMLElement[] $content
262
     * @return HTMLElement Itself
263
     */
264 3
    public function addContent($content, bool $raw = false): HTMLElement
265
    {
266 3
        $this->setContent($content, false, $raw);
267 3
        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
     * @return HTMLElement[]
308
     */
309 1
    public function get(string $selector)
310
    {
311 1
        $tag = null;
312 1
        $attr = null;
313 1
        $val = null;
314
315
        // 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 1
            $attr = "class";
330 1
            list($tag, $val) = explode(".", $selector);
331
        } else {
332 1
            $tag = $selector;
333
        }
334
335 1
        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
     * @return HTMLElement[]
347
     */
348 1
    public function getElements(string $tag, string $attr, string $val)
349
    {
350 1
        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
     * @return HTMLElement[]
360
     */
361 1
    protected function getInternal(HTMLElement $element, string $tag = null, string $attr = null, string $val = null)
362
    {
363 1
        if ($this->match($element, $tag, $attr, $val)) {
364 1
            $return = [$element];
365
        } else {
366 1
            $return = [];
367
        }
368
369 1
        foreach ($element->getContent() as $content) {
370 1
            if ($content instanceof HTMLElement) {
371 1
                $return = array_merge($return, $this->getInternal($content, $tag, $attr, $val));
372
            }
373
        }
374
375 1
        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
     * @return boolean - true when satisfy and false otherwise
385
     */
386 1
    protected function match(HTMLElement $element, $tag, $attr, $val): bool
387
    {
388 1
        if (!empty($tag)) {
389 1
            if ($element->getTag() != $tag) {
390 1
                return false;
391
            }
392
        } elseif (empty($attr)) {
393
            // Only when $tag and $attr is empty
394 1
            return false;
395
        }
396
397 1
        if (!empty($attr)) {
398 1
            $values = $element->getAttribute($attr);
399 1
            if (count($values) == 0) {
400 1
                return false;
401
            }
402
403 1
            if (!empty($val)) {
404 1
                return in_array($val, $values);
405
            }
406
        }
407
408 1
        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
     *
419
     * @param string $indentString String used to indent HTML code. Use '' for a compact version.
420
     * @param integer $level The current indentation level.
421
     * @return string (html code)
422
     */
423 5
    public function getRenderHTML($indentString = '  ', $level = 0): string
424
    {
425
426
        // skip empty non renderable
427 5
        if ($this->renderIfEmpty === false) {
428
            if (!count($this->content)) {
429
                return '';
430
            }
431
        }
432
433
        // start
434 5
        $data = [];
435
436
        // if this is not empty, the tag
437 5
        if (!empty($this->tag)) {
438 5
            $open = [];
439 5
            $open[] = ($level > 0 ? $indentString : '') . // initial indentation
440 5
                '<' . htmlspecialchars($this->tag);
441
442
            // render tag attributes
443 5
            foreach ($this->attributes as $atrib => $value) {
444 4
                $open[] = $atrib . '="' . htmlspecialchars(implode(' ', $value)) . '"';
445
            }
446 5
            $data[] = join(' ', $open) . (in_array($this->tag, self::STANDALONE_TAGS) ? '/>' : '>');
447
        }
448
449
        // recurse
450 5
        $contentdata = [];
451 5
        $emptyfieldset = ($this->tag == 'fieldset'); // avoid rendering fieldset with only a "legend"
452 5
        foreach ($this->content as $content) {
453 2
            if ($content instanceof HTMLElement) {
454 1
                $c = $content->getRenderHTML($indentString, $level + 1);
455 1
                if ($this->tag == 'fieldset' and $content->getTag() != 'legend' and $c) {
456
                    $emptyfieldset = false;
457
                }
458 1
                $contentdata[] = $c;
459
            } else {
460 2
                $emptyfieldset = false;
461 2
                $contentdata[] = $indentString . $content;
462
            }
463
        }
464
465
        // handle special empty cases
466 5
        if ($emptyfieldset) {
467
            return '';
468 5
        } elseif ($contentdata == [] && $this->renderIfEmpty === false) {
469
            return '';
470
        }
471
472
        // join content
473 5
        $data = array_merge($data, $contentdata);
474
475
        // handle closing
476 5
        if (!empty($this->tag)
477 5
            && !in_array($this->tag, self::STANDALONE_TAGS)
478
        ) {
479 5
            $data[] = '</' . htmlspecialchars($this->tag) . '>';
480
        }
481
482 5
        if ($indentString && $level === 0) {
483
            $data[] = "\n";
484
        }
485
486 5
        return implode(($indentString ? "\n" : '') . str_repeat($indentString, $level), $data);
487
    }
488
489
    /**
490
     * Clone HTMLElement object and its child
491
     * @return Object HTMLElement
492
     */
493
    public function __clone()
494
    {
495
        $obj = new HTMLElement($this->tag, $this->attributes);
496
        foreach ($this->content as $content) {
497
            if ($content instanceof HTMLElement) {
498
                $obj->addContent(clone $content);
499
            } else {
500
                $obj->addContent($content);
501
            }
502
        }
503
    }
504
505
    public function replace(HTMLElement $e): void
506
    {
507
        $this->tag = $e->tag;
508
        $this->attributes = $e->attributes;
509
        $this->content = $e->content;
510
    }
511
512
    /**
513
     * Clear All Attributes
514
     */
515
    public function clearAttributes(): HTMLElement
516
    {
517
        $this->attributes = [];
518
        return $this;
519
    }
520
521
522
    /**
523
     * Clear All Content
524
     */
525 1
    public function clearContent(): HTMLElement
526
    {
527 1
        $this->content = [];
528 1
        return $this;
529
    }
530
531
    /**
532
     * Similar to array_walk(). Applied to this HTMLElement and all its children.
533
     * Does not call callback for text content.
534
     *
535
     * @param callable $f
536
     * @return HTMLElement self
537
     */
538 1
    public function walk(callable $f): HTMLElement
539
    {
540 1
        $f($this);
541 1
        foreach ($this->content as $content) {
542 1
            if ($content instanceof HTMLElement) {
543 1
                $content->walk($f);
544
            }
545
        }
546 1
        return $this;
547
    }
548
549
    /**
550
     * Similar to array_map(). Calls callback for text content too.
551
     *
552
     * @param callable $f
553
     * @return array
554
     */
555 1
    public function map(callable $f, bool $recurse = true): array
556
    {
557 1
        $data = [$f($this)];
558 1
        foreach ($this->content as $content) {
559 1
            if ($recurse && $content instanceof HTMLElement) {
560 1
                $data = array_merge($data, $content->map($f, $recurse));
561
            } else {
562 1
                $data[] = $f($content);
563
            }
564
        }
565 1
        return $data;
566
    }
567
568
    /**
569
     * Similar to array_filter().
570
     * Does not call callback for text content.
571
     *
572
     * @param callable $f
573
     * @return HTMLElement[] The filtered elements.
574
     */
575 1
    public function filter(callable $f, bool $recurse = true): array
576
    {
577 1
        $deleted = [];
578 1
        foreach ($this->content as $key => $content) {
579 1
            if ($content instanceof HTMLElement) {
580 1
                if (!$f($content)) {
581 1
                    $deleted[] = $this->content[$key];
582 1
                    unset($this->content[$key]);
583
                } elseif ($recurse) {
584 1
                    $content->filter($f, $recurse);
585
                }
586
            }
587
        }
588 1
        return $deleted;
589
    }
590
}
591