<?php
/**
* toCSS Visitor
*
* @package Less
* @subpackage visitor
*/
class Less_Visitor_toCSS extends Less_VisitorReplacing{
private $charset;
public function __construct(){
parent::__construct();
}
/**
* @param Less_Tree_Ruleset $root
*/
public function run( $root ){
return $this->visitObj($root);
}
public function visitRule( $ruleNode ){
if( $ruleNode->variable ){
return array();
}
return $ruleNode;
}
public function visitMixinDefinition($mixinNode){
// mixin definitions do not get eval'd - this means they keep state
// so we have to clear that state here so it isn't used if toCSS is called twice
$mixinNode->frames = array();
return array();
}
public function visitExtend(){
return array();
}
public function visitComment( $commentNode ){
if( $commentNode->isSilent() ){
return array();
}
return $commentNode;
}
public function visitMedia( $mediaNode, &$visitDeeper ){
$mediaNode->accept($this);
$visitDeeper = false;
if( !$mediaNode->rules ){
return array();
}
return $mediaNode;
}
public function visitDirective( $directiveNode, &$visitDeeper ){
if( $directiveNode->name === '@charset' ){
if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){
return array();
}
// Only output the debug info together with subsequent @charset definitions
// a comment (or @media statement) before the actual @charset directive would
// be considered illegal css as it has to be on the first line
if( isset($this->charset) && $this->charset ){
//if( $directiveNode->debugInfo ){
// $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n");
// $comment->debugInfo = $directiveNode->debugInfo;
// return $this->visit($comment);
//}
return array();
}
$this->charset = true;
}
if ( $directiveNode->rules ){
$this->_mergeRules($directiveNode->rules[0]->rules);
$directiveNode->accept($this);
$visitDeeper = false;
if ( $directiveNode->getIsReferenced() ){
return $directiveNode;
}
if ( !$directiveNode->rules ){
return array();
}
if ( $this->hasVisibleChild($directiveNode) ){
$directiveNode->markReferenced();
return $directiveNode;
}
return array();
} else if ( !$directiveNode->getIsReferenced() ){
return array();
}
return $directiveNode;
}
protected function hasVisibleChild($directiveNode)
{
$bodyRules = $directiveNode->rules;
if ( count($bodyRules) == 1 && !$bodyRules[0]->paths ){
$bodyRules = $bodyRules[0]->rules;
}
foreach ($bodyRules AS $rule){
if ( !empty($rule->isReferenced) || (method_exists($rule, 'getIsReferenced') && $rule->getIsReferenced()) ){
return true;
}
}
return false;
}
public function checkPropertiesInRoot( $rulesetNode ){
if( !$rulesetNode->firstRoot ){
return;
}
foreach($rulesetNode->rules as $ruleNode){
if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){
$msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null);
throw new Less_Exception_Compiler($msg);
}
}
}
public function visitRuleset( $rulesetNode, &$visitDeeper ){
$visitDeeper = false;
$this->checkPropertiesInRoot( $rulesetNode );
if( $rulesetNode->root ){
return $this->visitRulesetRoot( $rulesetNode );
}
$rulesets = array();
$rulesetNode->paths = $this->visitRulesetPaths($rulesetNode);
// Compile rules and rulesets
$nodeRuleCnt = $rulesetNode->rules?count($rulesetNode->rules):0;
for( $i = 0; $i < $nodeRuleCnt; ){
$rule = $rulesetNode->rules[$i];
if( property_exists($rule,'rules') ){
// visit because we are moving them out from being a child
$rulesets[] = $this->visitObj($rule);
array_splice($rulesetNode->rules,$i,1);
$nodeRuleCnt--;
continue;
}
$i++;
}
// accept the visitor to remove rules and refactor itself
// then we can decide now whether we want it or not
if( $nodeRuleCnt > 0 ){
$rulesetNode->accept($this);
if( $rulesetNode->rules ){
if( count($rulesetNode->rules) > 1 ){
$this->_mergeRules( $rulesetNode->rules );
$this->_removeDuplicateRules( $rulesetNode->rules );
}
// now decide whether we keep the ruleset
if( $rulesetNode->paths ){
//array_unshift($rulesets, $rulesetNode);
array_splice($rulesets,0,0,array($rulesetNode));
}
}
}
if( count($rulesets) === 1 ){
return $rulesets[0];
}
return $rulesets;
}
/**
* Helper function for visitiRuleset
*
* return array|Less_Tree_Ruleset
*/
private function visitRulesetRoot( $rulesetNode ){
$rulesetNode->accept( $this );
if( $rulesetNode->firstRoot || $rulesetNode->rules ){
return $rulesetNode;
}
return array();
}
/**
* Helper function for visitRuleset()
*
* @return array
*/
private function visitRulesetPaths($rulesetNode){
$paths = array();
foreach($rulesetNode->paths as $p){
if( $p[0]->elements[0]->combinator === ' ' ){
$p[0]->elements[0]->combinator = '';
}
foreach($p as $pi){
if( $pi->getIsReferenced() && $pi->getIsOutput() ){
$paths[] = $p;
break;
}
}
}
return $paths;
}
protected function _removeDuplicateRules( &$rules ){
// remove duplicates
$ruleCache = array();
for( $i = count($rules)-1; $i >= 0 ; $i-- ){
$rule = $rules[$i];
if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){
if( !isset($ruleCache[$rule->name]) ){
$ruleCache[$rule->name] = $rule;
}else{
$ruleList =& $ruleCache[$rule->name];
if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){
$ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() );
}
$ruleCSS = $rule->toCSS();
if( array_search($ruleCSS,$ruleList) !== false ){
array_splice($rules,$i,1);
}else{
$ruleList[] = $ruleCSS;
}
}
}
}
}
protected function _mergeRules( &$rules ){
$groups = array();
//obj($rules);
$rules_len = count($rules);
for( $i = 0; $i < $rules_len; $i++ ){
$rule = $rules[$i];
if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){
$key = $rule->name;
if( $rule->important ){
$key .= ',!';
}
if( !isset($groups[$key]) ){
$groups[$key] = array();
}else{
array_splice($rules, $i--, 1);
$rules_len--;
}
$groups[$key][] = $rule;
}
}
foreach($groups as $parts){
if( count($parts) > 1 ){
$rule = $parts[0];
$spacedGroups = array();
$lastSpacedGroup = array();
$parts_mapped = array();
foreach($parts as $p){
if( $p->merge === '+' ){
if( $lastSpacedGroup ){
$spacedGroups[] = self::toExpression($lastSpacedGroup);
}
$lastSpacedGroup = array();
}
$lastSpacedGroup[] = $p;
}
$spacedGroups[] = self::toExpression($lastSpacedGroup);
$rule->value = self::toValue($spacedGroups);
}
}
}
public static function toExpression($values){
$mapped = array();
foreach($values as $p){
$mapped[] = $p->value;
}
return new Less_Tree_Expression( $mapped );
}
public static function toValue($values){
//return new Less_Tree_Value($values); ??
$mapped = array();
foreach($values as $p){
$mapped[] = $p;
}
return new Less_Tree_Value($mapped);
}
}