Skip to content

Commit 9eb7403

Browse files
committed
Improvements 👽
- Experimental\Dom support to PHP 5.3 improved - Rename class Dom\Query to Dom\Selector (fix bug in PHP 5.3) - Fixed :nth-child(n) selector - Support to :nth-last-child - Document::first method added - Improved :contains() selector - Document::query throws exception if selector is invalid - Fixed bug in descendant selector with global selector
1 parent 63ae70e commit 9eb7403

File tree

3 files changed

+114
-85
lines changed

3 files changed

+114
-85
lines changed

src/Experimental/Debug.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,12 @@ public static function classes()
241241
*
242242
* @param string $file
243243
* @param int $line
244-
* @return array|bool
244+
* @return array|null
245245
*/
246246
public static function source($file, $line)
247247
{
248248
if ($line <= 0 || is_file($file) === false) {
249-
return false;
249+
return null;
250250
} elseif ($line > 5) {
251251
$init = $line - 5;
252252
$end = $line + 5;
@@ -269,7 +269,7 @@ public static function source($file, $line)
269269
* Get caller
270270
*
271271
* @param int $level
272-
* @return array|bool
272+
* @return array|null
273273
*/
274274
public static function caller($level = 0)
275275
{
@@ -278,7 +278,7 @@ public static function caller($level = 0)
278278
if ($level < 0) {
279279
return $trace;
280280
} elseif (empty($trace[$level])) {
281-
return false;
281+
return null;
282282
} elseif (empty($trace[$level]['file'])) {
283283
$level = 1;
284284
}

src/Experimental/Dom/Document.php

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -252,18 +252,57 @@ public function loadHTMLFile($filename, $options = 0)
252252
* Use query-selector like CSS, jQuery, querySelectorAll
253253
*
254254
* @param string $selector
255-
* @return mixed
255+
* @param \DOMNode $context
256+
* @return \DOMNodeList
256257
*/
257-
public function query($selector)
258+
public function query($selector, \DOMNode $context = null)
258259
{
259-
return (new Query($this))->get($selector);
260+
$this->enableRestoreInternal(true);
261+
262+
$query = new Selector($this);
263+
264+
$nodes = $query->get($selector, $context);
265+
266+
self::raise(3);
267+
268+
$query = null;
269+
270+
$this->enableRestoreInternal(false);
271+
272+
return $nodes;
273+
}
274+
275+
/**
276+
* Use query-selector like CSS, jQuery, querySelector
277+
*
278+
* @param string $selector
279+
* @param \DOMNode $context
280+
* @return \DOMNode
281+
*/
282+
public function first($selector, \DOMNode $context = null)
283+
{
284+
$this->enableRestoreInternal(true);
285+
286+
$query = new Selector($this);
287+
288+
$nodes = $query->get($selector, $context);
289+
290+
self::raise(3);
291+
292+
$node = $nodes->length ? $nodes->item(0) : null;
293+
294+
$query = $nodes = null;
295+
296+
$this->enableRestoreInternal(false);
297+
298+
return $node;
260299
}
261300

262301
private function resource($function, $from, $options)
263302
{
264303
$this->enableRestoreInternal(true);
265304

266-
$resource = parent::$function($from, $options);
305+
$resource = PHP_VERSION_ID >= 50400 ? parent::$function($from, $options) : parent::$function($from);
267306

268307
self::raise(4);
269308

Lines changed: 67 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -9,72 +9,42 @@
99

1010
namespace Inphinit\Experimental\Dom;
1111

12-
class Query extends \DOMXPath
12+
class Selector extends \DOMXPath
1313
{
1414
private $prevent;
15-
private $restore;
1615
private $rules;
1716
private $qxs = array(
1817
array( '/^([^a-z*])/', '*\\1' ),
19-
array( '/([>\+~])([^a-z])/', '\\1*\\2' ),
18+
array( '/([>\+~])([^a-z*])/', '\\1*\\2' ),
2019
array( '/\[(.*?)\]/', '[@\\1]' ),
2120
array( '/#(\w+)/', '[@id="\\1"]' ),
2221
array( '/\:empty/', '[count(*)=0]'),
2322
array( '/\:last-child/', '[last()]' ),
2423
array( '/^(.*?)\:first-child $/', 'descendant::\\1' ),
25-
array( '/\:nth-child\(n\)/', '[position() mod n = 1]' ),
26-
array( '/\:nth-child\(odd\)/', '[(position() mod 2)=1]' ),
27-
array( '/\:nth-child\(even\)/', '[(position() mod 2)=0]' ),
28-
array( '/\:nth-child\((\d+)n\)/', '[(position() mod \\1)=0]' ),
29-
array( '/\:nth-child\((\d+)n\+(\d+)\)/', '[(position() mod \\1)=\\2]' ),
30-
array( '/\:lang\(([\w\-]+)\)/', '[starts-with(concat(@lang,"-"),concat("\\1","-"))]' ),
31-
array( '/\[(@\w+)\^=([^"\'])(.*?)\\1\]/', '[starts-with(\\1,\\2\\3\\2)]' ),
32-
array( '/\[(@\w+)\*=([^"\'])(.*?)\\1\]/', '[contains(\\1,\\2\\3\\2)]' ),
33-
array( '/\[(@\w+)\~=([^"\'])(.*?)\\1\]/', '[contains(concat(" ",\\1," "),\\2 \\3 \\2)]' ),
34-
array( '/\[(@\w+)\|=([^"\'])(.*?)\\1\]/', '[starts-with(concat(\\1,"-"),concat(\\2\\3\\2,"-"))]' ),
35-
array( '/\[(@\w+)\$=([^"\'])(.*?)\\1\]/', '[substring(\\1,string-length(\\1)-2)=\\2\\3\\2]' ),
36-
array( '/\.(\w+)/', '[contains(concat(" ",@class," ")," \\1 ")]' ),
24+
array( '/\:nth-(last-)?(child|of-type)\(n\)/', '' ),
25+
array( '/\:nth-child\(odd\)/', ':nth-child(2n+1)' ),
26+
array( '/\:nth-child\(even\)/', ':nth-child(2n)' ),
27+
array( '/\:nth-child\((\d+)\)/', '[position() mod \\1 = 1]' ),
28+
array( '/\:nth-child\((\d+)n\)/', '[position() mod \\1 = 0]' ),
29+
array( '/\:nth-child\((\d+)n\+(\d+)\)/', '[position() mod \\1 = \\2]' ),
30+
array( '/\:nth-last-child\(odd\)/', ':nth-last-child(2n+1)' ),
31+
array( '/\:nth-last-child\(even\)/', ':nth-last-child(2n)' ),
32+
array( '/\:nth-last-child\((\d+)\)/', '[(count() - position()) mod \\1 = 1]' ),
33+
array( '/\:nth-last-child\((\d+)n\)/', '[(count() - position()) mod \\1 = 0]' ),
34+
array( '/\:nth-last-child\((\d+)n\+(\d+)\)/', '[(count() - position()) mod \\1 = \\2]' ),
35+
array( '/\.(\w+)/', '[@class~="\\1"]' ),
36+
array( '/\:lang\(([\w\-]+)\)/', '[@lang|="\\1"]' ),
37+
array( '/\[(@\w+)\^=(["\'])(.*?)\\2\]/', '[starts-with(\\1,\\2\\3\\2)]' ),
38+
array( '/\[(@\w+)\*=(["\'])(.*?)\\2\]/', '[contains(\\1,\\2\\3\\2)]' ),
39+
array( '/\[(@\w+)\~=(["\'])(.*?)\\2\]/', '[contains(concat(" ",\\1," "),\\2 \\3 \\2)]' ),
40+
array( '/\[(@\w+)\|=(["\'])(.*?)\\2\]/', '[starts-with(concat(\\1,"-"),concat(\\2\\3\\2,"-"))]' ),
41+
array( '/\[(@\w+)\$=(["\'])(.*?)\\2\]/', '[substring(\\1,string-length(\\1)-2)=\\2\\3\\2]' ),
3742
array( '/\:contains\((.*?)\)/', '[contains(text(),\\1)]' ),
43+
array( '/[~]([*\w]+)/', '/following-sibling::*[count(\\1)]' ),
3844
array( '/\+/', '/following-sibling::' ),
39-
array( '/[~]([*\w]{1,})/', '/following-sibling::*[count(\\1)]' ),
4045
array( '/[>]/', '/' )
4146
);
4247

43-
/**
44-
* Count all nodes matching the given XPath expression
45-
*
46-
* @param string $selector
47-
* @param \DOMNode $context
48-
* @param bool $registerNodeNS
49-
* @throws \Inphinit\Experimental\Exception
50-
* @return \DOMNodeList
51-
*/
52-
public function evaluate($selector, \DOMNode $context = null, $registerNodeNS = true)
53-
{
54-
if (PHP_VERSION_ID >= 50303) {
55-
return parent::evaluate($selector, $context, $registerNodeNS);
56-
} else {
57-
return parent::evaluate($selector, $context);
58-
}
59-
}
60-
61-
/**
62-
* Returns a DOMNodeList containing all nodes matching the given XPath expression
63-
*
64-
* @param string $selector
65-
* @param \DOMNode $context
66-
* @param bool $registerNodeNS
67-
* @return \DOMNodeList
68-
*/
69-
public function query($selector, \DOMNode $context = null, $registerNodeNS = true)
70-
{
71-
if (PHP_VERSION_ID >= 50303) {
72-
return parent::query($selector, $context, $registerNodeNS);
73-
} else {
74-
return parent::query($selector, $context);
75-
}
76-
}
77-
7848
/**
7949
* Count all nodes matching the given CSS selector
8050
*
@@ -85,11 +55,7 @@ public function query($selector, \DOMNode $context = null, $registerNodeNS = tru
8555
*/
8656
public function count($selector, \DOMNode $context = null, $registerNodeNS = true)
8757
{
88-
$this->boot($selector);
89-
90-
$selector = $this->toXPath($selector);
91-
92-
return self::evaluate($selector, $context, $registerNodeNS);
58+
return $this->exec('evaluate', $selector, $context, $registerNodeNS);
9359
}
9460

9561
/**
@@ -98,19 +64,14 @@ public function count($selector, \DOMNode $context = null, $registerNodeNS = tru
9864
* @param string $selector
9965
* @param \DOMNode $context
10066
* @param bool $registerNodeNS
101-
* @throws \Inphinit\Experimental\Exception
10267
* @return \DOMNodeList
10368
*/
10469
public function get($selector, \DOMNode $context = null, $registerNodeNS = true)
10570
{
106-
$this->boot($selector);
107-
108-
$selector = $this->toXPath($selector);
109-
110-
return self::query($selector, $context, $registerNodeNS);
71+
return $this->exec('query', $selector, $context, $registerNodeNS);
11172
}
11273

113-
private function boot(&$query)
74+
private function exec($method, $query, $context, $registerNodeNS)
11475
{
11576
$dot = self::uniqueToken($query, 'dot');
11677
$hash = self::uniqueToken($query, 'hash');
@@ -138,26 +99,36 @@ private function boot(&$query)
13899
')' => $rparenthesis
139100
);
140101

141-
$this->restore = array_combine(array_values($this->prevent), array_keys($this->prevent));
142-
143102
$this->rules = array(
144103
' ' => $spaces,
145104
',' => $commas
146105
);
147106

148-
$query = preg_replace('#\[(\w+)(.)?[=]([^"\'])(.*?)\]#', '[$1$2="$4"]', $query);
149-
150-
$query = preg_replace_callback('#(["\'])(?:\\.|[^\\\\])*?\1#', array($this, 'preventInQuotes' ), $query);
107+
$query = $this->toXPath($query);
151108

152-
$query = preg_replace_callback('#:contains\(((?:\\.|[^\\\\])*?)\)#', array($this, 'preventInContains' ), $query);
109+
if (PHP_VERSION_ID >= 50303) {
110+
return $this->$method($query, $context, $registerNodeNS);
111+
} else if ($context !== null) {
112+
return $this->$method($query, $context);
113+
}
153114

154-
$query = preg_replace('#(\s+)?([>+~\s])(\s+)?#', '\\2', $query);
115+
return $this->$method($query);
155116
}
156117

157118
private function toXPath($query)
158119
{
120+
$query = preg_replace_callback('#\[(\w+)(.)?[=]([^"\'])(.*?)\]#', array($this, 'putQuotes'), $query);
121+
122+
$query = preg_replace_callback('#\:contains\(([^"\'])(.*?)\)#', array($this, 'putQuotes'), $query);
123+
124+
$query = preg_replace_callback('#(["\'])(?:\\\\.|[^\\\\])*?\\1#', array($this, 'preventInQuotes'), $query);
125+
126+
$query = preg_replace('#(\s+)?([>+~\s])(\s+)?#', '\\2', $query);
127+
159128
$queries = explode(',', $query);
160129

130+
$restore = array_combine(array_values($this->prevent), array_keys($this->prevent));
131+
161132
foreach ($queries as &$descendants) {
162133

163134
$descendants = explode(' ', trim($descendants));
@@ -167,6 +138,8 @@ private function toXPath($query)
167138
$r = strtr($qx[1], $this->rules);
168139

169140
$descendant = trim(preg_replace($qx[0], $r, $descendant));
141+
142+
var_dump( strtr($descendant, $restore) . ' => ' . $qx[0] );
170143
}
171144
}
172145

@@ -177,21 +150,38 @@ private function toXPath($query)
177150

178151
$queries = null;
179152

180-
return '//' . strtr($query, $this->restore);
153+
$restore = array_combine(array_values($this->prevent), array_keys($this->prevent));
154+
155+
return '//' . strtr($query, $restore);
181156
}
182157

183-
private function preventInQuotes($arg)
158+
private function putQuotes($arg)
184159
{
185-
return strtr($arg[0], $this->prevent);
160+
if (stripos($arg[0], ':contains') === 0) {
161+
return ':contains("' . addcslashes($arg[1] . $arg[2], '"') . '")';
162+
}
163+
164+
return sprintf('[%s%s="%s"]', $arg[1], $arg[2], addcslashes($arg[3] . $arg[4], '"'));
186165
}
187166

188-
private function preventInContains($arg)
167+
private function preventInQuotes($arg)
189168
{
190-
if ($arg[1]{0} === '\'' || $arg[1]{0} === '"') {
191-
return strtr(addcslashes($arg[0], '\\"'), $this->prevent);
169+
$quote = $arg[1];
170+
$inValue = substr($arg[0], 1, -1);
171+
172+
if (strpos($inValue, $quote) === false) {
173+
return strtr($arg[0], $this->prevent);
192174
}
193175

194-
return ':contains("' . strtr($arg[1], $this->prevent) . '")';
176+
$quotec = $quote === '"' ? '\'' : '"';
177+
$unes = '\\' . $quote;
178+
$glue = $quote . ',' . $quotec . $quote . $quotec . ',' . $quote;
179+
180+
$inValue = str_replace($unes, $quote, $inValue);
181+
182+
$inValue = 'concat(' . $quote . implode($glue, explode($quote, $inValue)) . $quote . ')';
183+
184+
return strtr($inValue, $this->prevent);
195185
}
196186

197187
private static function uniqueToken($query, $key, $token = null)

0 commit comments

Comments
 (0)