You are here

class SassRuleNode in Sassy 7.3

Same name and namespace in other branches
  1. 7 phamlp/sass/tree/SassRuleNode.php \SassRuleNode

SassRuleNode class. Represents a CSS rule. @package PHamlP @subpackage Sass.tree

Hierarchy

Expanded class hierarchy of SassRuleNode

File

phpsass/tree/SassRuleNode.php, line 18

View source
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;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
SassNode::$children public property
SassNode::$parent public property 1
SassNode::$root public property
SassNode::$token public property
SassNode::addChild public function Adds a child to this node.
SassNode::addWarning public function Adds a warning to the node.
SassNode::evaluate public function Evaluates a SassScript expression.
SassNode::getChildren public function Returns the node's children
SassNode::getDebug_info public function Returns the debug_info option setting for this node
SassNode::getFilename public function Returns the filename for this node
SassNode::getLastChild public function Returns the last child node of this node.
SassNode::getLevel public function Returns the level of this node.
SassNode::getLine public function Returns the line number for this node
SassNode::getLine_numbers public function Returns the line_numbers option setting for this node
SassNode::getParent public function Returns the node's parent
SassNode::getParser public function Returns the Sass parser.
SassNode::getPropertySyntax public function Returns the property syntax being used.
SassNode::getRenderer public function Returns the renderer.
SassNode::getScript public function Returns the SassScript parser.
SassNode::getSource public function Returns the source for this node
SassNode::getStyle public function Returns the render style of the document tree.
SassNode::getVendor_properties public function Returns vendor specific properties
SassNode::hasChildren public function Returns a value indicating if this node has children
SassNode::hasParent public function Return a value indicating if this node has a parent
SassNode::inDirective public function Returns a value indicating whether this node is in a directive
SassNode::inSassScriptDirective public function Returns a value indicating whether this node is in a SassScript directive
SassNode::interpolate public function Replace interpolated SassScript contained in '#{}' with the parsed value.
SassNode::isa public static function Returns a value indicating if the token represents this type of node. 9
SassNode::isChildOf public function Returns a value indicating if this node is a child of the passed node. This just checks the levels of the nodes. If this node is at a greater level than the passed node if is a child of it.
SassNode::parseChildren public function Parse the children of the node.
SassNode::__clone public function Resets children when cloned
SassNode::__get public function Getter.
SassNode::__set public function Setter.
SassRuleNode::$isContinued private property
SassRuleNode::$parentSelectors private property
SassRuleNode::$resolvedSelectors private property
SassRuleNode::$selectors private property
SassRuleNode::addSelectors public function Adds selector(s) to the rule. If the selectors are to continue for the rule the selector must end in a comma
SassRuleNode::CONTINUED constant
SassRuleNode::explode private function 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.
SassRuleNode::extend public function 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
SassRuleNode::getIsContinued public function Returns a value indicating if the selectors for this rule are to be continued.
SassRuleNode::getParentSelectors protected function Returns the parent selector(s) for this node. This in an empty array if there is no parent selector.
SassRuleNode::getSelectors public function Returns the selectors
SassRuleNode::hasParentReference private function Determines if there is a parent reference in the selector
SassRuleNode::isPsuedo private function Tests whether the selector is a psuedo selector
SassRuleNode::isSequence private function Tests whether the selector is a sequence selector
SassRuleNode::MATCH constant
SassRuleNode::mergeSequence private function Merges selector sequences
SassRuleNode::parentReferencePos private function 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…
SassRuleNode::PARENT_REFERENCE constant @const string that is replaced with the parent node selector
SassRuleNode::parse public function Parse this node and its children into static nodes.
SassRuleNode::render public function Render this node and its children to CSS.
SassRuleNode::resolveParentReferences private function Resolves parent references in the selector
SassRuleNode::resolveSelectors public function Resolves selectors. Interpolates SassScript in selectors and resolves any parent references or appends the parent selectors.
SassRuleNode::SELECTOR constant
SassRuleNode::__construct public function SassRuleNode constructor. Overrides SassNode::__construct