PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/library/css/css.php

http://github.com/pateketrueke/tetlphp
PHP | 522 lines | 431 code | 50 blank | 41 comment | 10 complexity | 206d0cc300ca2c54602007ed241e69fe MD5 | raw file
  1. <?php
  2. /**
  3. * CSS manipulation library
  4. */
  5. class css extends prototype
  6. {
  7. /**#@+
  8. * @ignore
  9. */
  10. // output
  11. private static $css = array();
  12. // attributes
  13. private static $sets = array();
  14. private static $props = array();
  15. private static $mixins = array();
  16. private static $imports = array();
  17. // hackish conversion
  18. private static $fixate_array_expr = array(
  19. '/\s*(@[\w\-]+)\s+([^\{\};]+);|\s*([^:\r\n]+)\s*:\s*([^\r\n;]+);/m' => "\ntrim('\\1\\3!').mt_rand()=>'\\2\\4',",
  20. '/\s*([^\r\n;]+)\s*(:?)\s*\{/m' => "\ntrim('\\1\\2!').mt_rand()=>array(",
  21. '/([^;]+);/m' => '\\1',
  22. '/(?<!\w)\}/' => '),',
  23. '/[\r\n]+/' => "\n",
  24. );
  25. // formatting
  26. private static $fixate_css_expr = array(
  27. '/[\s\r\n\t]*,+[\s\r\n\t]*/s' => ',',
  28. '/;\s*(?=[#@\w\-]+\s*:?)/' => ";\n",
  29. '/;?[\s\r\n\t]*\}/s' => ";\n}",
  30. '/[\s\r\n\t]+\{/s' => ' {',
  31. '/\{(?=\S)/' => "{\n",
  32. '/:\s+\{/' => ':{',
  33. );
  34. // defaults
  35. protected static $defs = array(
  36. 'path' => APP_PATH,
  37. 'extension' => '.css',
  38. );
  39. /**#@-*/
  40. /**
  41. * Assign properties
  42. *
  43. * @param array Hash
  44. * @return void
  45. */
  46. final public static function assign(array $vars) {
  47. foreach ($vars as $key => $val) {
  48. static::$props[$key] = $val;
  49. }
  50. }
  51. /**
  52. * Render file
  53. *
  54. * @param string Path
  55. * @return void
  56. */
  57. final public static function render($path) {
  58. return static::parse(static::load_file($path));
  59. }
  60. /**
  61. * Parse expression
  62. *
  63. * @param string CSS rules
  64. * @return void
  65. */
  66. final public static function parse($rules) {
  67. static::$css =
  68. static::$sets =
  69. static::$props =
  70. static::$mixins =
  71. static::$imports = array();
  72. static::parse_buffer($rules);
  73. static::build_properties(static::$sets);
  74. foreach (static::$sets as $key => $set) {
  75. static::$css []= static::build_rules($set, $key);
  76. }
  77. $text = join("\n", static::$css);
  78. $text = preg_replace('/\b(\w+)\!\(([^\(\)]+)\)/is', '\\1(\\2)', $text);
  79. $text = preg_replace('/\b0(?:p[xtc]|e[xm]|[cm]m|in|%)/', 0, $text);
  80. $text = preg_replace('/\b0+(?=\.)|\s+&/', '', $text);
  81. $text = preg_replace('/ +/', ' ', $text);
  82. return $text;
  83. }
  84. /**
  85. * Solve URLs
  86. *
  87. * @param string Path
  88. * @return string
  89. */
  90. final public static function path($path) {
  91. if ( ! is_url($path)) {
  92. $root = static::$defs['path'];
  93. $path = str_replace(array('\\', '/'), DS, $path);
  94. $path = preg_replace(sprintf('/^\.%s/', preg_quote(DS, '/')), $root, $path);
  95. while (substr($path, 0, 3) === '..'.DS) {
  96. $path = substr($path, 2);
  97. $root = dirname($root);
  98. }
  99. $abs = APP_PATH.DS.ltrim($path, DS);
  100. $path = is_file($abs) ? $abs : $root.DS.ltrim($path, DS);
  101. }
  102. return $path;
  103. }
  104. /**#@+
  105. * @ignore
  106. */
  107. // load file
  108. final private static function load_file($path, $parse = FALSE) {
  109. if ( ! is_file($path)) {
  110. $path = static::path($path);
  111. if (is_false(strrpos(basename($path), '.'))) {
  112. $path .= static::$defs['extension'];
  113. }
  114. }
  115. if ( ! is_file($path)) {
  116. raise(ln('file_not_exists', array('name' => $path)));
  117. }
  118. $text = read($path);
  119. if (is_true($parse)) {
  120. static::parse_buffer($text);
  121. }
  122. return $text;
  123. }
  124. // internal file append
  125. final private static function add_file($path, $parse = FALSE) {
  126. $text = static::load_file($path, $parse);
  127. if (is_false($parse)) {
  128. static::$css []= $text;
  129. }
  130. }
  131. // parse external rules
  132. final private static function fetch_externals($match) {
  133. switch ($match[1]) {
  134. case 'require';
  135. $inc_file = static::$defs['path'].DS.$match[3];
  136. $inc_file .= static::$defs['extension'];
  137. if ( ! is_file($inc_file)) {
  138. raise(ln('file_not_exists', array('name' => $inc_file)));
  139. }
  140. static::add_file($inc_file, TRUE);
  141. break;
  142. case 'use';
  143. if (in_array($match[3], static::$imports)) {
  144. break;
  145. }
  146. static::$imports []= $match[3];
  147. $css_file = __DIR__.DS.'assets'.DS.'styles'.DS."$match[3].css";
  148. if ( ! is_file($css_file)) {
  149. raise(ln('file_not_exists', array('name' => $css_file)));
  150. }
  151. static::add_file($css_file, TRUE);
  152. break;
  153. default;
  154. return static::load_file($match[3], FALSE);
  155. break;
  156. }
  157. }
  158. // set properties callback
  159. final private static function fetch_properties($match) {
  160. static::$props[$match[1]] = $match[2];
  161. }
  162. // fetch blocks callback
  163. final private static function fetch_blocks($match) {
  164. $test = explode('<', $match[1]);
  165. $part = trim(array_shift($test));
  166. if (substr($part, 0, 6) === '@mixin') {
  167. $args = array();
  168. $params = trim(substr($part, 7));
  169. if ($offset = strpos($params, '(')) {
  170. $parts = substr($params, $offset);
  171. $name = substr($params, 0, $offset);
  172. foreach (explode(',', substr($parts, 1, -1)) as $val) {
  173. if ( ! empty($val)) {
  174. @list($key, $val) = explode(':', $val);
  175. $args[substr($key, 1)] = trim($val);
  176. }
  177. }
  178. } else {
  179. $name = end(explode(' ', trim($match[1])));
  180. }
  181. static::$mixins[$name]['props'] = static::parse_properties($match[2]);
  182. static::$mixins[$name]['args'] = $args;
  183. } else {
  184. $props = static::parse_properties($match[2]);
  185. $parent = array_map('trim', array_filter($test));
  186. static::$sets[$part] = array();
  187. if ( ! empty($parent)) {
  188. foreach (array_keys(static::$sets) as $key) {
  189. if (in_array($key, $parent)) {
  190. static::$sets[$part] += static::$sets[$key];
  191. }
  192. }
  193. }
  194. static::$sets[$part] += static::parse_properties($match[2]);
  195. }
  196. }
  197. // parse entire buffer
  198. final private static function parse_buffer($text) {
  199. $text = preg_replace('/\/\*(.+?)\*\//s', '', $text);
  200. $text = preg_replace('/^(?:\/\/|;).+?$/m', '', $text);
  201. $text = preg_replace(array_keys(static::$fixate_css_expr), static::$fixate_css_expr, $text);
  202. $text = preg_replace_callback('/@(import|require|use)\s+([\'"]?)([^;\s]+)\\2;?/s', get_class() . '::fetch_externals', $text);
  203. $text = preg_replace_callback('/^\s*\$([a-z][$\w\d-]*)\s*=\s*(.+?)\s*;?\s*$/mi', get_class() . '::fetch_properties', $text);
  204. $depth = 0;
  205. $buffer = '';
  206. $length = strlen($text);
  207. $hash = uniqid('--block-mark');
  208. $regex = "/([^\r\n;\{\}]+)\{\[{$hash}#(.*?)#{$hash}\]\}/is";
  209. for ($i = 0; $i < $length; $i += 1) {
  210. switch ($char = substr($text, $i, 1)) {
  211. case '{';
  212. $buffer .= ++$depth === 1 ? "{[{$hash}#" : $char;
  213. break;
  214. case '}';
  215. $buffer .= --$depth <= 0 ? "#{$hash}]}" : $char;
  216. break;
  217. default;
  218. $buffer .= $char;
  219. break;
  220. }
  221. }
  222. preg_replace_callback($regex, get_class() . '::fetch_blocks', $buffer);
  223. }
  224. // hackish properties parsing
  225. final private static function parse_properties($text) {
  226. $out = array();
  227. $text = str_replace("'", "\'", $text);
  228. $text = preg_replace(array_keys(static::$fixate_array_expr), static::$fixate_array_expr, $text);
  229. @eval("\$out = array($text);");
  230. return $out;
  231. }
  232. // build css properties
  233. final private static function build_properties($set, $parent = '') {
  234. foreach ($set as $key => $val) {
  235. $key = preg_replace('/!\d*$/', '', $key);
  236. if (is_array($val)) {//FIX
  237. static::build_properties($val, trim("$parent $key"));
  238. } else {
  239. switch($key) {
  240. case '@extend';
  241. foreach (array_filter(explode(',', $val)) as $part) {
  242. if ( ! empty(static::$sets[$part])) {
  243. static::$sets[$part]['@children'] = $parent;
  244. }
  245. }
  246. break;
  247. case '@include';
  248. $mix = static::do_mixin($val);
  249. static::build_properties($mix, $parent);
  250. $old = isset(static::$sets[$parent]) ? static::$sets[$parent] : array();
  251. static::$sets[$parent] = array_merge($old, $mix);
  252. break;
  253. default;
  254. break;
  255. }
  256. }
  257. }
  258. }
  259. // build css rules
  260. final private static function build_rules($set, $parent = '') {
  261. $out = array();
  262. foreach ($set as $key => $val) {
  263. $key = preg_replace('/!\d*(:|)$/', '\\1', $key);
  264. if (substr($key, -1) === ':') {
  265. $out []= static::make_properties($val, $key);
  266. } elseif (is_array($val)) {
  267. if (substr($parent, 0, 1) === '@') {
  268. $out []= static::build_rules($val, $key);
  269. } elseif ($tmp = static::build_rules($val, "$parent $key")) {
  270. static::$css []= $tmp;
  271. }
  272. } elseif (substr($key, 0, 1) <> '@') {
  273. $out []= " $key: $val;";
  274. }
  275. }
  276. if ( ! empty($out)) {
  277. if ( ! empty($set['@children'])) {
  278. $parent .= ',' . join(',', $set['@children']);
  279. }
  280. $parent = preg_replace('/\s+:/', ':', wordwrap(str_replace(',', ', ', $parent)));
  281. return static::do_solve(sprintf("$parent {\n%s\n}", join("\n", $out)));
  282. }
  283. }
  284. // build raw-deep properties
  285. final private static function make_properties($test, $old = '') {
  286. if ( ! is_array($test)) {
  287. return " $old $test;";
  288. }
  289. $out = array();
  290. foreach ($test as $key => $val) {
  291. $val = trim($val);
  292. $key = preg_replace('/!\d*(:|)$/', '\\1', $key);
  293. if ( ! is_array($val)) {
  294. $out []= str_replace(':', '-', " $old$key: $val;");
  295. } else {
  296. $out []= static::make_properties($val, $old . $key);
  297. }
  298. }
  299. $out = join("\n", $out);
  300. $out = str_replace('- ', ': ', $out);
  301. return $out;
  302. }
  303. // replace variables
  304. final private static function do_vars($test, $set) {
  305. static $repl = 'isset($set["\\1"])?$set["\\1"]:NULL;';
  306. if (is_array($test)) {
  307. foreach ($test as $key => $val) {
  308. $test[$key] = static::do_vars($val, $set);
  309. }
  310. return $test;
  311. }
  312. $test = preg_replace('/%\{(\$[a-z_]\w*)\}/ei', $repl, $test);
  313. $test = preg_replace('/\$([a-z_]\w*)!?/ei', $repl, $test);
  314. return $test;
  315. }
  316. // compile mixin properties
  317. final private static function do_mixin($text) {
  318. $out = array();
  319. $text = static::do_solve($text);
  320. if (preg_match_all('/\s*([\w\-]+)!?(?:\((.+?)\))?\s*/', $text, $matches)) {
  321. foreach ($matches[1] as $i => $part) {
  322. if (array_key_exists($part, static::$mixins)) {
  323. $old = static::$mixins[$part]['args'];
  324. if ( ! empty($matches[2][$i])) {
  325. $new = array_filter(explode(',', $matches[2][$i]), 'strlen');
  326. $new = array_values($new) + array_values($old);
  327. if (sizeof($old) === sizeof($new)) {//FIX
  328. $old = array_combine(array_keys($old), $new);
  329. }
  330. }
  331. foreach ($old as $key => $val) {
  332. $old[substr($key, 1)] = trim(preg_match('/^\s*([\'"])(.+?)\\1\s*$/', $val, $match) ? $match[2] : $val);
  333. }
  334. $old = array_merge(static::$props, $old);
  335. $out = static::do_vars(static::$mixins[$part]['props'], $old);
  336. }
  337. }
  338. }
  339. return $out;
  340. }
  341. // solve css expressions
  342. final private static function do_solve($text) {
  343. static $set = NULL;
  344. if (is_null($set)) {
  345. $test = include __DIR__.DS.'assets'.DS.'scripts'.DS.'named_colors.php';
  346. foreach ($test as $key => $val) {
  347. $key = sprintf('/#%s\b/', preg_quote($key, '/'));
  348. $set[$key] = $val;
  349. }
  350. }
  351. if (is_array($text)) {
  352. foreach ($text as $key => $val) {
  353. $text[$key] = static::do_solve($val);
  354. }
  355. } else {//FIX
  356. do
  357. {
  358. $old = strlen($text);
  359. $text = preg_replace_callback('/(?<![\-._])([\w-]+?|%\w*?)\(([^\(\)]+)\)(\.\w+)?/', get_class() . '::do_helper', $text);
  360. $text = static::do_math(static::do_vars($text, static::$props));
  361. $text = preg_replace(array_keys($set), $set, $text);
  362. } while($old != strlen($text));
  363. }
  364. return $text;
  365. }
  366. // css helper callback
  367. final private static function do_helper($match) {
  368. $args = static::do_solve($match[2]);
  369. $args = array_filter(explode(',', $args), 'strlen');
  370. if ( ! css_helper::defined($match[1])) {
  371. return "$match[1]!($match[2])" . ( ! empty($match[3]) ? $match[3] : '');
  372. }
  373. $out = css_helper::apply($match[1], $args);
  374. $out = ! empty($match[3]) ? value($out, substr($match[3], 1)) : (array) $out;
  375. return is_closure($out) ? $out() : end($out);
  376. }
  377. // solve math operations
  378. final private static function do_math($text) {
  379. static $regex = '/(-?(?:\d*\.)?\d+)(p[xtc]|e[xm]|[cm]m|g?rad|deg|in|s|%)/';
  380. if (is_false(strpos($text, '['))) {
  381. return $text;
  382. }
  383. while (preg_match_all('/\[([^\[\]]+?)\]/', $text, $matches)) {
  384. foreach ($matches[0] as $i => $val) {
  385. preg_match($regex, $matches[1][$i], $unit);
  386. $ext = ! empty($unit[2]) ? $unit[2] : 'px';
  387. $expr = preg_replace($regex, '\\1', $matches[1][$i]);
  388. if (strpos($val, '#') !== FALSE) {
  389. $out = preg_replace('/#(\w+)(?=\b|$)/e', '"0x".static::hex("\\1");', $expr);
  390. $out = preg_replace('/[^\dxa-fA-F\s*\/.+-]/', '', $expr);
  391. $expr = "sprintf('#%06x', $out)";
  392. $ext = '';
  393. }
  394. @eval("\$out = $expr;");
  395. $out = isset($out) ? $out : '';
  396. $out .= is_numeric($out) ? $ext : '';
  397. $text = str_replace($val, $out, $text);
  398. }
  399. }
  400. return $text;
  401. }
  402. /**#@-*/
  403. }
  404. /* EOF: ./library/css/css.php */