diff options
Diffstat (limited to 'lib/less.php/Tree/Ruleset.php')
-rw-r--r-- | lib/less.php/Tree/Ruleset.php | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/lib/less.php/Tree/Ruleset.php b/lib/less.php/Tree/Ruleset.php new file mode 100644 index 0000000..bdf9fec --- /dev/null +++ b/lib/less.php/Tree/Ruleset.php @@ -0,0 +1,643 @@ +<?php + +/** + * Ruleset + * + * @package Less + * @subpackage tree + */ +class Less_Tree_Ruleset extends Less_Tree{ + + protected $lookups; + public $_variables; + public $_rulesets; + + public $strictImports; + + public $selectors; + public $rules; + public $root; + public $allowImports; + public $paths; + public $firstRoot; + public $type = 'Ruleset'; + public $multiMedia; + public $allExtends; + + public $ruleset_id; + public $originalRuleset; + + public $first_oelements; + + public function SetRulesetIndex(){ + $this->ruleset_id = Less_Parser::$next_id++; + $this->originalRuleset = $this->ruleset_id; + + if( $this->selectors ){ + foreach($this->selectors as $sel){ + if( $sel->_oelements ){ + $this->first_oelements[$sel->_oelements[0]] = true; + } + } + } + } + + public function __construct($selectors, $rules, $strictImports = null){ + $this->selectors = $selectors; + $this->rules = $rules; + $this->lookups = array(); + $this->strictImports = $strictImports; + $this->SetRulesetIndex(); + } + + public function accept( $visitor ){ + if( $this->paths ){ + $paths_len = count($this->paths); + for($i = 0,$paths_len; $i < $paths_len; $i++ ){ + $this->paths[$i] = $visitor->visitArray($this->paths[$i]); + } + }elseif( $this->selectors ){ + $this->selectors = $visitor->visitArray($this->selectors); + } + + if( $this->rules ){ + $this->rules = $visitor->visitArray($this->rules); + } + } + + public function compile($env){ + + $ruleset = $this->PrepareRuleset($env); + + + // Store the frames around mixin definitions, + // so they can be evaluated like closures when the time comes. + $rsRuleCnt = count($ruleset->rules); + for( $i = 0; $i < $rsRuleCnt; $i++ ){ + if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){ + $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env); + } + } + + $mediaBlockCount = 0; + if( $env instanceof Less_Environment ){ + $mediaBlockCount = count($env->mediaBlocks); + } + + // Evaluate mixin calls. + $this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt ); + + + // Evaluate everything else + for( $i=0; $i<$rsRuleCnt; $i++ ){ + if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){ + $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env); + } + } + + // Evaluate everything else + for( $i=0; $i<$rsRuleCnt; $i++ ){ + $rule = $ruleset->rules[$i]; + + // for rulesets, check if it is a css guard and can be removed + if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count($rule->selectors) === 1 ){ + + // check if it can be folded in (e.g. & where) + if( $rule->selectors[0]->isJustParentSelector() ){ + array_splice($ruleset->rules,$i--,1); + $rsRuleCnt--; + + for($j = 0; $j < count($rule->rules); $j++ ){ + $subRule = $rule->rules[$j]; + if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){ + array_splice($ruleset->rules, ++$i, 0, array($subRule)); + $rsRuleCnt++; + } + } + + } + } + } + + + // Pop the stack + $env->shiftFrame(); + + if ($mediaBlockCount) { + $len = count($env->mediaBlocks); + for($i = $mediaBlockCount; $i < $len; $i++ ){ + $env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors); + } + } + + return $ruleset; + } + + /** + * Compile Less_Tree_Mixin_Call objects + * + * @param Less_Tree_Ruleset $ruleset + * @param integer $rsRuleCnt + */ + private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){ + for($i=0; $i < $rsRuleCnt; $i++){ + $rule = $ruleset->rules[$i]; + + if( $rule instanceof Less_Tree_Mixin_Call ){ + $rule = $rule->compile($env); + + $temp = array(); + foreach($rule as $r){ + if( ($r instanceof Less_Tree_Rule) && $r->variable ){ + // do not pollute the scope if the variable is + // already there. consider returning false here + // but we need a way to "return" variable from mixins + if( !$ruleset->variable($r->name) ){ + $temp[] = $r; + } + }else{ + $temp[] = $r; + } + } + $temp_count = count($temp)-1; + array_splice($ruleset->rules, $i, 1, $temp); + $rsRuleCnt += $temp_count; + $i += $temp_count; + $ruleset->resetCache(); + + }elseif( $rule instanceof Less_Tree_RulesetCall ){ + + $rule = $rule->compile($env); + $rules = array(); + foreach($rule->rules as $r){ + if( ($r instanceof Less_Tree_Rule) && $r->variable ){ + continue; + } + $rules[] = $r; + } + + array_splice($ruleset->rules, $i, 1, $rules); + $temp_count = count($rules); + $rsRuleCnt += $temp_count - 1; + $i += $temp_count-1; + $ruleset->resetCache(); + } + + } + } + + + /** + * Compile the selectors and create a new ruleset object for the compile() method + * + */ + private function PrepareRuleset($env){ + + $hasOnePassingSelector = false; + $selectors = array(); + if( $this->selectors ){ + Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,"); + + foreach($this->selectors as $s){ + $selector = $s->compile($env); + $selectors[] = $selector; + if( $selector->evaldCondition ){ + $hasOnePassingSelector = true; + } + } + + Less_Tree_DefaultFunc::reset(); + } else { + $hasOnePassingSelector = true; + } + + if( $this->rules && $hasOnePassingSelector ){ + $rules = $this->rules; + }else{ + $rules = array(); + } + + $ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports); + + $ruleset->originalRuleset = $this->ruleset_id; + + $ruleset->root = $this->root; + $ruleset->firstRoot = $this->firstRoot; + $ruleset->allowImports = $this->allowImports; + + + // push the current ruleset to the frames stack + $env->unshiftFrame($ruleset); + + + // Evaluate imports + if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){ + $ruleset->evalImports($env); + } + + return $ruleset; + } + + function evalImports($env) { + + $rules_len = count($this->rules); + for($i=0; $i < $rules_len; $i++){ + $rule = $this->rules[$i]; + + if( $rule instanceof Less_Tree_Import ){ + $rules = $rule->compile($env); + if( is_array($rules) ){ + array_splice($this->rules, $i, 1, $rules); + $temp_count = count($rules)-1; + $i += $temp_count; + $rules_len += $temp_count; + }else{ + array_splice($this->rules, $i, 1, array($rules)); + } + + $this->resetCache(); + } + } + } + + function makeImportant(){ + + $important_rules = array(); + foreach($this->rules as $rule){ + if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset || $rule instanceof Less_Tree_NameValue ){ + $important_rules[] = $rule->makeImportant(); + }else{ + $important_rules[] = $rule; + } + } + + return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports ); + } + + public function matchArgs($args){ + return !$args; + } + + // lets you call a css selector with a guard + public function matchCondition( $args, $env ){ + $lastSelector = end($this->selectors); + + if( !$lastSelector->evaldCondition ){ + return false; + } + if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){ + return false; + } + return true; + } + + function resetCache(){ + $this->_rulesets = null; + $this->_variables = null; + $this->lookups = array(); + } + + public function variables(){ + $this->_variables = array(); + foreach( $this->rules as $r){ + if ($r instanceof Less_Tree_Rule && $r->variable === true) { + $this->_variables[$r->name] = $r; + } + } + } + + public function variable($name){ + + if( is_null($this->_variables) ){ + $this->variables(); + } + return isset($this->_variables[$name]) ? $this->_variables[$name] : null; + } + + public function find( $selector, $self = null ){ + + $key = implode(' ',$selector->_oelements); + + if( !isset($this->lookups[$key]) ){ + + if( !$self ){ + $self = $this->ruleset_id; + } + + $this->lookups[$key] = array(); + + $first_oelement = $selector->_oelements[0]; + + foreach($this->rules as $rule){ + if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){ + + if( isset($rule->first_oelements[$first_oelement]) ){ + + foreach( $rule->selectors as $ruleSelector ){ + $match = $selector->match($ruleSelector); + if( $match ){ + if( $selector->elements_len > $match ){ + $this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self)); + } else { + $this->lookups[$key][] = $rule; + } + break; + } + } + } + } + } + } + + return $this->lookups[$key]; + } + + + /** + * @see Less_Tree::genCSS + */ + public function genCSS( $output ){ + + if( !$this->root ){ + Less_Environment::$tabLevel++; + } + + $tabRuleStr = $tabSetStr = ''; + if( !Less_Parser::$options['compress'] ){ + if( Less_Environment::$tabLevel ){ + $tabRuleStr = "\n".str_repeat( Less_Parser::$options['indentation'] , Less_Environment::$tabLevel ); + $tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'] , Less_Environment::$tabLevel-1 ); + }else{ + $tabSetStr = $tabRuleStr = "\n"; + } + } + + + $ruleNodes = array(); + $rulesetNodes = array(); + foreach($this->rules as $rule){ + + $class = get_class($rule); + if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){ + $rulesetNodes[] = $rule; + }else{ + $ruleNodes[] = $rule; + } + } + + // If this is the root node, we don't render + // a selector, or {}. + if( !$this->root ){ + + /* + debugInfo = tree.debugInfo(env, this, tabSetStr); + + if (debugInfo) { + output.add(debugInfo); + output.add(tabSetStr); + } + */ + + $paths_len = count($this->paths); + for( $i = 0; $i < $paths_len; $i++ ){ + $path = $this->paths[$i]; + $firstSelector = true; + + foreach($path as $p){ + $p->genCSS( $output, $firstSelector ); + $firstSelector = false; + } + + if( $i + 1 < $paths_len ){ + $output->add( ',' . $tabSetStr ); + } + } + + $output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr ); + } + + // Compile rules and rulesets + $ruleNodes_len = count($ruleNodes); + $rulesetNodes_len = count($rulesetNodes); + for( $i = 0; $i < $ruleNodes_len; $i++ ){ + $rule = $ruleNodes[$i]; + + // @page{ directive ends up with root elements inside it, a mix of rules and rulesets + // In this instance we do not know whether it is the last property + if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){ + Less_Environment::$lastRule = true; + } + + $rule->genCSS( $output ); + + if( !Less_Environment::$lastRule ){ + $output->add( $tabRuleStr ); + }else{ + Less_Environment::$lastRule = false; + } + } + + if( !$this->root ){ + $output->add( $tabSetStr . '}' ); + Less_Environment::$tabLevel--; + } + + $firstRuleset = true; + $space = ($this->root ? $tabRuleStr : $tabSetStr); + for( $i = 0; $i < $rulesetNodes_len; $i++ ){ + + if( $ruleNodes_len && $firstRuleset ){ + $output->add( $space ); + }elseif( !$firstRuleset ){ + $output->add( $space ); + } + $firstRuleset = false; + $rulesetNodes[$i]->genCSS( $output); + } + + if( !Less_Parser::$options['compress'] && $this->firstRoot ){ + $output->add( "\n" ); + } + + } + + + function markReferenced(){ + if( !$this->selectors ){ + return; + } + foreach($this->selectors as $selector){ + $selector->markReferenced(); + } + } + + public function joinSelectors( $context, $selectors ){ + $paths = array(); + if( is_array($selectors) ){ + foreach($selectors as $selector) { + $this->joinSelector( $paths, $context, $selector); + } + } + return $paths; + } + + public function joinSelector( &$paths, $context, $selector){ + + $hasParentSelector = false; + + foreach($selector->elements as $el) { + if( $el->value === '&') { + $hasParentSelector = true; + } + } + + if( !$hasParentSelector ){ + if( $context ){ + foreach($context as $context_el){ + $paths[] = array_merge($context_el, array($selector) ); + } + }else { + $paths[] = array($selector); + } + return; + } + + + // The paths are [[Selector]] + // The first list is a list of comma separated selectors + // The inner list is a list of inheritance separated selectors + // e.g. + // .a, .b { + // .c { + // } + // } + // == [[.a] [.c]] [[.b] [.c]] + // + + // the elements from the current selector so far + $currentElements = array(); + // the current list of new selectors to add to the path. + // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors + // by the parents + $newSelectors = array(array()); + + + foreach( $selector->elements as $el){ + + // non parent reference elements just get added + if( $el->value !== '&' ){ + $currentElements[] = $el; + } else { + // the new list of selectors to add + $selectorsMultiplied = array(); + + // merge the current list of non parent selector elements + // on to the current list of selectors to add + if( $currentElements ){ + $this->mergeElementsOnToSelectors( $currentElements, $newSelectors); + } + + // loop through our current selectors + foreach($newSelectors as $sel){ + + // if we don't have any parent paths, the & might be in a mixin so that it can be used + // whether there are parents or not + if( !$context ){ + // the combinator used on el should now be applied to the next element instead so that + // it is not lost + if( $sel ){ + $sel[0]->elements = array_slice($sel[0]->elements,0); + $sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo ); + } + $selectorsMultiplied[] = $sel; + }else { + + // and the parent selectors + foreach($context as $parentSel){ + // We need to put the current selectors + // then join the last selector's elements on to the parents selectors + + // our new selector path + $newSelectorPath = array(); + // selectors from the parent after the join + $afterParentJoin = array(); + $newJoinedSelectorEmpty = true; + + //construct the joined selector - if & is the first thing this will be empty, + // if not newJoinedSelector will be the last set of elements in the selector + if( $sel ){ + $newSelectorPath = $sel; + $lastSelector = array_pop($newSelectorPath); + $newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) ); + $newJoinedSelectorEmpty = false; + } + else { + $newJoinedSelector = $selector->createDerived(array()); + } + + //put together the parent selectors after the join + if ( count($parentSel) > 1) { + $afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) ); + } + + if ( $parentSel ){ + $newJoinedSelectorEmpty = false; + + // join the elements so far with the first part of the parent + $newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo); + + $newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) ); + } + + if (!$newJoinedSelectorEmpty) { + // now add the joined selector + $newSelectorPath[] = $newJoinedSelector; + } + + // and the rest of the parent + $newSelectorPath = array_merge($newSelectorPath, $afterParentJoin); + + // add that to our new set of selectors + $selectorsMultiplied[] = $newSelectorPath; + } + } + } + + // our new selectors has been multiplied, so reset the state + $newSelectors = $selectorsMultiplied; + $currentElements = array(); + } + } + + // if we have any elements left over (e.g. .a& .b == .b) + // add them on to all the current selectors + if( $currentElements ){ + $this->mergeElementsOnToSelectors($currentElements, $newSelectors); + } + foreach( $newSelectors as $new_sel){ + if( $new_sel ){ + $paths[] = $new_sel; + } + } + } + + function mergeElementsOnToSelectors( $elements, &$selectors){ + + if( !$selectors ){ + $selectors[] = array( new Less_Tree_Selector($elements) ); + return; + } + + + foreach( $selectors as &$sel){ + + // if the previous thing in sel is a parent this needs to join on to it + if( $sel ){ + $last = count($sel)-1; + $sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) ); + }else{ + $sel[] = new Less_Tree_Selector( $elements ); + } + } + } +} |