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 $prefix; |
13
|
|
|
private $valueParser; |
14
|
|
|
|
15
|
|
|
public function __construct($tss, $baseDir, Value $valueParser, $prefix = '') { |
16
|
|
|
$this->tss = $this->stripComments($tss); |
17
|
|
|
$this->baseDir = $baseDir; |
18
|
|
|
$this->prefix = $prefix; |
19
|
|
|
$this->valueParser = $valueParser; |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
public function parse($pos = 0, $rules = [], $indexStart = 0) { |
23
|
|
|
while ($next = strpos($this->tss, '{', $pos)) { |
24
|
|
|
if ($processing = $this->processingInstructions($this->tss, $pos, $next, count($rules)+$indexStart)) { |
25
|
|
|
$pos = $processing['endPos']+1; |
26
|
|
|
$rules = array_merge($rules, $processing['rules']); |
27
|
|
|
} |
28
|
|
|
|
29
|
|
|
$selector = trim(substr($this->tss, $pos, $next-$pos)); |
30
|
|
|
$pos = strpos($this->tss, '}', $next)+1; |
31
|
|
|
$newRules = $this->cssToRules($selector, count($rules)+$indexStart, $this->getProperties(trim(substr($this->tss, $next+1, $pos-2-$next)))); |
32
|
|
|
$rules = $this->writeRule($rules, $newRules); |
33
|
|
|
} |
34
|
|
|
//there may be processing instructions at the end |
35
|
|
|
if ($processing = $this->processingInstructions($this->tss, $pos, strlen($this->tss), count($rules)+$indexStart)) $rules = array_merge($rules, $processing['rules']); |
36
|
|
|
usort($rules, [$this, 'sortRules']); |
37
|
|
|
return $rules; |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
private function CssToRules($selector, $index, $properties) { |
41
|
|
|
$parts = explode(',', $selector); |
42
|
|
|
$rules = []; |
43
|
|
|
foreach ($parts as $part) { |
44
|
|
|
$xPath = new CssToXpath($part, $this->valueParser, $this->prefix); |
45
|
|
|
$rules[$part] = new \Transphporm\Rule($xPath->getXpath(), $xPath->getPseudo(), $xPath->getDepth(), $index++); |
46
|
|
|
$rules[$part]->properties = $properties; |
|
|
|
|
47
|
|
|
} |
48
|
|
|
return $rules; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
private function writeRule($rules, $newRules) { |
52
|
|
|
foreach ($newRules as $selector => $newRule) { |
53
|
|
|
if (isset($rules[$selector])) { |
54
|
|
|
$newRule->properties = array_merge($rules[$selector]->properties, $newRule->properties); |
55
|
|
|
} |
56
|
|
|
$rules[$selector] = $newRule; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
return $rules; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
private function processingInstructions($tss, $pos, $next, $indexStart) { |
63
|
|
|
$rules = []; |
64
|
|
|
while (($atPos = strpos($tss, '@', $pos)) !== false) { |
65
|
|
|
if ($atPos <= (int) $next) { |
66
|
|
|
$spacePos = strpos($tss, ' ', $atPos); |
67
|
|
|
$funcName = substr($tss, $atPos+1, $spacePos-$atPos-1); |
68
|
|
|
$pos = strpos($tss, ';', $spacePos); |
69
|
|
|
$args = substr($tss, $spacePos+1, $pos-$spacePos-1); |
70
|
|
|
$rules = array_merge($rules, $this->$funcName($args, $indexStart)); |
71
|
|
|
} |
72
|
|
|
else { |
73
|
|
|
break; |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
return empty($rules) ? false : ['endPos' => $pos, 'rules' => $rules]; |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
private function import($args, $indexStart) { |
81
|
|
|
$sheet = new Sheet(file_get_contents($this->baseDir . trim($args, '\'" ')), $this->baseDir, $this->valueParser, $this->prefix); |
82
|
|
|
return $sheet->parse(0, [], $indexStart); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
private function sortRules($a, $b) { |
86
|
|
|
//If they have the same depth, compare on index |
87
|
|
|
if ($a->depth === $b->depth) return $a->index < $b->index ? -1 : 1; |
88
|
|
|
|
89
|
|
|
return ($a->depth < $b->depth) ? -1 : 1; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
private function stripComments($str) { |
93
|
|
|
$pos = 0; |
94
|
|
|
while (($pos = strpos($str, '/*', $pos)) !== false) { |
95
|
|
|
$end = strpos($str, '*/', $pos); |
96
|
|
|
$str = substr_replace($str, '', $pos, $end-$pos+2); |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return $str; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
private function getProperties($str) { |
103
|
|
|
$stringExtractor = new StringExtractor($str); |
104
|
|
|
$rules = explode(';', $stringExtractor); |
105
|
|
|
$return = []; |
106
|
|
|
|
107
|
|
|
foreach ($rules as $rule) { |
108
|
|
|
if (trim($rule) === '') continue; |
109
|
|
|
$parts = explode(':', $rule, 2); |
110
|
|
|
|
111
|
|
|
$parts[1] = $stringExtractor->rebuild($parts[1]); |
112
|
|
|
$return[trim($parts[0])] = isset($parts[1]) ? trim($parts[1]) : ''; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
return $return; |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
Since your code implements the magic setter
_set
, this function will be called for any write access on an undefined variable. You can add the@property
annotation to your class or interface to document the existence of this variable.Since the property has write access only, you can use the @property-write annotation instead.
Of course, you may also just have mistyped another name, in which case you should fix the error.
See also the PhpDoc documentation for @property.