Completed
Push — master ( df9391...319ec0 )
by Tom
01:40
created

Sheet::sortPseudo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 2
eloc 2
nc 2
nop 2
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
/** 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 $cache;
11
	private $tss;
12
	private $rules;
13
	private $file;
14
	private $valueParser;
15
	private $xPath;
16
	private $filePath;
17
	private $import = [];
18
19
	public function __construct($tss, CssToXpath $xPath, Value $valueParser, \Transphporm\TSSCache $cache, \Transphporm\FilePath $filePath) {
20
		$this->cache = $cache;
21
		$this->xPath = $xPath;
22
		$this->valueParser = $valueParser;
23
		$this->filePath = $filePath;
24
		if (is_file($tss)) {
25
			$this->file = $tss;
26
			$this->rules = $this->cache->load($tss);
27
			$this->filePath->addPath(dirname(realpath($tss)));
28
			if (empty($this->rules)) $tss = file_get_contents($tss);
29
			else return;
30
		}
31
		$this->tss = (new Tokenizer($tss))->getTokens();
32
	}
33
34
	public function parse($indexStart = 0) {
35
		if (!empty($this->rules)) return $this->rules['rules'];
36
		$rules = $this->parseTokens($indexStart);
37
		usort($rules, [$this, 'sortRules']);
38
		$this->checkError($rules);
39
		//var_dump($rules);
40
		return $this->cache->write($this->file, $rules, $this->import);
41
	}
42
43
	private function parseTokens($indexStart) {
44
		$this->rules = [];
45
		foreach (new TokenFilterIterator($this->tss, [Tokenizer::WHITESPACE]) as $token) {
46
			if ($processing = $this->processingInstructions($token, count($this->rules)+$indexStart)) {
47
				$this->rules = array_merge($this->rules, $processing);
48
			}
49
			else if ($token['type'] !== Tokenizer::NEW_LINE) $this->addRules($token, $indexStart++);
50
		}
51
52
		return $this->rules;
53
	}
54
55
	private function addRules($token, $indexStart) {
56
		$selector = $this->tss->from($token['type'], true)->to(Tokenizer::OPEN_BRACE);
57
58
		$this->tss->skip(count($selector));
59
		if (count($selector) === 0) return;
60
61
		$newRules = $this->cssToRules($selector, count($this->rules)+$indexStart, $this->getProperties($this->tss->current()['value']), $token['line']);
62
		$this->rules = $this->writeRule($this->rules, $newRules);
63
	}
64
65
	private function checkError($rules) {
66
		if (empty($rules) && count($this->tss) > 0) throw new \Exception('No TSS rules parsed');
67
	}
68
69
	private function CssToRules($selector, $index, $properties, $line) {
70
		$parts = $selector->trim()->splitOnToken(Tokenizer::ARG);
71
		$rules = [];
72
		foreach ($parts as $part) {
73
			$serialized = serialize($part->removeLine());
74
			$rules[$serialized] = new \Transphporm\Rule($this->xPath->getXpath($part), $this->xPath->getPseudo($part), $this->xPath->getDepth($part), $index, $this->file, $line);
75
			$rules[$serialized]->properties = $properties;
76
		}
77
		return $rules;
78
	}
79
80
	private function writeRule($rules, $newRules) {
81
		foreach ($newRules as $selector => $newRule) {
82
83
			if (isset($rules[$selector])) {
84
				$newRule->properties = array_merge($rules[$selector]->properties, $newRule->properties);
85
				$newRule->index = $rules[$selector]->index;
86
			}
87
			$rules[$selector] = $newRule;
88
		}
89
90
		return $rules;
91
	}
92
93
	private function processingInstructions($token, $indexStart) {
94
		if ($token['type'] !== Tokenizer::AT_SIGN) return false;
95
		$tokens = $this->tss->from(Tokenizer::AT_SIGN, false)->to(Tokenizer::SEMI_COLON, false);
96
		$funcName = $tokens->from(Tokenizer::NAME, true)->read();
97
		$args = $this->valueParser->parseTokens($tokens->from(Tokenizer::NAME));
98
		$rules = $this->$funcName($args, $indexStart);
99
100
		$this->tss->skip(count($tokens)+2);
101
102
		return $rules;
103
	}
104
105
	private function import($args, $indexStart) {
106
		$fileName = $this->filePath->getFilePath($args[0]);
107
		$this->import[] = $fileName;
108
		$sheet = new Sheet($fileName, $this->xPath, $this->valueParser, $this->cache, $this->filePath);
109
		return $sheet->parse($indexStart);
110
	}
111
112
	private function sortRules($a, $b) {
113
		//If they have the same depth, compare on index
114
		if ($a->query === $b->query) return $this->sortPseudo($a, $b);
115
116
		if ($a->depth === $b->depth) $property = 'index';
117
		else $property = 'depth'; 
118
		
119
		return ($a->$property < $b->$property) ? -1 : 1;
120
	}
121
122
123
	private function sortPseudo($a, $b) {
124
		return count($a->pseudo) < count($b->pseudo)  ? -1  :1;
125
	}
126
127
	private function getProperties($tokens) {
128
        $rules = $tokens->splitOnToken(Tokenizer::SEMI_COLON);
129
130
        $return = [];
131
        foreach ($rules as $rule) {
132
            $name = $rule->from(Tokenizer::NAME, true)->to(Tokenizer::COLON)->read();
133
            $return[$name] = $rule->from(Tokenizer::COLON)->trim();
134
        }
135
136
        return $return;
137
    }
138
}
139