Passed
Push — master ( 60319d...b48e8d )
by Daniele
08:21
created

CssTranslator::xpath()   B

Complexity

Conditions 4
Paths 2

Size

Total Lines 27
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 27
rs 8.5806
cc 4
eloc 17
nc 2
nop 1
1
<?php
2
3
namespace FluidXml;
4
5
class CssTranslator
6
{
7
        const TOKEN = '/{([[:alpha:]]+)(\d+)}/i';
8
        const MAP = [
9
                // Empty part of #id and .class
10
                [ '(?<=^|\s)      # The begining or an empty space.
11
                   (?=[.#\[])     # . | # | [',
12
                  '*',
13
                  'TAG',
14
                  false ],
15
                // #id
16
                [ '\#
17
                   ([\w\-]+)',
18
                  '[@id="\1"]',
19
                  'ID',
20
                  false ],
21
                // .class
22
                [ '\.
23
                   ([\w\-]+)',
24
                  '[ contains( concat(" ", normalize-space(@class), " "), concat(" ", "\1", " ") ) ]',
25
                  'CLASS',
26
                  false ],
27
                // [attr]
28
                [ '\[
29
                   ([\w\-]+)
30
                   \]',
31
                  '[@\1]',
32
                  'ATTR',
33
                  false ],
34
                // [attr="val"]
35
                [ '\[
36
                   ([\w\-]+)
37
                   =["\']
38
                   (.*)
39
                   ["\']
40
                   \]',
41
                  '[@\1="\2"]',
42
                  'ATTR',
43
                  false ],
44
                // ns|A
45
                [ '([\w\-]+)
46
                   \|
47
                   (?=\w)       # A namespace must be followed at least by a character.',
48
                  '\1:',
49
                  'NS',
50
                  false ],
51
                // :root
52
                [ ':root\b',
53
                  '/*',
54
                  'TAG',
55
                  false ],
56
                // A
57
                [ '(?<=^|\s|\})
58
                   ( [\w\-]+ | \* )',
59
                  '\1',
60
                  'TAG',
61
                  false ],
62
                // Aggregates the components of a tag in an expression.
63
                [ '({NS\d+})?
64
                   ({TAG\d+})
65
                   ((?:{ATTR\d+})*|)
66
                   ((?:{ID\d+})*|)
67
                   ((?:{CLASS\d+})*|)',
68
                  '\1\2\3\4\5',
69
                  'EXP',
70
                  false ],
71
                [ '({EXP\d+})
72
                   :first-child',
73
                  '*[1]/self::\1',
74
                  'EXP',
75
                  false ],
76
                // {} + {}
77
                [ '({EXP\d+})
78
                   \s* \+ \s*
79
                   ({EXP\d+})',
80
                  '\1/following-sibling::*[1]/self::\2',
81
                  'EXP',
82
                  true ],
83
                // {} ~ {}
84
                [ '({EXP\d+})
85
                   \s* \~ \s*
86
                   ({EXP\d+})',
87
                  '\1/following-sibling::*/self::\2',
88
                  'EXP',
89
                  true ],
90
                // {} > {}
91
                [ '({EXP\d+})
92
                   \s* > \s*
93
                   ({EXP\d+})',
94
                  '\1/\2',
95
                  'EXP',
96
                  true ],
97
                // {} {}
98
                [ '({EXP\d+})
99
                   \s+
100
                   ({EXP\d+})',
101
                  '\1//\2',
102
                  'EXP',
103
                  true ],
104
                // {}, {}
105
                [ '({EXP\d+})
106
                   \s* , \s*
107
                   ({EXP\d+})',
108
                  '\1|\2',
109
                  'EXP',
110
                  true ]
111
        ];
112
113
        public static function xpath($css)
114
        {
115
                $xpath = $css;
116
117
                $stack = [];
118
                $index = 0;
119
120
                foreach (self::MAP as $o) {
121
                        // The regexes have a common wrapper.
122
                        list($search, $replace, $id, $repeat) = $o;
123
                        $search = "/{$search}/xi";
124
125
                        do {
126
                                $prev_xpath = $xpath;
127
                                self::tokenize($search, $replace, $id, $xpath, $stack, $index);
128
                        } while ($repeat && $xpath !== $prev_xpath);
129
                }
130
131
                self::translateStack($stack, $xpath);
132
133
                $xpath = \trim($xpath);
134
                $xpath = ".//$xpath";
135
                $xpath = \str_replace('|', '|.//',   $xpath);
136
                $xpath = \str_replace('.///', '/', $xpath);
137
138
                return $xpath;
139
        }
140
141
        protected static function tokenize($search, $replace, $id, &$xpath, &$stack, &$index)
142
        {
143
                // The search can return 0, 1 or more fund patterns.
144
                $matches_count = \preg_match_all($search, $xpath, $matches, \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE);
145
146
                // \PREG_OFFSET_CAPTURE calculates offsets starting from the begining of the string $xpath.
147
                // Rewriting $xpath from left (first matches) creates a mismatch between the calculated offsets
148
                // and the actual ones. This problem can be avoided rewriting $xpath from right (last matches).
149
                for ($i = $matches_count - 1; $i >= 0; --$i) {
150
                        $match = $matches[$i];
151
152
                        // The count of the groups must exclude the entire recognized string entry.
153
                        $groups_count = \count($match) - 1;
154
155
                        // $match[0] is the entire recognized string.
156
                        // $match[>0] are the captured groups.
157
                        // $match[n][0] is the actual string.
158
                        // $match[n][1] is the position of the string.
159
                        list($pattern, $pattern_pos) = $match[0];
160
161
                        $xpath = \substr_replace($xpath, "{{$id}{$index}}", $pattern_pos, \strlen($pattern));
162
163
                        $groups_values = [];
164
                        for ($ii = 1; $ii <= $groups_count; ++$ii) {
165
                                // 0 is the group value, 1 is the group position.
166
                                $groups_values[$ii] = $match[$ii][0];
167
                        }
168
169
                        $stack[$index] = [$replace, $groups_values, $pattern];
170
                        ++$index;
171
                }
172
        }
173
174
        protected static function translateStack(&$stack, &$xpath)
175
        {
176
                do {
177
                        $matches_count = \preg_match_all(self::TOKEN, $xpath, $matches, \PREG_SET_ORDER);
178
179
                        for ($i = 0; $i < $matches_count; ++$i) {
180
                                list($pattern, $type, $id) = $matches[$i];
0 ignored issues
show
Unused Code introduced by
The assignment to $pattern is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
181
182
                                $id = \intval($id);
183
184
                                list($replace, $groups) = $stack[$id];
185
186
                                foreach ($groups as $k => $v) {
187
                                        $replace = \str_replace("\\$k", $v, $replace);
188
                                }
189
190
                                $xpath = \str_replace("{{$type}{$id}}", $replace, $xpath);
191
                        }
192
                } while ($matches_count > 0);
193
        }
194
}
195