Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
365 lars 1
<?php
2
 
3
declare(strict_types=1);
4
 
5
namespace PHPHtmlParser\Dom;
6
 
7
use PHPHtmlParser\Exceptions\ChildNotFoundException;
8
use PHPHtmlParser\Exceptions\CircularException;
9
use PHPHtmlParser\Exceptions\LogicalException;
10
use stringEncode\Encode;
11
 
12
/**
13
 * Inner node of the html tree, might have children.
14
 *
15
 * @package PHPHtmlParser\Dom
16
 */
17
abstract class InnerNode extends ArrayNode
18
{
19
 
20
    /**
21
     * An array of all the children.
22
     *
23
     * @var array
24
     */
25
    protected $children = [];
26
 
27
    /**
28
     * Sets the encoding class to this node and propagates it
29
     * to all its children.
30
     *
31
     * @param Encode $encode
32
     *
33
     * @return void
34
     */
35
    public function propagateEncoding(Encode $encode): void
36
    {
37
        $this->encode = $encode;
38
        $this->tag->setEncoding($encode);
39
        // check children
40
        foreach ($this->children as $child) {
41
            /** @var AbstractNode $node */
42
            $node = $child['node'];
43
            $node->propagateEncoding($encode);
44
        }
45
    }
46
 
47
    /**
48
     * Checks if this node has children.
49
     *
50
     * @return bool
51
     */
52
    public function hasChildren(): bool
53
    {
54
        return !empty($this->children);
55
    }
56
 
57
    /**
58
     * Returns the child by id.
59
     *
60
     * @param int $id
61
     *
62
     * @return AbstractNode
63
     * @throws ChildNotFoundException
64
     */
65
    public function getChild(int $id): AbstractNode
66
    {
67
        if (!isset($this->children[$id])) {
68
            throw new ChildNotFoundException("Child '$id' not found in this node.");
69
        }
70
 
71
        return $this->children[$id]['node'];
72
    }
73
 
74
    /**
75
     * Returns a new array of child nodes
76
     *
77
     * @return array
78
     */
79
    public function getChildren(): array
80
    {
81
        $nodes = [];
82
        $childrenIds = [];
83
        try {
84
            $child = $this->firstChild();
85
            do {
86
                $nodes[] = $child;
87
                $childrenIds[] = $child->id;
88
                $child = $this->nextChild($child->id());
89
                if (in_array($child->id, $childrenIds, true)) {
90
                    throw new CircularException('Circular sibling referance found. Child with id '.$child->id().' found twice.');
91
                }
92
            } while (true);
93
        } catch (ChildNotFoundException $e) {
94
            // we are done looking for children
95
            unset($e);
96
        }
97
 
98
        return $nodes;
99
    }
100
 
101
    /**
102
     * Counts children
103
     *
104
     * @return int
105
     */
106
    public function countChildren(): int
107
    {
108
        return count($this->children);
109
    }
110
 
111
    /**
112
     * Adds a child node to this node and returns the id of the child for this
113
     * parent.
114
     * @param AbstractNode $child
115
     * @param int          $before
116
     * @return bool
117
     * @throws ChildNotFoundException
118
     * @throws CircularException
119
     */
120
    public function addChild(AbstractNode $child, int $before = -1): bool
121
    {
122
        $key = null;
123
 
124
        // check integrity
125
        if ($this->isAncestor($child->id())) {
126
            throw new CircularException('Can not add child. It is my ancestor.');
127
        }
128
 
129
        // check if child is itself
130
        if ($child->id() == $this->id) {
131
            throw new CircularException('Can not set itself as a child.');
132
        }
133
 
134
        $next = null;
135
 
136
        if ($this->hasChildren()) {
137
            if (isset($this->children[$child->id()])) {
138
                // we already have this child
139
                return false;
140
            }
141
 
142
            if ($before >= 0) {
143
                if (!isset($this->children[$before])) {
144
                    return false;
145
                }
146
 
147
                $key = $this->children[$before]['prev'];
148
 
149
                if ($key) {
150
                    $this->children[$key]['next'] = $child->id();
151
                }
152
 
153
                $this->children[$before]['prev'] = $child->id();
154
                $next = $before;
155
            } else {
156
                $sibling = $this->lastChild();
157
                $key = $sibling->id();
158
 
159
                $this->children[$key]['next'] = $child->id();
160
            }
161
        }
162
 
163
        $keys = array_keys($this->children);
164
 
165
        $insert = [
166
          'node' => $child,
167
          'next' => $next,
168
          'prev' => $key,
169
        ];
170
 
171
        $index = $key ? (int) (array_search($key, $keys, true) + 1) : 0;
172
        array_splice($keys, $index, 0, (string) $child->id());
173
 
174
        $children = array_values($this->children);
175
        array_splice($children, $index, 0, [$insert]);
176
 
177
        // add the child
178
        $combination = array_combine($keys, $children);
179
        $this->children = $combination;
180
 
181
        // tell child I am the new parent
182
        $child->setParent($this);
183
 
184
        //clear any cache
185
        $this->clear();
186
 
187
        return true;
188
    }
189
 
190
    /**
191
     * Insert element before child with provided id
192
     * @param AbstractNode $child
193
     * @param int          $id
194
     * @return bool
195
     * @throws ChildNotFoundException
196
     * @throws CircularException
197
     */
198
    public function insertBefore(AbstractNode $child, int $id): bool
199
    {
200
        return $this->addChild($child, $id);
201
    }
202
 
203
    /**
204
     * Insert element before after with provided id
205
     * @param AbstractNode $child
206
     * @param int          $id
207
     * @return bool
208
     * @throws ChildNotFoundException
209
     * @throws CircularException
210
     */
211
    public function insertAfter(AbstractNode $child, int $id): bool
212
    {
213
        if (!isset($this->children[$id])) {
214
            return false;
215
        }
216
 
217
        if (isset($this->children[$id]['next']) && is_int($this->children[$id]['next'])) {
218
            return $this->addChild($child, (int) $this->children[$id]['next']);
219
        }
220
 
221
        // clear cache
222
        $this->clear();
223
 
224
        return $this->addChild($child);
225
    }
226
 
227
    /**
228
     * Removes the child by id.
229
     *
230
     * @param int $id
231
     *
232
     * @return InnerNode
233
     * @chainable
234
     */
235
    public function removeChild(int $id): InnerNode
236
    {
237
        if (!isset($this->children[$id])) {
238
            return $this;
239
        }
240
 
241
        // handle moving next and previous assignments.
242
        $next = $this->children[$id]['next'];
243
        $prev = $this->children[$id]['prev'];
244
        if (!is_null($next)) {
245
            $this->children[$next]['prev'] = $prev;
246
        }
247
        if (!is_null($prev)) {
248
            $this->children[$prev]['next'] = $next;
249
        }
250
 
251
        // remove the child
252
        unset($this->children[$id]);
253
 
254
        //clear any cache
255
        $this->clear();
256
 
257
        return $this;
258
    }
259
 
260
    /**
261
     * Check if has next Child
262
     *
263
     * @param int $id
264
     *
265
     * @return mixed
266
     * @throws ChildNotFoundException
267
     */
268
    public function hasNextChild(int $id)
269
    {
270
        $child = $this->getChild($id);
271
        return $this->children[$child->id()]['next'];
272
    }
273
 
274
    /**
275
     * Attempts to get the next child.
276
     *
277
     * @param int $id
278
     *
279
     * @return AbstractNode
280
     * @throws ChildNotFoundException
281
     * @uses $this->getChild()
282
     */
283
    public function nextChild(int $id): AbstractNode
284
    {
285
        $child = $this->getChild($id);
286
        $next = $this->children[$child->id()]['next'];
287
        if (is_null($next) || !is_int($next)) {
288
            throw new ChildNotFoundException("Child '$id' next sibling not found in this node.");
289
        }
290
 
291
        return $this->getChild($next);
292
    }
293
 
294
    /**
295
     * Attempts to get the previous child.
296
     *
297
     * @param int $id
298
     *
299
     * @return AbstractNode
300
     * @throws ChildNotFoundException
301
     * @uses $this->getChild()
302
     */
303
    public function previousChild(int $id): AbstractNode
304
    {
305
        $child = $this->getchild($id);
306
        $next = $this->children[$child->id()]['prev'];
307
        if (is_null($next) || !is_int($next)) {
308
            throw new ChildNotFoundException("Child '$id' previous not found in this node.");
309
        }
310
 
311
        return $this->getChild($next);
312
    }
313
 
314
    /**
315
     * Checks if the given node id is a child of the
316
     * current node.
317
     *
318
     * @param int $id
319
     *
320
     * @return bool
321
     */
322
    public function isChild(int $id): bool
323
    {
324
        foreach(array_keys($this->children) as $childId) {
325
            if ($id == $childId) {
326
                return true;
327
            }
328
        }
329
 
330
        return false;
331
    }
332
 
333
    /**
334
     * Removes the child with id $childId and replace it with the new child
335
     * $newChild.
336
     *
337
     * @param int          $childId
338
     * @param AbstractNode $newChild
339
     *
340
     * @return void
341
     */
342
    public function replaceChild(int $childId, AbstractNode $newChild): void
343
    {
344
        $oldChild = $this->children[$childId];
345
 
346
        $newChild->prev = (int) $oldChild['prev'];
347
        $newChild->next = (int) $oldChild['next'];
348
 
349
        $keys = array_keys($this->children);
350
        $index = array_search($childId, $keys, true);
351
        $keys[$index] = $newChild->id();
352
        $combination = array_combine($keys, $this->children);
353
        $this->children = $combination;
354
        $this->children[$newChild->id()] = [
355
          'prev' => $oldChild['prev'],
356
          'node' => $newChild,
357
          'next' => $oldChild['next']
358
        ];
359
 
360
        // change previous child id to new child
361
        if ($oldChild['prev'] && isset($this->children[$newChild->prev])) {
362
            $this->children[$oldChild['prev']]['next'] = $newChild->id();
363
        }
364
 
365
        // change next child id to new child
366
        if ($oldChild['next'] && isset($this->children[$newChild->next])) {
367
            $this->children[$oldChild['next']]['prev'] = $newChild->id();
368
        }
369
 
370
        // remove old child
371
        unset($this->children[$childId]);
372
 
373
        // clean out cache
374
        $this->clear();
375
    }
376
 
377
    /**
378
     * Shortcut to return the first child.
379
     *
380
     * @return AbstractNode
381
     * @throws ChildNotFoundException
382
     * @uses $this->getChild()
383
     */
384
    public function firstChild(): AbstractNode
385
    {
386
        if (count($this->children) == 0) {
387
            // no children
388
            throw new ChildNotFoundException("No children found in node.");
389
        }
390
 
391
        reset($this->children);
392
        $key = (int)key($this->children);
393
 
394
        return $this->getChild($key);
395
    }
396
 
397
    /**
398
     * Attempts to get the last child.
399
     *
400
     * @return AbstractNode
401
     * @throws ChildNotFoundException
402
     * @uses $this->getChild()
403
     */
404
    public function lastChild(): AbstractNode
405
    {
406
        if (count($this->children) == 0) {
407
            // no children
408
            throw new ChildNotFoundException("No children found in node.");
409
        }
410
 
411
        end($this->children);
412
        $key = key($this->children);
413
 
414
        if (!is_int($key)) {
415
            throw new LogicalException("Children array contain child with a key that is not an int.");
416
        }
417
 
418
        return $this->getChild($key);
419
    }
420
 
421
    /**
422
     * Checks if the given node id is a descendant of the
423
     * current node.
424
     *
425
     * @param int $id
426
     *
427
     * @return bool
428
     */
429
    public function isDescendant(int $id): bool
430
    {
431
        if ($this->isChild($id)) {
432
            return true;
433
        }
434
 
435
        foreach ($this->children as $child) {
436
            /** @var InnerNode $node */
437
            $node = $child['node'];
438
            if ($node instanceof InnerNode
439
              && $node->hasChildren()
440
              && $node->isDescendant($id)
441
            ) {
442
                return true;
443
            }
444
        }
445
 
446
        return false;
447
    }
448
 
449
    /**
450
     * Sets the parent node.
451
     * @param InnerNode $parent
452
     * @return AbstractNode
453
     * @throws ChildNotFoundException
454
     * @throws CircularException
455
     */
456
    public function setParent(InnerNode $parent): AbstractNode
457
    {
458
        // check integrity
459
        if ($this->isDescendant($parent->id())) {
460
            throw new CircularException('Can not add descendant "'
461
              . $parent->id() . '" as my parent.');
462
        }
463
 
464
        // clear cache
465
        $this->clear();
466
 
467
        return parent::setParent($parent);
468
    }
469
}