AbstractNode::find()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
c 0
b 0
f 0
nc 6
nop 3
dl 0
loc 18
ccs 9
cts 9
cp 1
crap 4
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPHtmlParser\Dom\Node;
6
7
use PHPHtmlParser\Contracts\Selector\SelectorInterface;
8
use PHPHtmlParser\Dom\Tag;
9
use PHPHtmlParser\Exceptions\ChildNotFoundException;
10
use PHPHtmlParser\Exceptions\CircularException;
11
use PHPHtmlParser\Exceptions\ParentNotFoundException;
12
use PHPHtmlParser\Exceptions\Tag\AttributeNotFoundException;
13
use PHPHtmlParser\Finder;
14
use PHPHtmlParser\Selector\Selector;
15
use stringEncode\Encode;
16
17
/**
18
 * Dom node object.
19
 *
20
 * @property-read string    $outerhtml
21
 * @property-read string    $innerhtml
22
 * @property-read string    $innerText
23
 * @property-read string    $text
24
 * @property-read Tag       $tag
25
 * @property-read InnerNode $parent
26
 */
27
abstract class AbstractNode
28
{
29
    /**
30
     * Contains the tag name/type.
31
     *
32
     * @var ?Tag
33
     */
34
    protected $tag;
35
36
    /**
37
     * Contains a list of attributes on this tag.
38
     *
39
     * @var array
40
     */
41
    protected $attr = [];
42
43
    /**
44
     * Contains the parent Node.
45
     *
46
     * @var ?InnerNode
47
     */
48
    protected $parent;
49
50
    /**
51
     * The unique id of the class. Given by PHP.
52
     *
53
     * @var int
54
     */
55
    protected $id;
56
57
    /**
58
     * The encoding class used to encode strings.
59
     *
60
     * @var mixed
61
     */
62
    protected $encode;
63
64
    /**
65
     * An array of all the children.
66
     *
67
     * @var array
68
     */
69
    protected $children = [];
70
71
    /**
72
     * @var bool
73
     */
74
    protected $htmlSpecialCharsDecode = false;
75
    /**
76
     * @var int
77
     */
78
    private static $count = 0;
79
80
    /**
81
     * Creates a unique id for this node.
82
     */
83 564
    public function __construct()
84
    {
85 564
        $this->id = self::$count;
86 564
        ++self::$count;
87 564
    }
88
89
    /**
90
     * Attempts to clear out any object references.
91
     */
92 134
    public function __destruct()
93
    {
94 134
        $this->tag = null;
0 ignored issues
show
Bug introduced by
The property tag is declared read-only in PHPHtmlParser\Dom\Node\AbstractNode.
Loading history...
95 134
        $this->parent = null;
0 ignored issues
show
Bug introduced by
The property parent is declared read-only in PHPHtmlParser\Dom\Node\AbstractNode.
Loading history...
96 134
        $this->attr = [];
97 134
        $this->children = [];
98 134
    }
99
100
    /**
101
     * Magic get method for attributes and certain methods.
102
     *
103
     * @return mixed
104
     */
105 330
    public function __get(string $key)
106
    {
107
        // check attribute first
108 330
        if ($this->getAttribute($key) !== null) {
109 18
            return $this->getAttribute($key);
110
        }
111 327
        switch (\strtolower($key)) {
112 327
            case 'outerhtml':
113 42
                return $this->outerHtml();
114 318
            case 'innerhtml':
115 81
                return $this->innerHtml();
116 315
            case 'innertext':
117 6
                return $this->innerText();
0 ignored issues
show
Bug introduced by
The method innerText() does not exist on PHPHtmlParser\Dom\Node\AbstractNode. It seems like you code against a sub-type of PHPHtmlParser\Dom\Node\AbstractNode such as PHPHtmlParser\Dom\Node\HtmlNode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

117
                return $this->/** @scrutinizer ignore-call */ innerText();
Loading history...
118 312
            case 'text':
119 51
                return $this->text();
120 297
            case 'tag':
121 297
                return $this->getTag();
122 3
            case 'parent':
123
                return $this->getParent();
124
        }
125 3
    }
126
127
    /**
128
     * Simply calls the outer text method.
129
     *
130
     * @return string
131
     */
132 9
    public function __toString()
133
    {
134 9
        return $this->outerHtml();
135
    }
136
137
    /**
138
     * @param bool $htmlSpecialCharsDecode
139
     */
140 294
    public function setHtmlSpecialCharsDecode($htmlSpecialCharsDecode = false): void
141
    {
142 294
        $this->htmlSpecialCharsDecode = $htmlSpecialCharsDecode;
143 294
    }
144
145
    /**
146
     * Returns the id of this object.
147
     */
148 495
    public function id(): int
149
    {
150 495
        return $this->id;
151
    }
152
153
    /**
154
     * Returns the parent of node.
155
     *
156
     * @return InnerNode
157
     */
158 264
    public function getParent(): ?InnerNode
159
    {
160 264
        return $this->parent;
161
    }
162
163
    /**
164
     * Sets the parent node.
165
     *
166
     * @throws ChildNotFoundException
167
     * @throws CircularException
168
     */
169 489
    public function setParent(InnerNode $parent): AbstractNode
170
    {
171
        // remove from old parent
172 489
        if ($this->parent !== null) {
173 33
            if ($this->parent->id() == $parent->id()) {
174
                // already the parent
175 27
                return $this;
176
            }
177
178 9
            $this->parent->removeChild($this->id);
179
        }
180
181 489
        $this->parent = $parent;
0 ignored issues
show
Bug introduced by
The property parent is declared read-only in PHPHtmlParser\Dom\Node\AbstractNode.
Loading history...
182
183
        // assign child to parent
184 489
        $this->parent->addChild($this);
185
186 489
        return $this;
187
    }
188
189
    /**
190
     * Removes this node and all its children from the
191
     * DOM tree.
192
     *
193
     * @return void
194
     */
195 3
    public function delete()
196
    {
197 3
        if ($this->parent !== null) {
198 3
            $this->parent->removeChild($this->id);
199
        }
200 3
        $this->parent->clear();
201 3
        $this->clear();
202 3
    }
203
204
    /**
205
     * Sets the encoding class to this node.
206
     *
207
     * @return void
208
     */
209 249
    public function propagateEncoding(Encode $encode)
210
    {
211 249
        $this->encode = $encode;
212 249
        $this->tag->setEncoding($encode);
213 249
    }
214
215
    /**
216
     * Checks if the given node id is an ancestor of
217
     * the current node.
218
     */
219 492
    public function isAncestor(int $id): bool
220
    {
221 492
        if ($this->getAncestor($id) !== null) {
222 6
            return true;
223
        }
224
225 492
        return false;
226
    }
227
228
    /**
229
     * Attempts to get an ancestor node by the given id.
230
     *
231
     * @return AbstractNode|null
232
     */
233 495
    public function getAncestor(int $id)
234
    {
235 495
        if ($this->parent !== null) {
236 333
            if ($this->parent->id() == $id) {
237 12
                return $this->parent;
238
            }
239
240 324
            return $this->parent->getAncestor($id);
241
        }
242 495
    }
243
244
    /**
245
     * Checks if the current node has a next sibling.
246
     */
247
    public function hasNextSibling(): bool
248
    {
249
        try {
250
            $this->nextSibling();
251
252
            // sibling found, return true;
253
            return true;
254
        } catch (ParentNotFoundException $e) {
255
            // no parent, no next sibling
256
            unset($e);
257
258
            return false;
259
        } catch (ChildNotFoundException $e) {
260
            // no sibling found
261
            unset($e);
262
263
            return false;
264
        }
265
    }
266
267
    /**
268
     * Attempts to get the next sibling.
269
     *
270
     * @throws ChildNotFoundException
271
     * @throws ParentNotFoundException
272
     */
273 30
    public function nextSibling(): AbstractNode
274
    {
275 30
        if ($this->parent === null) {
276 3
            throw new ParentNotFoundException('Parent is not set for this node.');
277
        }
278
279 27
        return $this->parent->nextChild($this->id);
280
    }
281
282
    /**
283
     * Attempts to get the previous sibling.
284
     *
285
     * @throws ChildNotFoundException
286
     * @throws ParentNotFoundException
287
     */
288 9
    public function previousSibling(): AbstractNode
289
    {
290 9
        if ($this->parent === null) {
291 3
            throw new ParentNotFoundException('Parent is not set for this node.');
292
        }
293
294 6
        return $this->parent->previousChild($this->id);
295
    }
296
297
    /**
298
     * Gets the tag object of this node.
299
     */
300 363
    public function getTag(): Tag
301
    {
302 363
        return $this->tag;
303
    }
304
305
    /**
306
     * Replaces the tag for this node.
307
     *
308
     * @param string|Tag $tag
309
     */
310 3
    public function setTag($tag): AbstractNode
311
    {
312 3
        if (\is_string($tag)) {
313 3
            $tag = new Tag($tag);
314
        }
315
316 3
        $this->tag = $tag;
0 ignored issues
show
Bug introduced by
The property tag is declared read-only in PHPHtmlParser\Dom\Node\AbstractNode.
Loading history...
317
318
        // clear any cache
319 3
        $this->clear();
320
321 3
        return $this;
322
    }
323
324
    /**
325
     * A wrapper method that simply calls the getAttribute method
326
     * on the tag of this node.
327
     */
328 6
    public function getAttributes(): array
329
    {
330 6
        $attributes = $this->tag->getAttributes();
331 6
        foreach ($attributes as $name => $attributeDTO) {
332 3
            $attributes[$name] = $attributeDTO->getValue();
333
        }
334
335 6
        return $attributes;
336
    }
337
338
    /**
339
     * A wrapper method that simply calls the getAttribute method
340
     * on the tag of this node.
341
     */
342 351
    public function getAttribute(string $key): ?string
343
    {
344
        try {
345 351
            $attributeDTO = $this->tag->getAttribute($key);
346 330
        } catch (AttributeNotFoundException $e) {
347
            // no attribute with this key exists, returning null.
348 330
            unset($e);
349
350 330
            return null;
351
        }
352
353 147
        return $attributeDTO->getValue();
354
    }
355
356
    /**
357
     * A wrapper method that simply calls the hasAttribute method
358
     * on the tag of this node.
359
     */
360 123
    public function hasAttribute(string $key): bool
361
    {
362 123
        return $this->tag->hasAttribute($key);
363
    }
364
365
    /**
366
     * A wrapper method that simply calls the setAttribute method
367
     * on the tag of this node.
368
     */
369 24
    public function setAttribute(string $key, ?string $value, bool $doubleQuote = true): AbstractNode
370
    {
371 24
        $this->tag->setAttribute($key, $value, $doubleQuote);
372
373
        //clear any cache
374 24
        $this->clear();
375
376 24
        return $this;
377
    }
378
379
    /**
380
     * A wrapper method that simply calls the removeAttribute method
381
     * on the tag of this node.
382
     */
383 3
    public function removeAttribute(string $key): void
384
    {
385 3
        $this->tag->removeAttribute($key);
386
387
        //clear any cache
388 3
        $this->clear();
389 3
    }
390
391
    /**
392
     * A wrapper method that simply calls the removeAllAttributes
393
     * method on the tag of this node.
394
     */
395 3
    public function removeAllAttributes(): void
396
    {
397 3
        $this->tag->removeAllAttributes();
398
399
        //clear any cache
400 3
        $this->clear();
401 3
    }
402
403
    /**
404
     * Function to locate a specific ancestor tag in the path to the root.
405
     *
406
     * @throws ParentNotFoundException
407
     */
408 6
    public function ancestorByTag(string $tag): AbstractNode
409
    {
410
        // Start by including ourselves in the comparison.
411 6
        $node = $this;
412
413
        do {
414 6
            if ($node->tag->name() == $tag) {
415 3
                return $node;
416
            }
417
418 3
            $node = $node->getParent();
419 3
        } while ($node !== null);
420
421 3
        throw new ParentNotFoundException('Could not find an ancestor with "' . $tag . '" tag');
422
    }
423
424
    /**
425
     * Find elements by css selector.
426
     *
427
     * @throws ChildNotFoundException
428
     *
429
     * @return mixed|Collection|null
430
     */
431 288
    public function find(string $selectorString, ?int $nth = null, ?SelectorInterface $selector = null)
432
    {
433 288
        if (\is_null($selector)) {
434 288
            $selector = new Selector($selectorString);
435
        }
436
437 288
        $nodes = $selector->find($this);
438
439 288
        if ($nth !== null) {
440
            // return nth-element or array
441 288
            if (isset($nodes[$nth])) {
442 132
                return $nodes[$nth];
443
            }
444
445 255
            return;
446
        }
447
448 111
        return $nodes;
449
    }
450
451
    /**
452
     * Find node by id.
453
     *
454
     * @throws ChildNotFoundException
455
     * @throws ParentNotFoundException
456
     *
457
     * @return bool|AbstractNode
458
     */
459
    public function findById(int $id)
460
    {
461
        $finder = new Finder($id);
462
463
        return $finder->find($this);
464
    }
465
466
    /**
467
     * Gets the inner html of this node.
468
     */
469
    abstract public function innerHtml(): string;
470
471
    /**
472
     * Gets the html of this node, including it's own
473
     * tag.
474
     */
475
    abstract public function outerHtml(): string;
476
477
    /**
478
     * Gets the text of this node (if there is any text).
479
     */
480
    abstract public function text(): string;
481
482
    /**
483
     * Check is node type textNode.
484
     */
485
    public function isTextNode(): bool
486
    {
487
        return false;
488
    }
489
490
    /**
491
     * Call this when something in the node tree has changed. Like a child has been added
492
     * or a parent has been changed.
493
     */
494
    abstract protected function clear(): void;
495
}
496