[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/lessphp/Tree/ -> Ruleset.php (source)

   1  <?php
   2  
   3  /**
   4   * Ruleset
   5   *
   6   * @package Less
   7   * @subpackage tree
   8   */
   9  class Less_Tree_Ruleset extends Less_Tree{
  10  
  11      protected $lookups;
  12      public $_variables;
  13      public $_rulesets;
  14  
  15      public $strictImports;
  16  
  17      public $selectors;
  18      public $rules;
  19      public $root;
  20      public $allowImports;
  21      public $paths;
  22      public $firstRoot;
  23      public $type = 'Ruleset';
  24      public $multiMedia;
  25      public $allExtends;
  26  
  27      public $ruleset_id;
  28      public $originalRuleset;
  29  
  30      public $first_oelements;
  31  
  32  	public function SetRulesetIndex(){
  33          $this->ruleset_id = Less_Parser::$next_id++;
  34          $this->originalRuleset = $this->ruleset_id;
  35  
  36          if( $this->selectors ){
  37              foreach($this->selectors as $sel){
  38                  if( $sel->_oelements ){
  39                      $this->first_oelements[$sel->_oelements[0]] = true;
  40                  }
  41              }
  42          }
  43      }
  44  
  45  	public function __construct($selectors, $rules, $strictImports = null){
  46          $this->selectors = $selectors;
  47          $this->rules = $rules;
  48          $this->lookups = array();
  49          $this->strictImports = $strictImports;
  50          $this->SetRulesetIndex();
  51      }
  52  
  53  	public function accept( $visitor ){
  54          if( $this->paths ){
  55              $paths_len = count($this->paths);
  56              for($i = 0,$paths_len; $i < $paths_len; $i++ ){
  57                  $this->paths[$i] = $visitor->visitArray($this->paths[$i]);
  58              }
  59          }elseif( $this->selectors ){
  60              $this->selectors = $visitor->visitArray($this->selectors);
  61          }
  62  
  63          if( $this->rules ){
  64              $this->rules = $visitor->visitArray($this->rules);
  65          }
  66      }
  67  
  68  	public function compile($env){
  69  
  70          $ruleset = $this->PrepareRuleset($env);
  71  
  72  
  73          // Store the frames around mixin definitions,
  74          // so they can be evaluated like closures when the time comes.
  75          $rsRuleCnt = count($ruleset->rules);
  76          for( $i = 0; $i < $rsRuleCnt; $i++ ){
  77              if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){
  78                  $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
  79              }
  80          }
  81  
  82          $mediaBlockCount = 0;
  83          if( $env instanceof Less_Environment ){
  84              $mediaBlockCount = count($env->mediaBlocks);
  85          }
  86  
  87          // Evaluate mixin calls.
  88          $this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt );
  89  
  90  
  91          // Evaluate everything else
  92          for( $i=0; $i<$rsRuleCnt; $i++ ){
  93              if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){
  94                  $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env);
  95              }
  96          }
  97  
  98          // Evaluate everything else
  99          for( $i=0; $i<$rsRuleCnt; $i++ ){
 100              $rule = $ruleset->rules[$i];
 101  
 102              // for rulesets, check if it is a css guard and can be removed
 103              if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count($rule->selectors) === 1 ){
 104  
 105                  // check if it can be folded in (e.g. & where)
 106                  if( $rule->selectors[0]->isJustParentSelector() ){
 107                      array_splice($ruleset->rules,$i--,1);
 108                      $rsRuleCnt--;
 109  
 110                      for($j = 0; $j < count($rule->rules); $j++ ){
 111                          $subRule = $rule->rules[$j];
 112                          if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){
 113                              array_splice($ruleset->rules, ++$i, 0, array($subRule));
 114                              $rsRuleCnt++;
 115                          }
 116                      }
 117  
 118                  }
 119              }
 120          }
 121  
 122  
 123          // Pop the stack
 124          $env->shiftFrame();
 125  
 126          if ($mediaBlockCount) {
 127              $len = count($env->mediaBlocks);
 128              for($i = $mediaBlockCount; $i < $len; $i++ ){
 129                  $env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors);
 130              }
 131          }
 132  
 133          return $ruleset;
 134      }
 135  
 136      /**
 137       * Compile Less_Tree_Mixin_Call objects
 138       *
 139       * @param Less_Tree_Ruleset $ruleset
 140       * @param integer $rsRuleCnt
 141       */
 142  	private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){
 143          for($i=0; $i < $rsRuleCnt; $i++){
 144              $rule = $ruleset->rules[$i];
 145  
 146              if( $rule instanceof Less_Tree_Mixin_Call ){
 147                  $rule = $rule->compile($env);
 148  
 149                  $temp = array();
 150                  foreach($rule as $r){
 151                      if( ($r instanceof Less_Tree_Rule) && $r->variable ){
 152                          // do not pollute the scope if the variable is
 153                          // already there. consider returning false here
 154                          // but we need a way to "return" variable from mixins
 155                          if( !$ruleset->variable($r->name) ){
 156                              $temp[] = $r;
 157                          }
 158                      }else{
 159                          $temp[] = $r;
 160                      }
 161                  }
 162                  $temp_count = count($temp)-1;
 163                  array_splice($ruleset->rules, $i, 1, $temp);
 164                  $rsRuleCnt += $temp_count;
 165                  $i += $temp_count;
 166                  $ruleset->resetCache();
 167  
 168              }elseif( $rule instanceof Less_Tree_RulesetCall ){
 169  
 170                  $rule = $rule->compile($env);
 171                  $rules = array();
 172                  foreach($rule->rules as $r){
 173                      if( ($r instanceof Less_Tree_Rule) && $r->variable ){
 174                          continue;
 175                      }
 176                      $rules[] = $r;
 177                  }
 178  
 179                  array_splice($ruleset->rules, $i, 1, $rules);
 180                  $temp_count = count($rules);
 181                  $rsRuleCnt += $temp_count - 1;
 182                  $i += $temp_count-1;
 183                  $ruleset->resetCache();
 184              }
 185  
 186          }
 187      }
 188  
 189  
 190      /**
 191       * Compile the selectors and create a new ruleset object for the compile() method
 192       *
 193       */
 194  	private function PrepareRuleset($env){
 195  
 196          $hasOnePassingSelector = false;
 197          $selectors = array();
 198          if( $this->selectors ){
 199              Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,");
 200  
 201              foreach($this->selectors as $s){
 202                  $selector = $s->compile($env);
 203                  $selectors[] = $selector;
 204                  if( $selector->evaldCondition ){
 205                      $hasOnePassingSelector = true;
 206                  }
 207              }
 208  
 209              Less_Tree_DefaultFunc::reset();
 210          } else {
 211              $hasOnePassingSelector = true;
 212          }
 213  
 214          if( $this->rules && $hasOnePassingSelector ){
 215              $rules = $this->rules;
 216          }else{
 217              $rules = array();
 218          }
 219  
 220          $ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports);
 221  
 222          $ruleset->originalRuleset = $this->ruleset_id;
 223  
 224          $ruleset->root = $this->root;
 225          $ruleset->firstRoot = $this->firstRoot;
 226          $ruleset->allowImports = $this->allowImports;
 227  
 228  
 229          // push the current ruleset to the frames stack
 230          $env->unshiftFrame($ruleset);
 231  
 232  
 233          // Evaluate imports
 234          if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){
 235              $ruleset->evalImports($env);
 236          }
 237  
 238          return $ruleset;
 239      }
 240  
 241  	function evalImports($env) {
 242  
 243          $rules_len = count($this->rules);
 244          for($i=0; $i < $rules_len; $i++){
 245              $rule = $this->rules[$i];
 246  
 247              if( $rule instanceof Less_Tree_Import ){
 248                  $rules = $rule->compile($env);
 249                  if( is_array($rules) ){
 250                      array_splice($this->rules, $i, 1, $rules);
 251                      $temp_count = count($rules)-1;
 252                      $i += $temp_count;
 253                      $rules_len += $temp_count;
 254                  }else{
 255                      array_splice($this->rules, $i, 1, array($rules));
 256                  }
 257  
 258                  $this->resetCache();
 259              }
 260          }
 261      }
 262  
 263  	function makeImportant(){
 264  
 265          $important_rules = array();
 266          foreach($this->rules as $rule){
 267              if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset || $rule instanceof Less_Tree_NameValue ){
 268                  $important_rules[] = $rule->makeImportant();
 269              }else{
 270                  $important_rules[] = $rule;
 271              }
 272          }
 273  
 274          return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports );
 275      }
 276  
 277  	public function matchArgs($args){
 278          return !$args;
 279      }
 280  
 281      // lets you call a css selector with a guard
 282  	public function matchCondition( $args, $env ){
 283          $lastSelector = end($this->selectors);
 284  
 285          if( !$lastSelector->evaldCondition ){
 286              return false;
 287          }
 288          if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){
 289              return false;
 290          }
 291          return true;
 292      }
 293  
 294  	function resetCache(){
 295          $this->_rulesets = null;
 296          $this->_variables = null;
 297          $this->lookups = array();
 298      }
 299  
 300  	public function variables(){
 301          $this->_variables = array();
 302          foreach( $this->rules as $r){
 303              if ($r instanceof Less_Tree_Rule && $r->variable === true) {
 304                  $this->_variables[$r->name] = $r;
 305              }
 306          }
 307      }
 308  
 309  	public function variable($name){
 310  
 311          if( is_null($this->_variables) ){
 312              $this->variables();
 313          }
 314          return isset($this->_variables[$name]) ? $this->_variables[$name] : null;
 315      }
 316  
 317  	public function find( $selector, $self = null ){
 318  
 319          $key = implode(' ',$selector->_oelements);
 320  
 321          if( !isset($this->lookups[$key]) ){
 322  
 323              if( !$self ){
 324                  $self = $this->ruleset_id;
 325              }
 326  
 327              $this->lookups[$key] = array();
 328  
 329              $first_oelement = $selector->_oelements[0];
 330  
 331              foreach($this->rules as $rule){
 332                  if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){
 333  
 334                      if( isset($rule->first_oelements[$first_oelement]) ){
 335  
 336                          foreach( $rule->selectors as $ruleSelector ){
 337                              $match = $selector->match($ruleSelector);
 338                              if( $match ){
 339                                  if( $selector->elements_len > $match ){
 340                                      $this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self));
 341                                  } else {
 342                                      $this->lookups[$key][] = $rule;
 343                                  }
 344                                  break;
 345                              }
 346                          }
 347                      }
 348                  }
 349              }
 350          }
 351  
 352          return $this->lookups[$key];
 353      }
 354  
 355  
 356      /**
 357       * @see Less_Tree::genCSS
 358       */
 359  	public function genCSS( $output ){
 360  
 361          if( !$this->root ){
 362              Less_Environment::$tabLevel++;
 363          }
 364  
 365          $tabRuleStr = $tabSetStr = '';
 366          if( !Less_Parser::$options['compress'] ){
 367              if( Less_Environment::$tabLevel ){
 368                  $tabRuleStr = "\n".str_repeat( Less_Parser::$options['indentation'] , Less_Environment::$tabLevel );
 369                  $tabSetStr = "\n".str_repeat( Less_Parser::$options['indentation'] , Less_Environment::$tabLevel-1 );
 370              }else{
 371                  $tabSetStr = $tabRuleStr = "\n";
 372              }
 373          }
 374  
 375  
 376          $ruleNodes = array();
 377          $rulesetNodes = array();
 378          foreach($this->rules as $rule){
 379  
 380              $class = get_class($rule);
 381              if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){
 382                  $rulesetNodes[] = $rule;
 383              }else{
 384                  $ruleNodes[] = $rule;
 385              }
 386          }
 387  
 388          // If this is the root node, we don't render
 389          // a selector, or {}.
 390          if( !$this->root ){
 391  
 392              /*
 393              debugInfo = tree.debugInfo(env, this, tabSetStr);
 394  
 395              if (debugInfo) {
 396                  output.add(debugInfo);
 397                  output.add(tabSetStr);
 398              }
 399              */
 400  
 401              $paths_len = count($this->paths);
 402              for( $i = 0; $i < $paths_len; $i++ ){
 403                  $path = $this->paths[$i];
 404                  $firstSelector = true;
 405  
 406                  foreach($path as $p){
 407                      $p->genCSS( $output, $firstSelector );
 408                      $firstSelector = false;
 409                  }
 410  
 411                  if( $i + 1 < $paths_len ){
 412                      $output->add( ',' . $tabSetStr );
 413                  }
 414              }
 415  
 416              $output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr );
 417          }
 418  
 419          // Compile rules and rulesets
 420          $ruleNodes_len = count($ruleNodes);
 421          $rulesetNodes_len = count($rulesetNodes);
 422          for( $i = 0; $i < $ruleNodes_len; $i++ ){
 423              $rule = $ruleNodes[$i];
 424  
 425              // @page{ directive ends up with root elements inside it, a mix of rules and rulesets
 426              // In this instance we do not know whether it is the last property
 427              if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){
 428                  Less_Environment::$lastRule = true;
 429              }
 430  
 431              $rule->genCSS( $output );
 432  
 433              if( !Less_Environment::$lastRule ){
 434                  $output->add( $tabRuleStr );
 435              }else{
 436                  Less_Environment::$lastRule = false;
 437              }
 438          }
 439  
 440          if( !$this->root ){
 441              $output->add( $tabSetStr . '}' );
 442              Less_Environment::$tabLevel--;
 443          }
 444  
 445          $firstRuleset = true;
 446          $space = ($this->root ? $tabRuleStr : $tabSetStr);
 447          for( $i = 0; $i < $rulesetNodes_len; $i++ ){
 448  
 449              if( $ruleNodes_len && $firstRuleset ){
 450                  $output->add( $space );
 451              }elseif( !$firstRuleset ){
 452                  $output->add( $space );
 453              }
 454              $firstRuleset = false;
 455              $rulesetNodes[$i]->genCSS( $output);
 456          }
 457  
 458          if( !Less_Parser::$options['compress'] && $this->firstRoot ){
 459              $output->add( "\n" );
 460          }
 461  
 462      }
 463  
 464  
 465  	function markReferenced(){
 466          if( !$this->selectors ){
 467              return;
 468          }
 469          foreach($this->selectors as $selector){
 470              $selector->markReferenced();
 471          }
 472      }
 473  
 474  	public function joinSelectors( $context, $selectors ){
 475          $paths = array();
 476          if( is_array($selectors) ){
 477              foreach($selectors as $selector) {
 478                  $this->joinSelector( $paths, $context, $selector);
 479              }
 480          }
 481          return $paths;
 482      }
 483  
 484  	public function joinSelector( &$paths, $context, $selector){
 485  
 486          $hasParentSelector = false;
 487  
 488          foreach($selector->elements as $el) {
 489              if( $el->value === '&') {
 490                  $hasParentSelector = true;
 491              }
 492          }
 493  
 494          if( !$hasParentSelector ){
 495              if( $context ){
 496                  foreach($context as $context_el){
 497                      $paths[] = array_merge($context_el, array($selector) );
 498                  }
 499              }else {
 500                  $paths[] = array($selector);
 501              }
 502              return;
 503          }
 504  
 505  
 506          // The paths are [[Selector]]
 507          // The first list is a list of comma seperated selectors
 508          // The inner list is a list of inheritance seperated selectors
 509          // e.g.
 510          // .a, .b {
 511          //   .c {
 512          //   }
 513          // }
 514          // == [[.a] [.c]] [[.b] [.c]]
 515          //
 516  
 517          // the elements from the current selector so far
 518          $currentElements = array();
 519          // the current list of new selectors to add to the path.
 520          // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
 521          // by the parents
 522          $newSelectors = array(array());
 523  
 524  
 525          foreach( $selector->elements as $el){
 526  
 527              // non parent reference elements just get added
 528              if( $el->value !== '&' ){
 529                  $currentElements[] = $el;
 530              } else {
 531                  // the new list of selectors to add
 532                  $selectorsMultiplied = array();
 533  
 534                  // merge the current list of non parent selector elements
 535                  // on to the current list of selectors to add
 536                  if( $currentElements ){
 537                      $this->mergeElementsOnToSelectors( $currentElements, $newSelectors);
 538                  }
 539  
 540                  // loop through our current selectors
 541                  foreach($newSelectors as $sel){
 542  
 543                      // if we don't have any parent paths, the & might be in a mixin so that it can be used
 544                      // whether there are parents or not
 545                      if( !$context ){
 546                          // the combinator used on el should now be applied to the next element instead so that
 547                          // it is not lost
 548                          if( $sel ){
 549                              $sel[0]->elements = array_slice($sel[0]->elements,0);
 550                              $sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo );
 551                          }
 552                          $selectorsMultiplied[] = $sel;
 553                      }else {
 554  
 555                          // and the parent selectors
 556                          foreach($context as $parentSel){
 557                              // We need to put the current selectors
 558                              // then join the last selector's elements on to the parents selectors
 559  
 560                              // our new selector path
 561                              $newSelectorPath = array();
 562                              // selectors from the parent after the join
 563                              $afterParentJoin = array();
 564                              $newJoinedSelectorEmpty = true;
 565  
 566                              //construct the joined selector - if & is the first thing this will be empty,
 567                              // if not newJoinedSelector will be the last set of elements in the selector
 568                              if( $sel ){
 569                                  $newSelectorPath = $sel;
 570                                  $lastSelector = array_pop($newSelectorPath);
 571                                  $newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) );
 572                                  $newJoinedSelectorEmpty = false;
 573                              }
 574                              else {
 575                                  $newJoinedSelector = $selector->createDerived(array());
 576                              }
 577  
 578                              //put together the parent selectors after the join
 579                              if ( count($parentSel) > 1) {
 580                                  $afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) );
 581                              }
 582  
 583                              if ( $parentSel ){
 584                                  $newJoinedSelectorEmpty = false;
 585  
 586                                  // join the elements so far with the first part of the parent
 587                                  $newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo);
 588  
 589                                  $newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) );
 590                              }
 591  
 592                              if (!$newJoinedSelectorEmpty) {
 593                                  // now add the joined selector
 594                                  $newSelectorPath[] = $newJoinedSelector;
 595                              }
 596  
 597                              // and the rest of the parent
 598                              $newSelectorPath = array_merge($newSelectorPath, $afterParentJoin);
 599  
 600                              // add that to our new set of selectors
 601                              $selectorsMultiplied[] = $newSelectorPath;
 602                          }
 603                      }
 604                  }
 605  
 606                  // our new selectors has been multiplied, so reset the state
 607                  $newSelectors = $selectorsMultiplied;
 608                  $currentElements = array();
 609              }
 610          }
 611  
 612          // if we have any elements left over (e.g. .a& .b == .b)
 613          // add them on to all the current selectors
 614          if( $currentElements ){
 615              $this->mergeElementsOnToSelectors($currentElements, $newSelectors);
 616          }
 617          foreach( $newSelectors as $new_sel){
 618              if( $new_sel ){
 619                  $paths[] = $new_sel;
 620              }
 621          }
 622      }
 623  
 624  	function mergeElementsOnToSelectors( $elements, &$selectors){
 625  
 626          if( !$selectors ){
 627              $selectors[] = array( new Less_Tree_Selector($elements) );
 628              return;
 629          }
 630  
 631  
 632          foreach( $selectors as &$sel){
 633  
 634              // if the previous thing in sel is a parent this needs to join on to it
 635              if( $sel ){
 636                  $last = count($sel)-1;
 637                  $sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) );
 638              }else{
 639                  $sel[] = new Less_Tree_Selector( $elements );
 640              }
 641          }
 642      }
 643  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1