toCSS.php 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. }