Completed
Push — master ( d8e3ee...0777f1 )
by Kevin
03:18
created

Element   C

Complexity

Total Complexity 67

Size/Duplication

Total Lines 540
Duplicated Lines 10 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 100%

Importance

Changes 10
Bugs 3 Features 7
Metric Value
wmc 67
c 10
b 3
f 7
lcom 1
cbo 4
dl 54
loc 540
ccs 327
cts 327
cp 1
rs 5.7097

24 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 17 3
A getAttributes() 0 4 1
A getAttribute() 0 8 2
A hasAttribute() 0 4 1
A addAttribute() 0 11 2
A removeAttribute() 0 11 2
A getChildren() 0 4 1
A hasChild() 0 4 1
A appendChild() 0 7 1
A prependChild() 0 7 1
A removeChild() 11 11 2
A getName() 0 4 1
F clean() 13 108 23
A fixSelf() 0 3 1
A removeInvalidChildren() 0 3 1
A removeInvalidSelf() 0 4 1
B getAllowedAttributes() 0 151 1
A getAttributeParameters() 0 15 3
C remove() 30 30 8
A toHtml() 0 11 2
B buildStartTag() 0 17 5
A buildChildrenHtml() 0 14 2
A getType() 0 4 1
A __toString() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Element often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Element, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Groundskeeper\Tokens;
4
5
use Groundskeeper\Configuration;
6
use Psr\Log\LoggerInterface;
7
8
class Element extends AbstractToken implements Cleanable, ContainsChildren, Removable
9
{
10
    const ATTR_BOOL      = 'ci_boo'; // boolean
11
    const ATTR_CI_ENUM   = 'ci_enu'; // case-insensitive enumeration
12
    const ATTR_CI_SSENUM = 'ci_sse'; // case-insensitive space-separated enumeration
13
    const ATTR_INT       = 'ci_int'; // integer
14
    const ATTR_JS        = 'cs_jsc'; // javascript
15
    const ATTR_CI_STRING = 'ci_str'; // case-insensitive string
16
    const ATTR_CS_STRING = 'cs_str'; // case-sensitive string
17
    const ATTR_URI       = 'cs_uri'; // uri
18
19
    /** @var array */
20
    protected $attributes;
21
22
    /** @var array[Token] */
23
    protected $children;
24
25
    /** @var string */
26
    private $name;
27
28
    /**
29
     * Constructor
30
     */
31 103
    public function __construct(Configuration $configuration, $name, array $attributes = array())
32
    {
33 103
        parent::__construct($configuration);
34
35 103
        $this->attributes = array();
36 103
        foreach ($attributes as $key => $value) {
37 56
            $this->addAttribute($key, $value);
38 103
        }
39
40 103
        $this->children = array();
41
42 103
        if (!is_string($name)) {
43 3
            throw new \InvalidArgumentException('Element name must be string type.');
44 1
        }
45
46 102
        $this->name = trim(strtolower($name));
47 102
    }
48
49
    /**
50
     * Getter for 'attributes'.
51
     */
52 2
    public function getAttributes()
53 2
    {
54 2
        return $this->attributes;
55
    }
56
57 3
    public function getAttribute($key)
58
    {
59 3
        if (!$this->hasAttribute($key)) {
60 1
            throw new \InvalidArgumentException('Invalid attribute key: ' . $key);
61
        }
62
63 3
        return $this->attributes[$key];
64 1
    }
65
66
    /**
67
     * Hasser for 'attributes'.
68
     *
69
     * @param string $key
70
     *
71
     * @return bool True if the attribute is present.
72
     */
73 24
    public function hasAttribute($key)
74
    {
75 24
        return array_key_exists($key, $this->attributes);
76
    }
77
78 56
    public function addAttribute($key, $value)
79 1
    {
80 56
        $key = trim(strtolower($key));
81 56
        if ($key == '') {
82 1
            throw new \InvalidArgumentException('Invalid empty attribute key.');
83 1
        }
84
85 55
        $this->attributes[$key] = $value;
86
87 55
        return $this;
88
    }
89
90 2
    public function removeAttribute($key)
91 1
    {
92 2
        $key = trim(strtolower($key));
93 2
        if (isset($this->attributes[$key])) {
94 2
            unset($this->attributes[$key]);
95
96 2
            return true;
97
        }
98
99 2
        return false;
100
    }
101
102
    /**
103
     * Required by ContainsChildren interface.
104
     */
105 6
    public function getChildren()
106
    {
107 6
        return $this->children;
108
    }
109
110
    /**
111
     * Required by ContainsChildren interface.
112
     */
113 1
    public function hasChild(Token $token)
114
    {
115 1
        return array_search($token, $this->children, true) !== false;
116
    }
117
118
    /**
119
     * Required by ContainsChildren interface.
120
     */
121 87
    public function appendChild(Token $token)
122
    {
123 87
        $token->setParent($this);
124 87
        $this->children[] = $token;
125
126 87
        return $this;
127
    }
128
129
    /**
130
     * Required by ContainsChildren interface.
131
     */
132 5
    public function prependChild(Token $token)
133
    {
134 5
        $token->setParent($this);
135 5
        array_unshift($this->children, $token);
136
137 5
        return $this;
138
    }
139
140
    /**
141
     * Required by the ContainsChildren interface.
142
     */
143 27 View Code Duplication
    public function removeChild(Token $token)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
144
    {
145 27
        $key = array_search($token, $this->children, true);
146 27
        if ($key !== false) {
147 27
            unset($this->children[$key]);
148
149 27
            return true;
150
        }
151
152 1
        return false;
153
    }
154
155
    /**
156
     * Getter for 'name'.
157
     */
158 83
    public function getName()
159
    {
160 83
        return $this->name;
161
    }
162
163
    /**
164
     * Required by the Cleanable interface.
165
     */
166 86
    public function clean(LoggerInterface $logger)
167
    {
168 86
        if ($this->configuration->get('clean-strategy') == Configuration::CLEAN_STRATEGY_NONE) {
169 1
            return true;
170
        }
171
172
        // Remove non-standard attributes.
173 85
        if ($this->configuration->get('clean-strategy') != Configuration::CLEAN_STRATEGY_LENIENT) {
174 85
            foreach ($this->attributes as $name => $value) {
175 46
                $attributeParameters = $this->getAttributeParameters($name);
176 46
                if (empty($attributeParameters)) {
177 7
                    $logger->debug('Removing non-standard attribute "' . $name . '" from ' . $this);
178 7
                    unset($this->attributes[$name]);
179 7
                }
180 85
            }
181 85
        }
182
183 85
        foreach ($this->attributes as $name => $value) {
184
            // Validate attribute value.
185 47
            $attributeParameters = $this->getAttributeParameters($name);
186 47
            if (empty($attributeParameters)) {
187
                $attributeParameters = array(
188 5
                    'name' => $name,
189 5
                    'regex' => '/\S*/i',
190
                    'valueType' => self::ATTR_CS_STRING
191 5
                );
192 5
            }
193
194
            list($caseSensitivity, $attributeType) =
195 47
                explode('_', $attributeParameters['valueType']);
196
197
            // Handle case-insensitivity.
198
            // Standard is case-insensitive attribute values should be lower case.
199 47
            if ($caseSensitivity == 'ci' && $value !== true) {
200 13
                $newValue = strtolower($value);
201 13
                if ($newValue !== $value) {
202 2
                    $logger->debug('Within ' . $this . ', the value for the attribute "' . $name . '" is case-insensitive.  The value has been converted to lower case.');
203 2
                    $this->attributes[$name] = $newValue;
204 2
                }
205 13
            }
206
207
            // Validate value types.
208 47
            switch (substr($attributeType, 0, 3)) {
209 47
            case 'boo': // boolean
210 1
                if ($this->attributes[$name] !== true) {
211 1
                    $logger->debug('Within ' . $this . ', the attribute "' . $name . '" is a boolean attribute.  The value has been removed.');
212 1
                    $this->attributes[$name] = true;
213 1
                }
214
215 1
                break;
216
217 46
            case 'enu': // enumeration
218
                /// @todo
219 1
                break;
220
221 45
            case 'int': // integer
222 4 View Code Duplication
                if ($this->attributes[$name] === true) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223 1
                    if ($this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
224 1
                        $logger->debug('Within ' . $this . ', the value for the attribute "' . $name . '" is required to be an positive, non-zero integer.  The value is invalid, therefore the attribute has been removed.');
225 1
                        unset($this->attributes[$name]);
226 1
                    }
227
228 1
                    break;
229
                }
230
231 4
                if (!is_int($this->attributes[$name])) {
232 4
                    $origonalValue = (string) $this->attributes[$name];
233 4
                    $this->attributes[$name] = (int) $this->attributes[$name];
234 4
                    if ($origonalValue != ((string) $this->attributes[$name])) {
235 2
                        $logger->debug('Within ' . $this . ', the value for the attribute "' . $name . '" is required to be an positive, non-zero integer.  The value has been converted to an integer.');
236 2
                    }
237 4
                }
238
239 4 View Code Duplication
                if ($this->attributes[$name] <= 0 &&
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
240 4
                    $this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
241 1
                    $logger->debug('Within ' . $this . ', the value for the attribute "' . $name . '" is required to be an positive, non-zero integer.  The value is invalid, therefore the attribute has been removed.');
242 1
                    unset($this->attributes[$name]);
243 1
                }
244
245 4
                break;
246
247 42
            case 'uri': // URI
248
                /// @todo
249 14
                break;
250 47
            }
251 85
        }
252
253
        // Fix self (if possible)
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
254 85
        $this->fixSelf($logger);
255
256
        // Remove self or children?
257 85
        if ($this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
258
            // Remove self?
259 85
            if ($this->removeInvalidSelf($logger)) {
260 19
                return false;
261
            }
262
263
            // Remove children?
264 85
            $this->removeInvalidChildren($logger);
265 85
        }
266
267
        // Clean children.
268 85
        return AbstractToken::cleanChildTokens(
269 85
            $this->configuration,
270 85
            $this->children,
271
            $logger
272 85
        );
273
    }
274
275 79
    protected function fixSelf(LoggerInterface $logger)
276
    {
277 79
    }
278
279 76
    protected function removeInvalidChildren(LoggerInterface $logger)
280
    {
281 76
    }
282
283 78
    protected function removeInvalidSelf(LoggerInterface $logger)
284
    {
285 78
        return false;
286
    }
287
288 47
    protected function getAllowedAttributes()
289
    {
290
        return array(
291
            // Global Attributes
292 47
            '/^accesskey$/i' => self::ATTR_CS_STRING,
293 47
            '/^class$/i' => self::ATTR_CS_STRING,
294 47
            '/^contenteditable$/i' => self::ATTR_CS_STRING,
295 47
            '/^contextmenu$/i' => self::ATTR_CS_STRING,
296 47
            '/^data-\S/i' => self::ATTR_CS_STRING,
297 47
            '/^dir$/i' => self::ATTR_CI_ENUM . '("ltr","rtl"|"ltr")',
298 47
            '/^draggable$/i' => self::ATTR_CS_STRING,
299 47
            '/^dropzone$/i' => self::ATTR_CS_STRING,
300 47
            '/^hidden$/i' => self::ATTR_CS_STRING,
301 47
            '/^id$/i' => self::ATTR_CS_STRING,
302 47
            '/^is$/i' => self::ATTR_CS_STRING,
303 47
            '/^itemid$/i' => self::ATTR_CS_STRING,
304 47
            '/^itemprop$/i' => self::ATTR_CS_STRING,
305 47
            '/^itemref$/i' => self::ATTR_CS_STRING,
306 47
            '/^itemscope$/i' => self::ATTR_CS_STRING,
307 47
            '/^itemtype$/i' => self::ATTR_CS_STRING,
308 47
            '/^lang$/i' => self::ATTR_CI_STRING,
309 47
            '/^slot$/i' => self::ATTR_CS_STRING,
310 47
            '/^spellcheck$/i' => self::ATTR_CS_STRING,
311 47
            '/^style$/i' => self::ATTR_CS_STRING,
312 47
            '/^tabindex$/i' => self::ATTR_CS_STRING,
313 47
            '/^title$/i' => self::ATTR_CS_STRING,
314 47
            '/^translate$/i' => self::ATTR_CI_ENUM . '("yes","no",""|"yes")',
315
316
            // Event Handler Content Attributes
317
            // https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-content-attributes
318 47
            '/^onabort$/i' => self::ATTR_JS,
319 47
            '/^onautocomplete$/i' => self::ATTR_JS,
320 47
            '/^onautocompleteerror$/i' => self::ATTR_JS,
321 47
            '/^onblur$/i' => self::ATTR_JS,
322 47
            '/^oncancel$/i' => self::ATTR_JS,
323 47
            '/^oncanplay$/i' => self::ATTR_JS,
324 47
            '/^oncanplaythrough$/i' => self::ATTR_JS,
325 47
            '/^onchange$/i' => self::ATTR_JS,
326 47
            '/^onclick$/i' => self::ATTR_JS,
327 47
            '/^onclose$/i' => self::ATTR_JS,
328 47
            '/^oncontextmenu$/i' => self::ATTR_JS,
329 47
            '/^oncuechange$/i' => self::ATTR_JS,
330 47
            '/^ondblclick$/i' => self::ATTR_JS,
331 47
            '/^ondrag$/i' => self::ATTR_JS,
332 47
            '/^ondragend$/i' => self::ATTR_JS,
333 47
            '/^ondragenter$/i' => self::ATTR_JS,
334 47
            '/^ondragexit$/i' => self::ATTR_JS,
335 47
            '/^ondragleave$/i' => self::ATTR_JS,
336 47
            '/^ondragover$/i' => self::ATTR_JS,
337 47
            '/^ondragstart$/i' => self::ATTR_JS,
338 47
            '/^ondrop$/i' => self::ATTR_JS,
339 47
            '/^ondurationchange$/i' => self::ATTR_JS,
340 47
            '/^onemptied$/i' => self::ATTR_JS,
341 47
            '/^onended$/i' => self::ATTR_JS,
342 47
            '/^onerror$/i' => self::ATTR_JS,
343 47
            '/^onfocus$/i' => self::ATTR_JS,
344 47
            '/^oninput$/i' => self::ATTR_JS,
345 47
            '/^oninvalid$/i' => self::ATTR_JS,
346 47
            '/^onkeydown$/i' => self::ATTR_JS,
347 47
            '/^onkeypress$/i' => self::ATTR_JS,
348 47
            '/^onkeyup$/i' => self::ATTR_JS,
349 47
            '/^onload$/i' => self::ATTR_JS,
350 47
            '/^onloadeddata$/i' => self::ATTR_JS,
351 47
            '/^onloadedmetadata$/i' => self::ATTR_JS,
352 47
            '/^onloadstart$/i' => self::ATTR_JS,
353 47
            '/^onmousedown$/i' => self::ATTR_JS,
354 47
            '/^onmouseenter$/i' => self::ATTR_JS,
355 47
            '/^onmouseleave$/i' => self::ATTR_JS,
356 47
            '/^onmousemove$/i' => self::ATTR_JS,
357 47
            '/^onmouseout$/i' => self::ATTR_JS,
358 47
            '/^onmouseover$/i' => self::ATTR_JS,
359 47
            '/^onmouseup$/i' => self::ATTR_JS,
360 47
            '/^onwheel$/i' => self::ATTR_JS,
361 47
            '/^onpause$/i' => self::ATTR_JS,
362 47
            '/^onplay$/i' => self::ATTR_JS,
363 47
            '/^onplaying$/i' => self::ATTR_JS,
364 47
            '/^onprogress$/i' => self::ATTR_JS,
365 47
            '/^onratechange$/i' => self::ATTR_JS,
366 47
            '/^onreset$/i' => self::ATTR_JS,
367 47
            '/^onresize$/i' => self::ATTR_JS,
368 47
            '/^onscroll$/i' => self::ATTR_JS,
369 47
            '/^onseeked$/i' => self::ATTR_JS,
370 47
            '/^onseeking$/i' => self::ATTR_JS,
371 47
            '/^onselect$/i' => self::ATTR_JS,
372 47
            '/^onshow$/i' => self::ATTR_JS,
373 47
            '/^onstalled$/i' => self::ATTR_JS,
374 47
            '/^onsubmit$/i' => self::ATTR_JS,
375 47
            '/^onsuspend$/i' => self::ATTR_JS,
376 47
            '/^ontimeupdate$/i' => self::ATTR_JS,
377 47
            '/^ontoggle$/i' => self::ATTR_JS,
378 47
            '/^onvolumechange$/i' => self::ATTR_JS,
379 47
            '/^onwaiting$/i' => self::ATTR_JS,
380
381
            // WAI-ARIA
382
            // https://w3c.github.io/aria/aria/aria.html
383 47
            '/^role$/i' => self::ATTR_CI_STRING,
384
385
            // ARIA global states and properties
386 47
            '/^aria-atomic$/i' => self::ATTR_CS_STRING,
387 47
            '/^aria-busy$/i' => self::ATTR_CS_STRING,
388 47
            '/^aria-controls$/i' => self::ATTR_CS_STRING,
389 47
            '/^aria-current$/i' => self::ATTR_CS_STRING,
390 47
            '/^aria-describedby$/i' => self::ATTR_CS_STRING,
391 47
            '/^aria-details$/i' => self::ATTR_CS_STRING,
392 47
            '/^aria-disabled$/i' => self::ATTR_CS_STRING,
393 47
            '/^aria-dropeffect$/i' => self::ATTR_CS_STRING,
394 47
            '/^aria-errormessage$/i' => self::ATTR_CS_STRING,
395 47
            '/^aria-flowto$/i' => self::ATTR_CS_STRING,
396 47
            '/^aria-grabbed$/i' => self::ATTR_CS_STRING,
397 47
            '/^aria-haspopup$/i' => self::ATTR_CS_STRING,
398 47
            '/^aria-hidden$/i' => self::ATTR_CS_STRING,
399 47
            '/^aria-invalid$/i' => self::ATTR_CS_STRING,
400 47
            '/^aria-label$/i' => self::ATTR_CS_STRING,
401 47
            '/^aria-labelledby$/i' => self::ATTR_CS_STRING,
402 47
            '/^aria-live$/i' => self::ATTR_CS_STRING,
403 47
            '/^aria-owns$/i' => self::ATTR_CS_STRING,
404 47
            '/^aria-relevant$/i' => self::ATTR_CS_STRING,
405 47
            '/^aria-roledescription$/i' => self::ATTR_CS_STRING,
406
407
            // ARIA widget attributes
408 47
            '/^aria-autocomplete$/i' => self::ATTR_CS_STRING,
409 47
            '/^aria-checked$/i' => self::ATTR_CS_STRING,
410 47
            '/^aria-expanded$/i' => self::ATTR_CS_STRING,
411 47
            '/^aria-level$/i' => self::ATTR_CS_STRING,
412 47
            '/^aria-modal$/i' => self::ATTR_CS_STRING,
413 47
            '/^aria-multiline$/i' => self::ATTR_CS_STRING,
414 47
            '/^aria-multiselectable$/i' => self::ATTR_CS_STRING,
415 47
            '/^aria-orientation$/i' => self::ATTR_CS_STRING,
416 47
            '/^aria-placeholder$/i' => self::ATTR_CS_STRING,
417 47
            '/^aria-pressed$/i' => self::ATTR_CS_STRING,
418 47
            '/^aria-readonly$/i' => self::ATTR_CS_STRING,
419 47
            '/^aria-required$/i' => self::ATTR_CS_STRING,
420 47
            '/^aria-selected$/i' => self::ATTR_CS_STRING,
421 47
            '/^aria-sort$/i' => self::ATTR_CS_STRING,
422 47
            '/^aria-valuemax$/i' => self::ATTR_CS_STRING,
423 47
            '/^aria-valuemin$/i' => self::ATTR_CS_STRING,
424 47
            '/^aria-valuenow$/i' => self::ATTR_CS_STRING,
425 47
            '/^aria-valuetext$/i' => self::ATTR_CS_STRING,
426
427
            // ARIA relationship attributes
428 47
            '/^aria-activedescendant$/i' => self::ATTR_CS_STRING,
429 47
            '/^aria-colcount$/i' => self::ATTR_CS_STRING,
430 47
            '/^aria-colindex$/i' => self::ATTR_CS_STRING,
431 47
            '/^aria-colspan$/i' => self::ATTR_CS_STRING,
432 47
            '/^aria-posinset$/i' => self::ATTR_CS_STRING,
433 47
            '/^aria-rowcount$/i' => self::ATTR_CS_STRING,
434 47
            '/^aria-rowindex$/i' => self::ATTR_CS_STRING,
435 47
            '/^aria-rowspan$/i' => self::ATTR_CS_STRING,
436
            '/^aria-setsize$/i' => self::ATTR_CS_STRING
437 47
        );
438
    }
439
440 47
    protected function getAttributeParameters($name)
441
    {
442 47
        $allowedAttributes = $this->getAllowedAttributes();
443 47
        foreach ($allowedAttributes as $attrRegex => $valueType) {
444 47
            if (preg_match($attrRegex, $name) === 1) {
445
                return array(
446 47
                    'name' => $name,
447 47
                    'regex' => $attrRegex,
448
                    'valueType' => $valueType
449 47
                );
450
            }
451 37
        }
452
453 7
        return array();
454
    }
455
456
    /**
457
     * Required by the Removable interface.
458
     */
459 83 View Code Duplication
    public function remove(LoggerInterface $logger)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
460
    {
461 83
        $hasRemovableElements = $this->configuration->get('element-blacklist') != '';
462 83
        $hasRemovableTypes = $this->configuration->get('type-blacklist') != '';
463 83
        foreach ($this->children as $child) {
464
            // Check types.
465 78
            if ($hasRemovableTypes &&
466 78
                !$this->configuration->isAllowedType($child->getType())) {
467 2
                $logger->debug('Removing ' . $child);
468 2
                $this->removeChild($child);
469
470 2
                continue;
471
            }
472
473
            // Check elements.
474 76
            if ($hasRemovableElements &&
475 76
                $child instanceof self &&
476 76
                !$this->configuration->isAllowedElement($child->getName())) {
477 3
                $logger->debug('Removing ' . $child);
478 3
                $this->removeChild($child);
479
480 3
                continue;
481
            }
482
483
            // Check children.
484 76
            if ($child instanceof Removable) {
485 65
                $child->remove($logger);
486 65
            }
487 83
        }
488 83
    }
489
490
    /**
491
     * Required by the Token interface.
492
     */
493 8
    public function toHtml($prefix, $suffix)
494
    {
495 8
        $output = $this->buildStartTag($prefix, $suffix);
496 8
        if (empty($this->children)) {
497 6
            return $output;
498
        }
499
500 6
        $output .= $this->buildChildrenHtml($prefix, $suffix);
501
502 6
        return $output . $prefix . '</' . $this->name . '>' . $suffix;
503
    }
504
505 89
    protected function buildStartTag($prefix, $suffix, $forceOpen = false)
506
    {
507 89
        $output = $prefix . '<' . $this->name;
508 89
        foreach ($this->attributes as $key => $value) {
509 49
            $output .= ' ' . strtolower($key);
510 49
            if ($value !== true) {
511
                /// @todo Escape double quotes in value.
512 49
                $output .= '="' . $value . '"';
513 49
            }
514 89
        }
515
516 89
        if (!$forceOpen && empty($this->children)) {
517 30
            return $output . '/>' . $suffix;
518
        }
519
520 85
        return $output . '>' . $suffix;
521
    }
522
523 85
    protected function buildChildrenHtml($prefix, $suffix)
524
    {
525 85
        $output = '';
526 85
        foreach ($this->children as $child) {
527
            $newPrefix = $prefix .
528 82
                str_repeat(
529 82
                    ' ',
530 82
                    $this->configuration->get('indent-spaces')
531 82
                );
532 82
            $output .= $child->toHtml($newPrefix, $suffix);
533 85
        }
534
535 85
        return $output;
536
    }
537
538 17
    public function getType()
539
    {
540 17
        return Token::ELEMENT;
541
    }
542
543 55
    public function __toString()
544
    {
545 55
        return '"' . $this->name . '" element';
546
    }
547
}
548