Completed
Push — master ( 3c077b...77d3a9 )
by Gilles
06:36
created
src/PHPHtmlParser/Dom/ArrayNode.php 1 patch
Indentation   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -12,30 +12,30 @@
 block discarded – undo
12 12
 abstract class ArrayNode extends AbstractNode implements IteratorAggregate, Countable
13 13
 {
14 14
 
15
-    /**
16
-     * Gets the iterator
17
-     *
18
-     * @return ArrayIterator
19
-     */
20
-    public function getIterator()
21
-    {
22
-        return new ArrayIterator($this->getIteratorArray());
23
-    }
15
+	/**
16
+	 * Gets the iterator
17
+	 *
18
+	 * @return ArrayIterator
19
+	 */
20
+	public function getIterator()
21
+	{
22
+		return new ArrayIterator($this->getIteratorArray());
23
+	}
24 24
 
25
-    /**
26
-     * Returns the count of the iterator array.
27
-     *
28
-     * @return int
29
-     */
30
-    public function count()
31
-    {
32
-        return count($this->getIteratorArray());
33
-    }
25
+	/**
26
+	 * Returns the count of the iterator array.
27
+	 *
28
+	 * @return int
29
+	 */
30
+	public function count()
31
+	{
32
+		return count($this->getIteratorArray());
33
+	}
34 34
 
35
-    /**
36
-     * Returns the array to be used the the iterator.
37
-     *
38
-     * @return array
39
-     */
40
-    abstract protected function getIteratorArray();
35
+	/**
36
+	 * Returns the array to be used the the iterator.
37
+	 *
38
+	 * @return array
39
+	 */
40
+	abstract protected function getIteratorArray();
41 41
 }
Please login to merge, or discard this patch.
src/PHPHtmlParser/Dom/MockNode.php 1 patch
Indentation   +36 added lines, -36 removed lines patch added patch discarded remove patch
@@ -11,44 +11,44 @@
 block discarded – undo
11 11
 class MockNode extends InnerNode
12 12
 {
13 13
 
14
-    /**
15
-     * Mock of innner html.
16
-     */
17
-    public function innerHtml()
18
-    {
19
-    }
14
+	/**
15
+	 * Mock of innner html.
16
+	 */
17
+	public function innerHtml()
18
+	{
19
+	}
20 20
 
21
-    /**
22
-     * Mock of outer html.
23
-     */
24
-    public function outerHtml()
25
-    {
26
-    }
21
+	/**
22
+	 * Mock of outer html.
23
+	 */
24
+	public function outerHtml()
25
+	{
26
+	}
27 27
 
28
-    /**
29
-     * Mock of text.
30
-     */
31
-    public function text()
32
-    {
33
-    }
28
+	/**
29
+	 * Mock of text.
30
+	 */
31
+	public function text()
32
+	{
33
+	}
34 34
 
35
-    /**
36
-     * Clear content of this node
37
-     */
38
-    protected function clear()
39
-    {
40
-        $this->innerHtml = null;
41
-        $this->outerHtml = null;
42
-        $this->text      = null;
43
-    }
35
+	/**
36
+	 * Clear content of this node
37
+	 */
38
+	protected function clear()
39
+	{
40
+		$this->innerHtml = null;
41
+		$this->outerHtml = null;
42
+		$this->text      = null;
43
+	}
44 44
 
45
-    /**
46
-     * Returns all children of this html node.
47
-     *
48
-     * @return array
49
-     */
50
-    protected function getIteratorArray()
51
-    {
52
-        return $this->getChildren();
53
-    }
45
+	/**
46
+	 * Returns all children of this html node.
47
+	 *
48
+	 * @return array
49
+	 */
50
+	protected function getIteratorArray()
51
+	{
52
+		return $this->getChildren();
53
+	}
54 54
 }
Please login to merge, or discard this patch.
src/PHPHtmlParser/Dom/InnerNode.php 1 patch
Indentation   +253 added lines, -253 removed lines patch added patch discarded remove patch
@@ -13,257 +13,257 @@
 block discarded – undo
13 13
 abstract class InnerNode extends ArrayNode
14 14
 {
15 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
-    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
-    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
-    }
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
+	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
+	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 269
 }
270 270
\ No newline at end of file
Please login to merge, or discard this patch.
src/PHPHtmlParser/Dom/AbstractNode.php 1 patch
Indentation   +455 added lines, -455 removed lines patch added patch discarded remove patch
@@ -16,459 +16,459 @@
 block discarded – undo
16 16
 abstract class AbstractNode
17 17
 {
18 18
 
19
-    /**
20
-     * Contains the tag name/type
21
-     *
22
-     * @var \PHPHtmlParser\Dom\Tag
23
-     */
24
-    protected $tag;
25
-
26
-    /**
27
-     * Contains a list of attributes on this tag.
28
-     *
29
-     * @var array
30
-     */
31
-    protected $attr = [];
32
-
33
-    /**
34
-     * Contains the parent Node.
35
-     *
36
-     * @var InnerNode
37
-     */
38
-    protected $parent = null;
39
-
40
-    /**
41
-     * The unique id of the class. Given by PHP.
42
-     *
43
-     * @var string
44
-     */
45
-    protected $id;
46
-
47
-    /**
48
-     * The encoding class used to encode strings.
49
-     *
50
-     * @var mixed
51
-     */
52
-    protected $encode;
53
-
54
-    /**
55
-     * Creates a unique spl hash for this node.
56
-     */
57
-    public function __construct()
58
-    {
59
-        $this->id = spl_object_hash($this);
60
-    }
61
-
62
-    /**
63
-     * Magic get method for attributes and certain methods.
64
-     *
65
-     * @param string $key
66
-     * @return mixed
67
-     */
68
-    public function __get($key)
69
-    {
70
-        // check attribute first
71
-        if ( ! is_null($this->getAttribute($key))) {
72
-            return $this->getAttribute($key);
73
-        }
74
-        switch (strtolower($key)) {
75
-            case 'outerhtml':
76
-                return $this->outerHtml();
77
-            case 'innerhtml':
78
-                return $this->innerHtml();
79
-            case 'text':
80
-                return $this->text();
81
-        }
82
-
83
-        return null;
84
-    }
85
-
86
-    /**
87
-     * Attempts to clear out any object references.
88
-     */
89
-    public function __destruct()
90
-    {
91
-        $this->tag      = null;
92
-        $this->attr     = [];
93
-        $this->parent   = null;
94
-        $this->children = [];
95
-    }
96
-
97
-    /**
98
-     * Simply calls the outer text method.
99
-     *
100
-     * @return string
101
-     */
102
-    public function __toString()
103
-    {
104
-        return $this->outerHtml();
105
-    }
106
-
107
-    /**
108
-     * Returns the id of this object.
109
-     */
110
-    public function id()
111
-    {
112
-        return $this->id;
113
-    }
114
-
115
-    /**
116
-     * Returns the parent of node.
117
-     *
118
-     * @return AbstractNode
119
-     */
120
-    public function getParent()
121
-    {
122
-        return $this->parent;
123
-    }
124
-
125
-    /**
126
-     * Sets the parent node.
127
-     *
128
-     * @param InnerNode $parent
129
-     * @return $this
130
-     * @throws CircularException
131
-     */
132
-    public function setParent(InnerNode $parent)
133
-    {
134
-        // remove from old parent
135
-        if ( ! is_null($this->parent)) {
136
-            if ($this->parent->id() == $parent->id()) {
137
-                // already the parent
138
-                return $this;
139
-            }
140
-
141
-            $this->parent->removeChild($this->id);
142
-        }
143
-
144
-        $this->parent = $parent;
145
-
146
-        // assign child to parent
147
-        $this->parent->addChild($this);
148
-
149
-        //clear any cache
150
-        $this->clear();
151
-
152
-        return $this;
153
-    }
154
-
155
-    /**
156
-     * Sets the encoding class to this node.
157
-     *
158
-     * @param Encode $encode
159
-     * @return void
160
-     */
161
-    public function propagateEncoding(Encode $encode)
162
-    {
163
-        $this->encode = $encode;
164
-        $this->tag->setEncoding($encode);
165
-    }
166
-
167
-    /**
168
-     * Checks if the given node id is an ancestor of
169
-     * the current node.
170
-     *
171
-     * @param int $id
172
-     * @return bool
173
-     */
174
-    public function isAncestor($id)
175
-    {
176
-        if ( ! is_null($this->getAncestor($id))) {
177
-            return true;
178
-        }
179
-
180
-        return false;
181
-    }
182
-
183
-    /**
184
-     * Attempts to get an ancestor node by the given id.
185
-     *
186
-     * @param int $id
187
-     * @return null|AbstractNode
188
-     */
189
-    public function getAncestor($id)
190
-    {
191
-        if ( ! is_null($this->parent)) {
192
-            if ($this->parent->id() == $id) {
193
-                return $this->parent;
194
-            }
195
-
196
-            return $this->parent->getAncestor($id);
197
-        }
198
-
199
-        return null;
200
-    }
201
-
202
-    /**
203
-     * Shortcut to return the first child.
204
-     *
205
-     * @return AbstractNode
206
-     * @uses $this->getChild()
207
-     */
208
-    public function firstChild()
209
-    {
210
-        reset($this->children);
211
-        $key = key($this->children);
212
-
213
-        return $this->getChild($key);
214
-    }
215
-
216
-    /**
217
-     * Attempts to get the last child.
218
-     *
219
-     * @return AbstractNode
220
-     */
221
-    public function lastChild()
222
-    {
223
-        end($this->children);
224
-        $key = key($this->children);
225
-
226
-        return $this->getChild($key);
227
-    }
228
-
229
-    /**
230
-     * Attempts to get the next sibling.
231
-     *
232
-     * @return AbstractNode
233
-     * @throws ParentNotFoundException
234
-     */
235
-    public function nextSibling()
236
-    {
237
-        if (is_null($this->parent)) {
238
-            throw new ParentNotFoundException('Parent is not set for this node.');
239
-        }
240
-
241
-        return $this->parent->nextChild($this->id);
242
-    }
243
-
244
-    /**
245
-     * Attempts to get the previous sibling
246
-     *
247
-     * @return AbstractNode
248
-     * @throws ParentNotFoundException
249
-     */
250
-    public function previousSibling()
251
-    {
252
-        if (is_null($this->parent)) {
253
-            throw new ParentNotFoundException('Parent is not set for this node.');
254
-        }
255
-
256
-        return $this->parent->previousChild($this->id);
257
-    }
258
-
259
-    /**
260
-     * Gets the tag object of this node.
261
-     *
262
-     * @return Tag
263
-     */
264
-    public function getTag()
265
-    {
266
-        return $this->tag;
267
-    }
268
-
269
-    /**
270
-     * A wrapper method that simply calls the getAttribute method
271
-     * on the tag of this node.
272
-     *
273
-     * @return array
274
-     */
275
-    public function getAttributes()
276
-    {
277
-        $attributes = $this->tag->getAttributes();
278
-        foreach ($attributes as $name => $info) {
279
-            $attributes[$name] = $info['value'];
280
-        }
281
-
282
-        return $attributes;
283
-    }
284
-
285
-    /**
286
-     * A wrapper method that simply calls the getAttribute method
287
-     * on the tag of this node.
288
-     *
289
-     * @param string $key
290
-     * @return mixed
291
-     */
292
-    public function getAttribute($key)
293
-    {
294
-        $attribute = $this->tag->getAttribute($key);
295
-        if ( ! is_null($attribute)) {
296
-            $attribute = $attribute['value'];
297
-        }
298
-
299
-        return $attribute;
300
-    }
301
-
302
-    /**
303
-     * A wrapper method that simply calls the setAttribute method
304
-     * on the tag of this node.
305
-     *
306
-     * @param string $key
307
-     * @param string $value
308
-     * @return $this
309
-     */
310
-    public function setAttribute($key, $value)
311
-    {
312
-        $this->tag->setAttribute($key, $value);
313
-
314
-        return $this;
315
-    }
316
-
317
-    /**
318
-     * Function to locate a specific ancestor tag in the path to the root.
319
-     *
320
-     * @param  string $tag
321
-     * @return AbstractNode
322
-     * @throws ParentNotFoundException
323
-     */
324
-    public function ancestorByTag($tag)
325
-    {
326
-        // Start by including ourselves in the comparison.
327
-        $node = $this;
328
-
329
-        while ( ! is_null($node)) {
330
-            if ($node->tag->name() == $tag) {
331
-                return $node;
332
-            }
333
-
334
-            $node = $node->getParent();
335
-        }
336
-
337
-        throw new ParentNotFoundException('Could not find an ancestor with "'.$tag.'" tag');
338
-    }
339
-
340
-    /**
341
-     * Find elements by css selector
342
-     *
343
-     * @param string $selector
344
-     * @param int $nth
345
-     * @return array|AbstractNode
346
-     */
347
-    public function find($selector, $nth = null)
348
-    {
349
-        $selector = new Selector($selector);
350
-        $nodes    = $selector->find($this);
351
-
352
-        if ( ! is_null($nth)) {
353
-            // return nth-element or array
354
-            if (isset($nodes[$nth])) {
355
-                return $nodes[$nth];
356
-            }
357
-
358
-            return null;
359
-        }
360
-
361
-        return $nodes;
362
-    }
363
-
364
-    /**
365
-     * Function to try a few tricks to determine the displayed size of an img on the page.
366
-     * NOTE: This will ONLY work on an IMG tag. Returns FALSE on all other tag types.
367
-     *
368
-     * Future enhancement:
369
-     * Look in the tag to see if there is a class or id specified that has a height or width attribute to it.
370
-     *
371
-     * Far future enhancement
372
-     * Look at all the parent tags of this image to see if they specify a class or id that has an img selector that specifies a height or width
373
-     * Note that in this case, the class or id will have the img sub-selector for it to apply to the image.
374
-     *
375
-     * ridiculously far future development
376
-     * If the class or id is specified in a SEPARATE css file that's not on the page, go get it and do what we were just doing for the ones on the page.
377
-     *
378
-     * @author John Schlick
379
-     * @return array an array containing the 'height' and 'width' of the image on the page or -1 if we can't figure it out.
380
-     */
381
-    public function get_display_size()
382
-    {
383
-        $width  = -1;
384
-        $height = -1;
385
-
386
-        if ($this->tag->name() != 'img') {
387
-            return false;
388
-        }
389
-
390
-        // See if there is a height or width attribute in the tag itself.
391
-        if ( ! is_null($this->tag->getAttribute('width'))) {
392
-            $width = $this->tag->getAttribute('width');
393
-        }
394
-
395
-        if ( ! is_null($this->tag->getAttribute('height'))) {
396
-            $height = $this->tag->getAttribute('height');
397
-        }
398
-
399
-        // Now look for an inline style.
400
-        if ( ! is_null($this->tag->getAttribute('style'))) {
401
-            // Thanks to user 'gnarf' from stackoverflow for this regular expression.
402
-            $attributes = [];
403
-            preg_match_all("/([\w-]+)\s*:\s*([^;]+)\s*;?/", $this->tag->getAttribute('style'), $matches,
404
-                PREG_SET_ORDER);
405
-            foreach ($matches as $match) {
406
-                $attributes[$match[1]] = $match[2];
407
-            }
408
-
409
-            $width = $this->getLength($attributes, $width, 'width');
410
-            $height = $this->getLength($attributes, $width, 'height');
411
-        }
412
-
413
-        $result = [
414
-            'height' => $height,
415
-            'width'  => $width,
416
-        ];
417
-
418
-        return $result;
419
-    }
420
-
421
-    /**
422
-     * If there is a length in the style attributes use it.
423
-     *
424
-     * @param array $attributes
425
-     * @param int $length
426
-     * @param string $key
427
-     * @return int
428
-     */
429
-    protected function getLength(array $attributes, $length, $key)
430
-    {
431
-        if (isset($attributes[$key]) && $length == -1) {
432
-            // check that the last two characters are px (pixels)
433
-            if (strtolower(substr($attributes[$key], -2)) == 'px') {
434
-                $proposed_length = substr($attributes[$key], 0, -2);
435
-                // Now make sure that it's an integer and not something stupid.
436
-                if (filter_var($proposed_length, FILTER_VALIDATE_INT)) {
437
-                    $length = $proposed_length;
438
-                }
439
-            }
440
-        }
441
-
442
-        return $length;
443
-    }
444
-
445
-    /**
446
-     * Gets the inner html of this node.
447
-     *
448
-     * @return string
449
-     */
450
-    abstract public function innerHtml();
451
-
452
-    /**
453
-     * Gets the html of this node, including it's own
454
-     * tag.
455
-     *
456
-     * @return string
457
-     */
458
-    abstract public function outerHtml();
459
-
460
-    /**
461
-     * Gets the text of this node (if there is any text).
462
-     *
463
-     * @return string
464
-     */
465
-    abstract public function text();
466
-
467
-    /**
468
-     * Call this when something in the node tree has changed. Like a child has been added
469
-     * or a parent has been changed.
470
-     *
471
-     * @return void
472
-     */
473
-    abstract protected function clear();
19
+	/**
20
+	 * Contains the tag name/type
21
+	 *
22
+	 * @var \PHPHtmlParser\Dom\Tag
23
+	 */
24
+	protected $tag;
25
+
26
+	/**
27
+	 * Contains a list of attributes on this tag.
28
+	 *
29
+	 * @var array
30
+	 */
31
+	protected $attr = [];
32
+
33
+	/**
34
+	 * Contains the parent Node.
35
+	 *
36
+	 * @var InnerNode
37
+	 */
38
+	protected $parent = null;
39
+
40
+	/**
41
+	 * The unique id of the class. Given by PHP.
42
+	 *
43
+	 * @var string
44
+	 */
45
+	protected $id;
46
+
47
+	/**
48
+	 * The encoding class used to encode strings.
49
+	 *
50
+	 * @var mixed
51
+	 */
52
+	protected $encode;
53
+
54
+	/**
55
+	 * Creates a unique spl hash for this node.
56
+	 */
57
+	public function __construct()
58
+	{
59
+		$this->id = spl_object_hash($this);
60
+	}
61
+
62
+	/**
63
+	 * Magic get method for attributes and certain methods.
64
+	 *
65
+	 * @param string $key
66
+	 * @return mixed
67
+	 */
68
+	public function __get($key)
69
+	{
70
+		// check attribute first
71
+		if ( ! is_null($this->getAttribute($key))) {
72
+			return $this->getAttribute($key);
73
+		}
74
+		switch (strtolower($key)) {
75
+			case 'outerhtml':
76
+				return $this->outerHtml();
77
+			case 'innerhtml':
78
+				return $this->innerHtml();
79
+			case 'text':
80
+				return $this->text();
81
+		}
82
+
83
+		return null;
84
+	}
85
+
86
+	/**
87
+	 * Attempts to clear out any object references.
88
+	 */
89
+	public function __destruct()
90
+	{
91
+		$this->tag      = null;
92
+		$this->attr     = [];
93
+		$this->parent   = null;
94
+		$this->children = [];
95
+	}
96
+
97
+	/**
98
+	 * Simply calls the outer text method.
99
+	 *
100
+	 * @return string
101
+	 */
102
+	public function __toString()
103
+	{
104
+		return $this->outerHtml();
105
+	}
106
+
107
+	/**
108
+	 * Returns the id of this object.
109
+	 */
110
+	public function id()
111
+	{
112
+		return $this->id;
113
+	}
114
+
115
+	/**
116
+	 * Returns the parent of node.
117
+	 *
118
+	 * @return AbstractNode
119
+	 */
120
+	public function getParent()
121
+	{
122
+		return $this->parent;
123
+	}
124
+
125
+	/**
126
+	 * Sets the parent node.
127
+	 *
128
+	 * @param InnerNode $parent
129
+	 * @return $this
130
+	 * @throws CircularException
131
+	 */
132
+	public function setParent(InnerNode $parent)
133
+	{
134
+		// remove from old parent
135
+		if ( ! is_null($this->parent)) {
136
+			if ($this->parent->id() == $parent->id()) {
137
+				// already the parent
138
+				return $this;
139
+			}
140
+
141
+			$this->parent->removeChild($this->id);
142
+		}
143
+
144
+		$this->parent = $parent;
145
+
146
+		// assign child to parent
147
+		$this->parent->addChild($this);
148
+
149
+		//clear any cache
150
+		$this->clear();
151
+
152
+		return $this;
153
+	}
154
+
155
+	/**
156
+	 * Sets the encoding class to this node.
157
+	 *
158
+	 * @param Encode $encode
159
+	 * @return void
160
+	 */
161
+	public function propagateEncoding(Encode $encode)
162
+	{
163
+		$this->encode = $encode;
164
+		$this->tag->setEncoding($encode);
165
+	}
166
+
167
+	/**
168
+	 * Checks if the given node id is an ancestor of
169
+	 * the current node.
170
+	 *
171
+	 * @param int $id
172
+	 * @return bool
173
+	 */
174
+	public function isAncestor($id)
175
+	{
176
+		if ( ! is_null($this->getAncestor($id))) {
177
+			return true;
178
+		}
179
+
180
+		return false;
181
+	}
182
+
183
+	/**
184
+	 * Attempts to get an ancestor node by the given id.
185
+	 *
186
+	 * @param int $id
187
+	 * @return null|AbstractNode
188
+	 */
189
+	public function getAncestor($id)
190
+	{
191
+		if ( ! is_null($this->parent)) {
192
+			if ($this->parent->id() == $id) {
193
+				return $this->parent;
194
+			}
195
+
196
+			return $this->parent->getAncestor($id);
197
+		}
198
+
199
+		return null;
200
+	}
201
+
202
+	/**
203
+	 * Shortcut to return the first child.
204
+	 *
205
+	 * @return AbstractNode
206
+	 * @uses $this->getChild()
207
+	 */
208
+	public function firstChild()
209
+	{
210
+		reset($this->children);
211
+		$key = key($this->children);
212
+
213
+		return $this->getChild($key);
214
+	}
215
+
216
+	/**
217
+	 * Attempts to get the last child.
218
+	 *
219
+	 * @return AbstractNode
220
+	 */
221
+	public function lastChild()
222
+	{
223
+		end($this->children);
224
+		$key = key($this->children);
225
+
226
+		return $this->getChild($key);
227
+	}
228
+
229
+	/**
230
+	 * Attempts to get the next sibling.
231
+	 *
232
+	 * @return AbstractNode
233
+	 * @throws ParentNotFoundException
234
+	 */
235
+	public function nextSibling()
236
+	{
237
+		if (is_null($this->parent)) {
238
+			throw new ParentNotFoundException('Parent is not set for this node.');
239
+		}
240
+
241
+		return $this->parent->nextChild($this->id);
242
+	}
243
+
244
+	/**
245
+	 * Attempts to get the previous sibling
246
+	 *
247
+	 * @return AbstractNode
248
+	 * @throws ParentNotFoundException
249
+	 */
250
+	public function previousSibling()
251
+	{
252
+		if (is_null($this->parent)) {
253
+			throw new ParentNotFoundException('Parent is not set for this node.');
254
+		}
255
+
256
+		return $this->parent->previousChild($this->id);
257
+	}
258
+
259
+	/**
260
+	 * Gets the tag object of this node.
261
+	 *
262
+	 * @return Tag
263
+	 */
264
+	public function getTag()
265
+	{
266
+		return $this->tag;
267
+	}
268
+
269
+	/**
270
+	 * A wrapper method that simply calls the getAttribute method
271
+	 * on the tag of this node.
272
+	 *
273
+	 * @return array
274
+	 */
275
+	public function getAttributes()
276
+	{
277
+		$attributes = $this->tag->getAttributes();
278
+		foreach ($attributes as $name => $info) {
279
+			$attributes[$name] = $info['value'];
280
+		}
281
+
282
+		return $attributes;
283
+	}
284
+
285
+	/**
286
+	 * A wrapper method that simply calls the getAttribute method
287
+	 * on the tag of this node.
288
+	 *
289
+	 * @param string $key
290
+	 * @return mixed
291
+	 */
292
+	public function getAttribute($key)
293
+	{
294
+		$attribute = $this->tag->getAttribute($key);
295
+		if ( ! is_null($attribute)) {
296
+			$attribute = $attribute['value'];
297
+		}
298
+
299
+		return $attribute;
300
+	}
301
+
302
+	/**
303
+	 * A wrapper method that simply calls the setAttribute method
304
+	 * on the tag of this node.
305
+	 *
306
+	 * @param string $key
307
+	 * @param string $value
308
+	 * @return $this
309
+	 */
310
+	public function setAttribute($key, $value)
311
+	{
312
+		$this->tag->setAttribute($key, $value);
313
+
314
+		return $this;
315
+	}
316
+
317
+	/**
318
+	 * Function to locate a specific ancestor tag in the path to the root.
319
+	 *
320
+	 * @param  string $tag
321
+	 * @return AbstractNode
322
+	 * @throws ParentNotFoundException
323
+	 */
324
+	public function ancestorByTag($tag)
325
+	{
326
+		// Start by including ourselves in the comparison.
327
+		$node = $this;
328
+
329
+		while ( ! is_null($node)) {
330
+			if ($node->tag->name() == $tag) {
331
+				return $node;
332
+			}
333
+
334
+			$node = $node->getParent();
335
+		}
336
+
337
+		throw new ParentNotFoundException('Could not find an ancestor with "'.$tag.'" tag');
338
+	}
339
+
340
+	/**
341
+	 * Find elements by css selector
342
+	 *
343
+	 * @param string $selector
344
+	 * @param int $nth
345
+	 * @return array|AbstractNode
346
+	 */
347
+	public function find($selector, $nth = null)
348
+	{
349
+		$selector = new Selector($selector);
350
+		$nodes    = $selector->find($this);
351
+
352
+		if ( ! is_null($nth)) {
353
+			// return nth-element or array
354
+			if (isset($nodes[$nth])) {
355
+				return $nodes[$nth];
356
+			}
357
+
358
+			return null;
359
+		}
360
+
361
+		return $nodes;
362
+	}
363
+
364
+	/**
365
+	 * Function to try a few tricks to determine the displayed size of an img on the page.
366
+	 * NOTE: This will ONLY work on an IMG tag. Returns FALSE on all other tag types.
367
+	 *
368
+	 * Future enhancement:
369
+	 * Look in the tag to see if there is a class or id specified that has a height or width attribute to it.
370
+	 *
371
+	 * Far future enhancement
372
+	 * Look at all the parent tags of this image to see if they specify a class or id that has an img selector that specifies a height or width
373
+	 * Note that in this case, the class or id will have the img sub-selector for it to apply to the image.
374
+	 *
375
+	 * ridiculously far future development
376
+	 * If the class or id is specified in a SEPARATE css file that's not on the page, go get it and do what we were just doing for the ones on the page.
377
+	 *
378
+	 * @author John Schlick
379
+	 * @return array an array containing the 'height' and 'width' of the image on the page or -1 if we can't figure it out.
380
+	 */
381
+	public function get_display_size()
382
+	{
383
+		$width  = -1;
384
+		$height = -1;
385
+
386
+		if ($this->tag->name() != 'img') {
387
+			return false;
388
+		}
389
+
390
+		// See if there is a height or width attribute in the tag itself.
391
+		if ( ! is_null($this->tag->getAttribute('width'))) {
392
+			$width = $this->tag->getAttribute('width');
393
+		}
394
+
395
+		if ( ! is_null($this->tag->getAttribute('height'))) {
396
+			$height = $this->tag->getAttribute('height');
397
+		}
398
+
399
+		// Now look for an inline style.
400
+		if ( ! is_null($this->tag->getAttribute('style'))) {
401
+			// Thanks to user 'gnarf' from stackoverflow for this regular expression.
402
+			$attributes = [];
403
+			preg_match_all("/([\w-]+)\s*:\s*([^;]+)\s*;?/", $this->tag->getAttribute('style'), $matches,
404
+				PREG_SET_ORDER);
405
+			foreach ($matches as $match) {
406
+				$attributes[$match[1]] = $match[2];
407
+			}
408
+
409
+			$width = $this->getLength($attributes, $width, 'width');
410
+			$height = $this->getLength($attributes, $width, 'height');
411
+		}
412
+
413
+		$result = [
414
+			'height' => $height,
415
+			'width'  => $width,
416
+		];
417
+
418
+		return $result;
419
+	}
420
+
421
+	/**
422
+	 * If there is a length in the style attributes use it.
423
+	 *
424
+	 * @param array $attributes
425
+	 * @param int $length
426
+	 * @param string $key
427
+	 * @return int
428
+	 */
429
+	protected function getLength(array $attributes, $length, $key)
430
+	{
431
+		if (isset($attributes[$key]) && $length == -1) {
432
+			// check that the last two characters are px (pixels)
433
+			if (strtolower(substr($attributes[$key], -2)) == 'px') {
434
+				$proposed_length = substr($attributes[$key], 0, -2);
435
+				// Now make sure that it's an integer and not something stupid.
436
+				if (filter_var($proposed_length, FILTER_VALIDATE_INT)) {
437
+					$length = $proposed_length;
438
+				}
439
+			}
440
+		}
441
+
442
+		return $length;
443
+	}
444
+
445
+	/**
446
+	 * Gets the inner html of this node.
447
+	 *
448
+	 * @return string
449
+	 */
450
+	abstract public function innerHtml();
451
+
452
+	/**
453
+	 * Gets the html of this node, including it's own
454
+	 * tag.
455
+	 *
456
+	 * @return string
457
+	 */
458
+	abstract public function outerHtml();
459
+
460
+	/**
461
+	 * Gets the text of this node (if there is any text).
462
+	 *
463
+	 * @return string
464
+	 */
465
+	abstract public function text();
466
+
467
+	/**
468
+	 * Call this when something in the node tree has changed. Like a child has been added
469
+	 * or a parent has been changed.
470
+	 *
471
+	 * @return void
472
+	 */
473
+	abstract protected function clear();
474 474
 }
Please login to merge, or discard this patch.
src/PHPHtmlParser/Selector.php 1 patch
Indentation   +357 added lines, -357 removed lines patch added patch discarded remove patch
@@ -15,361 +15,361 @@
 block discarded – undo
15 15
 class Selector
16 16
 {
17 17
 
18
-    /**
19
-     * Pattern of CSS selectors, modified from 'mootools'
20
-     *
21
-     * @var string
22
-     */
23
-    protected $pattern = "/([\w-:\*>]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is";
24
-
25
-    protected $selectors = [];
26
-
27
-    /**
28
-     * Constructs with the selector string
29
-     *
30
-     * @param string $selector
31
-     */
32
-    public function __construct($selector)
33
-    {
34
-        $this->parseSelectorString($selector);
35
-    }
36
-
37
-    /**
38
-     * Returns the selectors that where found in __construct
39
-     *
40
-     * @return array
41
-     */
42
-    public function getSelectors()
43
-    {
44
-        return $this->selectors;
45
-    }
46
-
47
-    /**
48
-     * Attempts to find the selectors starting from the given
49
-     * node object.
50
-     *
51
-     * @param AbstractNode $node
52
-     * @return array|Collection
53
-     */
54
-    public function find(AbstractNode $node)
55
-    {
56
-        $results = new Collection;
57
-        foreach ($this->selectors as $selector) {
58
-            $nodes = [$node];
59
-            if (count($selector) == 0) {
60
-                continue;
61
-            }
62
-
63
-            $options = [];
64
-            foreach ($selector as $rule) {
65
-                if ($rule['alterNext']) {
66
-                    $options[] = $this->alterNext($rule);
67
-                    continue;
68
-                }
69
-                $nodes = $this->seek($nodes, $rule, $options);
70
-                // clear the options
71
-                $options = [];
72
-            }
73
-
74
-            // this is the final set of nodes
75
-            foreach ($nodes as $result) {
76
-                $results[] = $result;
77
-            }
78
-        }
79
-
80
-        return $results;
81
-    }
82
-
83
-    /**
84
-     * Parses the selector string
85
-     *
86
-     * @param string $selector
87
-     */
88
-    protected function parseSelectorString($selector)
89
-    {
90
-        $matches = [];
91
-        preg_match_all($this->pattern, trim($selector).' ', $matches, PREG_SET_ORDER);
92
-
93
-        // skip tbody
94
-        $result = [];
95
-        foreach ($matches as $match) {
96
-            // default values
97
-            $tag       = strtolower(trim($match[1]));
98
-            $operator  = '=';
99
-            $key       = null;
100
-            $value     = null;
101
-            $noKey     = false;
102
-            $alterNext = false;
103
-
104
-            // check for elements that alter the behavior of the next element
105
-            if ($tag == '>') {
106
-                $alterNext = true;
107
-            }
108
-
109
-            // check for id selector
110
-            if ( ! empty($match[2])) {
111
-                $key   = 'id';
112
-                $value = $match[2];
113
-            }
114
-
115
-            // check for class selector
116
-            if ( ! empty($match[3])) {
117
-                $key   = 'class';
118
-                $value = $match[3];
119
-            }
120
-
121
-            // and final attribute selector
122
-            if ( ! empty($match[4])) {
123
-                $key = strtolower($match[4]);
124
-            }
125
-            if ( ! empty($match[5])) {
126
-                $operator = $match[5];
127
-            }
128
-            if ( ! empty($match[6])) {
129
-                $value = $match[6];
130
-            }
131
-
132
-            // check for elements that do not have a specified attribute
133
-            if (isset($key[0]) && $key[0] == '!') {
134
-                $key   = substr($key, 1);
135
-                $noKey = true;
136
-            }
137
-
138
-            $result[] = [
139
-                'tag'       => $tag,
140
-                'key'       => $key,
141
-                'value'     => $value,
142
-                'operator'  => $operator,
143
-                'noKey'     => $noKey,
144
-                'alterNext' => $alterNext,
145
-            ];
146
-            if (trim($match[7]) == ',') {
147
-                $this->selectors[] = $result;
148
-                $result            = [];
149
-            }
150
-        }
151
-
152
-        // save last results
153
-        if (count($result) > 0) {
154
-            $this->selectors[] = $result;
155
-        }
156
-    }
157
-
158
-    /**
159
-     * Attempts to find all children that match the rule
160
-     * given.
161
-     *
162
-     * @param array $nodes
163
-     * @param array $rule
164
-     * @param array $options
165
-     * @return array
166
-     * @recursive
167
-     */
168
-    protected function seek(array $nodes, array $rule, array $options)
169
-    {
170
-        // XPath index
171
-        if (count($rule['tag']) > 0 &&
172
-            count($rule['key']) > 0 &&
173
-            is_numeric($rule['key'])
174
-        ) {
175
-            $count = 0;
176
-            /** @var AbstractNode $node */
177
-            foreach ($nodes as $node) {
178
-                if ($rule['tag'] == '*' ||
179
-                    $rule['tag'] == $node->getTag()->name()) {
180
-                    ++$count;
181
-                    if ($count == $rule['key']) {
182
-                        // found the node we wanted
183
-                        return [$node];
184
-                    }
185
-                }
186
-            }
187
-
188
-            return [];
189
-        }
190
-
191
-        $options = $this->flattenOptions($options);
192
-
193
-        $return = [];
194
-        /** @var InnerNode $node */
195
-        foreach ($nodes as $node) {
196
-            // check if we are a leaf
197
-            if ($node instanceof LeafNode ||
198
-                 ! $node->hasChildren()) {
199
-                continue;
200
-            }
201
-
202
-            $children = [];
203
-            $child    = $node->firstChild();
204
-            while ( ! is_null($child)) {
205
-                // wild card, grab all
206
-                if ($rule['tag'] == '*' && is_null($rule['key'])) {
207
-                    $return[] = $child;
208
-                    try {
209
-                        $child = $node->nextChild($child->id());
210
-                    } catch (ChildNotFoundException $e) {
211
-                        // no more children
212
-                        $child = null;
213
-                    }
214
-                    continue;
215
-                }
216
-
217
-                $pass = true;
218
-                // check tag
219
-                if ( ! empty($rule['tag']) && $rule['tag'] != $child->getTag()->name() &&
220
-                    $rule['tag'] != '*'
221
-                ) {
222
-                    // child failed tag check
223
-                    $pass = false;
224
-                }
225
-
226
-                // check key
227
-                if ($pass && ! is_null($rule['key'])) {
228
-                    if ($rule['noKey']) {
229
-                        if ( ! is_null($child->getAttribute($rule['key']))) {
230
-                            $pass = false;
231
-                        }
232
-                    } else {
233
-                        if ($rule['key'] != 'plaintext' &&
234
-                            is_null($child->getAttribute($rule['key']))
235
-                        ) {
236
-                            $pass = false;
237
-                        }
238
-                    }
239
-                }
240
-
241
-                // compare values
242
-                if ($pass && ! is_null($rule['key']) &&
243
-                    ! is_null($rule['value']) && $rule['value'] != '*'
244
-                ) {
245
-                    if ($rule['key'] == 'plaintext') {
246
-                        // plaintext search
247
-                        $nodeValue = $child->text();
248
-                    } else {
249
-                        // normal search
250
-                        $nodeValue = $child->getAttribute($rule['key']);
251
-                    }
252
-
253
-                    $check = $this->match($rule['operator'], $rule['value'], $nodeValue);
254
-
255
-                    // handle multiple classes
256
-                    if ( ! $check && $rule['key'] == 'class') {
257
-                        $childClasses = explode(' ', $child->getAttribute('class'));
258
-                        foreach ($childClasses as $class) {
259
-                            if ( ! empty($class)) {
260
-                                $check = $this->match($rule['operator'], $rule['value'], $class);
261
-                            }
262
-                            if ($check) {
263
-                                break;
264
-                            }
265
-                        }
266
-                    }
267
-
268
-                    if ( ! $check) {
269
-                        $pass = false;
270
-                    }
271
-                }
272
-
273
-                if ($pass) {
274
-                    // it passed all checks
275
-                    $return[] = $child;
276
-                } else {
277
-                    // this child failed to be matched
278
-                    if ($child instanceof InnerNode &&
279
-                        $child->hasChildren()) {
280
-                        // we still want to check its children
281
-                        $children[] = $child;
282
-                    }
283
-                }
284
-
285
-                try {
286
-                    // get next child
287
-                    $child = $node->nextChild($child->id());
288
-                } catch (ChildNotFoundException $e) {
289
-                    // no more children
290
-                    $child = null;
291
-                }
292
-            }
293
-
294
-            if (( ! isset($options['checkGrandChildren']) ||
295
-                    $options['checkGrandChildren'])
296
-                && count($children) > 0
297
-            ) {
298
-                // we have children that failed but are not leaves.
299
-                $matches = $this->seek($children, $rule, $options);
300
-                foreach ($matches as $match) {
301
-                    $return[] = $match;
302
-                }
303
-            }
304
-        }
305
-
306
-        return $return;
307
-    }
308
-
309
-    /**
310
-     * Attempts to match the given arguments with the given operator.
311
-     *
312
-     * @param string $operator
313
-     * @param string $pattern
314
-     * @param string $value
315
-     * @return bool
316
-     */
317
-    protected function match($operator, $pattern, $value)
318
-    {
319
-        $value   = strtolower($value);
320
-        $pattern = strtolower($pattern);
321
-        switch ($operator) {
322
-            case '=':
323
-                return $value === $pattern;
324
-            case '!=':
325
-                return $value !== $pattern;
326
-            case '^=':
327
-                return preg_match('/^'.preg_quote($pattern, '/').'/', $value);
328
-            case '$=':
329
-                return preg_match('/'.preg_quote($pattern, '/').'$/', $value);
330
-            case '*=':
331
-                if ($pattern[0] == '/') {
332
-                    return preg_match($pattern, $value);
333
-                }
334
-
335
-                return preg_match("/".$pattern."/i", $value);
336
-        }
337
-
338
-        return false;
339
-    }
340
-
341
-    /**
342
-     * Attempts to figure out what the alteration will be for
343
-     * the next element.
344
-     *
345
-     * @param array $rule
346
-     * @return array
347
-     */
348
-    protected function alterNext($rule)
349
-    {
350
-        $options = [];
351
-        if ($rule['tag'] == '>') {
352
-            $options['checkGrandChildren'] = false;
353
-        }
354
-
355
-        return $options;
356
-    }
357
-
358
-    /**
359
-     * Flattens the option array.
360
-     *
361
-     * @param array $optionsArray
362
-     * @return array
363
-     */
364
-    protected function flattenOptions(array $optionsArray)
365
-    {
366
-        $options = [];
367
-        foreach ($optionsArray as $optionArray) {
368
-            foreach ($optionArray as $key => $option) {
369
-                $options[$key] = $option;
370
-            }
371
-        }
372
-
373
-        return $options;
374
-    }
18
+	/**
19
+	 * Pattern of CSS selectors, modified from 'mootools'
20
+	 *
21
+	 * @var string
22
+	 */
23
+	protected $pattern = "/([\w-:\*>]*)(?:\#([\w-]+)|\.([\w-]+))?(?:\[@?(!?[\w-:]+)(?:([!*^$]?=)[\"']?(.*?)[\"']?)?\])?([\/, ]+)/is";
24
+
25
+	protected $selectors = [];
26
+
27
+	/**
28
+	 * Constructs with the selector string
29
+	 *
30
+	 * @param string $selector
31
+	 */
32
+	public function __construct($selector)
33
+	{
34
+		$this->parseSelectorString($selector);
35
+	}
36
+
37
+	/**
38
+	 * Returns the selectors that where found in __construct
39
+	 *
40
+	 * @return array
41
+	 */
42
+	public function getSelectors()
43
+	{
44
+		return $this->selectors;
45
+	}
46
+
47
+	/**
48
+	 * Attempts to find the selectors starting from the given
49
+	 * node object.
50
+	 *
51
+	 * @param AbstractNode $node
52
+	 * @return array|Collection
53
+	 */
54
+	public function find(AbstractNode $node)
55
+	{
56
+		$results = new Collection;
57
+		foreach ($this->selectors as $selector) {
58
+			$nodes = [$node];
59
+			if (count($selector) == 0) {
60
+				continue;
61
+			}
62
+
63
+			$options = [];
64
+			foreach ($selector as $rule) {
65
+				if ($rule['alterNext']) {
66
+					$options[] = $this->alterNext($rule);
67
+					continue;
68
+				}
69
+				$nodes = $this->seek($nodes, $rule, $options);
70
+				// clear the options
71
+				$options = [];
72
+			}
73
+
74
+			// this is the final set of nodes
75
+			foreach ($nodes as $result) {
76
+				$results[] = $result;
77
+			}
78
+		}
79
+
80
+		return $results;
81
+	}
82
+
83
+	/**
84
+	 * Parses the selector string
85
+	 *
86
+	 * @param string $selector
87
+	 */
88
+	protected function parseSelectorString($selector)
89
+	{
90
+		$matches = [];
91
+		preg_match_all($this->pattern, trim($selector).' ', $matches, PREG_SET_ORDER);
92
+
93
+		// skip tbody
94
+		$result = [];
95
+		foreach ($matches as $match) {
96
+			// default values
97
+			$tag       = strtolower(trim($match[1]));
98
+			$operator  = '=';
99
+			$key       = null;
100
+			$value     = null;
101
+			$noKey     = false;
102
+			$alterNext = false;
103
+
104
+			// check for elements that alter the behavior of the next element
105
+			if ($tag == '>') {
106
+				$alterNext = true;
107
+			}
108
+
109
+			// check for id selector
110
+			if ( ! empty($match[2])) {
111
+				$key   = 'id';
112
+				$value = $match[2];
113
+			}
114
+
115
+			// check for class selector
116
+			if ( ! empty($match[3])) {
117
+				$key   = 'class';
118
+				$value = $match[3];
119
+			}
120
+
121
+			// and final attribute selector
122
+			if ( ! empty($match[4])) {
123
+				$key = strtolower($match[4]);
124
+			}
125
+			if ( ! empty($match[5])) {
126
+				$operator = $match[5];
127
+			}
128
+			if ( ! empty($match[6])) {
129
+				$value = $match[6];
130
+			}
131
+
132
+			// check for elements that do not have a specified attribute
133
+			if (isset($key[0]) && $key[0] == '!') {
134
+				$key   = substr($key, 1);
135
+				$noKey = true;
136
+			}
137
+
138
+			$result[] = [
139
+				'tag'       => $tag,
140
+				'key'       => $key,
141
+				'value'     => $value,
142
+				'operator'  => $operator,
143
+				'noKey'     => $noKey,
144
+				'alterNext' => $alterNext,
145
+			];
146
+			if (trim($match[7]) == ',') {
147
+				$this->selectors[] = $result;
148
+				$result            = [];
149
+			}
150
+		}
151
+
152
+		// save last results
153
+		if (count($result) > 0) {
154
+			$this->selectors[] = $result;
155
+		}
156
+	}
157
+
158
+	/**
159
+	 * Attempts to find all children that match the rule
160
+	 * given.
161
+	 *
162
+	 * @param array $nodes
163
+	 * @param array $rule
164
+	 * @param array $options
165
+	 * @return array
166
+	 * @recursive
167
+	 */
168
+	protected function seek(array $nodes, array $rule, array $options)
169
+	{
170
+		// XPath index
171
+		if (count($rule['tag']) > 0 &&
172
+			count($rule['key']) > 0 &&
173
+			is_numeric($rule['key'])
174
+		) {
175
+			$count = 0;
176
+			/** @var AbstractNode $node */
177
+			foreach ($nodes as $node) {
178
+				if ($rule['tag'] == '*' ||
179
+					$rule['tag'] == $node->getTag()->name()) {
180
+					++$count;
181
+					if ($count == $rule['key']) {
182
+						// found the node we wanted
183
+						return [$node];
184
+					}
185
+				}
186
+			}
187
+
188
+			return [];
189
+		}
190
+
191
+		$options = $this->flattenOptions($options);
192
+
193
+		$return = [];
194
+		/** @var InnerNode $node */
195
+		foreach ($nodes as $node) {
196
+			// check if we are a leaf
197
+			if ($node instanceof LeafNode ||
198
+				 ! $node->hasChildren()) {
199
+				continue;
200
+			}
201
+
202
+			$children = [];
203
+			$child    = $node->firstChild();
204
+			while ( ! is_null($child)) {
205
+				// wild card, grab all
206
+				if ($rule['tag'] == '*' && is_null($rule['key'])) {
207
+					$return[] = $child;
208
+					try {
209
+						$child = $node->nextChild($child->id());
210
+					} catch (ChildNotFoundException $e) {
211
+						// no more children
212
+						$child = null;
213
+					}
214
+					continue;
215
+				}
216
+
217
+				$pass = true;
218
+				// check tag
219
+				if ( ! empty($rule['tag']) && $rule['tag'] != $child->getTag()->name() &&
220
+					$rule['tag'] != '*'
221
+				) {
222
+					// child failed tag check
223
+					$pass = false;
224
+				}
225
+
226
+				// check key
227
+				if ($pass && ! is_null($rule['key'])) {
228
+					if ($rule['noKey']) {
229
+						if ( ! is_null($child->getAttribute($rule['key']))) {
230
+							$pass = false;
231
+						}
232
+					} else {
233
+						if ($rule['key'] != 'plaintext' &&
234
+							is_null($child->getAttribute($rule['key']))
235
+						) {
236
+							$pass = false;
237
+						}
238
+					}
239
+				}
240
+
241
+				// compare values
242
+				if ($pass && ! is_null($rule['key']) &&
243
+					! is_null($rule['value']) && $rule['value'] != '*'
244
+				) {
245
+					if ($rule['key'] == 'plaintext') {
246
+						// plaintext search
247
+						$nodeValue = $child->text();
248
+					} else {
249
+						// normal search
250
+						$nodeValue = $child->getAttribute($rule['key']);
251
+					}
252
+
253
+					$check = $this->match($rule['operator'], $rule['value'], $nodeValue);
254
+
255
+					// handle multiple classes
256
+					if ( ! $check && $rule['key'] == 'class') {
257
+						$childClasses = explode(' ', $child->getAttribute('class'));
258
+						foreach ($childClasses as $class) {
259
+							if ( ! empty($class)) {
260
+								$check = $this->match($rule['operator'], $rule['value'], $class);
261
+							}
262
+							if ($check) {
263
+								break;
264
+							}
265
+						}
266
+					}
267
+
268
+					if ( ! $check) {
269
+						$pass = false;
270
+					}
271
+				}
272
+
273
+				if ($pass) {
274
+					// it passed all checks
275
+					$return[] = $child;
276
+				} else {
277
+					// this child failed to be matched
278
+					if ($child instanceof InnerNode &&
279
+						$child->hasChildren()) {
280
+						// we still want to check its children
281
+						$children[] = $child;
282
+					}
283
+				}
284
+
285
+				try {
286
+					// get next child
287
+					$child = $node->nextChild($child->id());
288
+				} catch (ChildNotFoundException $e) {
289
+					// no more children
290
+					$child = null;
291
+				}
292
+			}
293
+
294
+			if (( ! isset($options['checkGrandChildren']) ||
295
+					$options['checkGrandChildren'])
296
+				&& count($children) > 0
297
+			) {
298
+				// we have children that failed but are not leaves.
299
+				$matches = $this->seek($children, $rule, $options);
300
+				foreach ($matches as $match) {
301
+					$return[] = $match;
302
+				}
303
+			}
304
+		}
305
+
306
+		return $return;
307
+	}
308
+
309
+	/**
310
+	 * Attempts to match the given arguments with the given operator.
311
+	 *
312
+	 * @param string $operator
313
+	 * @param string $pattern
314
+	 * @param string $value
315
+	 * @return bool
316
+	 */
317
+	protected function match($operator, $pattern, $value)
318
+	{
319
+		$value   = strtolower($value);
320
+		$pattern = strtolower($pattern);
321
+		switch ($operator) {
322
+			case '=':
323
+				return $value === $pattern;
324
+			case '!=':
325
+				return $value !== $pattern;
326
+			case '^=':
327
+				return preg_match('/^'.preg_quote($pattern, '/').'/', $value);
328
+			case '$=':
329
+				return preg_match('/'.preg_quote($pattern, '/').'$/', $value);
330
+			case '*=':
331
+				if ($pattern[0] == '/') {
332
+					return preg_match($pattern, $value);
333
+				}
334
+
335
+				return preg_match("/".$pattern."/i", $value);
336
+		}
337
+
338
+		return false;
339
+	}
340
+
341
+	/**
342
+	 * Attempts to figure out what the alteration will be for
343
+	 * the next element.
344
+	 *
345
+	 * @param array $rule
346
+	 * @return array
347
+	 */
348
+	protected function alterNext($rule)
349
+	{
350
+		$options = [];
351
+		if ($rule['tag'] == '>') {
352
+			$options['checkGrandChildren'] = false;
353
+		}
354
+
355
+		return $options;
356
+	}
357
+
358
+	/**
359
+	 * Flattens the option array.
360
+	 *
361
+	 * @param array $optionsArray
362
+	 * @return array
363
+	 */
364
+	protected function flattenOptions(array $optionsArray)
365
+	{
366
+		$options = [];
367
+		foreach ($optionsArray as $optionArray) {
368
+			foreach ($optionArray as $key => $option) {
369
+				$options[$key] = $option;
370
+			}
371
+		}
372
+
373
+		return $options;
374
+	}
375 375
 }
Please login to merge, or discard this patch.