281 lines
6.7 KiB
PHP
281 lines
6.7 KiB
PHP
|
<?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 );
|
||
|
}
|
||
|
}
|