private static function transformCssPseudoSelector($expression, array &$new_path_tokens)
    {
        if (preg_match('|not\((.+)\)|i', $expression, $matches)) {
            $parts = explode(',', $matches[1]);
            foreach ($parts as &$part) {
                $part = trim($part);
                $part = 'self::'.ltrim(self::transform($part), '/');
            }
            $not_selector = implode(' or ', $parts);
            return '[not('.$not_selector.')]';
        } elseif (preg_match('|contains\((.+)\)|i', $expression, $matches)) {
            return '[text()[contains(.,\''.$matches[1].'\')]]'; // contain the specified text
        } elseif (preg_match('|has\((.+)\)|i', $expression, $matches)) {
            if (strpos($matches[1], '> ') === 0) {
                return '[child::' . ltrim(self::transform($matches[1]), '/') .']';
            } else {
                return '[descendant::' . ltrim(self::transform($matches[1]), '/') .']';
            }
        } elseif ($expression === 'first' || $expression === 'last') { // new path inside selection
            array_unshift($new_path_tokens, '(');
            $new_path_tokens[] = ')';
        }

        //  static replacement

        $pseudo_class_selectors = array(
            'disabled' => '[@disabled]',
            'first-child' => '[not(preceding-sibling::*)]',
            'last-child' => '[not(following-sibling::*)]',
            'only-child' => '[not(preceding-sibling::*) and not(following-sibling::*)]',
            'empty' => '[count(*) = 0 and string-length() = 0]',
            'not-empty' => '[count(*) > 0 or string-length() > 0]',
            'parent' => '[count(*) > 0]',
            'header' => '[self::h1 or self::h2 or self::h3 or self::h5 or self::h5 or self::h6]',
            'odd' => '[position() mod 2 = 0]',
            'even' => '[position() mod 2 = 1]',
            'first' => '[1]',
            'last' => '[last()]',
            'root' => '[not(parent::*)]'
        );

        if (!isset($pseudo_class_selectors[$expression])) {
            throw new \Exception('Pseudo class '.$expression.' unknown');
        }

        return $pseudo_class_selectors[$expression];
    }
private static function transformCssPseudoSelector($expression, array &$new_path_tokens)
    {
        if (preg_match('|not\((.+)\)|i', $expression, $matches)) {
            $parts = explode(',', $matches[1]);
            foreach ($parts as &$part) {
                $part = trim($part);
                $part = 'self::'.ltrim(self::transform($part), '/');
            }
            $not_selector = implode(' or ', $parts);
            return '[not('.$not_selector.')]';
        } elseif (preg_match('|contains\((.+)\)|i', $expression, $matches)) {
            return '[text()[contains(.,\''.$matches[1].'\')]]'; // contain the specified text
        } elseif (preg_match('|has\((.+)\)|i', $expression, $matches)) {
            if (strpos($matches[1], '> ') === 0) {
                return '[child::' . ltrim(self::transform($matches[1]), '/') .']';
            } else {
                return '[descendant::' . ltrim(self::transform($matches[1]), '/') .']';
            }
        } elseif ($expression === 'first' || $expression === 'last') { // new path inside selection
            array_unshift($new_path_tokens, '(');
            $new_path_tokens[] = ')';
        }

        //  static replacement

        $pseudo_class_selectors = array(
            'disabled' => '[@disabled]',
            'first-child' => '[not(preceding-sibling::*)]',
            'last-child' => '[not(following-sibling::*)]',
            'only-child' => '[not(preceding-sibling::*) and not(following-sibling::*)]',
            'empty' => '[count(*) = 0 and string-length() = 0]',
            'not-empty' => '[count(*) > 0 or string-length() > 0]',
            'parent' => '[count(*) > 0]',
            'header' => '[self::h1 or self::h2 or self::h3 or self::h5 or self::h5 or self::h6]',
            'odd' => '[position() mod 2 = 0]',
            'even' => '[position() mod 2 = 1]',
            'first' => '[1]',
            'last' => '[last()]',
            'root' => '[not(parent::*)]'
        );

        if (!isset($pseudo_class_selectors[$expression])) {
            throw new \Exception('Pseudo class '.$expression.' unknown');
        }

        return $pseudo_class_selectors[$expression];
    }
private static function transformCssPseudoSelector ( $ expression , array & $ new_path_tokens ) { if ( preg_match ( '|not\((.+)\)|i' , $ expression , $ matches ) ) { $ parts = explode ( ',' , $ matches [ 1 ] ) ; foreach ( $ parts as & $ part ) { $ part = trim ( $ part ) ; $ part = 'self::' . ltrim ( self :: transform ( $ part ) , '/' ) ; } $ not_selector = implode ( ' or ' , $ parts ) ; return '[not(' . $ not_selector . ')]' ; } elseif ( preg_match ( '|contains\((.+)\)|i' , $ expression , $ matches ) ) { return '[text()[contains(.,\'' . $ matches [ 1 ] . '\')]]' ; // contain the specified text } elseif ( preg_match ( '|has\((.+)\)|i' , $ expression , $ matches ) ) { if ( strpos ( $ matches [ 1 ] , '> ' ) === 0 ) { return '[child::' . ltrim ( self :: transform ( $ matches [ 1 ] ) , '/' ) . ']' ; } else { return '[descendant::' . ltrim ( self :: transform ( $ matches [ 1 ] ) , '/' ) . ']' ; } } elseif ( $ expression === 'first' || $ expression === 'last' ) { // new path inside selection array_unshift ( $ new_path_tokens , '(' ) ; $ new_path_tokens [ ] = ')' ; } //  static replacement $ pseudo_class_selectors = array ( 'disabled' => '[@disabled]' , 'first-child' => '[not(preceding-sibling::*)]' , 'last-child' => '[not(following-sibling::*)]' , 'only-child' => '[not(preceding-sibling::*) and not(following-sibling::*)]' , 'empty' => '[count(*) = 0 and string-length() = 0]' , 'not-empty' => '[count(*) > 0 or string-length() > 0]' , 'parent' => '[count(*) > 0]' , 'header' => '[self::h1 or self::h2 or self::h3 or self::h5 or self::h5 or self::h6]' , 'odd' => '[position() mod 2 = 0]' , 'even' => '[position() mod 2 = 1]' , 'first' => '[1]' , 'last' => '[last()]' , 'root' => '[not(parent::*)]' ) ; if ( ! isset ( $ pseudo_class_selectors [ $ expression ] ) ) { throw new \ Exception ( 'Pseudo class ' . $ expression . ' unknown' ) ; } return $ pseudo_class_selectors [ $ expression ] ; }
Transform 'css pseudo selector' expression to xpath expression

@param string $expression
@param string[] $new_path_tokens

@return string transformed expression (xpath)
@throws \Exception
Transform css pseudo selector expression to xpath expression