Completed
Push — master ( 5ed2e8...45d18c )
by Gilles
02:48
created

InnerNode::firstChild()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
c 1
b 1
f 1
dl 0
loc 7
rs 9.4285
cc 1
eloc 4
nc 1
nop 0
1
<?php
2
namespace PHPHtmlParser\Dom;
3
4
use PHPHtmlParser\Exceptions\ChildNotFoundException;
5
use PHPHtmlParser\Exceptions\CircularException;
6
use stringEncode\Encode;
7
8
/**
9
 * Inner node of the html tree, might have children.
10
 *
11
 * @package PHPHtmlParser\Dom
12
 */
13
abstract class InnerNode extends ArrayNode
14
{
15
16
    /**
17
     * An array of all the children.
18
     *
19
     * @var array
20
     */
21
    protected $children = [];
22
23
    /**
24
     * Sets the encoding class to this node and propagates it
25
     * to all its children.
26
     *
27
     * @param Encode $encode
28
     * @return void
29
     */
30
    public function propagateEncoding(Encode $encode)
31
    {
32
        $this->encode = $encode;
33
        $this->tag->setEncoding($encode);
34
        // check children
35
        foreach ($this->children as $id => $child) {
36
            /** @var AbstractNode $node */
37
            $node = $child['node'];
38
            $node->propagateEncoding($encode);
39
        }
40
    }
41
42
    /**
43
     * Checks if this node has children.
44
     *
45
     * @return bool
46
     */
47
    public function hasChildren()
48
    {
49
        return ! empty($this->children);
50
    }
51
52
    /**
53
     * Returns the child by id.
54
     *
55
     * @param int $id
56
     * @return AbstractNode
57
     * @throws ChildNotFoundException
58
     */
59
    public function getChild($id)
60
    {
61
        if ( ! isset($this->children[$id])) {
62
            throw new ChildNotFoundException("Child '$id' not found in this node.");
63
        }
64
65
        return $this->children[$id]['node'];
66
    }
67
68
    /**
69
     * Returns a new array of child nodes
70
     *
71
     * @return array
72
     */
73
    public function getChildren()
74
    {
75
        $nodes = [];
76
        try {
77
            $child = $this->firstChild();
78
            do {
79
                $nodes[] = $child;
80
                $child   = $this->nextChild($child->id());
81
            } while ( ! is_null($child));
82
        } catch (ChildNotFoundException $e) {
83
            // we are done looking for children
84
        }
85
86
        return $nodes;
87
    }
88
89
    /**
90
     * Counts children
91
     *
92
     * @return int
93
     */
94
    public function countChildren()
95
    {
96
        return count($this->children);
97
    }
98
99
    /**
100
     * Adds a child node to this node and returns the id of the child for this
101
     * parent.
102
     *
103
     * @param AbstractNode $child
104
     * @return bool
105
     * @throws CircularException
106
     */
107
    public function addChild(AbstractNode $child)
108
    {
109
        $key = null;
110
111
        // check integrity
112
        if ($this->isAncestor($child->id())) {
113
            throw new CircularException('Can not add child. It is my ancestor.');
114
        }
115
116
        // check if child is itself
117
        if ($child->id() == $this->id) {
118
            throw new CircularException('Can not set itself as a child.');
119
        }
120
121
        if ($this->hasChildren()) {
122
            if (isset($this->children[$child->id()])) {
123
                // we already have this child
124
                return false;
125
            }
126
            $sibling                      = $this->lastChild();
127
            $key                          = $sibling->id();
128
            $this->children[$key]['next'] = $child->id();
129
        }
130
131
        // add the child
132
        $this->children[$child->id()] = [
133
            'node' => $child,
134
            'next' => null,
135
            'prev' => $key,
136
        ];
137
138
        // tell child I am the new parent
139
        $child->setParent($this);
140
141
        //clear any cache
142
        $this->clear();
143
144
        return true;
145
    }
146
147
    /**
148
     * Removes the child by id.
149
     *
150
     * @param int $id
151
     * @return $this
152
     */
153
    public function removeChild($id)
154
    {
155
        if ( ! isset($this->children[$id])) {
156
            return $this;
157
        }
158
159
        // handle moving next and previous assignments.
160
        $next = $this->children[$id]['next'];
161
        $prev = $this->children[$id]['prev'];
162
        if ( ! is_null($next)) {
163
            $this->children[$next]['prev'] = $prev;
164
        }
165
        if ( ! is_null($prev)) {
166
            $this->children[$prev]['next'] = $next;
167
        }
168
169
        // remove the child
170
        unset($this->children[$id]);
171
172
        //clear any cache
173
        $this->clear();
174
175
        return $this;
176
    }
177
178
    /**
179
     * Attempts to get the next child.
180
     *
181
     * @param int $id
182
     * @return AbstractNode
183
     * @uses $this->getChild()
184
     * @throws ChildNotFoundException
185
     */
186 View Code Duplication
    public function nextChild($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
187
    {
188
        $child = $this->getChild($id);
189
        $next  = $this->children[$child->id()]['next'];
190
191
        return $this->getChild($next);
192
    }
193
194
    /**
195
     * Attempts to get the previous child.
196
     *
197
     * @param int $id
198
     * @return AbstractNode
199
     * @uses $this->getChild()
200
     * @throws ChildNotFoundException
201
     */
202 View Code Duplication
    public function previousChild($id)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203
    {
204
        $child = $this->getchild($id);
205
        $next  = $this->children[$child->id()]['prev'];
206
207
        return $this->getChild($next);
208
    }
209
210
    /**
211
     * Checks if the given node id is a child of the
212
     * current node.
213
     *
214
     * @param int $id
215
     * @return bool
216
     */
217
    public function isChild($id)
218
    {
219
        foreach ($this->children as $childId => $child) {
220
            if ($id == $childId) {
221
                return true;
222
            }
223
        }
224
225
        return false;
226
    }
227
228
    /**
229
     * Removes the child with id $childId and replace it with the new child
230
     * $newChild.
231
     *
232
     * @param int $childId
233
     * @param AbstractNode $newChild
234
     * @throws ChildNotFoundException
235
     */
236
    public function replaceChild($childId, AbstractNode $newChild)
237
    {
238
        $oldChild = $this->getChild($childId);
239
        $keys = array_keys($this->children);
240
        $index = array_search($childId, $keys, true);
241
        $keys[$index] = $newChild->id();
242
        $this->children = array_combine($keys, $this->children);
243
        $this->children[$newChild->id()] = $newChild;
244
        unset($oldChild);
245
    }
246
247
    /**
248
     * Shortcut to return the first child.
249
     *
250
     * @return AbstractNode
251
     * @uses $this->getChild()
252
     */
253
    public function firstChild()
254
    {
255
        reset($this->children);
256
        $key = key($this->children);
257
258
        return $this->getChild($key);
259
    }
260
261
    /**
262
     * Attempts to get the last child.
263
     *
264
     * @return AbstractNode
265
     */
266
    public function lastChild()
267
    {
268
        end($this->children);
269
        $key = key($this->children);
270
271
        return $this->getChild($key);
272
    }
273
274
    /**
275
     * Checks if the given node id is a descendant of the
276
     * current node.
277
     *
278
     * @param int $id
279
     * @return bool
280
     */
281
    public function isDescendant($id)
282
    {
283
        if ($this->isChild($id)) {
284
            return true;
285
        }
286
287
        foreach ($this->children as $childId => $child) {
288
            /** @var InnerNode $node */
289
            $node = $child['node'];
290
            if ($node instanceof InnerNode &&
291
                $node->hasChildren() &&
292
                $node->isDescendant($id)
293
            ) {
294
                return true;
295
            }
296
        }
297
298
        return false;
299
    }
300
301
    /**
302
     * Sets the parent node.
303
     *
304
     * @param InnerNode $parent
305
     * @return $this
306
     * @throws CircularException
307
     */
308
    public function setParent(InnerNode $parent)
309
    {
310
        // check integrity
311
        if ($this->isDescendant($parent->id())) {
312
            throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
313
        }
314
315
        return parent::setParent($parent);
316
    }
317
}