You are here

class SassRuleNode in Sassy 7

Same name and namespace in other branches
  1. 7.3 phpsass/tree/SassRuleNode.php \SassRuleNode

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

Hierarchy

Expanded class hierarchy of SassRuleNode

File

phamlp/sass/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);
          }
        }
      }
    }
  }

  /**
   * 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();
    while ($extender[0] === $selector[0]) {
      $common[] = array_shift($selector);
      array_shift($extender);
    }
    $begining = !empty($common) ? join(' ', $common) . ' ' : '';
    return array(
      $begining . join(' ', $selector) . ' ' . join(' ', $extender) . $end,
      $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', array(), $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 protected property *
SassNode::$parent protected property *
SassNode::$root protected property *
SassNode::$token protected property *
SassNode::addChild public function * Adds a child to this node. *
SassNode::addWarning public function * Adds a warning to the node. *
SassNode::evaluate protected function * Evaluates a SassScript expression. *
SassNode::getChildren public function * Returns the node's children *
SassNode::getDebug_info private function * Returns the debug_info option setting for this node *
SassNode::getFilename private function * Returns the filename for this node *
SassNode::getLastChild public function * Returns the last child node of this node. *
SassNode::getLevel private function * Returns the level of this node. *
SassNode::getLine private function * Returns the line number for this node *
SassNode::getLine_numbers private 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 private function * Returns the source for this node *
SassNode::getStyle public function * Returns the render style of the document tree. *
SassNode::getVendor_properties private 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 protected 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. * 7
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 protected 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.…
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