Completed
Push — master ( 39abe4...b867be )
by Richard
02:31
created

Sheet::stripComments()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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