Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <fabien@symfony.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
 
12
namespace Symfony\Component\CssSelector\XPath;
13
 
14
use Symfony\Component\CssSelector\Exception\ExpressionErrorException;
15
use Symfony\Component\CssSelector\Node\FunctionNode;
16
use Symfony\Component\CssSelector\Node\NodeInterface;
17
use Symfony\Component\CssSelector\Node\SelectorNode;
18
use Symfony\Component\CssSelector\Parser\Parser;
19
use Symfony\Component\CssSelector\Parser\ParserInterface;
20
 
21
/**
22
 * XPath expression translator interface.
23
 *
24
 * This component is a port of the Python cssselect library,
25
 * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
26
 *
27
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
28
 *
29
 * @internal
30
 */
31
class Translator implements TranslatorInterface
32
{
33
    private ParserInterface $mainParser;
34
 
35
    /**
36
     * @var ParserInterface[]
37
     */
38
    private array $shortcutParsers = [];
39
 
40
    /**
41
     * @var Extension\ExtensionInterface[]
42
     */
43
    private array $extensions = [];
44
 
45
    private array $nodeTranslators = [];
46
    private array $combinationTranslators = [];
47
    private array $functionTranslators = [];
48
    private array $pseudoClassTranslators = [];
49
    private array $attributeMatchingTranslators = [];
50
 
51
    public function __construct(ParserInterface $parser = null)
52
    {
53
        $this->mainParser = $parser ?? new Parser();
54
 
55
        $this
56
            ->registerExtension(new Extension\NodeExtension())
57
            ->registerExtension(new Extension\CombinationExtension())
58
            ->registerExtension(new Extension\FunctionExtension())
59
            ->registerExtension(new Extension\PseudoClassExtension())
60
            ->registerExtension(new Extension\AttributeMatchingExtension())
61
        ;
62
    }
63
 
64
    public static function getXpathLiteral(string $element): string
65
    {
66
        if (!str_contains($element, "'")) {
67
            return "'".$element."'";
68
        }
69
 
70
        if (!str_contains($element, '"')) {
71
            return '"'.$element.'"';
72
        }
73
 
74
        $string = $element;
75
        $parts = [];
76
        while (true) {
77
            if (false !== $pos = strpos($string, "'")) {
78
                $parts[] = sprintf("'%s'", substr($string, 0, $pos));
79
                $parts[] = "\"'\"";
80
                $string = substr($string, $pos + 1);
81
            } else {
82
                $parts[] = "'$string'";
83
                break;
84
            }
85
        }
86
 
87
        return sprintf('concat(%s)', implode(', ', $parts));
88
    }
89
 
90
    public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string
91
    {
92
        $selectors = $this->parseSelectors($cssExpr);
93
 
94
        /** @var SelectorNode $selector */
95
        foreach ($selectors as $index => $selector) {
96
            if (null !== $selector->getPseudoElement()) {
97
                throw new ExpressionErrorException('Pseudo-elements are not supported.');
98
            }
99
 
100
            $selectors[$index] = $this->selectorToXPath($selector, $prefix);
101
        }
102
 
103
        return implode(' | ', $selectors);
104
    }
105
 
106
    public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string
107
    {
108
        return ($prefix ?: '').$this->nodeToXPath($selector);
109
    }
110
 
111
    /**
112
     * @return $this
113
     */
114
    public function registerExtension(Extension\ExtensionInterface $extension): static
115
    {
116
        $this->extensions[$extension->getName()] = $extension;
117
 
118
        $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators());
119
        $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators());
120
        $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators());
121
        $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators());
122
        $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators());
123
 
124
        return $this;
125
    }
126
 
127
    /**
128
     * @throws ExpressionErrorException
129
     */
130
    public function getExtension(string $name): Extension\ExtensionInterface
131
    {
132
        if (!isset($this->extensions[$name])) {
133
            throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name));
134
        }
135
 
136
        return $this->extensions[$name];
137
    }
138
 
139
    /**
140
     * @return $this
141
     */
142
    public function registerParserShortcut(ParserInterface $shortcut): static
143
    {
144
        $this->shortcutParsers[] = $shortcut;
145
 
146
        return $this;
147
    }
148
 
149
    /**
150
     * @throws ExpressionErrorException
151
     */
152
    public function nodeToXPath(NodeInterface $node): XPathExpr
153
    {
154
        if (!isset($this->nodeTranslators[$node->getNodeName()])) {
155
            throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName()));
156
        }
157
 
158
        return $this->nodeTranslators[$node->getNodeName()]($node, $this);
159
    }
160
 
161
    /**
162
     * @throws ExpressionErrorException
163
     */
164
    public function addCombination(string $combiner, NodeInterface $xpath, NodeInterface $combinedXpath): XPathExpr
165
    {
166
        if (!isset($this->combinationTranslators[$combiner])) {
167
            throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner));
168
        }
169
 
170
        return $this->combinationTranslators[$combiner]($this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath));
171
    }
172
 
173
    /**
174
     * @throws ExpressionErrorException
175
     */
176
    public function addFunction(XPathExpr $xpath, FunctionNode $function): XPathExpr
177
    {
178
        if (!isset($this->functionTranslators[$function->getName()])) {
179
            throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName()));
180
        }
181
 
182
        return $this->functionTranslators[$function->getName()]($xpath, $function);
183
    }
184
 
185
    /**
186
     * @throws ExpressionErrorException
187
     */
188
    public function addPseudoClass(XPathExpr $xpath, string $pseudoClass): XPathExpr
189
    {
190
        if (!isset($this->pseudoClassTranslators[$pseudoClass])) {
191
            throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass));
192
        }
193
 
194
        return $this->pseudoClassTranslators[$pseudoClass]($xpath);
195
    }
196
 
197
    /**
198
     * @throws ExpressionErrorException
199
     */
200
    public function addAttributeMatching(XPathExpr $xpath, string $operator, string $attribute, ?string $value): XPathExpr
201
    {
202
        if (!isset($this->attributeMatchingTranslators[$operator])) {
203
            throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator));
204
        }
205
 
206
        return $this->attributeMatchingTranslators[$operator]($xpath, $attribute, $value);
207
    }
208
 
209
    /**
210
     * @return SelectorNode[]
211
     */
212
    private function parseSelectors(string $css): array
213
    {
214
        foreach ($this->shortcutParsers as $shortcut) {
215
            $tokens = $shortcut->parse($css);
216
 
217
            if ($tokens) {
218
                return $tokens;
219
            }
220
        }
221
 
222
        return $this->mainParser->parse($css);
223
    }
224
}