You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
292 lines
6.5 KiB
292 lines
6.5 KiB
<?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 ){
|
|
if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){
|
|
return array();
|
|
}
|
|
if( $directiveNode->name === '@charset' ){
|
|
// 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;
|
|
}
|
|
return $directiveNode;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|