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
|
|
|
*/
|
185
|
|
View Code Duplication |
public function nextChild($id)
|
|
|
|
|
186
|
|
|
{
|
187
|
|
|
$child = $this->getChild($id);
|
188
|
|
|
$next = $this->children[$child->id()]['next'];
|
189
|
|
|
|
190
|
|
|
return $this->getChild($next);
|
191
|
|
|
}
|
192
|
|
|
|
193
|
|
|
/**
|
194
|
|
|
* Attempts to get the previous child.
|
195
|
|
|
*
|
196
|
|
|
* @param int $id
|
197
|
|
|
* @return AbstractNode
|
198
|
|
|
* @uses $this->getChild()
|
199
|
|
|
*/
|
200
|
|
View Code Duplication |
public function previousChild($id)
|
|
|
|
|
201
|
|
|
{
|
202
|
|
|
$child = $this->getchild($id);
|
203
|
|
|
$next = $this->children[$child->id()]['prev'];
|
204
|
|
|
|
205
|
|
|
return $this->getChild($next);
|
206
|
|
|
}
|
207
|
|
|
|
208
|
|
|
/**
|
209
|
|
|
* Checks if the given node id is a child of the
|
210
|
|
|
* current node.
|
211
|
|
|
*
|
212
|
|
|
* @param int $id
|
213
|
|
|
* @return bool
|
214
|
|
|
*/
|
215
|
|
|
public function isChild($id)
|
216
|
|
|
{
|
217
|
|
|
foreach ($this->children as $childId => $child) {
|
218
|
|
|
if ($id == $childId) {
|
219
|
|
|
return true;
|
220
|
|
|
}
|
221
|
|
|
}
|
222
|
|
|
|
223
|
|
|
return false;
|
224
|
|
|
}
|
225
|
|
|
|
226
|
|
|
/**
|
227
|
|
|
* Checks if the given node id is a descendant of the
|
228
|
|
|
* current node.
|
229
|
|
|
*
|
230
|
|
|
* @param int $id
|
231
|
|
|
* @return bool
|
232
|
|
|
*/
|
233
|
|
|
public function isDescendant($id)
|
234
|
|
|
{
|
235
|
|
|
if ($this->isChild($id)) {
|
236
|
|
|
return true;
|
237
|
|
|
}
|
238
|
|
|
|
239
|
|
|
foreach ($this->children as $childId => $child) {
|
240
|
|
|
/** @var InnerNode $node */
|
241
|
|
|
$node = $child['node'];
|
242
|
|
|
if ($node instanceof InnerNode &&
|
243
|
|
|
$node->hasChildren() &&
|
244
|
|
|
$node->isDescendant($id)
|
245
|
|
|
) {
|
246
|
|
|
return true;
|
247
|
|
|
}
|
248
|
|
|
}
|
249
|
|
|
|
250
|
|
|
return false;
|
251
|
|
|
}
|
252
|
|
|
|
253
|
|
|
/**
|
254
|
|
|
* Sets the parent node.
|
255
|
|
|
*
|
256
|
|
|
* @param InnerNode $parent
|
257
|
|
|
* @return $this
|
258
|
|
|
* @throws CircularException
|
259
|
|
|
*/
|
260
|
|
|
public function setParent(InnerNode $parent)
|
261
|
|
|
{
|
262
|
|
|
// check integrity
|
263
|
|
|
if ($this->isDescendant($parent->id())) {
|
264
|
|
|
throw new CircularException('Can not add descendant "'.$parent->id().'" as my parent.');
|
265
|
|
|
}
|
266
|
|
|
|
267
|
|
|
return parent::setParent($parent);
|
268
|
|
|
}
|
269
|
|
|
} |
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.