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
|
|
|
|
15
|
|
|
public function __construct($tss, $baseDir, CssToXpath $xPath, Value $valueParser) { |
16
|
|
|
$this->tss = $this->stripComments($tss, '//', "\n"); |
17
|
|
|
$this->tss = $this->stripComments($this->tss, '/*', '*/'); |
18
|
|
|
$this->baseDir = $baseDir; |
19
|
|
|
$this->xPath = $xPath; |
20
|
|
|
$this->valueParser = $valueParser; |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
public function parse($pos = 0, $rules = [], $indexStart = 0) { |
24
|
|
|
while ($next = strpos($this->tss, '{', $pos)) { |
25
|
|
|
if ($processing = $this->processingInstructions($this->tss, $pos, $next, count($rules)+$indexStart)) { |
26
|
|
|
$pos = $processing['endPos']+1; |
27
|
|
|
$rules = array_merge($rules, $processing['rules']); |
28
|
|
|
} |
29
|
|
|
|
30
|
|
|
$selector = trim(substr($this->tss, $pos, $next-$pos)); |
31
|
|
|
$pos = strpos($this->tss, '}', $next)+1; |
32
|
|
|
$newRules = $this->cssToRules($selector, count($rules)+$indexStart, $this->getProperties(trim(substr($this->tss, $next+1, $pos-2-$next)))); |
33
|
|
|
$rules = $this->writeRule($rules, $newRules); |
34
|
|
|
} |
35
|
|
|
//there may be processing instructions at the end |
36
|
|
|
if ($processing = $this->processingInstructions($this->tss, $pos, strlen($this->tss), count($rules)+$indexStart)) $rules = array_merge($rules, $processing['rules']); |
37
|
|
|
usort($rules, [$this, 'sortRules']); |
38
|
|
|
if (empty($rules) && !empty($this->tss)) throw new \Exception("No TSS rules parsed"); |
39
|
|
|
return $rules; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
private function CssToRules($selector, $index, $properties) { |
43
|
|
|
$parts = explode(',', $selector); |
44
|
|
|
$rules = []; |
45
|
|
|
foreach ($parts as $part) { |
46
|
|
|
$rules[$part] = new \Transphporm\Rule($this->xPath->getXpath($part), $this->xPath->getPseudo($part), $this->xPath->getDepth($part), $this->baseDir, $index++); |
47
|
|
|
$rules[$part]->properties = $properties; |
48
|
|
|
} |
49
|
|
|
return $rules; |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
private function writeRule($rules, $newRules) { |
53
|
|
|
foreach ($newRules as $selector => $newRule) { |
54
|
|
|
if (isset($rules[$selector])) { |
55
|
|
|
$newRule->properties = array_merge($rules[$selector]->properties, $newRule->properties); |
56
|
|
|
} |
57
|
|
|
$rules[$selector] = $newRule; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
return $rules; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
private function processingInstructions($tss, $pos, $next, $indexStart) { |
64
|
|
|
$rules = []; |
65
|
|
|
while (($atPos = strpos($tss, '@', $pos)) !== false) { |
66
|
|
|
if ($atPos <= (int) $next) { |
67
|
|
|
$spacePos = strpos($tss, ' ', $atPos); |
68
|
|
|
$funcName = substr($tss, $atPos+1, $spacePos-$atPos-1); |
69
|
|
|
$pos = strpos($tss, ';', $spacePos); |
70
|
|
|
$args = substr($tss, $spacePos+1, $pos-$spacePos-1); |
71
|
|
|
$rules = array_merge($rules, $this->$funcName($args, $indexStart)); |
72
|
|
|
} |
73
|
|
|
else { |
74
|
|
|
break; |
75
|
|
|
} |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
return empty($rules) ? false : ['endPos' => $pos, 'rules' => $rules]; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
private function import($args, $indexStart) { |
82
|
|
|
if (is_file(trim($args,'\'" '))) $fileName = trim($args,'\'" '); |
83
|
|
|
else $fileName = $this->valueParser->parse($args)[0]; |
84
|
|
|
$sheet = new Sheet(file_get_contents($this->baseDir . $fileName), dirname(realpath($this->baseDir . $fileName)) . DIRECTORY_SEPARATOR, $this->xPath, $this->valueParser); |
85
|
|
|
return $sheet->parse(0, [], $indexStart); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
private function sortRules($a, $b) { |
89
|
|
|
//If they have the same depth, compare on index |
90
|
|
|
if ($a->depth === $b->depth) return $a->index < $b->index ? -1 : 1; |
91
|
|
|
|
92
|
|
|
return ($a->depth < $b->depth) ? -1 : 1; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
private function stripComments($str, $open, $close) { |
96
|
|
|
$pos = 0; |
97
|
|
|
while (($pos = strpos($str, $open, $pos)) !== false) { |
98
|
|
|
$end = strpos($str, $close, $pos); |
99
|
|
|
if ($end === false) break; |
100
|
|
|
$str = substr_replace($str, '', $pos, $end-$pos+strlen($close)); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
return $str; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
private function getProperties($str) { |
107
|
|
|
$tokenizer = new Tokenizer($str); |
108
|
|
|
$tokens = $tokenizer->getTokens(); |
109
|
|
|
|
110
|
|
|
$rules = []; |
111
|
|
|
$i = 0; |
112
|
|
|
foreach ($tokens as $token) { |
113
|
|
|
if ($token['type'] === Tokenizer::SEMI_COLON) $i++; |
114
|
|
|
else $rules[$i][] = $token; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
$return = []; |
118
|
|
|
foreach ($rules as $rule) { |
119
|
|
|
if ($rule[1]['type'] === Tokenizer::COLON) $return[$rule[0]['value']] = array_slice($rule, 2); |
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
return $return; |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
|