Completed
Pull Request — master (#119)
by Richard
02:59
created

Sheet::getProperties()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 18
rs 8.8571
cc 5
eloc 12
nc 9
nop 1
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
/** Parses a .tss file into individual rules, each rule has a query e,g, `ul li` and a set of rules e.g. `display: none; bind: iteration(id);` */
9
class Sheet {
10
	private $tss;
11
	private $baseDir;
12
	private $valueParser;
13
	private $xPath;
14
15
	public function __construct($tss, $baseDir, CssToXpath $xPath, Value $valueParser) {
16
		$this->tss = $this->stripComments($tss, '//', "\n");
17
		$this->tss = $this->stripComments($this->tss, '/*', '*/');
18
		$this->baseDir = $baseDir;
19
		$this->xPath = $xPath;
20
		$this->valueParser = $valueParser;
21
	}
22
23
	public function parse($pos = 0, $rules = [], $indexStart = 0) {
24
		while ($next = strpos($this->tss, '{', $pos)) {
25
			if ($processing = $this->processingInstructions($this->tss, $pos, $next, count($rules)+$indexStart)) {
26
				$pos = $processing['endPos']+1;
27
				$rules = array_merge($rules, $processing['rules']);
28
			}
29
30
			$selector = trim(substr($this->tss, $pos, $next-$pos));
31
			$pos =  strpos($this->tss, '}', $next)+1;
32
			$newRules = $this->cssToRules($selector, count($rules)+$indexStart, $this->getProperties(trim(substr($this->tss, $next+1, $pos-2-$next))));
33
			$rules = $this->writeRule($rules, $newRules);
34
		}
35
		//there may be processing instructions at the end
36
		if ($processing = $this->processingInstructions($this->tss, $pos, strlen($this->tss), count($rules)+$indexStart)) $rules = array_merge($rules, $processing['rules']);
37
		usort($rules, [$this, 'sortRules']);
38
		if (empty($rules) && !empty($this->tss)) throw new \Exception("No TSS rules parsed");
39
		return $rules;
40
	}
41
42
	private function CssToRules($selector, $index, $properties) {
43
		$parts = explode(',', $selector);
44
		$rules = [];
45
		foreach ($parts as $part) {
46
			$rules[$part] = new \Transphporm\Rule($this->xPath->getXpath($part), $this->xPath->getPseudo($part), $this->xPath->getDepth($part), $this->baseDir, $index++);
47
			$rules[$part]->properties = $properties;
48
		}
49
		return $rules;
50
	}
51
52
	private function writeRule($rules, $newRules) {
53
		foreach ($newRules as $selector => $newRule) {
54
			if (isset($rules[$selector])) {
55
				$newRule->properties = array_merge($rules[$selector]->properties, $newRule->properties);
56
			}
57
			$rules[$selector] = $newRule;
58
		}
59
60
		return $rules;
61
	}
62
63
	private function processingInstructions($tss, $pos, $next, $indexStart) {
64
		$rules = [];
65
		while (($atPos = strpos($tss, '@', $pos)) !== false) {
66
			if ($atPos  <= (int) $next) {
67
				$spacePos = strpos($tss, ' ', $atPos);
68
				$funcName = substr($tss, $atPos+1, $spacePos-$atPos-1);
69
				$pos = strpos($tss, ';', $spacePos);
70
				$args = substr($tss, $spacePos+1, $pos-$spacePos-1);
71
				$rules = array_merge($rules, $this->$funcName($args, $indexStart));
72
			}
73
			else {
74
				break;
75
			}
76
		}
77
78
		return empty($rules) ? false : ['endPos' => $pos, 'rules' => $rules];
79
	}
80
81
	private function import($args, $indexStart) {
82
		if (is_file(trim($args,'\'" '))) $fileName = trim($args,'\'" ');
83
		else $fileName = $this->valueParser->parse($args)[0];
84
		$sheet = new Sheet(file_get_contents($this->baseDir . $fileName), dirname(realpath($this->baseDir . $fileName)) . DIRECTORY_SEPARATOR, $this->xPath, $this->valueParser);
85
		return $sheet->parse(0, [], $indexStart);
86
	}
87
88
	private function sortRules($a, $b) {
89
		//If they have the same depth, compare on index
90
		if ($a->depth === $b->depth) return $a->index < $b->index ? -1 : 1;
91
92
		return ($a->depth < $b->depth) ? -1 : 1;
93
	}
94
95
	private function stripComments($str, $open, $close) {
96
		$pos = 0;
97
		while (($pos = strpos($str, $open, $pos)) !== false) {
98
			$end = strpos($str, $close, $pos);
99
			if ($end === false) break;
100
			$str = substr_replace($str, '', $pos, $end-$pos+strlen($close));
101
		}
102
103
		return $str;
104
	}
105
106
	private function getProperties($str) {
107
		$tokenizer = new Tokenizer($str);
108
		$tokens = $tokenizer->getTokens();
109
110
		$rules = [];
111
		$i = 0;
112
		foreach ($tokens as $token) {
113
			if ($token['type'] === Tokenizer::SEMI_COLON) $i++;
114
			else $rules[$i][] = $token;
115
		}
116
117
		$return = [];
118
		foreach ($rules as $rule) {
119
			if ($rule[1]['type'] === Tokenizer::COLON) $return[$rule[0]['value']] = array_slice($rule, 2);
120
		}
121
122
		return $return;
123
	}
124
}
125