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 ); } }