Completed
Pull Request — master (#90)
by
unknown
03:21
created

InnerNode   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
wmc 35
c 0
b 0
f 0
lcom 1
cbo 5
dl 0
loc 317
rs 9

16 Methods

Rating   Name   Duplication   Size   Complexity  
A propagateEncoding() 0 11 2
A hasChildren() 0 4 1
A getChild() 0 8 2
A getChildren() 0 15 3
A countChildren() 0 4 1
B addChild() 0 39 5
B removeChild() 0 24 4
A hasNextChild() 0 5 1
A nextChild() 0 7 1
A previousChild() 0 7 1
A isChild() 0 10 3
A replaceChild() 0 10 1
A firstChild() 0 7 1
A lastChild() 0 7 1
B isDescendant() 0 19 6
A setParent() 0 9 2
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
     * Check if has next Child
180
     *
181
     * @param $id childId
182
     * @return mixed
183
     */
184
    public function hasNextChild($id)
185
    {
186
        $child= $this->getChild($id);
187
        return $this->children[$child->id()]['next'];
188
    }
189
190
    /**
191
     * Attempts to get the next child.
192
     *
193
     * @param int $id
194
     * @return AbstractNode
195
     * @uses $this->getChild()
196
     * @throws ChildNotFoundException
197
     */
198
    public function nextChild($id)
199
    {
200
        $child = $this->getChild($id);
201
        $next  = $this->children[$child->id()]['next'];
202
203
        return $this->getChild($next);
204
    }
205
206
    /**
207
     * Attempts to get the previous child.
208
     *
209
     * @param int $id
210
     * @return AbstractNode
211
     * @uses $this->getChild()
212
     * @throws ChildNotFoundException
213
     */
214
    public function previousChild($id)
215
    {
216
        $child = $this->getchild($id);
217
        $next  = $this->children[$child->id()]['prev'];
218
219
        return $this->getChild($next);
220
    }
221
222
    /**
223
     * Checks if the given node id is a child of the
224
     * current node.
225
     *
226
     * @param int $id
227
     * @return bool
228
     */
229
    public function isChild($id)
230
    {
231
        foreach ($this->children as $childId => $child) {
232
            if ($id == $childId) {
233
                return true;
234
            }
235
        }
236
237
        return false;
238
    }
239
240
    /**
241
     * Removes the child with id $childId and replace it with the new child
242
     * $newChild.
243
     *
244
     * @param int $childId
245
     * @param AbstractNode $newChild
246
     * @throws ChildNotFoundException
247
     */
248
    public function replaceChild($childId, AbstractNode $newChild)
249
    {
250
        $oldChild                        = $this->getChild($childId);
251
        $keys                            = array_keys($this->children);
252
        $index                           = array_search($childId, $keys, true);
253
        $keys[$index]                    = $newChild->id();
254
        $this->children                  = array_combine($keys, $this->children);
255
        $this->children[$newChild->id()] = $newChild;
256
        unset($oldChild);
257
    }
258
259
    /**
260
     * Shortcut to return the first child.
261
     *
262
     * @return AbstractNode
263
     * @uses $this->getChild()
264
     */
265
    public function firstChild()
266
    {
267
        reset($this->children);
268
        $key = key($this->children);
269
270
        return $this->getChild($key);
271
    }
272
273
    /**
274
     * Attempts to get the last child.
275
     *
276
     * @return AbstractNode
277
     */
278
    public function lastChild()
279
    {
280
        end($this->children);
281
        $key = key($this->children);
282
283
        return $this->getChild($key);
284
    }
285
286
    /**
287
     * Checks if the given node id is a descendant of the
288
     * current node.
289
     *
290
     * @param int $id
291
     * @return bool
292
     */
293
    public function isDescendant($id)
294
    {
295
        if ($this->isChild($id)) {
296
            return true;
297
        }
298
299
        foreach ($this->children as $childId => $child) {
300
            /** @var InnerNode $node */
301
            $node = $child['node'];
302
            if ($node instanceof InnerNode &&
303
                $node->hasChildren() &&
304
                $node->isDescendant($id)
305
            ) {
306
                return true;
307
            }
308
        }
309
310
        return false;
311
    }
312
313
    /**
314
     * Sets the parent node.
315
     *
316
     * @param InnerNode $parent
317
     * @return $this
318
     * @throws CircularException
319
     */
320
    public function setParent(InnerNode $parent)
321
    {
322
        // check integrity
323
        if ($this->isDescendant($parent->id())) {
324
            throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
325
        }
326
327
        return parent::setParent($parent);
328
    }
329
}