Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@0
|
4 * This file is part of the Mink package.
|
Chris@0
|
5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
Chris@0
|
6 *
|
Chris@0
|
7 * For the full copyright and license information, please view the LICENSE
|
Chris@0
|
8 * file that was distributed with this source code.
|
Chris@0
|
9 */
|
Chris@0
|
10
|
Chris@0
|
11 namespace Behat\Mink\Selector\Xpath;
|
Chris@0
|
12
|
Chris@0
|
13 /**
|
Chris@0
|
14 * XPath manipulation utility.
|
Chris@0
|
15 *
|
Chris@0
|
16 * @author Graham Bates
|
Chris@0
|
17 * @author Christophe Coevoet <stof@notk.org>
|
Chris@0
|
18 */
|
Chris@0
|
19 class Manipulator
|
Chris@0
|
20 {
|
Chris@0
|
21 /**
|
Chris@0
|
22 * Regex to find union operators not inside brackets.
|
Chris@0
|
23 */
|
Chris@0
|
24 const UNION_PATTERN = '/\|(?![^\[]*\])/';
|
Chris@0
|
25
|
Chris@0
|
26 /**
|
Chris@0
|
27 * Prepends the XPath prefix to the given XPath.
|
Chris@0
|
28 *
|
Chris@0
|
29 * The returned XPath will match elements matching the XPath inside an element
|
Chris@0
|
30 * matching the prefix.
|
Chris@0
|
31 *
|
Chris@0
|
32 * @param string $xpath
|
Chris@0
|
33 * @param string $prefix
|
Chris@0
|
34 *
|
Chris@0
|
35 * @return string
|
Chris@0
|
36 */
|
Chris@0
|
37 public function prepend($xpath, $prefix)
|
Chris@0
|
38 {
|
Chris@0
|
39 $expressions = array();
|
Chris@0
|
40
|
Chris@0
|
41 // If the xpath prefix contains a union we need to wrap it in parentheses.
|
Chris@0
|
42 if (preg_match(self::UNION_PATTERN, $prefix)) {
|
Chris@0
|
43 $prefix = '('.$prefix.')';
|
Chris@0
|
44 }
|
Chris@0
|
45
|
Chris@0
|
46 // Split any unions into individual expressions.
|
Chris@0
|
47 foreach ($this->splitUnionParts($xpath) as $expression) {
|
Chris@0
|
48 $expression = trim($expression);
|
Chris@0
|
49 $parenthesis = '';
|
Chris@0
|
50
|
Chris@0
|
51 // If the union is inside some braces, we need to preserve the opening braces and apply
|
Chris@0
|
52 // the prefix only inside it.
|
Chris@0
|
53 if (preg_match('/^[\(\s*]+/', $expression, $matches)) {
|
Chris@0
|
54 $parenthesis = $matches[0];
|
Chris@0
|
55 $expression = substr($expression, strlen($parenthesis));
|
Chris@0
|
56 }
|
Chris@0
|
57
|
Chris@0
|
58 // add prefix before element selector
|
Chris@0
|
59 if (0 === strpos($expression, '/')) {
|
Chris@0
|
60 $expression = $prefix.$expression;
|
Chris@0
|
61 } else {
|
Chris@0
|
62 $expression = $prefix.'/'.$expression;
|
Chris@0
|
63 }
|
Chris@0
|
64 $expressions[] = $parenthesis.$expression;
|
Chris@0
|
65 }
|
Chris@0
|
66
|
Chris@0
|
67 return implode(' | ', $expressions);
|
Chris@0
|
68 }
|
Chris@0
|
69
|
Chris@0
|
70 /**
|
Chris@0
|
71 * Splits the XPath into parts that are separated by the union operator.
|
Chris@0
|
72 *
|
Chris@0
|
73 * @param string $xpath
|
Chris@0
|
74 *
|
Chris@0
|
75 * @return string[]
|
Chris@0
|
76 */
|
Chris@0
|
77 private function splitUnionParts($xpath)
|
Chris@0
|
78 {
|
Chris@0
|
79 if (false === strpos($xpath, '|')) {
|
Chris@0
|
80 return array($xpath); // If there is no pipe in the string, we know for sure that there is no union
|
Chris@0
|
81 }
|
Chris@0
|
82
|
Chris@0
|
83 $xpathLen = strlen($xpath);
|
Chris@0
|
84 $openedBrackets = 0;
|
Chris@0
|
85 // Consume whitespaces chars at the beginning of the string (this is the list of chars removed by trim() by default)
|
Chris@0
|
86 $startPosition = strspn($xpath, " \t\n\r\0\x0B");
|
Chris@0
|
87
|
Chris@0
|
88 $unionParts = array();
|
Chris@0
|
89
|
Chris@0
|
90 for ($i = $startPosition; $i <= $xpathLen; ++$i) {
|
Chris@0
|
91 // Consume all chars until we reach a quote, a bracket or a pipe
|
Chris@0
|
92 $i += strcspn($xpath, '"\'[]|', $i);
|
Chris@0
|
93
|
Chris@0
|
94 if ($i < $xpathLen) {
|
Chris@0
|
95 switch ($xpath[$i]) {
|
Chris@0
|
96 case '"':
|
Chris@0
|
97 case "'":
|
Chris@0
|
98 // Move to the end of the string literal
|
Chris@0
|
99 if (false === $i = strpos($xpath, $xpath[$i], $i + 1)) {
|
Chris@0
|
100 return array($xpath); // The XPath expression is invalid, don't split it
|
Chris@0
|
101 }
|
Chris@0
|
102 continue 2;
|
Chris@0
|
103 case '[':
|
Chris@0
|
104 ++$openedBrackets;
|
Chris@0
|
105 continue 2;
|
Chris@0
|
106 case ']':
|
Chris@0
|
107 --$openedBrackets;
|
Chris@0
|
108 continue 2;
|
Chris@0
|
109 }
|
Chris@0
|
110 }
|
Chris@0
|
111 if ($openedBrackets) {
|
Chris@0
|
112 continue;
|
Chris@0
|
113 }
|
Chris@0
|
114
|
Chris@0
|
115 $unionParts[] = substr($xpath, $startPosition, $i - $startPosition);
|
Chris@0
|
116
|
Chris@0
|
117 if ($i === $xpathLen) {
|
Chris@0
|
118 return $unionParts;
|
Chris@0
|
119 }
|
Chris@0
|
120
|
Chris@0
|
121 // Consume any whitespace chars after the pipe
|
Chris@0
|
122 $i += strspn($xpath, " \t\n\r\0\x0B", $i + 1);
|
Chris@0
|
123 $startPosition = $i + 1;
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 return array($xpath); // The XPath expression is invalid
|
Chris@0
|
127 }
|
Chris@0
|
128
|
Chris@0
|
129 }
|