Completed
Push — master ( 77e4a4...d10009 )
by Gilles
03:09
created

AbstractNode::removeAttribute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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