Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Symfony package.
|
Chris@0
|
5 *
|
Chris@0
|
6 * (c) Fabien Potencier <fabien@symfony.com>
|
Chris@0
|
7 *
|
Chris@0
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
9 * file that was distributed with this source code.
|
Chris@0
|
10 */
|
Chris@0
|
11
|
Chris@0
|
12 namespace Symfony\Component\CssSelector\XPath\Extension;
|
Chris@0
|
13
|
Chris@0
|
14 use Symfony\Component\CssSelector\Node;
|
Chris@0
|
15 use Symfony\Component\CssSelector\XPath\Translator;
|
Chris@0
|
16 use Symfony\Component\CssSelector\XPath\XPathExpr;
|
Chris@0
|
17
|
Chris@0
|
18 /**
|
Chris@0
|
19 * XPath expression translator node extension.
|
Chris@0
|
20 *
|
Chris@0
|
21 * This component is a port of the Python cssselect library,
|
Chris@0
|
22 * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
Chris@0
|
23 *
|
Chris@0
|
24 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
|
Chris@0
|
25 *
|
Chris@0
|
26 * @internal
|
Chris@0
|
27 */
|
Chris@0
|
28 class NodeExtension extends AbstractExtension
|
Chris@0
|
29 {
|
Chris@0
|
30 const ELEMENT_NAME_IN_LOWER_CASE = 1;
|
Chris@0
|
31 const ATTRIBUTE_NAME_IN_LOWER_CASE = 2;
|
Chris@0
|
32 const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4;
|
Chris@0
|
33
|
Chris@0
|
34 private $flags;
|
Chris@0
|
35
|
Chris@0
|
36 /**
|
Chris@0
|
37 * @param int $flags
|
Chris@0
|
38 */
|
Chris@0
|
39 public function __construct($flags = 0)
|
Chris@0
|
40 {
|
Chris@0
|
41 $this->flags = $flags;
|
Chris@0
|
42 }
|
Chris@0
|
43
|
Chris@0
|
44 /**
|
Chris@0
|
45 * @param int $flag
|
Chris@0
|
46 * @param bool $on
|
Chris@0
|
47 *
|
Chris@0
|
48 * @return $this
|
Chris@0
|
49 */
|
Chris@0
|
50 public function setFlag($flag, $on)
|
Chris@0
|
51 {
|
Chris@0
|
52 if ($on && !$this->hasFlag($flag)) {
|
Chris@0
|
53 $this->flags += $flag;
|
Chris@0
|
54 }
|
Chris@0
|
55
|
Chris@0
|
56 if (!$on && $this->hasFlag($flag)) {
|
Chris@0
|
57 $this->flags -= $flag;
|
Chris@0
|
58 }
|
Chris@0
|
59
|
Chris@0
|
60 return $this;
|
Chris@0
|
61 }
|
Chris@0
|
62
|
Chris@0
|
63 /**
|
Chris@0
|
64 * @param int $flag
|
Chris@0
|
65 *
|
Chris@0
|
66 * @return bool
|
Chris@0
|
67 */
|
Chris@0
|
68 public function hasFlag($flag)
|
Chris@0
|
69 {
|
Chris@0
|
70 return (bool) ($this->flags & $flag);
|
Chris@0
|
71 }
|
Chris@0
|
72
|
Chris@0
|
73 /**
|
Chris@0
|
74 * {@inheritdoc}
|
Chris@0
|
75 */
|
Chris@0
|
76 public function getNodeTranslators()
|
Chris@0
|
77 {
|
Chris@17
|
78 return [
|
Chris@17
|
79 'Selector' => [$this, 'translateSelector'],
|
Chris@17
|
80 'CombinedSelector' => [$this, 'translateCombinedSelector'],
|
Chris@17
|
81 'Negation' => [$this, 'translateNegation'],
|
Chris@17
|
82 'Function' => [$this, 'translateFunction'],
|
Chris@17
|
83 'Pseudo' => [$this, 'translatePseudo'],
|
Chris@17
|
84 'Attribute' => [$this, 'translateAttribute'],
|
Chris@17
|
85 'Class' => [$this, 'translateClass'],
|
Chris@17
|
86 'Hash' => [$this, 'translateHash'],
|
Chris@17
|
87 'Element' => [$this, 'translateElement'],
|
Chris@17
|
88 ];
|
Chris@0
|
89 }
|
Chris@0
|
90
|
Chris@0
|
91 /**
|
Chris@0
|
92 * @return XPathExpr
|
Chris@0
|
93 */
|
Chris@0
|
94 public function translateSelector(Node\SelectorNode $node, Translator $translator)
|
Chris@0
|
95 {
|
Chris@0
|
96 return $translator->nodeToXPath($node->getTree());
|
Chris@0
|
97 }
|
Chris@0
|
98
|
Chris@0
|
99 /**
|
Chris@0
|
100 * @return XPathExpr
|
Chris@0
|
101 */
|
Chris@0
|
102 public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator)
|
Chris@0
|
103 {
|
Chris@0
|
104 return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector());
|
Chris@0
|
105 }
|
Chris@0
|
106
|
Chris@0
|
107 /**
|
Chris@0
|
108 * @return XPathExpr
|
Chris@0
|
109 */
|
Chris@0
|
110 public function translateNegation(Node\NegationNode $node, Translator $translator)
|
Chris@0
|
111 {
|
Chris@0
|
112 $xpath = $translator->nodeToXPath($node->getSelector());
|
Chris@0
|
113 $subXpath = $translator->nodeToXPath($node->getSubSelector());
|
Chris@0
|
114 $subXpath->addNameTest();
|
Chris@0
|
115
|
Chris@0
|
116 if ($subXpath->getCondition()) {
|
Chris@0
|
117 return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition()));
|
Chris@0
|
118 }
|
Chris@0
|
119
|
Chris@0
|
120 return $xpath->addCondition('0');
|
Chris@0
|
121 }
|
Chris@0
|
122
|
Chris@0
|
123 /**
|
Chris@0
|
124 * @return XPathExpr
|
Chris@0
|
125 */
|
Chris@0
|
126 public function translateFunction(Node\FunctionNode $node, Translator $translator)
|
Chris@0
|
127 {
|
Chris@0
|
128 $xpath = $translator->nodeToXPath($node->getSelector());
|
Chris@0
|
129
|
Chris@0
|
130 return $translator->addFunction($xpath, $node);
|
Chris@0
|
131 }
|
Chris@0
|
132
|
Chris@0
|
133 /**
|
Chris@0
|
134 * @return XPathExpr
|
Chris@0
|
135 */
|
Chris@0
|
136 public function translatePseudo(Node\PseudoNode $node, Translator $translator)
|
Chris@0
|
137 {
|
Chris@0
|
138 $xpath = $translator->nodeToXPath($node->getSelector());
|
Chris@0
|
139
|
Chris@0
|
140 return $translator->addPseudoClass($xpath, $node->getIdentifier());
|
Chris@0
|
141 }
|
Chris@0
|
142
|
Chris@0
|
143 /**
|
Chris@0
|
144 * @return XPathExpr
|
Chris@0
|
145 */
|
Chris@0
|
146 public function translateAttribute(Node\AttributeNode $node, Translator $translator)
|
Chris@0
|
147 {
|
Chris@0
|
148 $name = $node->getAttribute();
|
Chris@0
|
149 $safe = $this->isSafeName($name);
|
Chris@0
|
150
|
Chris@0
|
151 if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) {
|
Chris@0
|
152 $name = strtolower($name);
|
Chris@0
|
153 }
|
Chris@0
|
154
|
Chris@0
|
155 if ($node->getNamespace()) {
|
Chris@0
|
156 $name = sprintf('%s:%s', $node->getNamespace(), $name);
|
Chris@0
|
157 $safe = $safe && $this->isSafeName($node->getNamespace());
|
Chris@0
|
158 }
|
Chris@0
|
159
|
Chris@0
|
160 $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name));
|
Chris@0
|
161 $value = $node->getValue();
|
Chris@0
|
162 $xpath = $translator->nodeToXPath($node->getSelector());
|
Chris@0
|
163
|
Chris@0
|
164 if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) {
|
Chris@0
|
165 $value = strtolower($value);
|
Chris@0
|
166 }
|
Chris@0
|
167
|
Chris@0
|
168 return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value);
|
Chris@0
|
169 }
|
Chris@0
|
170
|
Chris@0
|
171 /**
|
Chris@0
|
172 * @return XPathExpr
|
Chris@0
|
173 */
|
Chris@0
|
174 public function translateClass(Node\ClassNode $node, Translator $translator)
|
Chris@0
|
175 {
|
Chris@0
|
176 $xpath = $translator->nodeToXPath($node->getSelector());
|
Chris@0
|
177
|
Chris@0
|
178 return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName());
|
Chris@0
|
179 }
|
Chris@0
|
180
|
Chris@0
|
181 /**
|
Chris@0
|
182 * @return XPathExpr
|
Chris@0
|
183 */
|
Chris@0
|
184 public function translateHash(Node\HashNode $node, Translator $translator)
|
Chris@0
|
185 {
|
Chris@0
|
186 $xpath = $translator->nodeToXPath($node->getSelector());
|
Chris@0
|
187
|
Chris@0
|
188 return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId());
|
Chris@0
|
189 }
|
Chris@0
|
190
|
Chris@0
|
191 /**
|
Chris@0
|
192 * @return XPathExpr
|
Chris@0
|
193 */
|
Chris@0
|
194 public function translateElement(Node\ElementNode $node)
|
Chris@0
|
195 {
|
Chris@0
|
196 $element = $node->getElement();
|
Chris@0
|
197
|
Chris@0
|
198 if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) {
|
Chris@0
|
199 $element = strtolower($element);
|
Chris@0
|
200 }
|
Chris@0
|
201
|
Chris@0
|
202 if ($element) {
|
Chris@0
|
203 $safe = $this->isSafeName($element);
|
Chris@0
|
204 } else {
|
Chris@0
|
205 $element = '*';
|
Chris@0
|
206 $safe = true;
|
Chris@0
|
207 }
|
Chris@0
|
208
|
Chris@0
|
209 if ($node->getNamespace()) {
|
Chris@0
|
210 $element = sprintf('%s:%s', $node->getNamespace(), $element);
|
Chris@0
|
211 $safe = $safe && $this->isSafeName($node->getNamespace());
|
Chris@0
|
212 }
|
Chris@0
|
213
|
Chris@0
|
214 $xpath = new XPathExpr('', $element);
|
Chris@0
|
215
|
Chris@0
|
216 if (!$safe) {
|
Chris@0
|
217 $xpath->addNameTest();
|
Chris@0
|
218 }
|
Chris@0
|
219
|
Chris@0
|
220 return $xpath;
|
Chris@0
|
221 }
|
Chris@0
|
222
|
Chris@0
|
223 /**
|
Chris@0
|
224 * {@inheritdoc}
|
Chris@0
|
225 */
|
Chris@0
|
226 public function getName()
|
Chris@0
|
227 {
|
Chris@0
|
228 return 'node';
|
Chris@0
|
229 }
|
Chris@0
|
230
|
Chris@0
|
231 /**
|
Chris@0
|
232 * Tests if given name is safe.
|
Chris@0
|
233 *
|
Chris@0
|
234 * @param string $name
|
Chris@0
|
235 *
|
Chris@0
|
236 * @return bool
|
Chris@0
|
237 */
|
Chris@0
|
238 private function isSafeName($name)
|
Chris@0
|
239 {
|
Chris@0
|
240 return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name);
|
Chris@0
|
241 }
|
Chris@0
|
242 }
|