SassRuleNode.php in Sassy 7.3
File
phpsass/tree/SassRuleNode.phpView source
<?php
/* SVN FILE: $Id$ */
/**
* SassRuleNode class file.
* @author Chris Yates <chris.l.yates@gmail.com>
* @copyright Copyright (c) 2010 PBM Web Development
* @license http://phamlp.googlecode.com/files/license.txt
* @package PHamlP
* @subpackage Sass.tree
*/
/**
* SassRuleNode class.
* Represents a CSS rule.
* @package PHamlP
* @subpackage Sass.tree
*/
class SassRuleNode extends SassNode {
const MATCH = '/^(.+?)(?:\\s*\\{)?$/';
const SELECTOR = 1;
const CONTINUED = ',';
/**
* @const string that is replaced with the parent node selector
*/
const PARENT_REFERENCE = '&';
/**
* @var array selector(s)
*/
private $selectors = array();
/**
* @var array parent selectors
*/
private $parentSelectors = array();
/**
* @var array resolved selectors
*/
private $resolvedSelectors = array();
/**
* @var boolean whether the node expects more selectors
*/
private $isContinued;
/**
* SassRuleNode constructor.
* @param object source token
* @param string rule selector
* @return SassRuleNode
*/
public function __construct($token) {
parent::__construct($token);
preg_match(self::MATCH, $token->source, $matches);
$this
->addSelectors($matches[SassRuleNode::SELECTOR]);
}
/**
* Adds selector(s) to the rule.
* If the selectors are to continue for the rule the selector must end in a comma
* @param string selector
*/
public function addSelectors($selectors) {
$this->isContinued = substr($selectors, -1) === self::CONTINUED;
$this->selectors = array_merge($this->selectors, $this
->explode($selectors));
}
/**
* Returns a value indicating if the selectors for this rule are to be continued.
* @param boolean true if the selectors for this rule are to be continued,
* false if not
*/
public function getIsContinued() {
return $this->isContinued;
}
/**
* Parse this node and its children into static nodes.
* @param SassContext the context in which this node is parsed
* @return array the parsed node and its children
*/
public function parse($context) {
$node = clone $this;
$node->selectors = $this
->resolveSelectors($context);
$node->children = $this
->parseChildren($context);
return array(
$node,
);
}
/**
* Render this node and its children to CSS.
* @return string the rendered node
*/
public function render() {
$this
->extend();
$rules = '';
$properties = array();
foreach ($this->children as $child) {
$child->parent = $this;
if ($child instanceof SassRuleNode) {
$rules .= $child
->render();
}
else {
$properties[] = $child
->render();
}
}
// foreach
return $this->renderer
->renderRule($this, $properties, $rules);
}
/**
* Extend this nodes selectors
* $extendee is the subject of the @extend directive
* $extender is the selector that contains the @extend directive
* $selector a selector or selector sequence that is to be extended
*/
public function extend() {
foreach ($this->root->extenders as $extendee => $extenders) {
if ($this
->isPsuedo($extendee)) {
$extendee = explode(':', $extendee);
$pattern = preg_quote($extendee[0]) . '((\\.[-\\w]+)*):' . preg_quote($extendee[1]);
}
else {
$pattern = preg_quote($extendee);
}
foreach (preg_grep('/' . $pattern . '/', $this->selectors) as $selector) {
foreach ($extenders as $extender) {
if (is_array($extendee)) {
$this->selectors[] = preg_replace('/(.*?)' . $pattern . '$/', "\\1{$extender}\\2", $selector);
}
elseif ($this
->isSequence($extender) || $this
->isSequence($selector)) {
$this->selectors = array_merge($this->selectors, $this
->mergeSequence($extender, $selector));
}
else {
$this->selectors[] = str_replace($extendee, $extender, $selector);
}
}
}
$this->selectors = array_unique($this->selectors);
}
}
/**
* Tests whether the selector is a psuedo selector
* @param string selector to test
* @return boolean true if the selector is a psuedo selector, false if not
*/
private function isPsuedo($selector) {
return strpos($selector, ':') !== false;
}
/**
* Tests whether the selector is a sequence selector
* @param string selector to test
* @return boolean true if the selector is a sequence selector, false if not
*/
private function isSequence($selector) {
return strpos($selector, ' ') !== false;
}
/**
* Merges selector sequences
* @param string the extender selector
* @param string selector to extend
* @return array the merged sequences
*/
private function mergeSequence($extender, $selector) {
$extender = explode(' ', $extender);
$end = array_pop($extender);
$selector = explode(' ', $selector);
array_pop($selector);
$common = array();
if (count($extender) && count($selector)) {
while (trim($extender[0]) === trim($selector[0])) {
$common[] = array_shift($selector);
array_shift($extender);
if (!count($extender)) {
break;
}
}
}
$begining = !empty($common) ? join(' ', $common) . ' ' : '';
# Richard Lyon - 2011-10-25 - removes duplicates by uniquing and trimming.
# regex removes whitespace from start and and end of string as well as removing
# whitespace following whitespace. slightly quicker than a trim and simpler replace
return array_unique(array(
preg_replace('/(^\\s+|(\\s)\\s+|\\s+$)/', '$2', $begining . join(' ', $selector) . ' ' . join(' ', $extender) . ' ' . $end),
preg_replace('/(^\\s+|(\\s)\\s+|\\s+$)/', '$2', $begining . join(' ', $extender) . ' ' . join(' ', $selector) . ' ' . $end),
));
}
/**
* Returns the selectors
* @return array selectors
*/
public function getSelectors() {
return $this->selectors;
}
/**
* Resolves selectors.
* Interpolates SassScript in selectors and resolves any parent references or
* appends the parent selectors.
* @param SassContext the context in which this node is parsed
*/
public function resolveSelectors($context) {
$resolvedSelectors = array();
$this->parentSelectors = $this
->getParentSelectors($context);
foreach ($this->selectors as $key => $selector) {
$selector = $this
->interpolate($selector, $context);
//$selector = $this->evaluate($this->interpolate($selector, $context), $context)->toString();
if ($this
->hasParentReference($selector)) {
$resolvedSelectors = array_merge($resolvedSelectors, $this
->resolveParentReferences($selector, $context));
}
elseif ($this->parentSelectors) {
foreach ($this->parentSelectors as $parentSelector) {
$resolvedSelectors[] = "{$parentSelector} {$selector}";
}
// foreach
}
else {
$resolvedSelectors[] = $selector;
}
}
// foreach
sort($resolvedSelectors);
return $resolvedSelectors;
}
/**
* Returns the parent selector(s) for this node.
* This in an empty array if there is no parent selector.
* @return array the parent selector for this node
*/
protected function getParentSelectors($context) {
$ancestor = $this->parent;
while (!$ancestor instanceof SassRuleNode && $ancestor
->hasParent()) {
$ancestor = $ancestor->parent;
}
if ($ancestor instanceof SassRuleNode) {
return $ancestor
->resolveSelectors($context);
}
return array();
}
/**
* Returns the position of the first parent reference in the selector.
* If there is no parent reference in the selector this function returns
* boolean FALSE.
* Note that the return value may be non-Boolean that evaluates to FALSE,
* i.e. 0. The return value should be tested using the === operator.
* @param string selector to test
* @return mixed integer: position of the the first parent reference,
* boolean: false if there is no parent reference.
*/
private function parentReferencePos($selector) {
$inString = '';
for ($i = 0, $l = strlen($selector); $i < $l; $i++) {
$c = $selector[$i];
if ($c === self::PARENT_REFERENCE && empty($inString)) {
return $i;
}
elseif (empty($inString) && ($c === '"' || $c === "'")) {
$inString = $c;
}
elseif ($c === $inString) {
$inString = '';
}
}
return false;
}
/**
* Determines if there is a parent reference in the selector
* @param string selector
* @return boolean true if there is a parent reference in the selector
*/
private function hasParentReference($selector) {
return $this
->parentReferencePos($selector) !== false;
}
/**
* Resolves parent references in the selector
* @param string selector
* @return string selector with parent references resolved
*/
private function resolveParentReferences($selector, $context) {
$resolvedReferences = array();
if (!count($this->parentSelectors)) {
throw new SassRuleNodeException('Can not use parent selector (' . self::PARENT_REFERENCE . ') when no parent selectors', $this);
}
foreach ($this
->getParentSelectors($context) as $parentSelector) {
$resolvedReferences[] = str_replace(self::PARENT_REFERENCE, $parentSelector, $selector);
}
return $resolvedReferences;
}
/**
* Explodes a string of selectors into an array.
* We can't use PHP::explode as this will potentially explode attribute
* matches in the selector, e.g. div[title="some,value"] and interpolations.
* @param string selectors
* @return array selectors
*/
private function explode($string) {
$selectors = array();
$inString = false;
$interpolate = false;
$selector = '';
for ($i = 0, $l = strlen($string); $i < $l; $i++) {
$c = $string[$i];
if ($c === self::CONTINUED && !$inString && !$interpolate) {
$selectors[] = trim($selector);
$selector = '';
}
else {
$selector .= $c;
if ($c === '"' || $c === "'") {
do {
$_c = $string[++$i];
$selector .= $_c;
} while ($_c !== $c);
}
elseif ($c === '#' && $string[$i + 1] === '{') {
do {
$c = $string[++$i];
$selector .= $c;
} while ($c !== '}');
}
}
}
if (!empty($selector)) {
$selectors[] = trim($selector);
}
return $selectors;
}
}
Classes
Name | Description |
---|---|
SassRuleNode | SassRuleNode class. Represents a CSS rule. @package PHamlP @subpackage Sass.tree |