toCSS.php 6.7 KB

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