Moodle release 2.7.8
[moodle.git] / lib / lessphp / Tree / Ruleset.php
blob908e5a81a4041f6345a4bceab0cc688672652de3
1 <?php
3 /**
4 * Ruleset
6 * @package Less
7 * @subpackage tree
8 */
9 class Less_Tree_Ruleset extends Less_Tree{
11 protected $lookups;
12 public $_variables;
13 public $_rulesets;
15 public $strictImports;
17 public $selectors;
18 public $rules;
19 public $root;
20 public $allowImports;
21 public $paths;
22 public $firstRoot;
23 public $type = 'Ruleset';
24 public $multiMedia;
25 public $allExtends;
27 var $ruleset_id;
28 var $originalRuleset;
30 var $first_oelements;
32 public function SetRulesetIndex(){
33 $this->ruleset_id = Less_Parser::$next_id++;
34 $this->originalRuleset = $this->ruleset_id;
36 if( $this->selectors ){
37 foreach($this->selectors as $sel){
38 if( $sel->_oelements ){
39 $this->first_oelements[$sel->_oelements[0]] = true;
45 public function __construct($selectors, $rules, $strictImports = null){
46 $this->selectors = $selectors;
47 $this->rules = $rules;
48 $this->lookups = array();
49 $this->strictImports = $strictImports;
50 $this->SetRulesetIndex();
53 function accept( $visitor ){
54 if( $this->paths ){
55 $paths_len = count($this->paths);
56 for($i = 0,$paths_len; $i < $paths_len; $i++ ){
57 $this->paths[$i] = $visitor->visitArray($this->paths[$i]);
59 }elseif( $this->selectors ){
60 $this->selectors = $visitor->visitArray($this->selectors);
63 if( $this->rules ){
64 $this->rules = $visitor->visitArray($this->rules);
68 public function compile($env){
70 $ruleset = $this->PrepareRuleset($env);
73 // Store the frames around mixin definitions,
74 // so they can be evaluated like closures when the time comes.
75 $rsRuleCnt = count($ruleset->rules);
76 for( $i = 0; $i < $rsRuleCnt; $i++ ){
77 if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){
78 $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
82 $mediaBlockCount = 0;
83 if( $env instanceof Less_Environment ){
84 $mediaBlockCount = count($env->mediaBlocks);
87 // Evaluate mixin calls.
88 $this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
91 // Evaluate everything else
92 for( $i=0; $i<$rsRuleCnt; $i++ ){
93 if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){
94 $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
98 // Evaluate everything else
99 for( $i=0; $i<$rsRuleCnt; $i++ ){
100 $rule = $ruleset->rules[$i];
102 // for rulesets, check if it is a css guard and can be removed
103 if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count($rule->selectors) === 1 ){
105 // check if it can be folded in (e.g. & where)
106 if( $rule->selectors[0]->isJustParentSelector() ){
107 array_splice($ruleset->rules,$i--,1);
108 $rsRuleCnt--;
110 for($j = 0; $j < count($rule->rules); $j++ ){
111 $subRule = $rule->rules[$j];
112 if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){
113 array_splice($ruleset->rules, ++$i, 0, array($subRule));
114 $rsRuleCnt++;
123 // Pop the stack
124 $env->shiftFrame();
126 if ($mediaBlockCount) {
127 $len = count($env->mediaBlocks);
128 for($i = $mediaBlockCount; $i < $len; $i++ ){
129 $env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors);
133 return $ruleset;
137 * Compile Less_Tree_Mixin_Call objects
139 * @param Less_Tree_Ruleset $ruleset
140 * @param integer $rsRuleCnt
142 private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){
143 for($i=0; $i < $rsRuleCnt; $i++){
144 $rule = $ruleset->rules[$i];
146 if( $rule instanceof Less_Tree_Mixin_Call ){
147 $rule = $rule->compile($env);
149 $temp = array();
150 foreach($rule as $r){
151 if( ($r instanceof Less_Tree_Rule) && $r->variable ){
152 // do not pollute the scope if the variable is
153 // already there. consider returning false here
154 // but we need a way to "return" variable from mixins
155 if( !$ruleset->variable($r->name) ){
156 $temp[] = $r;
158 }else{
159 $temp[] = $r;
162 $temp_count = count($temp)-1;
163 array_splice($ruleset->rules, $i, 1, $temp);
164 $rsRuleCnt += $temp_count;
165 $i += $temp_count;
166 $ruleset->resetCache();
168 }elseif( $rule instanceof Less_Tree_RulesetCall ){
170 $rule = $rule->compile($env);
171 $rules = array();
172 foreach($rule->rules as $r){
173 if( ($r instanceof Less_Tree_Rule) && $r->variable ){
174 continue;
176 $rules[] = $r;
179 array_splice($ruleset->rules, $i, 1, $rules);
180 $temp_count = count($rules);
181 $rsRuleCnt += $temp_count - 1;
182 $i += $temp_count-1;
183 $ruleset->resetCache();
191 * Compile the selectors and create a new ruleset object for the compile() method
194 private function PrepareRuleset($env){
196 $hasOnePassingSelector = false;
197 $selectors = array();
198 if( $this->selectors ){
199 Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,");
201 foreach($this->selectors as $s){
202 $selector = $s->compile($env);
203 $selectors[] = $selector;
204 if( $selector->evaldCondition ){
205 $hasOnePassingSelector = true;
209 Less_Tree_DefaultFunc::reset();
210 } else {
211 $hasOnePassingSelector = true;
214 if( $this->rules && $hasOnePassingSelector ){
215 $rules = $this->rules;
216 }else{
217 $rules = array();
220 $ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports);
222 $ruleset->originalRuleset = $this->ruleset_id;
224 $ruleset->root = $this->root;
225 $ruleset->firstRoot = $this->firstRoot;
226 $ruleset->allowImports = $this->allowImports;
229 // push the current ruleset to the frames stack
230 $env->unshiftFrame($ruleset);
233 // Evaluate imports
234 if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){
235 $ruleset->evalImports($env);
238 return $ruleset;
241 function evalImports($env) {
243 $rules_len = count($this->rules);
244 for($i=0; $i < $rules_len; $i++){
245 $rule = $this->rules[$i];
247 if( $rule instanceof Less_Tree_Import ){
248 $rules = $rule->compile($env);
249 if( is_array($rules) ){
250 array_splice($this->rules, $i, 1, $rules);
251 $temp_count = count($rules)-1;
252 $i += $temp_count;
253 $rules_len += $temp_count;
254 }else{
255 array_splice($this->rules, $i, 1, array($rules));
258 $this->resetCache();
263 function makeImportant(){
265 $important_rules = array();
266 foreach($this->rules as $rule){
267 if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset ){
268 $important_rules[] = $rule->makeImportant();
269 }else{
270 $important_rules[] = $rule;
274 return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports );
277 public function matchArgs($args){
278 return !$args;
281 // lets you call a css selector with a guard
282 public function matchCondition( $args, $env ){
283 $lastSelector = end($this->selectors);
285 if( !$lastSelector->evaldCondition ){
286 return false;
288 if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){
289 return false;
291 return true;
294 function resetCache(){
295 $this->_rulesets = null;
296 $this->_variables = null;
297 $this->lookups = array();
300 public function variables(){
301 $this->_variables = array();
302 foreach( $this->rules as $r){
303 if ($r instanceof Less_Tree_Rule && $r->variable === true) {
304 $this->_variables[$r->name] = $r;
309 public function variable($name){
311 if( is_null($this->_variables) ){
312 $this->variables();
314 return isset($this->_variables[$name]) ? $this->_variables[$name] : null;
317 public function find( $selector, $self = null ){
319 $key = implode(' ',$selector->_oelements);
321 if( !isset($this->lookups[$key]) ){
323 if( !$self ){
324 $self = $this->ruleset_id;
327 $this->lookups[$key] = array();
329 $first_oelement = $selector->_oelements[0];
331 foreach($this->rules as $rule){
332 if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){
334 if( isset($rule->first_oelements[$first_oelement]) ){
336 foreach( $rule->selectors as $ruleSelector ){
337 $match = $selector->match($ruleSelector);
338 if( $match ){
339 if( $selector->elements_len > $match ){
340 $this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self));
341 } else {
342 $this->lookups[$key][] = $rule;
344 break;
352 return $this->lookups[$key];
357 * @see Less_Tree::genCSS
359 public function genCSS( $output ){
361 if( !$this->root ){
362 Less_Environment::$tabLevel++;
365 $tabRuleStr = $tabSetStr = '';
366 if( !Less_Parser::$options['compress'] ){
367 if( Less_Environment::$tabLevel ){
368 $tabRuleStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel );
369 $tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 );
370 }else{
371 $tabSetStr = $tabRuleStr = "\n";
376 $ruleNodes = array();
377 $rulesetNodes = array();
378 foreach($this->rules as $rule){
380 $class = get_class($rule);
381 if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){
382 $rulesetNodes[] = $rule;
383 }else{
384 $ruleNodes[] = $rule;
388 // If this is the root node, we don't render
389 // a selector, or {}.
390 if( !$this->root ){
393 debugInfo = tree.debugInfo(env, this, tabSetStr);
395 if (debugInfo) {
396 output.add(debugInfo);
397 output.add(tabSetStr);
401 $paths_len = count($this->paths);
402 for( $i = 0; $i < $paths_len; $i++ ){
403 $path = $this->paths[$i];
404 $firstSelector = true;
406 foreach($path as $p){
407 $p->genCSS( $output, $firstSelector );
408 $firstSelector = false;
411 if( $i + 1 < $paths_len ){
412 $output->add( ',' . $tabSetStr );
416 $output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr );
419 // Compile rules and rulesets
420 $ruleNodes_len = count($ruleNodes);
421 $rulesetNodes_len = count($rulesetNodes);
422 for( $i = 0; $i < $ruleNodes_len; $i++ ){
423 $rule = $ruleNodes[$i];
425 // @page{ directive ends up with root elements inside it, a mix of rules and rulesets
426 // In this instance we do not know whether it is the last property
427 if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){
428 Less_Environment::$lastRule = true;
431 $rule->genCSS( $output );
433 if( !Less_Environment::$lastRule ){
434 $output->add( $tabRuleStr );
435 }else{
436 Less_Environment::$lastRule = false;
440 if( !$this->root ){
441 $output->add( $tabSetStr . '}' );
442 Less_Environment::$tabLevel--;
445 $firstRuleset = true;
446 $space = ($this->root ? $tabRuleStr : $tabSetStr);
447 for( $i = 0; $i < $rulesetNodes_len; $i++ ){
449 if( $ruleNodes_len && $firstRuleset ){
450 $output->add( $space );
451 }elseif( !$firstRuleset ){
452 $output->add( $space );
454 $firstRuleset = false;
455 $rulesetNodes[$i]->genCSS( $output);
458 if( !Less_Parser::$options['compress'] && $this->firstRoot ){
459 $output->add( "\n" );
465 function markReferenced(){
466 if( !$this->selectors ){
467 return;
469 foreach($this->selectors as $selector){
470 $selector->markReferenced();
474 public function joinSelectors( $context, $selectors ){
475 $paths = array();
476 if( is_array($selectors) ){
477 foreach($selectors as $selector) {
478 $this->joinSelector( $paths, $context, $selector);
481 return $paths;
484 public function joinSelector( &$paths, $context, $selector){
486 $hasParentSelector = false;
488 foreach($selector->elements as $el) {
489 if( $el->value === '&') {
490 $hasParentSelector = true;
494 if( !$hasParentSelector ){
495 if( $context ){
496 foreach($context as $context_el){
497 $paths[] = array_merge($context_el, array($selector) );
499 }else {
500 $paths[] = array($selector);
502 return;
506 // The paths are [[Selector]]
507 // The first list is a list of comma seperated selectors
508 // The inner list is a list of inheritance seperated selectors
509 // e.g.
510 // .a, .b {
511 // .c {
512 // }
513 // }
514 // == [[.a] [.c]] [[.b] [.c]]
517 // the elements from the current selector so far
518 $currentElements = array();
519 // the current list of new selectors to add to the path.
520 // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
521 // by the parents
522 $newSelectors = array(array());
525 foreach( $selector->elements as $el){
527 // non parent reference elements just get added
528 if( $el->value !== '&' ){
529 $currentElements[] = $el;
530 } else {
531 // the new list of selectors to add
532 $selectorsMultiplied = array();
534 // merge the current list of non parent selector elements
535 // on to the current list of selectors to add
536 if( $currentElements ){
537 $this->mergeElementsOnToSelectors( $currentElements, $newSelectors);
540 // loop through our current selectors
541 foreach($newSelectors as $sel){
543 // if we don't have any parent paths, the & might be in a mixin so that it can be used
544 // whether there are parents or not
545 if( !$context ){
546 // the combinator used on el should now be applied to the next element instead so that
547 // it is not lost
548 if( $sel ){
549 $sel[0]->elements = array_slice($sel[0]->elements,0);
550 $sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo );
552 $selectorsMultiplied[] = $sel;
553 }else {
555 // and the parent selectors
556 foreach($context as $parentSel){
557 // We need to put the current selectors
558 // then join the last selector's elements on to the parents selectors
560 // our new selector path
561 $newSelectorPath = array();
562 // selectors from the parent after the join
563 $afterParentJoin = array();
564 $newJoinedSelectorEmpty = true;
566 //construct the joined selector - if & is the first thing this will be empty,
567 // if not newJoinedSelector will be the last set of elements in the selector
568 if( $sel ){
569 $newSelectorPath = $sel;
570 $lastSelector = array_pop($newSelectorPath);
571 $newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) );
572 $newJoinedSelectorEmpty = false;
574 else {
575 $newJoinedSelector = $selector->createDerived(array());
578 //put together the parent selectors after the join
579 if ( count($parentSel) > 1) {
580 $afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) );
583 if ( $parentSel ){
584 $newJoinedSelectorEmpty = false;
586 // join the elements so far with the first part of the parent
587 $newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo);
589 $newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) );
592 if (!$newJoinedSelectorEmpty) {
593 // now add the joined selector
594 $newSelectorPath[] = $newJoinedSelector;
597 // and the rest of the parent
598 $newSelectorPath = array_merge($newSelectorPath, $afterParentJoin);
600 // add that to our new set of selectors
601 $selectorsMultiplied[] = $newSelectorPath;
606 // our new selectors has been multiplied, so reset the state
607 $newSelectors = $selectorsMultiplied;
608 $currentElements = array();
612 // if we have any elements left over (e.g. .a& .b == .b)
613 // add them on to all the current selectors
614 if( $currentElements ){
615 $this->mergeElementsOnToSelectors($currentElements, $newSelectors);
617 foreach( $newSelectors as $new_sel){
618 if( $new_sel ){
619 $paths[] = $new_sel;
624 function mergeElementsOnToSelectors( $elements, &$selectors){
626 if( !$selectors ){
627 $selectors[] = array( new Less_Tree_Selector($elements) );
628 return;
632 foreach( $selectors as &$sel){
634 // if the previous thing in sel is a parent this needs to join on to it
635 if( $sel ){
636 $last = count($sel)-1;
637 $sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) );
638 }else{
639 $sel[] = new Less_Tree_Selector( $elements );