Completed
Push — master ( e38a6e...39b3df )
by Richard
02:41
created

Sheet::getRulesFromCache()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
rs 9.2
cc 4
eloc 7
nc 3
nop 0
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 $rules;
12
	private $file;
13
	private $baseDir;
14
	private $valueParser;
15
	private $xPath;
16
	private $tokenizer;
17
	private $import = [];
18
19
	public function __construct($tss, &$baseDir, CssToXpath $xPath, Value $valueParser, \Transphporm\TSSCache $cache) {
20
		$this->cache = $cache;
0 ignored issues
show
Bug introduced by
The property cache does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

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