1<?php 2 3/** 4 * toCSS Visitor 5 * 6 * @package Less 7 * @subpackage visitor 8 */ 9class Less_Visitor_toCSS extends Less_VisitorReplacing { 10 11 private $charset; 12 13 public function __construct() { 14 parent::__construct(); 15 } 16 17 /** 18 * @param Less_Tree_Ruleset $root 19 */ 20 public function run( $root ) { 21 return $this->visitObj( $root ); 22 } 23 24 public function visitRule( $ruleNode ) { 25 if ( $ruleNode->variable ) { 26 return array(); 27 } 28 return $ruleNode; 29 } 30 31 public function visitMixinDefinition( $mixinNode ) { 32 // mixin definitions do not get eval'd - this means they keep state 33 // so we have to clear that state here so it isn't used if toCSS is called twice 34 $mixinNode->frames = array(); 35 return array(); 36 } 37 38 public function visitExtend() { 39 return array(); 40 } 41 42 public function visitComment( $commentNode ) { 43 if ( $commentNode->isSilent() ) { 44 return array(); 45 } 46 return $commentNode; 47 } 48 49 public function visitMedia( $mediaNode, &$visitDeeper ) { 50 $mediaNode->accept( $this ); 51 $visitDeeper = false; 52 53 if ( !$mediaNode->rules ) { 54 return array(); 55 } 56 return $mediaNode; 57 } 58 59 public function visitDirective( $directiveNode ) { 60 if ( isset( $directiveNode->currentFileInfo['reference'] ) && ( !property_exists( $directiveNode, 'isReferenced' ) || !$directiveNode->isReferenced ) ) { 61 return array(); 62 } 63 if ( $directiveNode->name === '@charset' ) { 64 // Only output the debug info together with subsequent @charset definitions 65 // a comment (or @media statement) before the actual @charset directive would 66 // be considered illegal css as it has to be on the first line 67 if ( isset( $this->charset ) && $this->charset ) { 68 69 // if( $directiveNode->debugInfo ){ 70 // $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n"); 71 // $comment->debugInfo = $directiveNode->debugInfo; 72 // return $this->visit($comment); 73 //} 74 75 return array(); 76 } 77 $this->charset = true; 78 } 79 return $directiveNode; 80 } 81 82 public function checkPropertiesInRoot( $rulesetNode ) { 83 if ( !$rulesetNode->firstRoot ) { 84 return; 85 } 86 87 foreach ( $rulesetNode->rules as $ruleNode ) { 88 if ( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ) { 89 $msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.( $ruleNode->currentFileInfo ? ( ' Filename: '.$ruleNode->currentFileInfo['filename'] ) : null ); 90 throw new Less_Exception_Compiler( $msg ); 91 } 92 } 93 } 94 95 public function visitRuleset( $rulesetNode, &$visitDeeper ) { 96 $visitDeeper = false; 97 98 $this->checkPropertiesInRoot( $rulesetNode ); 99 100 if ( $rulesetNode->root ) { 101 return $this->visitRulesetRoot( $rulesetNode ); 102 } 103 104 $rulesets = array(); 105 $rulesetNode->paths = $this->visitRulesetPaths( $rulesetNode ); 106 107 // Compile rules and rulesets 108 $nodeRuleCnt = $rulesetNode->rules ? count( $rulesetNode->rules ) : 0; 109 for ( $i = 0; $i < $nodeRuleCnt; ) { 110 $rule = $rulesetNode->rules[$i]; 111 112 if ( property_exists( $rule, 'rules' ) ) { 113 // visit because we are moving them out from being a child 114 $rulesets[] = $this->visitObj( $rule ); 115 array_splice( $rulesetNode->rules, $i, 1 ); 116 $nodeRuleCnt--; 117 continue; 118 } 119 $i++; 120 } 121 122 // accept the visitor to remove rules and refactor itself 123 // then we can decide now whether we want it or not 124 if ( $nodeRuleCnt > 0 ) { 125 $rulesetNode->accept( $this ); 126 127 if ( $rulesetNode->rules ) { 128 129 if ( count( $rulesetNode->rules ) > 1 ) { 130 $this->_mergeRules( $rulesetNode->rules ); 131 $this->_removeDuplicateRules( $rulesetNode->rules ); 132 } 133 134 // now decide whether we keep the ruleset 135 if ( $rulesetNode->paths ) { 136 // array_unshift($rulesets, $rulesetNode); 137 array_splice( $rulesets, 0, 0, array( $rulesetNode ) ); 138 } 139 } 140 141 } 142 143 if ( count( $rulesets ) === 1 ) { 144 return $rulesets[0]; 145 } 146 return $rulesets; 147 } 148 149 /** 150 * Helper function for visitiRuleset 151 * 152 * return array|Less_Tree_Ruleset 153 */ 154 private function visitRulesetRoot( $rulesetNode ) { 155 $rulesetNode->accept( $this ); 156 if ( $rulesetNode->firstRoot || $rulesetNode->rules ) { 157 return $rulesetNode; 158 } 159 return array(); 160 } 161 162 /** 163 * Helper function for visitRuleset() 164 * 165 * @return array 166 */ 167 private function visitRulesetPaths( $rulesetNode ) { 168 $paths = array(); 169 foreach ( $rulesetNode->paths as $p ) { 170 if ( $p[0]->elements[0]->combinator === ' ' ) { 171 $p[0]->elements[0]->combinator = ''; 172 } 173 174 foreach ( $p as $pi ) { 175 if ( $pi->getIsReferenced() && $pi->getIsOutput() ) { 176 $paths[] = $p; 177 break; 178 } 179 } 180 } 181 182 return $paths; 183 } 184 185 protected function _removeDuplicateRules( &$rules ) { 186 // remove duplicates 187 $ruleCache = array(); 188 for ( $i = count( $rules ) - 1; $i >= 0; $i-- ) { 189 $rule = $rules[$i]; 190 if ( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ) { 191 192 if ( !isset( $ruleCache[$rule->name] ) ) { 193 $ruleCache[$rule->name] = $rule; 194 } else { 195 $ruleList =& $ruleCache[$rule->name]; 196 197 if ( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ) { 198 $ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() ); 199 } 200 201 $ruleCSS = $rule->toCSS(); 202 if ( array_search( $ruleCSS, $ruleList ) !== false ) { 203 array_splice( $rules, $i, 1 ); 204 } else { 205 $ruleList[] = $ruleCSS; 206 } 207 } 208 } 209 } 210 } 211 212 protected function _mergeRules( &$rules ) { 213 $groups = array(); 214 215 // obj($rules); 216 217 $rules_len = count( $rules ); 218 for ( $i = 0; $i < $rules_len; $i++ ) { 219 $rule = $rules[$i]; 220 221 if ( ( $rule instanceof Less_Tree_Rule ) && $rule->merge ) { 222 223 $key = $rule->name; 224 if ( $rule->important ) { 225 $key .= ',!'; 226 } 227 228 if ( !isset( $groups[$key] ) ) { 229 $groups[$key] = array(); 230 } else { 231 array_splice( $rules, $i--, 1 ); 232 $rules_len--; 233 } 234 235 $groups[$key][] = $rule; 236 } 237 } 238 239 foreach ( $groups as $parts ) { 240 241 if ( count( $parts ) > 1 ) { 242 $rule = $parts[0]; 243 $spacedGroups = array(); 244 $lastSpacedGroup = array(); 245 $parts_mapped = array(); 246 foreach ( $parts as $p ) { 247 if ( $p->merge === '+' ) { 248 if ( $lastSpacedGroup ) { 249 $spacedGroups[] = self::toExpression( $lastSpacedGroup ); 250 } 251 $lastSpacedGroup = array(); 252 } 253 $lastSpacedGroup[] = $p; 254 } 255 256 $spacedGroups[] = self::toExpression( $lastSpacedGroup ); 257 $rule->value = self::toValue( $spacedGroups ); 258 } 259 } 260 261 } 262 263 public static function toExpression( $values ) { 264 $mapped = array(); 265 foreach ( $values as $p ) { 266 $mapped[] = $p->value; 267 } 268 return new Less_Tree_Expression( $mapped ); 269 } 270 271 public static function toValue( $values ) { 272 // return new Less_Tree_Value($values); ?? 273 274 $mapped = array(); 275 foreach ( $values as $p ) { 276 $mapped[] = $p; 277 } 278 return new Less_Tree_Value( $mapped ); 279 } 280} 281