PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/vendors/sass/SassParser.class.php

http://github.com/m3nt0r/chaml---cakephp-haml-sass-integration
PHP | 379 lines | 257 code | 13 blank | 109 comment | 40 complexity | acfdbf3ae969d4751190255f403ef752 MD5 | raw file
  1. <?php
  2. /**
  3. * Sass parser.
  4. *
  5. * @link http://haml.hamptoncatlin.com/ Original Sass parser (for Ruby)
  6. * @link http://phphaml.sourceforge.net/ Online documentation
  7. * @link http://sourceforge.net/projects/phphaml/ SourceForge project page
  8. * @license http://www.opensource.org/licenses/mit-license.php MIT (X11) License
  9. * @author Amadeusz Jasak <amadeusz.jasak@gmail.com>
  10. * @package phpHaml
  11. * @subpackage Sass
  12. */
  13. require_once dirname(__FILE__) . '/../common/CommonCache.class.php';
  14. require_once dirname(__FILE__) . '/../common/CommonParser.class.php';
  15. require_once dirname(__FILE__) . '/SassException.class.php';
  16. require_once dirname(__FILE__) . '/SassCalculator.interface.php';
  17. require_once dirname(__FILE__) . '/SassElement.class.php';
  18. require_once dirname(__FILE__) . '/SassElementsList.class.php';
  19. require_once dirname(__FILE__) . '/SassRenderer.class.php';
  20. /**
  21. * Sass parser.
  22. *
  23. * @link http://haml.hamptoncatlin.com/ Original Sass parser (for Ruby)
  24. * @link http://phphaml.sourceforge.net/ Online documentation
  25. * @link http://sourceforge.net/projects/phphaml/ SourceForge project page
  26. * @license http://www.opensource.org/licenses/mit-license.php MIT (X11) License
  27. * @author Amadeusz Jasak <amadeusz.jasak@gmail.com>
  28. * @package phpHaml
  29. * @subpackage Sass
  30. */
  31. class SassParser extends CommonParser
  32. {
  33. /**
  34. * Instance of SassParser
  35. *
  36. * @var SassParser
  37. */
  38. protected static $instance;
  39. /**
  40. * Parse Sass code
  41. *
  42. * @param string Source
  43. * @param array Array of constants
  44. * @return string
  45. */
  46. public static function sass($source, Array $constants = array())
  47. {
  48. if (!(self::$instance instanceof SassParser))
  49. self::$instance = new SassParser();
  50. return self::$instance->setSource($source)->render();
  51. }
  52. /**
  53. * The constructor
  54. *
  55. * @param string Path
  56. * @param string Temporary directory
  57. * @param string SassRenderer type
  58. */
  59. public function __construct($path = null, $tmp = null, $type = null)
  60. {
  61. if (!is_null($path))
  62. $this->setPath($path);
  63. if (is_string($tmp))
  64. $this->setTmp($tmp); else
  65. if (!is_null($path))
  66. $this->setTmp($path);
  67. else
  68. $this->setTmp(ini_get('session.save_path'));
  69. if (!is_null($type))
  70. $this->setRenderer($type);
  71. $this->setElements(new SassElementsList());
  72. }
  73. /**
  74. * Render Sass code
  75. *
  76. * @param string Filename
  77. * @param string SassRenderer type
  78. * @return string
  79. */
  80. public function render($file = null, $type = null)
  81. {
  82. if (!is_null($file))
  83. $this->setFile($file);
  84. if (!is_null($type))
  85. $this->setRenderer($type);
  86. $cache = new CommonCache($this->getTmp(), 'css', $this->getSource());
  87. if (!$cache->isCached())
  88. {
  89. // BEGIN: Flat to tree structure
  90. $lines = explode(self::TOKEN_LINE, $this->getSource());
  91. $last = array(-1 => null);
  92. $attributes = array();
  93. $attributeLevel = 0;
  94. $namespace = null;
  95. $namespaceLevel = 0;
  96. foreach ($lines as $line)
  97. {
  98. $stop = true;
  99. $cleaned = $this->cleanLine($line);
  100. $level = $this->countLevel($line);
  101. // Check for constant definition
  102. if (preg_match('/^'.preg_quote(self::TOKEN_CONSTANT).'(.+?)[ ]?'.preg_quote(self::TOKEN_CONSTANT_VALUE).'[ ]?["]?(.+)["]?/', $cleaned, $matches))
  103. $this->setConstant($matches[1], $matches[2]); else
  104. // Check for dynamic attribute definition
  105. if (preg_match('/^'.preg_quote(self::TOKEN_ATTRIBUTE).'(.+?)[ ]?'.preg_quote(self::TOKEN_ATTRIBUTE_CALCULATE ).'[ ]?(.+)/', $cleaned, $matches))
  106. {
  107. if ($namespaceLevel+1 != $level)
  108. $namespace = null;
  109. $attributes[empty($namespace) ? $matches[1] : "$namespace-{$matches[1]}"] = $this->calculate($matches[2]);
  110. $attributeLevel = $level - (empty($namespace) ? 1 : 2);
  111. } else
  112. // Check for attribute definition
  113. if (preg_match('/^'.preg_quote(self::TOKEN_ATTRIBUTE).'(.+?) (.+)/', $cleaned, $matches))
  114. {
  115. if ($namespaceLevel+1 != $level)
  116. $namespace = null;
  117. $attributes[empty($namespace) ? $matches[1] : "$namespace-{$matches[1]}"] = $matches[2];
  118. $attributeLevel = $level - (empty($namespace) ? 1 : 2);
  119. } else
  120. // Check for attribute namespace
  121. if (preg_match('/^'.preg_quote(self::TOKEN_ATTRIBUTE_NAMESPACE).'(.+)/', $cleaned, $matches))
  122. {
  123. $namespace = $matches[1];
  124. $namespaceLevel = $level;
  125. } else
  126. // Check for comment
  127. if (preg_match('|^'.preg_quote(self::TOKEN_COMMENT).'|', $cleaned))
  128. $stop = true;
  129. // Nothing special
  130. else
  131. {
  132. $stop = false;
  133. $namespace = null;
  134. }
  135. // Remove blank lines
  136. if (empty($cleaned) || $stop)
  137. continue;
  138. // Assign attributes
  139. if (!empty($attributes) && $last[$attributeLevel] instanceof SassElement)
  140. {
  141. foreach ($attributes as $name => $value)
  142. $last[$attributeLevel]->setAttribute($name, $value);
  143. $attributeLevel = 0;
  144. $attributes = array();
  145. }
  146. // Get parent element
  147. $parent = $last[$level - 1];
  148. // Create new element
  149. $element = new SassElement($parent, $cleaned);
  150. // Mark as parent
  151. $last[$level] = $element;
  152. if ($level == 0)
  153. $this->addElement($element);
  154. }
  155. // Assign attributes
  156. if (!empty($attributes) && $last[$attributeLevel] instanceof SassElement)
  157. {
  158. foreach ($attributes as $name => $value)
  159. $last[$attributeLevel]->setAttribute($name, $value);
  160. $attributeLevel = 0;
  161. $attributes = array();
  162. }
  163. // END: Flat to tree structure
  164. // Render to CSS
  165. return $cache->setCached(SassRenderer::getInstance($this->getElements(), $this->getRenderer())->render())->cacheIt()->getCached();
  166. }
  167. else
  168. return $cache->getCached();
  169. }
  170. /**
  171. * List of constants assigned to parser
  172. *
  173. * @var array
  174. */
  175. protected $constants;
  176. /**
  177. * Get the constants. List of constants assigned to parser
  178. *
  179. * @return array
  180. */
  181. public function getConstants()
  182. {
  183. return $this->constants;
  184. }
  185. /**
  186. * Set the constants. List of constants assigned to parser
  187. *
  188. * @param array Constants data
  189. * @return object
  190. */
  191. public function setConstants($constants)
  192. {
  193. $this->constants = $constants;
  194. return $this;
  195. }
  196. /**
  197. * Set constant
  198. *
  199. * @param string Name
  200. * @param mixed Data
  201. * @return object
  202. */
  203. public function setConstant($name, $value)
  204. {
  205. $this->constants[$name] = $value;
  206. return $this;
  207. }
  208. /**
  209. * Calculate attribute data
  210. *
  211. * @param string Expression
  212. * @return string Result
  213. */
  214. public function calculate($expression)
  215. {
  216. // Replace constants with values
  217. foreach ($this->getConstants() as $name => $value)
  218. $expression = str_ireplace(self::TOKEN_CONSTANT.$name, $value, $expression);
  219. // Replace colors names with RGB codes
  220. foreach (self::$colors as $name => $rgb)
  221. $expression = str_ireplace($name, 'rgb('.implode(', ', $rgb).')', $expression);
  222. // Replace colors representation in HEX to rgb()
  223. $expression = preg_replace_callback('/#(['.self::HEX.']{6})|#(['.self::HEX.']{3})/i', array($this, 'hex2rgb'), $expression);
  224. $right_left = $last_left = 0;
  225. $rgb = $is_string = false;
  226. for ($i = 0; $i < strlen($expression); $i++)
  227. {
  228. $token = $expression{$i};
  229. if ($token == self::TOKEN_STRING && $is_string)
  230. $is_string = false; else
  231. if ($token == self::TOKEN_STRING)
  232. $is_string = true; else
  233. if ($token == self::TOKEN_LEFT_BRACKET)
  234. {
  235. $last_left = $i;
  236. $rgb = substr($expression, $i-3, 3) == 'rgb';
  237. } else
  238. if ($token == self::TOKEN_RIGHT_BRACKET)
  239. {
  240. if ($rgb)
  241. $rgb = false;
  242. else
  243. {
  244. $last_right = $i;
  245. $expression = str_replace('+-', '-', str_replace('--', '+', str_replace(substr($expression, $last_left, $last_right - $last_left + 1), $this->rcalc(substr($expression, $last_left+1, $last_right - $last_left-1)), $expression)));
  246. $i = 0;
  247. }
  248. }
  249. }
  250. $expression = $is_string ? trim($expression, self::TOKEN_STRING) : $this->rcalc($expression);
  251. return $expression;
  252. }
  253. /**
  254. * Execute opearation on numbers, colors, strings
  255. *
  256. * @param mixed Value
  257. * @param mixed Other
  258. * @param mixed Operation (add, sub, mul, div)
  259. * @return string
  260. */
  261. public function calc_call($value, $other, $operation)
  262. {
  263. $value = $this->rcalc($value);
  264. $other = $this->rcalc($other);
  265. if (preg_match('/^[-0-9.]+$/', $value))
  266. $v = new NumberSassCalculator(trim($value)); else
  267. if (preg_match('/^rgb\((.+?)\)$/i', $value))
  268. $v = new ColorSassCalculator($value); else
  269. if (preg_match('/([-0-9.]+)([a-z]{1,2})/i', $value))
  270. $v = new LengthSassCalculator($value);
  271. else
  272. $v = new StringSassCalculator($value);
  273. return str_replace('+-', '-', str_replace('--', '+', call_user_func(array($v, $operation), $other)->get()));;
  274. }
  275. /**
  276. * Simple calculator (without parenthesis)
  277. *
  278. * @param string Expression
  279. * @return string
  280. */
  281. public function rcalc($e)
  282. {
  283. $e = str_replace('+-', '-', str_replace('--', '+', trim($e)));
  284. $operands = self::TOKEN_ADDITION.'\\'.self::TOKEN_SUBTRACTION.self::TOKEN_MULTIPLICATION.self::TOKEN_DIVISION;
  285. $e = preg_replace("#\s*([$operands])\s*#", '$1', $e);
  286. if (preg_match("#(.+?)\+(.+)#", $e, $m))
  287. $e = $this->calc_call($m[1], $m[2], 'add');
  288. if (preg_match("#([^$operands]+?)-(.+)#", $e, $m))
  289. $e = $this->calc_call($m[1], $m[2], 'sub');
  290. if (preg_match("#(.+?)\*(.+)#", $e, $m))
  291. $e = $this->calc_call($m[1], $m[2], 'mul');
  292. if (preg_match("#(.+?)/(.+)#", $e, $m))
  293. $e = $this->calc_call($m[1], $m[2], 'div');
  294. return $e;
  295. }
  296. /**
  297. * Replace HEX color representation
  298. * to CSS rgb() function
  299. *
  300. * @param string HEX color (3 or 6 letters)
  301. * @return string
  302. */
  303. public function hex2rgb($m)
  304. {
  305. $hex = $hex2 = is_array($m) ? $m[1] : $m;
  306. if (strlen($hex2) == 3)
  307. for ($i = 0; $i < 3; $i++)
  308. $hex{$i} = $hex{$i+1} = $hex2{$i};
  309. $r = hexdec(substr($hex, 0, 2));
  310. $g = hexdec(substr($hex, 2, 2));
  311. $b = hexdec(substr($hex, 4, 2));
  312. $rgb = "rgb($r, $g, $b)";
  313. return $rgb;
  314. }
  315. /**
  316. * Hex characters
  317. *
  318. * @var string
  319. */
  320. const HEX = '0123456789ABCDEF';
  321. /**
  322. * RGB representation of colors by names
  323. *
  324. * @var array
  325. */
  326. public static $colors = array
  327. (
  328. // Name Red Green Blue
  329. 'aqua' => array(0, 255, 255),
  330. 'black' => array(0, 0, 0),
  331. 'blue' => array(0, 0, 255),
  332. 'fuchsia' => array(255, 0, 255),
  333. 'gray' => array(128, 128, 128),
  334. 'green' => array(0, 128, 0),
  335. 'lime' => array(0, 255, 0),
  336. 'maroon' => array(128, 0, 0),
  337. 'navy' => array(0, 0, 128),
  338. 'olive' => array(128, 128, 0),
  339. 'purple' => array(128, 0, 128),
  340. 'red' => array(255, 0, 0),
  341. 'silver' => array(192, 192, 192),
  342. 'teal' => array(0, 128, 128),
  343. 'white' => array(255, 255, 255),
  344. 'yellow' => array(255, 255, 0)
  345. );
  346. const TOKEN_LINE = "\n";
  347. const TOKEN_COMMENT = '/';
  348. const TOKEN_ATTRIBUTE = ':';
  349. const TOKEN_ATTRIBUTE_NAMESPACE = ':';
  350. const TOKEN_CONSTANT = '!';
  351. const TOKEN_CONSTANT_VALUE = '=';
  352. const TOKEN_ATTRIBUTE_CALCULATE = '=';
  353. const TOKEN_MULTIPLICATION = '*';
  354. const TOKEN_DIVISION = '/';
  355. const TOKEN_ADDITION = '+';
  356. const TOKEN_SUBTRACTION = '-';
  357. const TOKEN_LEFT_BRACKET = '(';
  358. const TOKEN_RIGHT_BRACKET = ')';
  359. const TOKEN_STRING = '"';
  360. }
  361. ?>