* @copyright Copyright (c) 2010 PBM Web Development * @license http://phamlp.googlecode.com/files/license.txt * @package PHamlP * @subpackage Sass.tree */ /** * SassPropertyNode class. * Represents a CSS property. * @package PHamlP * @subpackage Sass.tree */ class SassPropertyNode extends SassNode { const MATCH_PROPERTY_NEW = '/^([^\s=:"]+)\s*(?:(= )|:)(.*?)$/'; const MATCH_PROPERTY_OLD = '/^:([^\s=:]+)(?:\s*(=)\s*|\s+|$)(.*)/'; const MATCH_PSUEDO_SELECTOR = '/^:?\w[-\w]+\(?/i'; const MATCH_INTERPOLATION = '/^#\{(.*?)\}/i'; const NAME = 1; const SCRIPT = 2; const VALUE = 3; const IS_SCRIPT = '= '; private static $psuedoSelectors = array( 'root', 'nth-child(', 'nth-last-child(', 'nth-of-type(', 'nth-last-of-type(', 'first-child', 'last-child', 'first-of-type', 'last-of-type', 'only-child', 'only-of-type', 'empty', 'link', 'visited', 'active', 'hover', 'focus', 'target', 'lang(', 'enabled', 'disabled', 'checked', ':first-line', ':first-letter', ':before', ':after', // CSS 2.1 'first-line', 'first-letter', 'before', 'after' ); /** * @var string property name */ private $name; /** * @var string property value or expression to evaluate */ private $value; /** * SassPropertyNode constructor. * @param object source token * @param string property syntax * @return SassPropertyNode */ public function __construct($token, $syntax = 'new') { parent::__construct($token); $matches = self::match($token, $syntax); $this->name = $matches[self::NAME]; $this->value = $matches[self::VALUE]; if ($matches[self::SCRIPT] === self::IS_SCRIPT) { $this->addWarning('Setting CSS properties with "=" is deprecated; use "{name}: {value};"', array('{name}'=>$this->name, '{value}'=>$this->value) ); } } /** * Parse this node. * If the node is a property namespace return all parsed child nodes. If not * return the parsed version of this node. * @param SassContext the context in which this node is parsed * @return array the parsed node */ public function parse($context) { $return = array(); if ($this->value) { $node = clone $this; $node->name = ($this->inNamespace() ? "{$this->namespace}-" : '') . $this->interpolate($this->name, $context); $node->value = $this->evaluate($this->interpolate($this->value, $context), $context, SassScriptParser::CSS_PROPERTY)->toString(); if (array_key_exists($node->name, $this->vendor_properties)) { foreach ($this->vendor_properties[$node->name] as $vendorProperty) { $_node = clone $node; $_node->name = $vendorProperty; $return[] = $_node; } } $return[] = $node; } if ($this->children) { $return = array_merge($return, $this->parseChildren($context)); } return $return; } /** * Render this node. * @return string the rendered node */ public function render() { return $this->renderer->renderProperty($this); } /** * Returns a value indicating if this node is in a namespace * @return boolean true if this node is in a property namespace, false if not */ public function inNamespace() { $parent = $this->parent; do { if ($parent instanceof SassPropertyNode) { return true; } $parent = $parent->parent; } while (is_object($parent)); return false; } /** * Returns the namespace for this node * @return string the namespace for this node */ protected function getNamespace() { $namespace = array(); $parent = $this->parent; do { if ($parent instanceof SassPropertyNode) { $namespace[] = $parent->name; } $parent = $parent->parent; } while (is_object($parent)); return join('-', array_reverse($namespace)); } /** * Returns the name of this property. * If the property is in a namespace the namespace is prepended * @return string the name of this property */ public function getName() { return $this->name; } /** * Returns the parsed value of this property. * @return string the parsed value of this property */ public function getValue() { return $this->value; } /** * Returns a value indicating if the token represents this type of node. * @param object token * @param string the property syntax being used * @return boolean true if the token represents this type of node, false if not */ public static function isa($token, $syntax) { $matches = self::match($token, $syntax); if (!empty($matches)) { if (isset($matches[self::VALUE]) && self::isPseudoSelector($matches[self::VALUE])) { return false; } if ($token->level === 0) { throw new SassPropertyNodeException('Properties can not be assigned at root level', array(), $this); } else { return true; } } else { return false; } } /** * Returns the matches for this type of node. * @param array the line to match * @param string the property syntax being used * @return array matches */ public static function match($token, $syntax) { switch ($syntax) { case 'new': preg_match(self::MATCH_PROPERTY_NEW, $token->source, $matches); break; case 'old': preg_match(self::MATCH_PROPERTY_OLD, $token->source, $matches); break; default: if (preg_match(self::MATCH_PROPERTY_NEW, $token->source, $matches) == 0) { preg_match(self::MATCH_PROPERTY_OLD, $token->source, $matches); } break; } return $matches; } /** * Returns a value indicating if the string starts with a pseudo selector. * This is used to reject pseudo selectors as property values as, for example, * "a:hover" and "text-decoration:underline" look the same to the property * match regex. * It will also match interpolation to allow for constructs such as * content:#{$pos} * @see isa() * @param string the string to test * @return bool true if the string starts with a pseudo selector, false if not */ private static function isPseudoSelector($string) { preg_match(self::MATCH_PSUEDO_SELECTOR, $string, $matches); return (isset($matches[0]) && in_array($matches[0], self::$psuedoSelectors)) || preg_match(self::MATCH_INTERPOLATION, $string); } }