Completed
Push — master ( 39b3df...5ec81c )
by Richard
03:15
created

Sheet::parseTokens()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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