9 class Less_Tree_Ruleset
extends Less_Tree
{
15 public $strictImports;
23 public $type = 'Ruleset';
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 ){
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
);
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);
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);
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));
126 if ($mediaBlockCount) {
127 $len = count($env->mediaBlocks
);
128 for($i = $mediaBlockCount; $i < $len; $i++
){
129 $env->mediaBlocks
[$i]->bubbleSelectors($ruleset->selectors
);
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);
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
) ){
162 $temp_count = count($temp)-1;
163 array_splice($ruleset->rules
, $i, 1, $temp);
164 $rsRuleCnt +
= $temp_count;
166 $ruleset->resetCache();
168 }elseif( $rule instanceof Less_Tree_RulesetCall
){
170 $rule = $rule->compile($env);
172 foreach($rule->rules
as $r){
173 if( ($r instanceof Less_Tree_Rule
) && $r->variable
){
179 array_splice($ruleset->rules
, $i, 1, $rules);
180 $temp_count = count($rules);
181 $rsRuleCnt +
= $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();
211 $hasOnePassingSelector = true;
214 if( $this->rules
&& $hasOnePassingSelector ){
215 $rules = $this->rules
;
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);
234 if( $ruleset->root ||
$ruleset->allowImports ||
!$ruleset->strictImports
){
235 $ruleset->evalImports($env);
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;
253 $rules_len +
= $temp_count;
255 array_splice($this->rules
, $i, 1, array($rules));
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();
270 $important_rules[] = $rule;
274 return new Less_Tree_Ruleset($this->selectors
, $important_rules, $this->strictImports
);
277 public function matchArgs($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
){
288 if( $lastSelector->condition
&& !$lastSelector->condition
->compile( $env->copyEvalEnv( $env->frames
) ) ){
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
) ){
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]) ){
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);
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));
342 $this->lookups
[$key][] = $rule;
352 return $this->lookups
[$key];
357 * @see Less_Tree::genCSS
359 public function genCSS( $output ){
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 );
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;
384 $ruleNodes[] = $rule;
388 // If this is the root node, we don't render
389 // a selector, or {}.
393 debugInfo = tree.debugInfo(env, this, tabSetStr);
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 );
436 Less_Environment
::$lastRule = false;
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
){
469 foreach($this->selectors
as $selector){
470 $selector->markReferenced();
474 public function joinSelectors( $context, $selectors ){
476 if( is_array($selectors) ){
477 foreach($selectors as $selector) {
478 $this->joinSelector( $paths, $context, $selector);
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 ){
496 foreach($context as $context_el){
497 $paths[] = array_merge($context_el, array($selector) );
500 $paths[] = array($selector);
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
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
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;
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
546 // the combinator used on el should now be applied to the next element instead so that
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;
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
569 $newSelectorPath = $sel;
570 $lastSelector = array_pop($newSelectorPath);
571 $newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements
,0) );
572 $newJoinedSelectorEmpty = false;
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) );
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){
624 function mergeElementsOnToSelectors( $elements, &$selectors){
627 $selectors[] = array( new Less_Tree_Selector($elements) );
632 foreach( $selectors as &$sel){
634 // if the previous thing in sel is a parent this needs to join on to it
636 $last = count($sel)-1;
637 $sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements
, $elements) );
639 $sel[] = new Less_Tree_Selector( $elements );