Completed
Pull Request — master (#74)
by
unknown
09:10
created

InnerNode::insertAfter()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 11
rs 9.4285
cc 3
eloc 6
nc 3
nop 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, $before = null)
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
		$next = null;
122
123
        if ($this->hasChildren()) {
124
			if (isset($this->children[$child->id()])) {
125
				// we already have this child
126
				return false;
127
			}
128
129
			if ($before) {
130
				if (!isset($this->children[$before])) {
131
					return false;
132
				}
133
134
				$key = $this->children[$before]['prev'];
135
136
				if($key){
137
					$this->children[$key]['next'] = $child->id();
138
				}
139
140
				$this->children[$before]['prev'] = $child->id();
141
				$next = $before;
142
			} else {
143
				$sibling = $this->lastChild();
144
				$key = $sibling->id();
145
146
				$this->children[$key]['next'] = $child->id();
147
			}
148
        }
149
150
		$keys = array_keys($this->children);
151
152
		$insert = [
153
			'node' => $child,
154
			'next' => $next,
155
			'prev' => $key,
156
		];
157
158
		$index = $key ? (array_search($key, $keys, true) + 1) : 0;
159
		array_splice($keys, $index, 0, $child->id());
160
161
		$children = array_values($this->children);
162
		array_splice($children, $index, 0, [$insert]);
163
164
		// add the child
165
		$this->children = array_combine($keys, $children);
166
167
        // tell child I am the new parent
168
        $child->setParent($this);
169
170
        //clear any cache
171
        $this->clear();
172
173
        return true;
174
    }
175
176
	/**
177
	 * Insert element before child with provided id
178
	 *
179
	 * @param AbstractNode $child
180
	 * @return bool
181
	 * @param int $id
182
	 */
183
	public function insertBefore(AbstractNode $child, $id){
184
		$this->addChild($child, $id);
185
	}
186
187
	/**
188
	 * Insert element before after with provided id
189
	 *
190
	 * @param AbstractNode $child
191
	 * @return bool
192
	 * @param int $id
193
	 */
194
	public function insertAfter(AbstractNode $child, $id){
195
		if (!isset($this->children[$id])) {
196
			return false;
197
		}
198
199
		if ($this->children[$id]['next']) {
200
			return $this->addChild($child, $this->children[$id]['next']);
201
		}
202
203
		return $this->addChild($child);
204
	}
205
206
    /**
207
     * Removes the child by id.
208
     *
209
     * @param int $id
210
     * @return $this
211
     */
212
    public function removeChild($id)
213
    {
214
        if ( ! isset($this->children[$id])) {
215
            return $this;
216
        }
217
218
        // handle moving next and previous assignments.
219
        $next = $this->children[$id]['next'];
220
        $prev = $this->children[$id]['prev'];
221
        if ( ! is_null($next)) {
222
            $this->children[$next]['prev'] = $prev;
223
        }
224
        if ( ! is_null($prev)) {
225
            $this->children[$prev]['next'] = $next;
226
        }
227
228
        // remove the child
229
        unset($this->children[$id]);
230
231
        //clear any cache
232
        $this->clear();
233
234
        return $this;
235
    }
236
237
    /**
238
     * Attempts to get the next child.
239
     *
240
     * @param int $id
241
     * @return AbstractNode
242
     * @uses $this->getChild()
243
     * @throws ChildNotFoundException
244
     */
245
    public function nextChild($id)
246
    {
247
        $child = $this->getChild($id);
248
        $next  = $this->children[$child->id()]['next'];
249
250
        return $this->getChild($next);
251
    }
252
253
    /**
254
     * Attempts to get the previous child.
255
     *
256
     * @param int $id
257
     * @return AbstractNode
258
     * @uses $this->getChild()
259
     * @throws ChildNotFoundException
260
     */
261
    public function previousChild($id)
262
    {
263
        $child = $this->getchild($id);
264
        $next  = $this->children[$child->id()]['prev'];
265
266
        return $this->getChild($next);
267
    }
268
269
    /**
270
     * Checks if the given node id is a child of the
271
     * current node.
272
     *
273
     * @param int $id
274
     * @return bool
275
     */
276
    public function isChild($id)
277
    {
278
        foreach ($this->children as $childId => $child) {
279
            if ($id == $childId) {
280
                return true;
281
            }
282
        }
283
284
        return false;
285
    }
286
287
    /**
288
     * Removes the child with id $childId and replace it with the new child
289
     * $newChild.
290
     *
291
     * @param int $childId
292
     * @param AbstractNode $newChild
293
     * @throws ChildNotFoundException
294
     */
295
    public function replaceChild($childId, AbstractNode $newChild)
296
    {
297
        $oldChild                        = $this->getChild($childId);
298
        $keys                            = array_keys($this->children);
299
        $index                           = array_search($childId, $keys, true);
300
        $keys[$index]                    = $newChild->id();
301
        $this->children                  = array_combine($keys, $this->children);
302
        $this->children[$newChild->id()] = $newChild;
303
        unset($oldChild);
304
    }
305
306
    /**
307
     * Shortcut to return the first child.
308
     *
309
     * @return AbstractNode
310
     * @uses $this->getChild()
311
     */
312
    public function firstChild()
313
    {
314
        reset($this->children);
315
        $key = key($this->children);
316
317
        return $this->getChild($key);
318
    }
319
320
    /**
321
     * Attempts to get the last child.
322
     *
323
     * @return AbstractNode
324
     */
325
    public function lastChild()
326
    {
327
        end($this->children);
328
        $key = key($this->children);
329
330
        return $this->getChild($key);
331
    }
332
333
    /**
334
     * Checks if the given node id is a descendant of the
335
     * current node.
336
     *
337
     * @param int $id
338
     * @return bool
339
     */
340
    public function isDescendant($id)
341
    {
342
        if ($this->isChild($id)) {
343
            return true;
344
        }
345
346
        foreach ($this->children as $childId => $child) {
347
            /** @var InnerNode $node */
348
            $node = $child['node'];
349
            if ($node instanceof InnerNode &&
350
                $node->hasChildren() &&
351
                $node->isDescendant($id)
352
            ) {
353
                return true;
354
            }
355
        }
356
357
        return false;
358
    }
359
360
    /**
361
     * Sets the parent node.
362
     *
363
     * @param InnerNode $parent
364
     * @return $this
365
     * @throws CircularException
366
     */
367
    public function setParent(InnerNode $parent)
368
    {
369
        // check integrity
370
        if ($this->isDescendant($parent->id())) {
371
            throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
372
        }
373
374
        return parent::setParent($parent);
375
    }
376
}