Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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