Completed
Push — master ( 02c8a1...58faf5 )
by Tom
02:14
created

src/Parser/CssToXpath.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/* @description     Transformation Style Sheets - Revolutionising PHP templating    *
3
 * @author          Tom Butler [email protected]                                             *
4
 * @copyright       2017 Tom Butler <[email protected]> | https://r.je/                      *
5
 * @license         http://www.opensource.org/licenses/bsd-license.php  BSD License *
6
 * @version         1.2                                                             */
7
namespace Transphporm\Parser;
8
class CssToXpath {
9
	private $specialChars = [Tokenizer::WHITESPACE, Tokenizer::DOT, Tokenizer::GREATER_THAN,
10
		'~', Tokenizer::NUM_SIGN, Tokenizer::OPEN_SQUARE_BRACKET, Tokenizer::MULTIPLY];
11
	private $translators = [];
12
	private static $instances = [];
13
	private $functionSet;
14
	private $id;
15
16
	public function __construct(\Transphporm\FunctionSet $functionSet, $prefix = '', $id = null) {
17
		$this->id = $id;
18
		self::$instances[$this->id] = $this;
19
		$this->functionSet = $functionSet;
20
21
		$this->translators = [
22
			Tokenizer::WHITESPACE => function($string) use ($prefix) { return '//' . $prefix . $string;	},
23
			Tokenizer::MULTIPLY => function () { return '*'; },
24
			'' => function($string) use ($prefix) { return '/' . $prefix . $string;	},
25
			Tokenizer::GREATER_THAN => function($string) use ($prefix) { return '/' . $prefix  . $string; },
26
			Tokenizer::NUM_SIGN => function($string) { return '[@id=\'' . $string . '\']'; },
27
			Tokenizer::DOT => function($string) { return '[contains(concat(\' \', normalize-space(@class), \' \'), \' ' . $string . ' \')]'; },
28
			Tokenizer::OPEN_SQUARE_BRACKET => function($string) { return '[' .'php:function(\'\Transphporm\Parser\CssToXpath::processAttr\', \'' . base64_encode(serialize($string)) . '\', ., "' .  $this->id . '")' . ']';	}
29
		];
30
	}
31
32
	private function createSelector() {
33
		$selector = new \stdclass;
34
		$selector->type = '';
35
		$selector->string = '';
36
		return $selector;
37
	}
38
39
	//XPath only allows registering of static functions... this is a hacky workaround for that
40
	public static function processAttr($attr, $element, $hash) {
41
		$attr = unserialize(base64_decode($attr));
42
43
		$functionSet = self::$instances[$hash]->functionSet;
44
		$functionSet->setElement($element[0]);
45
46
		$attributes = [];
47
		foreach($element[0]->attributes as $name => $node) {
48
			$attributes[$name] = $node->nodeValue;
49
		}
50
51
		$parser = new \Transphporm\Parser\Value($functionSet, true);
52
		$return = $parser->parseTokens($attr, $attributes);
53
		return is_array($return[0]) || $return[0] === '' ? false : $return[0];
54
	}
55
56
	public function cleanup() {
57
		unset(self::$instances[$this->id]);
58
	}
59
60
	//split the css into indivudal functions
61
	private function split($css) {
62
		$selectors = [];
63
		$selector = $this->createSelector();
64
		$selectors[] = $selector;
65
66
		foreach ($css as $token) {
67
			if (in_array($token['type'], $this->specialChars)) {
68
				$selector = $this->createSelector();
69
				$selector->type = $token['type'];
70
				$selectors[] = $selector;
71
			}
72
			if (isset($token['value'])) $selectors[count($selectors)-1]->string = $token['value'];
73
		}
74
		return $selectors;
75
	}
76
77
	public function getXpath($css) {
78
		$css = $this->removeSpacesFromDirectDecend($css)->splitOnToken(Tokenizer::COLON)[0]->trim();
79
		$selectors = $this->split($css);
80
		$xpath = '/';
81
		foreach ($selectors as $selector) {
82
			if (isset($this->translators[$selector->type])) $xpath .= $this->translators[$selector->type]($selector->string, $xpath);
83
		}
84
85
		$xpath = str_replace('/[', '/*[', $xpath);
86
87
		return $xpath;
88
	}
89
90
	private function removeSpacesFromDirectDecend($css) {
91
		$tokens = new Tokens;
92
		$split = $css->splitOnToken(Tokenizer::GREATER_THAN);
93
94
		if (count($split) <= 1) return $css;
95
96
		for ($i = 0; $i < count($split); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
97
			$tokens->add($split[$i]->trim());
98
			if (isset($split[$i+1])) $tokens->add(['type' => Tokenizer::GREATER_THAN]);
99
		}
100
101
		return $tokens;
102
	}
103
104
105
	public function getDepth($css) {
106
		return count($this->split($css));
107
	}
108
109
	public function getPseudo($css) {
110
		$parts = $css->splitOnToken(Tokenizer::COLON);
111
		array_shift($parts);
112
		return $parts;
113
	}
114
}
115