[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |