MDL-62294 lessphp: Fix for PHP 7.2 compatibility
[moodle.git] / lib / lessphp / Visitor / toCSS.php
1 <?php
3 /**
4  * toCSS Visitor
5  *
6  * @package Less
7  * @subpackage visitor
8  */
9 class Less_Visitor_toCSS extends Less_VisitorReplacing{
11         private $charset;
13         public function __construct(){
14                 parent::__construct();
15         }
17         /**
18          * @param Less_Tree_Ruleset $root
19          */
20         public function run( $root ){
21                 return $this->visitObj($root);
22         }
24         public function visitRule( $ruleNode ){
25                 if( $ruleNode->variable ){
26                         return array();
27                 }
28                 return $ruleNode;
29         }
31         public function visitMixinDefinition($mixinNode){
32                 // mixin definitions do not get eval'd - this means they keep state
33                 // so we have to clear that state here so it isn't used if toCSS is called twice
34                 $mixinNode->frames = array();
35                 return array();
36         }
38         public function visitExtend(){
39                 return array();
40         }
42         public function visitComment( $commentNode ){
43                 if( $commentNode->isSilent() ){
44                         return array();
45                 }
46                 return $commentNode;
47         }
49         public function visitMedia( $mediaNode, &$visitDeeper ){
50                 $mediaNode->accept($this);
51                 $visitDeeper = false;
53                 if( !$mediaNode->rules ){
54                         return array();
55                 }
56                 return $mediaNode;
57         }
59         public function visitDirective( $directiveNode ){
60                 if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){
61                         return array();
62                 }
63                 if( $directiveNode->name === '@charset' ){
64                         // Only output the debug info together with subsequent @charset definitions
65                         // a comment (or @media statement) before the actual @charset directive would
66                         // be considered illegal css as it has to be on the first line
67                         if( isset($this->charset) && $this->charset ){
69                                 //if( $directiveNode->debugInfo ){
70                                 //      $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
71                                 //      $comment->debugInfo = $directiveNode->debugInfo;
72                                 //      return $this->visit($comment);
73                                 //}
76                                 return array();
77                         }
78                         $this->charset = true;
79                 }
80                 return $directiveNode;
81         }
83         public function checkPropertiesInRoot( $rulesetNode ){
85                 if( !$rulesetNode->firstRoot ){
86                         return;
87                 }
89                 foreach($rulesetNode->rules as $ruleNode){
90                         if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){
91                                 $msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null);
92                                 throw new Less_Exception_Compiler($msg);
93                         }
94                 }
95         }
98         public function visitRuleset( $rulesetNode, &$visitDeeper ){
100                 $visitDeeper = false;
102                 $this->checkPropertiesInRoot( $rulesetNode );
104                 if( $rulesetNode->root ){
105                         return $this->visitRulesetRoot( $rulesetNode );
106                 }
108                 $rulesets = array();
109                 $rulesetNode->paths = $this->visitRulesetPaths($rulesetNode);
112                 // Compile rules and rulesets
113                 $nodeRuleCnt = $rulesetNode->rules?count($rulesetNode->rules):0;
114                 for( $i = 0; $i < $nodeRuleCnt; ){
115                         $rule = $rulesetNode->rules[$i];
117                         if( property_exists($rule,'rules') ){
118                                 // visit because we are moving them out from being a child
119                                 $rulesets[] = $this->visitObj($rule);
120                                 array_splice($rulesetNode->rules,$i,1);
121                                 $nodeRuleCnt--;
122                                 continue;
123                         }
124                         $i++;
125                 }
128                 // accept the visitor to remove rules and refactor itself
129                 // then we can decide now whether we want it or not
130                 if( $nodeRuleCnt > 0 ){
131                         $rulesetNode->accept($this);
133                         if( $rulesetNode->rules ){
135                                 if( count($rulesetNode->rules) >  1 ){
136                                         $this->_mergeRules( $rulesetNode->rules );
137                                         $this->_removeDuplicateRules( $rulesetNode->rules );
138                                 }
140                                 // now decide whether we keep the ruleset
141                                 if( $rulesetNode->paths ){
142                                         //array_unshift($rulesets, $rulesetNode);
143                                         array_splice($rulesets,0,0,array($rulesetNode));
144                                 }
145                         }
147                 }
150                 if( count($rulesets) === 1 ){
151                         return $rulesets[0];
152                 }
153                 return $rulesets;
154         }
157         /**
158          * Helper function for visitiRuleset
159          *
160          * return array|Less_Tree_Ruleset
161          */
162         private function visitRulesetRoot( $rulesetNode ){
163                 $rulesetNode->accept( $this );
164                 if( $rulesetNode->firstRoot || $rulesetNode->rules ){
165                         return $rulesetNode;
166                 }
167                 return array();
168         }
171         /**
172          * Helper function for visitRuleset()
173          *
174          * @return array
175          */
176         private function visitRulesetPaths($rulesetNode){
178                 $paths = array();
179                 foreach($rulesetNode->paths as $p){
180                         if( $p[0]->elements[0]->combinator === ' ' ){
181                                 $p[0]->elements[0]->combinator = '';
182                         }
184                         foreach($p as $pi){
185                                 if( $pi->getIsReferenced() && $pi->getIsOutput() ){
186                                         $paths[] = $p;
187                                         break;
188                                 }
189                         }
190                 }
192                 return $paths;
193         }
195         protected function _removeDuplicateRules( &$rules ){
196                 // remove duplicates
197                 $ruleCache = array();
198                 for( $i = count($rules)-1; $i >= 0 ; $i-- ){
199                         $rule = $rules[$i];
200                         if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){
202                                 if( !isset($ruleCache[$rule->name]) ){
203                                         $ruleCache[$rule->name] = $rule;
204                                 }else{
205                                         $ruleList =& $ruleCache[$rule->name];
207                                         if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){
208                                                 $ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );
209                                         }
211                                         $ruleCSS = $rule->toCSS();
212                                         if( array_search($ruleCSS,$ruleList) !== false ){
213                                                 array_splice($rules,$i,1);
214                                         }else{
215                                                 $ruleList[] = $ruleCSS;
216                                         }
217                                 }
218                         }
219                 }
220         }
222         protected function _mergeRules( &$rules ){
223                 $groups = array();
225                 //obj($rules);
227                 $rules_len = count($rules);
228                 for( $i = 0; $i < $rules_len; $i++ ){
229                         $rule = $rules[$i];
231                         if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){
233                                 $key = $rule->name;
234                                 if( $rule->important ){
235                                         $key .= ',!';
236                                 }
238                                 if( !isset($groups[$key]) ){
239                                         $groups[$key] = array();
240                                 }else{
241                                         array_splice($rules, $i--, 1);
242                                         $rules_len--;
243                                 }
245                                 $groups[$key][] = $rule;
246                         }
247                 }
250                 foreach($groups as $parts){
252                         if( count($parts) > 1 ){
253                                 $rule = $parts[0];
254                                 $spacedGroups = array();
255                                 $lastSpacedGroup = array();
256                                 $parts_mapped = array();
257                                 foreach($parts as $p){
258                                         if( $p->merge === '+' ){
259                                                 if( $lastSpacedGroup ){
260                                                         $spacedGroups[] = self::toExpression($lastSpacedGroup);
261                                                 }
262                                                 $lastSpacedGroup = array();
263                                         }
264                                         $lastSpacedGroup[] = $p;
265                                 }
267                                 $spacedGroups[] = self::toExpression($lastSpacedGroup);
268                                 $rule->value = self::toValue($spacedGroups);
269                         }
270                 }
272         }
274         public static function toExpression($values){
275                 $mapped = array();
276                 foreach($values as $p){
277                         $mapped[] = $p->value;
278                 }
279                 return new Less_Tree_Expression( $mapped );
280         }
282         public static function toValue($values){
283                 //return new Less_Tree_Value($values); ??
285                 $mapped = array();
286                 foreach($values as $p){
287                         $mapped[] = $p;
288                 }
289                 return new Less_Tree_Value($mapped);
290         }