diff --git a/src/DiDom/Query.php b/src/DiDom/Query.php index 596d3ae..9bd47f7 100644 --- a/src/DiDom/Query.php +++ b/src/DiDom/Query.php @@ -292,13 +292,15 @@ public static function buildXpath(array $segments, $prefix = '//') } // if the pseudo class specified - if (isset($segments['pseudo'])) { - $expression = isset($segments['pseudo-expression']) ? trim($segments['pseudo-expression']) : ''; + if (array_key_exists('pseudo', $segments)) { + foreach ($segments['pseudo'] as $pseudo) { + $expression = $pseudo['expression'] !== null ? $pseudo['expression'] : ''; - $parameters = explode(',', $expression); - $parameters = array_map('trim', $parameters); + $parameters = explode(',', $expression); + $parameters = array_map('trim', $parameters); - $attributes[] = self::convertPseudo($segments['pseudo'], $tagName, $parameters); + $attributes[] = self::convertPseudo($pseudo['type'], $tagName, $parameters); + } } if (count($attributes) === 0 && ! isset($segments['tag'])) { @@ -511,10 +513,15 @@ public static function getSegments($selector) // if the pseudo class specified if (isset($segments['pseudo']) && $segments['pseudo'] !== '') { - $result['pseudo'] = $segments['pseudo']; + preg_match_all('/:(?P[\w\-]+)(?:\((?P[^\)]+)\))?/', $segments['pseudo'], $pseudoClasses); + + $result['pseudo'] = []; - if (isset($segments['pseudoExpr']) && $segments['pseudoExpr'] !== '') { - $result['pseudo-expression'] = $segments['pseudoExpr']; + foreach ($pseudoClasses['type'] as $index => $pseudoType) { + $result['pseudo'][] = [ + 'type' => $pseudoType, + 'expression' => $pseudoClasses['expr'][$index] !== '' ? $pseudoClasses['expr'][$index] : null, + ]; } } @@ -532,9 +539,9 @@ private static function getSelectorRegex() $id = '(?:#(?P[\w|\-]+))?'; $classes = '(?P\.[\w|\-|\.]+)*'; $attrs = '(?P(?:\[.+?\])*)?'; - $pseudoType = '(?P[\w\-]+)'; - $pseudoExpr = '(?:\((?P[^\)]+)\))'; - $pseudo = '(?::' . $pseudoType . $pseudoExpr . '?)?'; + $pseudoType = '[\w\-]+'; + $pseudoExpr = '(?:\([^\)]+\))?'; + $pseudo = '(?P(?::' . $pseudoType . $pseudoExpr . ')+)?'; $rel = '\s*(?P>)?'; return '/' . $tag . $id . $classes . $attrs . $pseudo . $rel . '/is'; diff --git a/tests/QueryTest.php b/tests/QueryTest.php index 0b61e51..34849f4 100644 --- a/tests/QueryTest.php +++ b/tests/QueryTest.php @@ -258,6 +258,7 @@ public function compileCssTests() ['li:first-child', '//li[position() = 1]'], ['li:last-child', '//li[position() = last()]'], ['*:not(a[href*="example.com"])', '//*[not(self::a[contains(@href, "example.com")])]'], + ['*:not(a[href*="example.com"]):not(.foo)', '//*[(not(self::a[contains(@href, "example.com")])) and (not(self::*[contains(concat(" ", normalize-space(@class), " "), " foo ")]))]'], ['ul:empty', '//ul[count(descendant::*) = 0]'], ['ul:not-empty', '//ul[count(descendant::*) > 0]'], ['li:nth-child(odd)', '//*[(name()="li") and (position() mod 2 = 1 and position() >= 1)]'], @@ -388,8 +389,8 @@ public function buildXpathTests() ['tag' => 'a', 'attributes' => ['href*' => 'example']], ['tag' => 'a', 'attributes' => ['href!' => 'http://foo.com/']], ['tag' => 'script', 'attributes' => ['!src' => null]], - ['tag' => 'li', 'pseudo' => 'first-child'], - ['tag' => '*', 'id' => 'id', 'classes' => ['foo'], 'attributes' => ['name' => 'value'], 'pseudo' => 'first-child', 'rel' => '>'], + ['tag' => 'li', 'pseudo' => [['type' => 'first-child', 'expression' => null]]], + ['tag' => '*', 'id' => 'id', 'classes' => ['foo'], 'attributes' => ['name' => 'value'], 'pseudo' => [['type' => 'first-child', 'expression' => null]], 'rel' => '>'], ]; $parameters = []; @@ -416,10 +417,10 @@ public function getSegmentsTests() ['selector' => 'a[href=http://example.com/][title=Example Domain]', 'tag' => 'a', 'attributes' => ['href' => 'http://example.com/', 'title' => 'Example Domain']], ['selector' => 'a[href=http://example.com/][href=http://example.com/404]', 'tag' => 'a', 'attributes' => ['href' => 'http://example.com/404']], ['selector' => 'a[href^=https]', 'tag' => 'a', 'attributes' => ['href^' => 'https']], - ['selector' => 'li:first-child', 'tag' => 'li', 'pseudo' => 'first-child'], + ['selector' => 'li:first-child', 'tag' => 'li', 'pseudo' => [['type' => 'first-child', 'expression' => null]]], ['selector' => 'ul >', 'tag' => 'ul', 'rel' => '>'], - ['selector' => '#id.foo[name=value]:first-child >', 'id' => 'id', 'classes' => ['foo'], 'attributes' => ['name' => 'value'], 'pseudo' => 'first-child', 'rel' => '>'], - ['selector' => 'li.bar:nth-child(2n)', 'tag' => 'li', 'classes' => ['bar'], 'pseudo' => 'nth-child', 'pseudo-expression' => '2n'], + ['selector' => '#id.foo[name=value]:first-child >', 'id' => 'id', 'classes' => ['foo'], 'attributes' => ['name' => 'value'], 'pseudo' => [['type' => 'first-child', 'expression' => null]], 'rel' => '>'], + ['selector' => 'li.bar:nth-child(2n)', 'tag' => 'li', 'classes' => ['bar'], 'pseudo' => [['type' => 'nth-child', 'expression' => '2n']]], ]; $parameters = [];