Passed
Branch dev/3.0.0 (c487fc)
by Gilles
01:48
created

AbstractNode::__get()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 17
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7.0178

Importance

Changes 0
Metric Value
cc 7
eloc 13
c 0
b 0
f 0
nc 7
nop 1
dl 0
loc 17
ccs 13
cts 14
cp 0.9286
crap 7.0178
rs 8.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PHPHtmlParser\Dom\Node;
6
7
use PHPHtmlParser\Contracts\Selector\SelectorInterface;
8
use PHPHtmlParser\Exceptions\ChildNotFoundException;
9
use PHPHtmlParser\Exceptions\CircularException;
10
use PHPHtmlParser\Exceptions\ParentNotFoundException;
11
use PHPHtmlParser\Exceptions\Tag\AttributeNotFoundException;
12
use PHPHtmlParser\Dom\Tag;
13
use PHPHtmlParser\Finder;
14
use PHPHtmlParser\Selector\Selector;
15
use stringEncode\Encode;
16
17
/**
18
 * Dom node object.
19
 *
20
 * @property string    $outerhtml
21
 * @property string    $innerhtml
22
 * @property string    $text
23
 * @property int       $prev
24
 * @property int       $next
25
 * @property Tag       $tag
26
 * @property InnerNode $parent
27
 */
28
abstract class AbstractNode
29
{
30
    /**
31
     * Contains the tag name/type.
32
     *
33
     * @var ?Tag
34
     */
35
    protected $tag;
36
37
    /**
38
     * Contains a list of attributes on this tag.
39
     *
40
     * @var array
41
     */
42
    protected $attr = [];
43
44
    /**
45
     * Contains the parent Node.
46
     *
47
     * @var ?InnerNode
48
     */
49
    protected $parent;
50
51
    /**
52
     * The unique id of the class. Given by PHP.
53
     *
54
     * @var int
55
     */
56
    protected $id;
57
58
    /**
59
     * The encoding class used to encode strings.
60
     *
61
     * @var mixed
62
     */
63
    protected $encode;
64
65
    /**
66
     * An array of all the children.
67
     *
68
     * @var array
69
     */
70
    protected $children = [];
71
72
    /**
73
     * @var bool
74
     */
75
    protected $htmlSpecialCharsDecode = false;
76
    /**
77
     * @var int
78
     */
79
    private static $count = 0;
80
81
    /**
82
     * Creates a unique id for this node.
83
     */
84 552
    public function __construct()
85
    {
86 552
        $this->id = self::$count;
87 552
        ++self::$count;
88 552
    }
89
90
    /**
91
     * Attempts to clear out any object references.
92
     */
93 133
    public function __destruct()
94
    {
95 133
        $this->tag = null;
96 133
        $this->parent = null;
97 133
        $this->attr = [];
98 133
        $this->children = [];
99 133
    }
100
101
    /**
102
     * Magic get method for attributes and certain methods.
103
     *
104
     * @return mixed
105
     */
106 318
    public function __get(string $key)
107
    {
108
        // check attribute first
109 318
        if ($this->getAttribute($key) !== null) {
110 18
            return $this->getAttribute($key);
111
        }
112 315
        switch (\strtolower($key)) {
113 315
            case 'outerhtml':
114 39
                return $this->outerHtml();
115 306
            case 'innerhtml':
116 78
                return $this->innerHtml();
117 303
            case 'text':
118 51
                return $this->text();
119 288
            case 'tag':
120 288
                return $this->getTag();
121 3
            case 'parent':
122
                return $this->getParent();
123
        }
124 3
    }
125
126
    /**
127
     * Simply calls the outer text method.
128
     *
129
     * @return string
130
     */
131 9
    public function __toString()
132
    {
133 9
        return $this->outerHtml();
134
    }
135
136
    /**
137
     * @param bool $htmlSpecialCharsDecode
138
     */
139 285
    public function setHtmlSpecialCharsDecode($htmlSpecialCharsDecode = false): void
140
    {
141 285
        $this->htmlSpecialCharsDecode = $htmlSpecialCharsDecode;
142 285
    }
143
144
    /**
145
     * Returns the id of this object.
146
     */
147 483
    public function id(): int
148
    {
149 483
        return $this->id;
150
    }
151
152
    /**
153
     * Returns the parent of node.
154
     *
155
     * @return AbstractNode
156
     */
157 258
    public function getParent()
158
    {
159 258
        return $this->parent;
160
    }
161
162
    /**
163
     * Sets the parent node.
164
     *
165
     * @throws ChildNotFoundException
166
     * @throws CircularException
167
     */
168 477
    public function setParent(InnerNode $parent): AbstractNode
169
    {
170
        // remove from old parent
171 477
        if ($this->parent !== null) {
172 33
            if ($this->parent->id() == $parent->id()) {
173
                // already the parent
174 27
                return $this;
175
            }
176
177 9
            $this->parent->removeChild($this->id);
178
        }
179
180 477
        $this->parent = $parent;
181
182
        // assign child to parent
183 477
        $this->parent->addChild($this);
184
185 477
        return $this;
186
    }
187
188
    /**
189
     * Removes this node and all its children from the
190
     * DOM tree.
191
     *
192
     * @return void
193
     */
194 3
    public function delete()
195
    {
196 3
        if ($this->parent !== null) {
197 3
            $this->parent->removeChild($this->id);
198
        }
199 3
        $this->parent->clear();
200 3
        $this->clear();
201 3
    }
202
203
    /**
204
     * Sets the encoding class to this node.
205
     *
206
     * @return void
207
     */
208 243
    public function propagateEncoding(Encode $encode)
209
    {
210 243
        $this->encode = $encode;
211 243
        $this->tag->setEncoding($encode);
212 243
    }
213
214
    /**
215
     * Checks if the given node id is an ancestor of
216
     * the current node.
217
     */
218 480
    public function isAncestor(int $id): bool
219
    {
220 480
        if ($this->getAncestor($id) !== null) {
221 6
            return true;
222
        }
223
224 480
        return false;
225
    }
226
227
    /**
228
     * Attempts to get an ancestor node by the given id.
229
     *
230
     * @return AbstractNode|null
231
     */
232 483
    public function getAncestor(int $id)
233
    {
234 483
        if ($this->parent !== null) {
235 327
            if ($this->parent->id() == $id) {
236 12
                return $this->parent;
237
            }
238
239 318
            return $this->parent->getAncestor($id);
240
        }
241 483
    }
242
243
    /**
244
     * Checks if the current node has a next sibling.
245
     */
246
    public function hasNextSibling(): bool
247
    {
248
        try {
249
            $this->nextSibling();
250
251
            // sibling found, return true;
252
            return true;
253
        } catch (ParentNotFoundException $e) {
254
            // no parent, no next sibling
255
            unset($e);
256
257
            return false;
258
        } catch (ChildNotFoundException $e) {
259
            // no sibling found
260
            unset($e);
261
262
            return false;
263
        }
264
    }
265
266
    /**
267
     * Attempts to get the next sibling.
268
     *
269
     * @throws ChildNotFoundException
270
     * @throws ParentNotFoundException
271
     */
272 30
    public function nextSibling(): AbstractNode
273
    {
274 30
        if ($this->parent === null) {
275 3
            throw new ParentNotFoundException('Parent is not set for this node.');
276
        }
277
278 27
        return $this->parent->nextChild($this->id);
279
    }
280
281
    /**
282
     * Attempts to get the previous sibling.
283
     *
284
     * @throws ChildNotFoundException
285
     * @throws ParentNotFoundException
286
     */
287 9
    public function previousSibling(): AbstractNode
288
    {
289 9
        if ($this->parent === null) {
290 3
            throw new ParentNotFoundException('Parent is not set for this node.');
291
        }
292
293 6
        return $this->parent->previousChild($this->id);
294
    }
295
296
    /**
297
     * Gets the tag object of this node.
298
     */
299 354
    public function getTag(): Tag
300
    {
301 354
        return $this->tag;
302
    }
303
304
    /**
305
     * Replaces the tag for this node.
306
     *
307
     * @param string|Tag $tag
308
     *
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;
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 339
    public function getAttribute(string $key): ?string
343
    {
344
        try {
345 339
            $attributeDTO = $this->tag->getAttribute($key);
346 318
        } catch (AttributeNotFoundException $e) {
347
            // no attribute with this key exists, returning null.
348 318
            unset($e);
349
350 318
            return null;
351
        }
352
353 144
        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 120
    public function hasAttribute(string $key): bool
361
    {
362 120
        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
     *
370
     */
371 24
    public function setAttribute(string $key, ?string $value, bool $doubleQuote = true): AbstractNode
372
    {
373 24
        $this->tag->setAttribute($key, $value, $doubleQuote);
374
375
        //clear any cache
376 24
        $this->clear();
377
378 24
        return $this;
379
    }
380
381
    /**
382
     * A wrapper method that simply calls the removeAttribute method
383
     * on the tag of this node.
384
     */
385 3
    public function removeAttribute(string $key): void
386
    {
387 3
        $this->tag->removeAttribute($key);
388
389
        //clear any cache
390 3
        $this->clear();
391 3
    }
392
393
    /**
394
     * A wrapper method that simply calls the removeAllAttributes
395
     * method on the tag of this node.
396
     */
397 3
    public function removeAllAttributes(): void
398
    {
399 3
        $this->tag->removeAllAttributes();
400
401
        //clear any cache
402 3
        $this->clear();
403 3
    }
404
405
    /**
406
     * Function to locate a specific ancestor tag in the path to the root.
407
     *
408
     * @throws ParentNotFoundException
409
     */
410 6
    public function ancestorByTag(string $tag): AbstractNode
411
    {
412
        // Start by including ourselves in the comparison.
413 6
        $node = $this;
414
415
        do {
416 6
            if ($node->tag->name() == $tag) {
417 3
                return $node;
418
            }
419
420 3
            $node = $node->getParent();
421 3
        } while ($node !== null);
422
423 3
        throw new ParentNotFoundException('Could not find an ancestor with "' . $tag . '" tag');
424
    }
425
426
    /**
427
     * Find elements by css selector.
428
     *
429
     * @throws ChildNotFoundException
430
     *
431
     * @return mixed|Collection|null
432
     */
433 279
    public function find(string $selectorString, ?int $nth = null, ?SelectorInterface $selector = null)
434
    {
435 279
        if (\is_null($selector)) {
436 279
            $selector = new Selector($selectorString);
437
        }
438
439 279
        $nodes = $selector->find($this);
440
441 279
        if ($nth !== null) {
442
            // return nth-element or array
443 279
            if (isset($nodes[$nth])) {
444 129
                return $nodes[$nth];
445
            }
446
447 249
            return;
448
        }
449
450 111
        return $nodes;
451
    }
452
453
    /**
454
     * Find node by id.
455
     *
456
     * @throws ChildNotFoundException
457
     * @throws ParentNotFoundException
458
     *
459
     * @return bool|AbstractNode
460
     */
461
    public function findById(int $id)
462
    {
463
        $finder = new Finder($id);
464
465
        return $finder->find($this);
466
    }
467
468
    /**
469
     * Gets the inner html of this node.
470
     */
471
    abstract public function innerHtml(): string;
472
473
    /**
474
     * Gets the html of this node, including it's own
475
     * tag.
476
     */
477
    abstract public function outerHtml(): string;
478
479
    /**
480
     * Gets the text of this node (if there is any text).
481
     */
482
    abstract public function text(): string;
483
484
    /**
485
     * Check is node type textNode.
486
     */
487
    public function isTextNode(): bool
488
    {
489
        return false;
490
    }
491
492
    /**
493
     * Call this when something in the node tree has changed. Like a child has been added
494
     * or a parent has been changed.
495
     */
496
    abstract protected function clear(): void;
497
}
498