Skip to content

Commit 6362bda

Browse files
committed
multi statement in :not() selector fix
1 parent 127ebc5 commit 6362bda

File tree

2 files changed

+45
-13
lines changed

2 files changed

+45
-13
lines changed

src/Rct567/DomQuery/DomQuery.php

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -732,8 +732,34 @@ public function getOuterHtml()
732732
*/
733733
public function __toString()
734734
{
735+
return $this->getOuterHtml();
736+
}
735737

736-
return $this->getOuterHtml();
738+
/**
739+
* Replace char with null bytes inside (optionaly specified) enclosure
740+
*
741+
* @param string $str
742+
* @param string $search
743+
* @param string $enclosure_open
744+
* @param string $enclosure_close
745+
*
746+
* @return string $str
747+
*/
748+
private static function replaceInsideEnclosure($str, $search_char, $enclosure_open='(', $enclosure_close=')')
749+
{
750+
if ($str == '') {
751+
return $str;
752+
}
753+
754+
for ($i = 0; $i < strlen($str); $i++) {
755+
if ($str[$i] === $search_char && $i > 0) {
756+
if (substr_count($str, $enclosure_open, 0, $i) != substr_count($str, $enclosure_close, 0, $i)) {
757+
$str[$i] = "\0";
758+
}
759+
}
760+
}
761+
762+
return $str;
737763
}
738764

739765
/**
@@ -745,12 +771,14 @@ public function __toString()
745771
*/
746772
public static function cssToXpath(string $path)
747773
{
748-
if (strstr($path, ',')) {
749-
$paths = explode(',', $path);
774+
$tmp_path = self::replaceInsideEnclosure($path, ',');
775+
if (strstr($tmp_path, ',')) {
776+
$paths = explode(',', $tmp_path);
750777
$expressions = array();
751778

752779
foreach ($paths as $path) {
753-
$xpath = static::cssToXpath(trim($path));
780+
$path = str_replace("\0", ',', $path); // restore commas
781+
$xpath = static::cssToXpath(trim($path));
754782

755783
if (is_string($xpath)) {
756784
$expressions[] = $xpath;
@@ -764,13 +792,7 @@ public static function cssToXpath(string $path)
764792

765793
// replace spaces inside (), to correcly create tokens
766794

767-
for ($i = 0; $i < strlen($path); $i++) {
768-
if ($path[$i] === ' ') {
769-
if (substr_count($path, '(', 0, $i) != substr_count($path, ')', 0, $i)) {
770-
$path[$i] = "\0";
771-
}
772-
}
773-
}
795+
$path = self::replaceInsideEnclosure($path, ' ');
774796

775797
// create and analyze tokens and create segments
776798

@@ -781,7 +803,7 @@ public static function cssToXpath(string $path)
781803
$relation_tokens = array('>', '~', '+');
782804

783805
foreach ($tokens as $key => $token) {
784-
$token = str_replace("\0", ' ', $token); // restore spaces
806+
$token = str_replace("\0", ' ', $token); // restore spaces
785807

786808
if (!in_array($token, $relation_tokens)) {
787809
$segment = (object) array('selector' => '', 'relation_filter' => false, 'attribute_filters' => array(), 'pseudo_filters' => array());
@@ -872,7 +894,13 @@ private static function transformCssPseudoSelector($expression, array &$new_path
872894
$expression = preg_replace_callback(
873895
'|not\((.+)\)|i',
874896
function ($matches) {
875-
return '[not(self::' . ltrim(self::cssToXpath($matches[1]), '/') .')]';
897+
$parts = explode(',', $matches[1]);
898+
foreach ($parts as &$part) {
899+
$part = trim($part);
900+
$part = 'self::'.ltrim(self::cssToXpath($part), '/');
901+
}
902+
$not_selector = implode(' or ', $parts);
903+
return '[not('.$not_selector.')]';
876904
},
877905
$expression
878906
);

tests/Rct567/DomQuery/Tests/DomQuerySelectorsTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public function testCssToXpath()
3939
'a.hidden[href]' => '//a[contains(concat(\' \', normalize-space(@class), \' \'), \' hidden \')][@href]',
4040
'a[href] > .hidden' => '//a[@href]/*[contains(concat(\' \', normalize-space(@class), \' \'), \' hidden \')]',
4141
'a:not(b[co-ol])' => '//a[not(self::b[@co-ol])]',
42+
'a:not(b,c)' => '//a[not(self::b or self::c)]',
4243
'a:not(.cool)' => '//a[not(self::*[contains(concat(\' \', normalize-space(@class), \' \'), \' cool \')])]',
4344
'a:contains(txt)' => '//a[text()[contains(.,\'txt\')]]',
4445
'h1 ~ ul' => '//h1/following-sibling::ul',
@@ -137,7 +138,10 @@ public function testNotFilterSelector()
137138

138139
$this->assertEquals(2, $dom->find('a:not(.monkey)')->length);
139140
$this->assertEquals(2, $dom->find('a:not([id])')->length);
141+
$this->assertEquals(1, $dom->find('a:not([id],[class])')->length);
140142
$this->assertEquals(2, $dom->find('a:not(#some-monkey)')->length);
143+
$this->assertEquals(1, $dom->find('a:not(#some-monkey, .monkey)')->length);
144+
$this->assertEquals(1, $dom->find('a:not(.monkey,#some-monkey)')->length);
141145
$this->assertEquals(3, $dom->find('a:not(b)')->length);
142146
}
143147

0 commit comments

Comments
 (0)