Transform 'css attribute selector' expression to xpath expression

@param string $expression

@return string transformed expression (xpath)
489601
private static function transformAttrSelector($expression)
    {
        if (preg_match('|@?([a-z0-9_]+)(([\!\*\^\$\~\|]{0,1})=)[\'"]([^\'"]+)[\'"]|i', $expression, $matches)) {
            if ($matches[3] === '') { // arbitrary attribute strict value equality
                return '[@' . strtolower($matches[1]) . "='" . $matches[4] . "']";
            } elseif ($matches[3] === '!') { // arbitrary attribute negation strict value
                return '[@' . strtolower($matches[1]) . "!='" . $matches[4] . "']";
            } elseif ($matches[3] === '~') { // arbitrary attribute value contains full word
                return "[contains(concat(' ', normalize-space(@" . strtolower($matches[1]) . "), ' '), ' ". $matches[4] . " ')]";
            } elseif ($matches[3] === '*') {  // arbitrary attribute value contains specified content
                return "[contains(@" . strtolower($matches[1]) . ", '" . $matches[4] . "')]";
            } elseif ($matches[3] === '^') { // attribute value starts with specified content
                return "[starts-with(@" . strtolower($matches[1]) . ", '" . $matches[4] . "')]";
            } elseif ($matches[3] === '$') { // attribute value ends with specified content
                return "[@".$matches[1]." and substring(@".$matches[1].", string-length(@".$matches[1].")-".
                (\strlen($matches[4])-1).") = '".$matches[4]."']";
            } elseif ($matches[3] === '|') { // attribute has prefix selector
                return '[@' . strtolower($matches[1]) . "='" . $matches[4] . "' or starts-with(@".
                strtolower($matches[1]) . ", '" . $matches[4].'-' . "')]";
            }
        }

        // attribute without value
        if (preg_match('|([a-z0-9_]{1})([a-z0-9_-]*)|i', $expression, $matches)) {
            return "[@" . $matches[1].$matches[2] . "]";
        }

        throw new \Exception('Attribute selector is malformed or contains unsupported characters.');
    }
756117
public function elemMatch($expression) : self
    {
        return $this->operator('$elemMatch', $expression instanceof Expr ? $expression->getQuery() : $expression);
    }
847208
protected static function _tokenize($expression_src)
    {
        // Child selectors
        $expression = str_replace('>', '/', $expression_src);

        // IDs
        $expression = preg_replace('|#([a-z][a-z0-9_-]*)|i', "[@id='$1']", $expression);
        $expression = preg_replace('|(?<![a-z0-9_-])(\[@id=)|i', '*$1', $expression);

        // arbitrary attribute strict equality
        $expression = preg_replace_callback(
            '|\[@?([a-z0-9_-]*)([~\*\^\$\|\!])?=([^\[]+)\]|i',
            static function ($matches) {
                $attr = strtolower($matches[1]);
                $type = $matches[2];
                $val = trim($matches[3], "'\" \t");

                $field = ($attr === '' ? 'text()' : '@' . $attr);

                $items = [];
                foreach (explode(strpos($val, '|') !== false ? '|' : '&', $val) as $word) {
                    if ($type === '') {
                        $items[] = "$field='$word'";
                    } elseif ($type === '~') {
                        $items[] = "contains(concat(' ', normalize-space($field), ' '), ' $word ')";
                    } elseif ($type === '|') {
                        $item = "$field='$word' or starts-with($field,'$word-')";
                        $items[] = $word === $val ? $item : "($item)";
                    } elseif ($type === '!') {
                        $item = "not($field) or $field!='$word'";
                        $items[] = $word === $val ? $item : "($item)";
                    } else {
                        $items[] = ['*' => 'contains', '^' => 'starts-with', '$' => 'ends-with'][$type] . "($field, '$word')";
                    }
                }
                return '[' . implode(strpos($val, '|') !== false ? ' or ' : ' and ', $items) . ']';
            },
            $expression
        );

        //attribute contains specified content
        $expression = preg_replace_callback(
            '|\[(!?)([a-z][a-z0-9_-\|&]*)\]|i',
            static function ($matches) {
                $val = $matches[2];
                $op = strpos($val, '|') !== false ? '|' : '&';

                $items = [];
                foreach (explode($op, $val) as $word) {
                    $items[] = '@' . $word;
                }

                $r = '[' . implode($op === '|' ? ' or ' : ' and ', $items) . ']';
                return $matches[1] === '!' ? "not($r)" : $r;
            },
            $expression
        );
        // Classes
        if (false === strpos($expression, '[@')) {
            $expression = preg_replace(
                '|\.([a-z][a-z0-9_-]*)|i',
                "[contains(concat(' ', normalize-space(@class), ' '), ' \$1 ')]",
                $expression
            );
        }

        /** ZF-9764 -- remove double asterisk */
        $expression = str_replace('**', '*', $expression);

        return $expression;
    }
159886
public static function parseWithFilters(string $expression): string
    {
        $expression = \trim($expression);

        if ($expression === '' || \is_numeric($expression)) {
            return $expression;
        }

        if (!isset(self::$cache[$expression])) {
            $tree = self::treeStruct($expression);
            [$tree, $filters] = self::splitFilters($tree);

            $code = self::transform($tree);
            if ($filters !== []) {
                $code = '$this->filter(' . $code . ')';

                /* @var Token $filter */
                foreach ($filters as [$filter, $args]) {
                    $code .= '->' . $filter->getContent();
                    if ($args === []) {
                        $code .= '()';
                    } else {
                        $code .= self::transform($args);
                    }
                }
            }

            self::$cache[$expression] = $code . '->getResult()';
        }

        return self::$cache[$expression];
    }
459836
protected function compileExtends($expression) {
        if (Str::startsWith($expression, '(')) {
            $expression = substr($expression, 1, -1);
        }

        $data = "<?php echo \$__env->make($expression, array_except(get_defined_vars(), array('__data', '__path')))->render(); ?>";

        $this->footer[] = $data;

        return '';
    }
986929
public function extractCss($html)
    {
        $document = $this->createDomDocumentFromHtml($html);

        $xPath = new DOMXPath($document);

        $cssIdentifier = join('-', $this->loadedfiles);
        $htmlIdentifier = md5($html);

        $applicable_rules = array_filter(
            $this->rules,
            function (Rule $rule) use ($xPath, $cssIdentifier, $htmlIdentifier) {
                if (isset($this->cachedRules[$cssIdentifier][$htmlIdentifier][$rule->getSelector()])) {
                    return $this->cachedRules[$cssIdentifier][$htmlIdentifier][$rule->getSelector()];
                }

                $expression = $this->buildExpressionForSelector($rule->getSelector());

                if (!$expression) {
                    return false;
                }

                /** @var DOMNodeList $elements */
                $elements = $xPath->query($expression);

                if ($elements->length === 0) {
                    $this->cachedRules[$cssIdentifier][$htmlIdentifier][$rule->getSelector()] = false;
                    return false;
                }

                $this->cachedRules[$cssIdentifier][$htmlIdentifier][$rule->getSelector()] = true;

                return true;
            }
        );


        $this->resultCache->save('cachedRules', $this->cachedRules);

        return $applicable_rules;
    }
470852
protected function compileInject($expression)
    {
        $regex = "/\#inject\s*\(((?:\n|\s|\t)*(?:.+?)(?:\n|\s|\t)*)\)/sm";

        $output = preg_replace_callback($regex, function ($match) {
            return "<?php echo \$__tintin->getStackManager()->getStack({$match[1]}); ?>";
        }, $expression);

        return $output == $expression ? '' : $output;
    }
1223966
private function truncateCronExpression(string $expression): string
    {
        $expressionParts = preg_split('/\s/', $expression, -1, PREG_SPLIT_NO_EMPTY);
        if (count($expressionParts) === 5) {
            return $expression;
        }

        $expressionParts = array_slice($expressionParts, 0, 5);
        return implode(' ', $expressionParts);
    }
285933
public function removeExpression($expression)
    {
        if ($this->expressions->contains($expression)) {
            $this->expressions->removeElement($expression);
        }

        return $this;
    }
397707
public String getString(String expression) {
		return (String) evalXPath(expression, null, XPathConstants.STRING);
	}
127253
public static function parse($expression)
    {
        if (trim($expression) === '') {
            throw new InvalidArgumentException('Console command definition is empty.');
        }

        preg_match('/[^\s]+/', $expression, $matches);

        if (isset($matches[0])) {
            $name = $matches[0];
        } else {
            throw new InvalidArgumentException('Unable to determine command name from signature.');
        }

        preg_match_all('/\{\s*(.*?)\s*\}/', $expression, $matches);

        $tokens = isset($matches[1]) ? $matches[1] : [];

        if (count($tokens)) {
            return array_merge([$name], static::parameters($tokens));
        }

        return [$name, [], []];
    }
4451
public function evaluateExpression($expression)
    {
        if ($this->getTalesMode() === 'php') {
            return PHPTAL_Php_TalesInternal::php($expression);
        }
        return PHPTAL_Php_TalesInternal::compileToPHPExpressions($expression, false);
    }
949636
private function parseExpression($expression)
    {
        $result = array();

        if (isset(self::$operatorHash[$expression[0]])) {
            $result['operator'] = $expression[0];
            $expression = substr($expression, 1);
        } else {
            $result['operator'] = '';
        }

        foreach (explode(',', $expression) as $value) {
            $value = trim($value);
            $varspec = array();
            if ($colonPos = strpos($value, ':')) {
                $varspec['value'] = substr($value, 0, $colonPos);
                $varspec['modifier'] = ':';
                $varspec['position'] = (int) substr($value, $colonPos + 1);
            } elseif (substr($value, -1) == '*') {
                $varspec['modifier'] = '*';
                $varspec['value'] = substr($value, 0, -1);
            } else {
                $varspec['value'] = (string) $value;
                $varspec['modifier'] = '';
            }
            $result['values'][] = $varspec;
        }

        return $result;
    }
563578
public static function cssToXpath($selector, $prefix = '//')
    {
        $paths = [];

        while ($selector !== '') {
            list($xpath, $selector) = static::parseAndConvertSelector($selector, $prefix);

            if (substr($selector, 0, 1) === ',') {
                $selector = trim($selector, ', ');
            }

            $paths[] = $xpath;
        }

        return implode('|', $paths);
    }
592493
public function select($expression = '*')
    {
        if (!is_array($expression)) {
            $expression = explode(',', $expression);
        }

        $this->type        = self::TYPE_SELECT;
        $this->expressions = array_merge($this->expressions, array_map(function ($expression, $alias) {
            return is_string($alias) ? "$expression AS $alias" : $expression;
        }, $expression, array_keys($expression)));

        return $this;
    }
7664
protected function compileExtends($expression)
    {
        $expression = $this->stripParentheses($expression);

        $data = "<?php echo \$__env->make($expression, array_except(get_defined_vars(), array('__data', '__path')))->render(); ?>";

        $this->footer[] = $data;

        return '';
    }
75122
private static function treeStruct(string $expression): array
    {
        if (\strpos($expression, '<?') === 0 || $expression[-1] === ';') {
            throw new \InvalidArgumentException('Expression syntax error');
        }

        $expression = '<?php ' . $expression . ';';

        $tokens = \token_get_all($expression);

        $temp = [];
        $level = 0;

        $n = \count($tokens);
        $i = 1; // skip T_OPEN_TAG
        for (; $i < $n; ++$i) {
            $token = $tokens[$i];

            $type = null;
            if (\is_array($token)) {
                $type = $token[0];

                if ($type === T_WHITESPACE) {
                    continue;
                }

                if (!\in_array($type, self::SUPPORT_TOKEN, true)) {
                    throw new \InvalidArgumentException('Expression contains not support token "' . \token_name($type) . '""');
                }

                if ($type === T_INC && ($tokens[$i - 1] === '+' || $tokens[$i + 1] === '+')) {
                    throw new \InvalidArgumentException('Expression contains error code: "+++');
                }

                if ($type === T_DEC && ($tokens[$i - 1] === '-' || $tokens[$i + 1] === '-')) {
                    throw new \InvalidArgumentException('Expression contains error code: "---');
                }
            }

            switch ($token) {
                case '?':
                    $part = $temp[$level];

                    if ($level === 0) {
                        $temp[$level++] = [$part, new Token($token)];
                    } else {
                        $parentLevel = $level - 1;
                        $temp[$parentLevel][] = $part;
                        $temp[$parentLevel][] = new Token($token);
                    }

                    $temp[$level] = [];
                    break;

                case ':':
                    if ($level === 0) {
                        throw new \InvalidArgumentException('Expression ternary syntax error');
                    }

                    $part = $temp[$level];

                    $parentLevel = $level - 1;
                    $temp[$parentLevel][] = $part;
                    $temp[$parentLevel][] = new Token($token);

                    $temp[$level] = [];
                    break;

                case ';':
                    if ($level > 0) {
                        $part = $temp[$level];
                        unset($temp[$level]);

                        $temp[--$level][] = $part;
                    }
                    ++$i;
                    break 2;
                case ')':
                case ']':
                    $temp[$level][] = new Token($token);
                    $part = $temp[$level];
                    unset($temp[$level]);

                    $temp[--$level][] = $part;
                    break;

                case '(':
                case '[':
                    $temp[++$level] = [new Token($token)];
                    break;

                default:
                    $temp[$level][] = new Token($token);
            }
        }

        if ($i !== $n) {
            throw new \InvalidArgumentException('The code has multi-line expression');
        }

        if (\count($temp) > 1) {
            throw new \InvalidArgumentException('Expression syntax error');
        }

        return $temp[0];
    }