Completed
Push — master ( 2c6d44...280d74 )
by Lars
01:56
created

SimpleXmlDom::nextNonWhitespaceSibling()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 16
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 0
dl 16
loc 16
ccs 0
cts 7
cp 0
crap 20
rs 9.7333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/**
8
 * @noinspection PhpHierarchyChecksInspection
9
 *
10
 * {@inheritdoc}
11
 */
12
class SimpleXmlDom extends AbstractSimpleXmlDom implements \IteratorAggregate, SimpleXmlDomInterface
13
{
14
    /**
15
     * @param \DOMElement|\DOMNode $node
16
     */
17 1
    public function __construct(\DOMNode $node)
18
    {
19 1
        $this->node = $node;
20 1
    }
21
22
    /**
23
     * @param string $name
24
     * @param array  $arguments
25
     *
26
     * @throws \BadMethodCallException
27
     *
28
     * @return SimpleXmlDomInterface|string|null
29
     */
30
    public function __call($name, $arguments)
31
    {
32
        $name = \strtolower($name);
33
34
        if (isset(self::$functionAliases[$name])) {
35
            return \call_user_func_array([$this, self::$functionAliases[$name]], $arguments);
36
        }
37
38
        throw new \BadMethodCallException('Method does not exist');
39
    }
40
41
    /**
42
     * Find list of nodes with a CSS selector.
43
     *
44
     * @param string   $selector
45
     * @param int|null $idx
46
     *
47
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
48
     */
49
    public function find(string $selector, $idx = null)
50
    {
51
        return $this->getXmlDomParser()->find($selector, $idx);
52
    }
53
54
    /**
55
     * Returns an array of attributes.
56
     *
57
     * @return array|null
58
     */
59 View Code Duplication
    public function getAllAttributes()
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...
60
    {
61
        if ($this->node->hasAttributes()) {
62
            $attributes = [];
63
            foreach ($this->node->attributes as $attr) {
64
                $attributes[$attr->name] = XmlDomParser::putReplacedBackToPreserveHtmlEntities($attr->value);
65
            }
66
67
            return $attributes;
68
        }
69
70
        return null;
71
    }
72
73
    /**
74
     * @return bool
75
     */
76
    public function hasAttributes(): bool
77
    {
78
        return $this->node->hasAttributes();
79
    }
80
81
    /**
82
     * Return attribute value.
83
     *
84
     * @param string $name
85
     *
86
     * @return string
87
     */
88
    public function getAttribute(string $name): string
89
    {
90
        if ($this->node instanceof \DOMElement) {
91
            return XmlDomParser::putReplacedBackToPreserveHtmlEntities(
92
                $this->node->getAttribute($name)
93
            );
94
        }
95
96
        return '';
97
    }
98
99
    /**
100
     * Determine if an attribute exists on the element.
101
     *
102
     * @param string $name
103
     *
104
     * @return bool
105
     */
106
    public function hasAttribute(string $name): bool
107
    {
108
        if (!$this->node instanceof \DOMElement) {
109
            return false;
110
        }
111
112
        return $this->node->hasAttribute($name);
113
    }
114
115
    /**
116
     * Get dom node's inner html.
117
     *
118
     * @param bool $multiDecodeNewHtmlEntity
119
     *
120
     * @return string
121
     */
122
    public function innerXml(bool $multiDecodeNewHtmlEntity = false): string
123
    {
124
        return $this->getXmlDomParser()->innerXml($multiDecodeNewHtmlEntity);
125
    }
126
127
    /**
128
     * Remove attribute.
129
     *
130
     * @param string $name <p>The name of the html-attribute.</p>
131
     *
132
     * @return SimpleXmlDomInterface
133
     */
134
    public function removeAttribute(string $name): SimpleXmlDomInterface
135
    {
136
        if (\method_exists($this->node, 'removeAttribute')) {
137
            $this->node->removeAttribute($name);
0 ignored issues
show
Bug introduced by
The method removeAttribute does only exist in DOMElement, but not in DOMNode.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
138
        }
139
140
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (voku\helper\SimpleXmlDom) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::removeAttribute of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
141
    }
142
143
    /**
144
     * Replace child node.
145
     *
146
     * @param string $string
147
     *
148
     * @return SimpleXmlDomInterface
149
     */
150 View Code Duplication
    protected function replaceChildWithString(string $string): SimpleXmlDomInterface
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...
151
    {
152
        if (!empty($string)) {
153
            $newDocument = new XmlDomParser($string);
154
155
            $tmpDomString = $this->normalizeStringForComparision($newDocument);
156
            $tmpStr = $this->normalizeStringForComparision($string);
157
            if ($tmpDomString !== $tmpStr) {
158
                throw new \RuntimeException(
159
                    'Not valid HTML fragment!' . "\n" .
160
                    $tmpDomString . "\n" .
161
                    $tmpStr
162
                );
163
            }
164
        }
165
166
        /** @var \DOMNode[] $remove_nodes */
167
        $remove_nodes = [];
168
        if ($this->node->childNodes->length > 0) {
169
            // INFO: We need to fetch the nodes first, before we can delete them, because of missing references in the dom,
170
            // if we delete the elements on the fly.
171
            foreach ($this->node->childNodes as $node) {
172
                $remove_nodes[] = $node;
173
            }
174
        }
175
        foreach ($remove_nodes as $remove_node) {
176
            $this->node->removeChild($remove_node);
177
        }
178
179
        if (!empty($newDocument)) {
180
            $ownerDocument = $this->node->ownerDocument;
181
            if (
182
                $ownerDocument !== null
183
                &&
184
                $newDocument->getDocument()->documentElement !== null
185
            ) {
186
                $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
187
                /** @noinspection UnusedFunctionResultInspection */
188
                $this->node->appendChild($newNode);
189
            }
190
        }
191
192
        return $this;
193
    }
194
195
    /**
196
     * Replace this node.
197
     *
198
     * @param string $string
199
     *
200
     * @return SimpleXmlDomInterface
201
     */
202
    protected function replaceNodeWithString(string $string): SimpleXmlDomInterface
203
    {
204
        if (empty($string)) {
205
            $this->node->parentNode->removeChild($this->node);
206
207
            return $this;
208
        }
209
210
        $newDocument = new XmlDomParser($string);
211
212
        $tmpDomOuterTextString = $this->normalizeStringForComparision($newDocument);
213
        $tmpStr = $this->normalizeStringForComparision($string);
214 View Code Duplication
        if ($tmpDomOuterTextString !== $tmpStr) {
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...
215
            throw new \RuntimeException(
216
                'Not valid HTML fragment!' . "\n"
217
                . $tmpDomOuterTextString . "\n" .
218
                $tmpStr
219
            );
220
        }
221
222
        $ownerDocument = $this->node->ownerDocument;
223
        if (
224
            $ownerDocument === null
225
            ||
226
            $newDocument->getDocument()->documentElement === null
227
        ) {
228
            return $this;
229
        }
230
231
        $newNode = $ownerDocument->importNode($newDocument->getDocument()->documentElement, true);
232
233
        $this->node->parentNode->replaceChild($newNode, $this->node);
234
        $this->node = $newNode;
235
236
        return $this;
237
    }
238
239
    /**
240
     * Replace this node with text
241
     *
242
     * @param string $string
243
     *
244
     * @return SimpleXmlDomInterface
245
     */
246 View Code Duplication
    protected function replaceTextWithString($string): SimpleXmlDomInterface
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...
247
    {
248
        if (empty($string)) {
249
            $this->node->parentNode->removeChild($this->node);
250
251
            return $this;
252
        }
253
254
        $ownerDocument = $this->node->ownerDocument;
255
        if ($ownerDocument !== null) {
256
            $newElement = $ownerDocument->createTextNode($string);
257
            $newNode = $ownerDocument->importNode($newElement, true);
258
            $this->node->parentNode->replaceChild($newNode, $this->node);
259
            $this->node = $newNode;
260
        }
261
262
        return $this;
263
    }
264
265
    /**
266
     * Set attribute value.
267
     *
268
     * @param string      $name       <p>The name of the html-attribute.</p>
269
     * @param string|null $value      <p>Set to NULL or empty string, to remove the attribute.</p>
270
     * @param bool        $strict     </p>
271
     *                                $value must be NULL, to remove the attribute,
272
     *                                so that you can set an empty string as attribute-value e.g. autofocus=""
273
     *                                </p>
274
     *
275
     * @return SimpleXmlDomInterface
276
     */
277 View Code Duplication
    public function setAttribute(string $name, $value = null, bool $strict = false): SimpleXmlDomInterface
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...
278
    {
279
        if (
280
            ($strict && $value === null)
281
            ||
282
            (!$strict && empty($value))
283
        ) {
284
            /** @noinspection UnusedFunctionResultInspection */
285
            $this->removeAttribute($name);
286
        } elseif (\method_exists($this->node, 'setAttribute')) {
287
            /** @noinspection UnusedFunctionResultInspection */
288
            $this->node->setAttribute($name, $value);
0 ignored issues
show
Bug introduced by
The method setAttribute does only exist in DOMElement, but not in DOMNode.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
289
        }
290
291
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (voku\helper\SimpleXmlDom) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::setAttribute of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
292
    }
293
294
    /**
295
     * Get dom node's plain text.
296
     *
297
     * @return string
298
     */
299
    public function text(): string
300
    {
301
        return $this->getXmlDomParser()->fixHtmlOutput($this->node->textContent);
302
    }
303
304
    /**
305
     * Get dom node's outer html.
306
     *
307
     * @param bool $multiDecodeNewHtmlEntity
308
     *
309
     * @return string
310
     */
311
    public function xml(bool $multiDecodeNewHtmlEntity = false): string
312
    {
313
        return $this->getXmlDomParser()->xml($multiDecodeNewHtmlEntity, false);
314
    }
315
316
    /**
317
     * Change the name of a tag in a "DOMNode".
318
     *
319
     * @param \DOMNode $node
320
     * @param string   $name
321
     *
322
     * @return \DOMElement|false
323
     *                          <p>DOMElement a new instance of class DOMElement or false
324
     *                          if an error occured.</p>
325
     */
326 View Code Duplication
    protected function changeElementName(\DOMNode $node, string $name)
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...
327
    {
328
        $ownerDocument = $node->ownerDocument;
329
        if ($ownerDocument) {
330
            $newNode = $ownerDocument->createElement($name);
331
        } else {
332
            return false;
333
        }
334
335
        foreach ($node->childNodes as $child) {
336
            $child = $ownerDocument->importNode($child, true);
337
            /** @noinspection UnusedFunctionResultInspection */
338
            $newNode->appendChild($child);
339
        }
340
341
        foreach ($node->attributes as $attrName => $attrNode) {
342
            /** @noinspection UnusedFunctionResultInspection */
343
            $newNode->setAttribute($attrName, $attrNode);
344
        }
345
346
        /** @noinspection UnusedFunctionResultInspection */
347
        $newNode->ownerDocument->replaceChild($newNode, $node);
348
349
        return $newNode;
350
    }
351
352
    /**
353
     * Returns children of node.
354
     *
355
     * @param int $idx
356
     *
357
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface|null
358
     */
359 View Code Duplication
    public function childNodes(int $idx = -1)
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...
360
    {
361
        $nodeList = $this->getIterator();
362
363
        if ($idx === -1) {
364
            return $nodeList;
365
        }
366
367
        return $nodeList[$idx] ?? null;
368
    }
369
370
    /**
371
     * Find nodes with a CSS selector.
372
     *
373
     * @param string $selector
374
     *
375
     * @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
376
     */
377
    public function findMulti(string $selector): SimpleXmlDomNodeInterface
378
    {
379
        return $this->getXmlDomParser()->findMulti($selector);
380
    }
381
382
    /**
383
     * Find nodes with a CSS selector.
384
     *
385
     * @param string $selector
386
     *
387
     * @return false|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
388
     */
389
    public function findMultiOrFalse(string $selector)
390
    {
391
        return $this->getXmlDomParser()->findMultiOrFalse($selector);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getXmlDomParser()...ultiOrFalse($selector); of type false|voku\helper\Simple...mpleXmlDomNodeInterface adds the type voku\helper\SimpleXmlDomInterface to the return on line 391 which is incompatible with the return type declared by the interface voku\helper\SimpleXmlDom...rface::findMultiOrFalse of type false|voku\helper\Simple...mpleXmlDomNodeInterface.
Loading history...
392
    }
393
394
    /**
395
     * Find one node with a CSS selector.
396
     *
397
     * @param string $selector
398
     *
399
     * @return SimpleXmlDomInterface
400
     */
401
    public function findOne(string $selector): SimpleXmlDomInterface
402
    {
403
        return $this->getXmlDomParser()->findOne($selector);
404
    }
405
406
    /**
407
     * Find one node with a CSS selector or false, if no element is found.
408
     *
409
     * @param string $selector
410
     *
411
     * @return false|SimpleXmlDomInterface
412
     */
413
    public function findOneOrFalse(string $selector)
414
    {
415
        return $this->getXmlDomParser()->findOneOrFalse($selector);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getXmlDomP...dOneOrFalse($selector); (false|voku\helper\Simple...mpleXmlDomNodeInterface) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::findOneOrFalse of type false|voku\helper\SimpleXmlDomInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
416
    }
417
418
    /**
419
     * Returns the first child of node.
420
     *
421
     * @return SimpleXmlDomInterface|null
422
     */
423 View Code Duplication
    public function firstChild()
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...
424
    {
425
        /** @var \DOMNode|null $node */
426
        $node = $this->node->firstChild;
427
428
        if ($node === null) {
429
            return null;
430
        }
431
432
        return new static($node);
433
    }
434
435
    /**
436
     * Return elements by ".class".
437
     *
438
     * @param string $class
439
     *
440
     * @return SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
441
     */
442
    public function getElementByClass(string $class): SimpleXmlDomNodeInterface
443
    {
444
        return $this->findMulti(".${class}");
445
    }
446
447
    /**
448
     * Return element by #id.
449
     *
450
     * @param string $id
451
     *
452
     * @return SimpleXmlDomInterface
453
     */
454
    public function getElementById(string $id): SimpleXmlDomInterface
455
    {
456
        return $this->findOne("#${id}");
457
    }
458
459
    /**
460
     * Return element by tag name.
461
     *
462
     * @param string $name
463
     *
464
     * @return SimpleXmlDomInterface
465
     */
466 View Code Duplication
    public function getElementByTagName(string $name): SimpleXmlDomInterface
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...
467
    {
468
        if ($this->node instanceof \DOMElement) {
469
            $node = $this->node->getElementsByTagName($name)->item(0);
470
        } else {
471
            $node = null;
472
        }
473
474
        if ($node === null) {
475
            return new SimpleXmlDomBlank();
476
        }
477
478
        return new static($node);
479
    }
480
481
    /**
482
     * Returns elements by "#id".
483
     *
484
     * @param string   $id
485
     * @param int|null $idx
486
     *
487
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
488
     */
489
    public function getElementsById(string $id, $idx = null)
490
    {
491
        return $this->find("#${id}", $idx);
492
    }
493
494
    /**
495
     * Returns elements by tag name.
496
     *
497
     * @param string   $name
498
     * @param int|null $idx
499
     *
500
     * @return SimpleXmlDomInterface|SimpleXmlDomInterface[]|SimpleXmlDomNodeInterface
501
     */
502 View Code Duplication
    public function getElementsByTagName(string $name, $idx = null)
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...
503
    {
504
        if ($this->node instanceof \DOMElement) {
505
            $nodesList = $this->node->getElementsByTagName($name);
506
        } else {
507
            $nodesList = [];
508
        }
509
510
        $elements = new SimpleXmlDomNode();
511
512
        foreach ($nodesList as $node) {
513
            $elements[] = new static($node);
514
        }
515
516
        // return all elements
517
        if ($idx === null) {
518
            if (\count($elements) === 0) {
519
                return new SimpleXmlDomNodeBlank();
520
            }
521
522
            return $elements;
523
        }
524
525
        // handle negative values
526
        if ($idx < 0) {
527
            $idx = \count($elements) + $idx;
528
        }
529
530
        // return one element
531
        return $elements[$idx] ?? new SimpleXmlDomBlank();
532
    }
533
534
    /**
535
     * @return \DOMNode
536
     */
537 1
    public function getNode(): \DOMNode
538
    {
539 1
        return $this->node;
540
    }
541
542
    /**
543
     * Create a new "XmlDomParser"-object from the current context.
544
     *
545
     * @return XmlDomParser
546
     */
547
    public function getXmlDomParser(): XmlDomParser
548
    {
549
        return new XmlDomParser($this);
550
    }
551
552
    /**
553
     * Get dom node's inner html.
554
     *
555
     * @param bool $multiDecodeNewHtmlEntity
556
     *
557
     * @return string
558
     */
559
    public function innerHtml(bool $multiDecodeNewHtmlEntity = false): string
560
    {
561
        return $this->getXmlDomParser()->innerHtml($multiDecodeNewHtmlEntity);
562
    }
563
564
    /**
565
     * Nodes can get partially destroyed in which they're still an
566
     * actual DOM node (such as \DOMElement) but almost their entire
567
     * body is gone, including the `nodeType` attribute.
568
     *
569
     * @return bool true if node has been destroyed
570
     */
571
    public function isRemoved(): bool
572
    {
573
        return !isset($this->node->nodeType);
574
    }
575
576
    /**
577
     * Returns the last child of node.
578
     *
579
     * @return SimpleXmlDomInterface|null
580
     */
581 View Code Duplication
    public function lastChild()
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...
582
    {
583
        /** @var \DOMNode|null $node */
584
        $node = $this->node->lastChild;
585
586
        if ($node === null) {
587
            return null;
588
        }
589
590
        return new static($node);
591
    }
592
593
    /**
594
     * Returns the next sibling of node.
595
     *
596
     * @return SimpleXmlDomInterface|null
597
     */
598 View Code Duplication
    public function nextSibling()
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...
599
    {
600
        /** @var \DOMNode|null $node */
601
        $node = $this->node->nextSibling;
602
603
        if ($node === null) {
604
            return null;
605
        }
606
607
        return new static($node);
608
    }
609
610
    /**
611
     * Returns the next sibling of node.
612
     *
613
     * @return SimpleXmlDomInterface|null
614
     */
615 View Code Duplication
    public function nextNonWhitespaceSibling()
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...
616
    {
617
        /** @var \DOMNode|null $node */
618
        $node = $this->node->nextSibling;
619
620
        if ($node === null) {
621
            return null;
622
        }
623
624
        while ($node && !\trim($node->textContent)) {
625
            /** @var \DOMNode|null $node */
626
            $node = $node->nextSibling;
627
        }
628
629
        return new static($node);
0 ignored issues
show
Bug introduced by
It seems like $node can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
630
    }
631
632
    /**
633
     * Returns the parent of node.
634
     *
635
     * @return SimpleXmlDomInterface
636
     */
637
    public function parentNode(): SimpleXmlDomInterface
638
    {
639
        return new static($this->node->parentNode);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new static($this->node->parentNode); (voku\helper\SimpleXmlDom) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::parentNode of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
640
    }
641
642
    /**
643
     * Returns the previous sibling of node.
644
     *
645
     * @return SimpleXmlDomInterface|null
646
     */
647 View Code Duplication
    public function previousSibling()
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...
648
    {
649
        /** @var \DOMNode|null $node */
650
        $node = $this->node->previousSibling;
651
652
        if ($node === null) {
653
            return null;
654
        }
655
656
        return new static($node);
657
    }
658
659
    /**
660
     * @param string|string[]|null $value <p>
661
     *                                    null === get the current input value
662
     *                                    text === set a new input value
663
     *                                    </p>
664
     *
665
     * @return string|string[]|null
666
     */
667 View Code Duplication
    public function val($value = null)
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...
668
    {
669
        if ($value === null) {
670
            if (
671
                $this->tag === 'input'
0 ignored issues
show
Documentation introduced by
The property tag does not exist on object<voku\helper\SimpleXmlDom>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
672
                &&
673
                (
674
                    $this->getAttribute('type') === 'text'
675
                    ||
676
                    !$this->hasAttribute('type')
677
                )
678
            ) {
679
                return $this->getAttribute('value');
680
            }
681
682
            if (
683
                $this->hasAttribute('checked')
684
                &&
685
                \in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)
686
            ) {
687
                return $this->getAttribute('value');
688
            }
689
690
            if ($this->node->nodeName === 'select') {
691
                $valuesFromDom = [];
692
                $options = $this->getElementsByTagName('option');
693
                if ($options instanceof SimpleXmlDomNode) {
694
                    foreach ($options as $option) {
695
                        if ($this->hasAttribute('checked')) {
696
                            /** @noinspection UnnecessaryCastingInspection */
697
                            $valuesFromDom[] = (string) $option->getAttribute('value');
698
                        }
699
                    }
700
                }
701
702
                if (\count($valuesFromDom) === 0) {
703
                    return null;
704
                }
705
706
                return $valuesFromDom;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $valuesFromDom; (array) is incompatible with the return type declared by the interface voku\helper\SimpleXmlDomInterface::val of type string|string[]|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
707
            }
708
709
            if ($this->node->nodeName === 'textarea') {
710
                return $this->node->nodeValue;
711
            }
712
        } else {
713
            /** @noinspection NestedPositiveIfStatementsInspection */
714
            if (\in_array($this->getAttribute('type'), ['checkbox', 'radio'], true)) {
715
                if ($value === $this->getAttribute('value')) {
716
                    /** @noinspection UnusedFunctionResultInspection */
717
                    $this->setAttribute('checked', 'checked');
718
                } else {
719
                    /** @noinspection UnusedFunctionResultInspection */
720
                    $this->removeAttribute('checked');
721
                }
722
            } elseif ($this->node instanceof \DOMElement && $this->node->nodeName === 'select') {
723
                foreach ($this->node->getElementsByTagName('option') as $option) {
724
                    /** @var \DOMElement $option */
725
                    if ($value === $option->getAttribute('value')) {
726
                        /** @noinspection UnusedFunctionResultInspection */
727
                        $option->setAttribute('selected', 'selected');
728
                    } else {
729
                        $option->removeAttribute('selected');
730
                    }
731
                }
732
            } elseif ($this->node->nodeName === 'input' && \is_string($value)) {
733
                // Set value for input elements
734
                /** @noinspection UnusedFunctionResultInspection */
735
                $this->setAttribute('value', $value);
736
            } elseif ($this->node->nodeName === 'textarea' && \is_string($value)) {
737
                $this->node->nodeValue = $value;
738
            }
739
        }
740
741
        return null;
742
    }
743
744
    /**
745
     * Retrieve an external iterator.
746
     *
747
     * @see  http://php.net/manual/en/iteratoraggregate.getiterator.php
748
     *
749
     * @return SimpleXmlDomNode
750
     *                           <p>
751
     *                              An instance of an object implementing <b>Iterator</b> or
752
     *                              <b>Traversable</b>
753
     *                           </p>
754
     */
755 View Code Duplication
    public function getIterator(): SimpleXmlDomNodeInterface
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...
756
    {
757
        $elements = new SimpleXmlDomNode();
758
        if ($this->node->hasChildNodes()) {
759
            foreach ($this->node->childNodes as $node) {
760
                $elements[] = new static($node);
761
            }
762
        }
763
764
        return $elements;
765
    }
766
767
    /**
768
     * Normalize the given input for comparision.
769
     *
770
     * @param string|XmlDomParser $input
771
     *
772
     * @return string
773
     */
774
    private function normalizeStringForComparision($input): string
775
    {
776
        if ($input instanceof XmlDomParser) {
777
            $string = $input->plaintext;
778
        } else {
779
            $string = (string) $input;
780
        }
781
782
        return
783
            \urlencode(
784
                \urldecode(
785
                    \trim(
786
                        \str_replace(
787
                            [
788
                                ' ',
789
                                "\n",
790
                                "\r",
791
                                '/>',
792
                            ],
793
                            [
794
                                '',
795
                                '',
796
                                '',
797
                                '>',
798
                            ],
799
                            \strtolower($string)
800
                        )
801
                    )
802
                )
803
            );
804
    }
805
}
806