1
|
|
|
<?php |
2
|
|
|
/* @description Transformation Style Sheets - Revolutionising PHP templating * |
3
|
|
|
* @author Tom Butler [email protected] * |
4
|
|
|
* @copyright 2015 Tom Butler <[email protected]> | https://r.je/ * |
5
|
|
|
* @license http://www.opensource.org/licenses/bsd-license.php BSD License * |
6
|
|
|
* @version 1.0 */ |
7
|
|
|
namespace Transphporm\Parser; |
8
|
|
|
class CssToXpath { |
9
|
|
|
private $specialChars = [Tokenizer::WHITESPACE, Tokenizer::DOT, Tokenizer::GREATER_THAN, '~', Tokenizer::NUM_SIGN, Tokenizer::COLON, Tokenizer::OPEN_SQUARE_BRACKET]; |
10
|
|
|
private $translators = []; |
11
|
|
|
private $valueParser; |
12
|
|
|
private static $instances = []; |
13
|
|
|
private $functionSet; |
14
|
|
|
|
15
|
|
|
|
16
|
|
|
public function __construct(Value $valueParser, \Transphporm\FunctionSet $functionSet, $prefix = '') { |
17
|
|
|
$hash = $this->registerInstance(); |
18
|
|
|
$this->valueParser = $valueParser; |
19
|
|
|
$this->functionSet = $functionSet; |
20
|
|
|
|
21
|
|
|
$this->translators = [ |
22
|
|
|
Tokenizer::WHITESPACE => function($string) use ($prefix) { return '//' . $prefix . $string; }, |
23
|
|
|
'' => function($string) use ($prefix) { return '/' . $prefix . $string; }, |
24
|
|
|
Tokenizer::GREATER_THAN => function($string) use ($prefix) { return '/' . $prefix . $string; }, |
25
|
|
|
Tokenizer::NUM_SIGN => function($string) { return '[@id=\'' . $string . '\']'; }, |
26
|
|
|
Tokenizer::DOT => function($string) { return '[contains(concat(\' \', normalize-space(@class), \' \'), \' ' . $string . ' \')]'; }, |
27
|
|
|
Tokenizer::OPEN_SQUARE_BRACKET => function($string) use ($hash) { return '[' .'php:function(\'\Transphporm\Parser\CssToXpath::processAttr\', \'' . json_encode($string) . '\', ., "' . $hash . '")' . ']'; } |
28
|
|
|
]; |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
private function registerInstance() { |
32
|
|
|
$hash = spl_object_hash($this); |
33
|
|
|
self::$instances[$hash] = $this; |
34
|
|
|
return $hash; |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
private function createSelector() { |
38
|
|
|
$selector = new \stdclass; |
39
|
|
|
$selector->type = ''; |
40
|
|
|
$selector->string = ''; |
41
|
|
|
return $selector; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
//XPath only allows registering of static functions... this is a hacky workaround for that |
45
|
|
|
public static function processAttr($attr, $element, $hash) { |
46
|
|
|
$attr = json_decode($attr, true); |
47
|
|
|
$valueParser = self::$instances[$hash]->valueParser; |
48
|
|
|
self::$instances[$hash]->functionSet->setElement($element[0]); |
49
|
|
|
|
50
|
|
|
$parts = self::$instances[$hash]->splitOnToken($attr, Tokenizer::EQUALS); |
51
|
|
|
|
52
|
|
|
if ($parts[0] === $attr) return $element[0]->getAttribute($valueParser->parseTokens($attr)[0]) !== ''; |
53
|
|
|
|
54
|
|
|
if ($parts[0][count($parts[0])-1]['type'] === Tokenizer::NOT) { |
55
|
|
|
$attr = [ |
56
|
|
|
['type' => Tokenizer::NAME, 'value' => 'attr'], |
57
|
|
|
['type' => Tokenizer::OPEN_BRACKET, 'value' => $parts[0]], |
58
|
|
|
['type' => Tokenizer::NOT], |
59
|
|
|
['type' => Tokenizer::EQUALS] |
60
|
|
|
]; |
61
|
|
|
$attr = array_merge($attr, $parts[1]); |
62
|
|
|
} |
63
|
|
|
else { |
64
|
|
|
$attr = [ |
65
|
|
|
['type' => Tokenizer::NAME, 'value' => 'attr'], |
66
|
|
|
['type' => Tokenizer::OPEN_BRACKET, 'value' => $parts[0]], |
67
|
|
|
['type' => Tokenizer::EQUALS] |
68
|
|
|
]; |
69
|
|
|
$attr = array_merge($attr, $parts[1]); |
70
|
|
|
} |
71
|
|
|
return $valueParser->parseTokens($attr)[0]; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
View Code Duplication |
private function splitOnToken($tokens, $splitOn) { |
|
|
|
|
75
|
|
|
$splitTokens = []; |
76
|
|
|
$i = 0; |
77
|
|
|
foreach ($tokens as $token) { |
78
|
|
|
if ($token['type'] === $splitOn) $i++; |
79
|
|
|
else $splitTokens[$i][] = $token; |
80
|
|
|
} |
81
|
|
|
return $splitTokens; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
//split the css into indivudal functions |
85
|
|
|
private function split($css) { |
86
|
|
|
$selectors = []; |
87
|
|
|
$selector = $this->createSelector(); |
88
|
|
|
$selectors[] = $selector; |
89
|
|
|
|
90
|
|
|
foreach ($css as $token) { |
91
|
|
|
if (in_array($token['type'], $this->specialChars)) { |
92
|
|
|
$selector = $this->createSelector(); |
93
|
|
|
$selector->type = $token['type']; |
94
|
|
|
$selectors[] = $selector; |
95
|
|
|
} |
96
|
|
|
if (isset($token['value'])) $selectors[count($selectors)-1]->string = $token['value']; |
97
|
|
|
} |
98
|
|
|
return $selectors; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
public function getXpath($css) { |
102
|
|
|
foreach ($css as $key => $token) { |
103
|
|
|
if ($token['type'] === Tokenizer::WHITESPACE && |
104
|
|
|
(isset($css[$key+1]) && $css[$key+1]['type'] === Tokenizer::GREATER_THAN)) unset($css[$key]); |
105
|
|
|
else if ($token['type'] === Tokenizer::WHITESPACE && |
106
|
|
|
(isset($css[$key-1]) && $css[$key-1]['type'] === Tokenizer::GREATER_THAN)) unset($css[$key]); |
107
|
|
|
} |
108
|
|
|
$css = $this->splitOnToken(array_values($css), Tokenizer::COLON)[0]; |
109
|
|
|
$selectors = $this->split($css); |
110
|
|
|
$xpath = '/'; |
111
|
|
|
foreach ($selectors as $selector) { |
112
|
|
|
if (isset($this->translators[$selector->type])) $xpath .= $this->translators[$selector->type]($selector->string, $xpath); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
$xpath = str_replace('/[', '/*[', $xpath); |
116
|
|
|
|
117
|
|
|
return $xpath; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
public function getDepth($css) { |
121
|
|
|
return count($this->split($css)); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
public function getPseudo($css) { |
125
|
|
|
$parts = $this->splitOnToken($css, Tokenizer::COLON); |
126
|
|
|
array_shift($parts); |
127
|
|
|
return $parts; |
128
|
|
|
} |
129
|
|
|
} |
130
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.