diff --git a/src/DiDom/Query.php b/src/DiDom/Query.php index 85f2d1c..596d3ae 100644 --- a/src/DiDom/Query.php +++ b/src/DiDom/Query.php @@ -293,7 +293,7 @@ public static function buildXpath(array $segments, $prefix = '//') // if the pseudo class specified if (isset($segments['pseudo'])) { - $expression = isset($segments['expr']) ? trim($segments['expr']) : ''; + $expression = isset($segments['pseudo-expression']) ? trim($segments['pseudo-expression']) : ''; $parameters = explode(',', $expression); $parameters = array_map('trim', $parameters); @@ -327,7 +327,7 @@ protected static function convertAttribute($name, $value) if ($isSimpleSelector) { // if specified only the attribute name - $xpath = $value === null ? '@'.$name : sprintf('@%s="%s"', $name, $value); + $xpath = $value === null ? '@' . $name : sprintf('@%s="%s"', $name, $value); return $xpath; } @@ -458,85 +458,86 @@ public static function getSegments($selector) $selector = trim($selector); if ($selector === '') { - throw new InvalidSelectorException('The selector must not be empty'); + throw new InvalidSelectorException('The selector must not be empty.'); } - $tag = '(?P[\*|\w|\-]+)?'; - $id = '(?:#(?P[\w|\-]+))?'; - $classes = '(?P\.[\w|\-|\.]+)*'; - $attrs = '(?P(?:\[.+?\])*)?'; - $name = '(?P[\w\-]+)'; - $expr = '(?:\((?P[^\)]+)\))'; - $pseudo = '(?::'.$name.$expr.'?)?'; - $rel = '\s*(?P>)?'; + $pregMatchResult = preg_match(self::getSelectorRegex(), $selector, $segments); - $regexp = '/'.$tag.$id.$classes.$attrs.$pseudo.$rel.'/is'; - - if (preg_match($regexp, $selector, $segments)) { - if ($segments[0] === '') { - throw new InvalidSelectorException(sprintf('Invalid selector "%s"', $selector)); - } - - $result['selector'] = $segments[0]; + if ($pregMatchResult === false || $pregMatchResult === 0 || $segments[0] === '') { + throw new InvalidSelectorException(sprintf('Invalid selector "%s".', $selector)); + } - if (isset($segments['tag']) && $segments['tag'] !== '') { - $result['tag'] = $segments['tag']; - } + $result = ['selector' => $segments[0]]; - // if the id attribute specified - if (isset($segments['id']) && $segments['id'] !== '') { - $result['id'] = $segments['id']; - } + if (isset($segments['tag']) && $segments['tag'] !== '') { + $result['tag'] = $segments['tag']; + } - // if the attributes specified - if (isset($segments['attrs'])) { - $attributes = trim($segments['attrs'], '[]'); - $attributes = explode('][', $attributes); + // if the id attribute specified + if (isset($segments['id']) && $segments['id'] !== '') { + $result['id'] = $segments['id']; + } - foreach ($attributes as $attribute) { - if ($attribute !== '') { - list($name, $value) = array_pad(explode('=', $attribute, 2), 2, null); + // if the attributes specified + if (isset($segments['attrs'])) { + $attributes = trim($segments['attrs'], '[]'); + $attributes = explode('][', $attributes); - if ($name === '') { - throw new InvalidSelectorException(sprintf('Invalid selector "%s": attribute name must not be empty', $selector)); - } + foreach ($attributes as $attribute) { + if ($attribute !== '') { + list($name, $value) = array_pad(explode('=', $attribute, 2), 2, null); - // equal null if specified only the attribute name - $result['attributes'][$name] = is_string($value) ? trim($value, '\'"') : null; + if ($name === '') { + throw new InvalidSelectorException(sprintf('Invalid selector "%s": attribute name must not be empty', $selector)); } - } - } - - // if the class attribute specified - if (isset($segments['classes'])) { - $classes = trim($segments['classes'], '.'); - $classes = explode('.', $classes); - foreach ($classes as $class) { - if ($class !== '') { - $result['classes'][] = $class; - } + // equal null if specified only the attribute name + $result['attributes'][$name] = is_string($value) ? trim($value, '\'"') : null; } } + } - // if the pseudo class specified - if (isset($segments['pseudo']) && $segments['pseudo'] !== '') { - $result['pseudo'] = $segments['pseudo']; + // if the class attribute specified + if (isset($segments['classes'])) { + $classes = trim($segments['classes'], '.'); + $classes = explode('.', $classes); - if (isset($segments['expr']) && $segments['expr'] !== '') { - $result['expr'] = $segments['expr']; + foreach ($classes as $class) { + if ($class !== '') { + $result['classes'][] = $class; } } + } - // if it is a direct descendant - if (isset($segments['rel'])) { - $result['rel'] = $segments['rel']; + // if the pseudo class specified + if (isset($segments['pseudo']) && $segments['pseudo'] !== '') { + $result['pseudo'] = $segments['pseudo']; + + if (isset($segments['pseudoExpr']) && $segments['pseudoExpr'] !== '') { + $result['pseudo-expression'] = $segments['pseudoExpr']; } + } - return $result; + // if it is a direct descendant + if (isset($segments['rel'])) { + $result['rel'] = $segments['rel']; } - throw new InvalidSelectorException(sprintf('Invalid selector "%s"', $selector)); + return $result; + } + + private static function getSelectorRegex() + { + $tag = '(?P[\*|\w|\-]+)?'; + $id = '(?:#(?P[\w|\-]+))?'; + $classes = '(?P\.[\w|\-|\.]+)*'; + $attrs = '(?P(?:\[.+?\])*)?'; + $pseudoType = '(?P[\w\-]+)'; + $pseudoExpr = '(?:\((?P[^\)]+)\))'; + $pseudo = '(?::' . $pseudoType . $pseudoExpr . '?)?'; + $rel = '\s*(?P>)?'; + + return '/' . $tag . $id . $classes . $attrs . $pseudo . $rel . '/is'; } /** diff --git a/tests/QueryTest.php b/tests/QueryTest.php index b8da361..0b61e51 100644 --- a/tests/QueryTest.php +++ b/tests/QueryTest.php @@ -93,7 +93,7 @@ public function testCompileWithEmptyCssExpression() /** * @expectedException \DiDom\Exceptions\InvalidSelectorException - * @expectedExceptionMessage The selector must not be empty + * @expectedExceptionMessage The selector must not be empty. */ public function testGetSegmentsWithEmptySelector() { @@ -177,7 +177,7 @@ public function testUnknownNthExpression() /** * @expectedException \DiDom\Exceptions\InvalidSelectorException - * @expectedExceptionMessage Invalid selector "." + * @expectedExceptionMessage Invalid selector ".". */ public function testGetSegmentsWithEmptyClassName() { @@ -186,7 +186,7 @@ public function testGetSegmentsWithEmptyClassName() /** * @expectedException \DiDom\Exceptions\InvalidSelectorException - * @expectedExceptionMessage Invalid selector "." + * @expectedExceptionMessage Invalid selector ".". */ public function testCompilehWithEmptyClassName() { @@ -239,7 +239,8 @@ public function compileCssTests() ['foo bar baz', '//foo//bar//baz'], ['foo > bar > baz', '//foo/bar/baz'], ['#foo', '//*[@id="foo"]'], - ['.bar', '//*[contains(concat(" ", normalize-space(@class), " "), " bar ")]'], + ['.foo', '//*[contains(concat(" ", normalize-space(@class), " "), " foo ")]'], + ['.foo.bar', '//*[(contains(concat(" ", normalize-space(@class), " "), " foo ")) and (contains(concat(" ", normalize-space(@class), " "), " bar "))]'], ['*[foo=bar]', '//*[@foo="bar"]'], ['*[foo="bar"]', '//*[@foo="bar"]'], ['*[foo=\'bar\']', '//*[@foo="bar"]'], @@ -418,7 +419,7 @@ public function getSegmentsTests() ['selector' => 'li:first-child', 'tag' => 'li', 'pseudo' => 'first-child'], ['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', 'expr' => '2n'], + ['selector' => 'li.bar:nth-child(2n)', 'tag' => 'li', 'classes' => ['bar'], 'pseudo' => 'nth-child', 'pseudo-expression' => '2n'], ]; $parameters = [];