Completed
Push — master ( 0ada29...ecc437 )
by Richard
03:53 queued 11s
created

Sheet::parse()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

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