Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lessify.inc.php 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. <?php
  2. /**
  3. * lessify
  4. * Convert a css file into a less file
  5. * http://leafo.net/lessphp
  6. * Copyright 2010, leaf corcoran <leafot@gmail.com>
  7. *
  8. * WARNING: THIS DOES NOT WORK ANYMORE. NEEDS TO BE UPDATED FOR
  9. * LATEST VERSION OF LESSPHP.
  10. *
  11. */
  12. require "lessc.inc.php";
  13. //
  14. // check if the merge during mixin is overwriting values. should or should it not?
  15. //
  16. //
  17. // 1. split apart class tags
  18. //
  19. class easyparse {
  20. var $buffer;
  21. var $count;
  22. function __construct($str) {
  23. $this->count = 0;
  24. $this->buffer = trim($str);
  25. }
  26. function seek($where = null) {
  27. if ($where === null) return $this->count;
  28. else $this->count = $where;
  29. return true;
  30. }
  31. function preg_quote($what) {
  32. return preg_quote($what, '/');
  33. }
  34. function match($regex, &$out, $eatWhitespace = true) {
  35. $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
  36. if (preg_match($r, $this->buffer, $out, null, $this->count)) {
  37. $this->count += strlen($out[0]);
  38. return true;
  39. }
  40. return false;
  41. }
  42. function literal($what, $eatWhitespace = true) {
  43. // this is here mainly prevent notice from { } string accessor
  44. if ($this->count >= strlen($this->buffer)) return false;
  45. // shortcut on single letter
  46. if (!$eatWhitespace and strlen($what) == 1) {
  47. if ($this->buffer{$this->count} == $what) {
  48. $this->count++;
  49. return true;
  50. }
  51. else return false;
  52. }
  53. return $this->match($this->preg_quote($what), $m, $eatWhitespace);
  54. }
  55. }
  56. class tagparse extends easyparse {
  57. static private $combinators = null;
  58. static private $match_opts = null;
  59. function parse() {
  60. if (empty(self::$combinators)) {
  61. self::$combinators = '('.implode('|', array_map(array($this, 'preg_quote'),
  62. array('+', '>', '~'))).')';
  63. self::$match_opts = '('.implode('|', array_map(array($this, 'preg_quote'),
  64. array('=', '~=', '|=', '$=', '*='))).')';
  65. }
  66. // crush whitespace
  67. $this->buffer = preg_replace('/\s+/', ' ', $this->buffer).' ';
  68. $tags = array();
  69. while ($this->tag($t)) $tags[] = $t;
  70. return $tags;
  71. }
  72. static function compileString($string) {
  73. list(, $delim, $str) = $string;
  74. $str = str_replace($delim, "\\".$delim, $str);
  75. $str = str_replace("\n", "\\\n", $str);
  76. return $delim.$str.$delim;
  77. }
  78. static function compilePaths($paths) {
  79. return implode(', ', array_map(array('self', 'compilePath'), $paths));
  80. }
  81. // array of tags
  82. static function compilePath($path) {
  83. return implode(' ', array_map(array('self', 'compileTag'), $path));
  84. }
  85. static function compileTag($tag) {
  86. ob_start();
  87. if (isset($tag['comb'])) echo $tag['comb']." ";
  88. if (isset($tag['front'])) echo $tag['front'];
  89. if (isset($tag['attr'])) {
  90. echo '['.$tag['attr'];
  91. if (isset($tag['op'])) {
  92. echo $tag['op'].$tag['op_value'];
  93. }
  94. echo ']';
  95. }
  96. return ob_get_clean();
  97. }
  98. function string(&$out) {
  99. $s = $this->seek();
  100. if ($this->literal('"')) {
  101. $delim = '"';
  102. } elseif ($this->literal("'")) {
  103. $delim = "'";
  104. } else {
  105. return false;
  106. }
  107. while (true) {
  108. // step through letters looking for either end or escape
  109. $buff = "";
  110. $escapeNext = false;
  111. $finished = false;
  112. for ($i = $this->count; $i < strlen($this->buffer); $i++) {
  113. $char = $this->buffer[$i];
  114. switch ($char) {
  115. case $delim:
  116. if ($escapeNext) {
  117. $buff .= $char;
  118. $escapeNext = false;
  119. break;
  120. }
  121. $finished = true;
  122. break 2;
  123. case "\\":
  124. if ($escapeNext) {
  125. $buff .= $char;
  126. $escapeNext = false;
  127. } else {
  128. $escapeNext = true;
  129. }
  130. break;
  131. case "\n":
  132. if (!$escapeNext) {
  133. break 3;
  134. }
  135. $buff .= $char;
  136. $escapeNext = false;
  137. break;
  138. default:
  139. if ($escapeNext) {
  140. $buff .= "\\";
  141. $escapeNext = false;
  142. }
  143. $buff .= $char;
  144. }
  145. }
  146. if (!$finished) break;
  147. $out = array('string', $delim, $buff);
  148. $this->seek($i+1);
  149. return true;
  150. }
  151. $this->seek($s);
  152. return false;
  153. }
  154. function tag(&$out) {
  155. $s = $this->seek();
  156. $tag = array();
  157. if ($this->combinator($op)) $tag['comb'] = $op;
  158. if (!$this->match('(.*?)( |$|\[|'.self::$combinators.')', $match)) {
  159. $this->seek($s);
  160. return false;
  161. }
  162. if (!empty($match[3])) {
  163. // give back combinator
  164. $this->count-=strlen($match[3]);
  165. }
  166. if (!empty($match[1])) $tag['front'] = $match[1];
  167. if ($match[2] == '[') {
  168. if ($this->ident($i)) {
  169. $tag['attr'] = $i;
  170. if ($this->match(self::$match_opts, $m) && $this->value($v)) {
  171. $tag['op'] = $m[1];
  172. $tag['op_value'] = $v;
  173. }
  174. if ($this->literal(']')) {
  175. $out = $tag;
  176. return true;
  177. }
  178. }
  179. } elseif (isset($tag['front'])) {
  180. $out = $tag;
  181. return true;
  182. }
  183. $this->seek($s);
  184. return false;
  185. }
  186. function ident(&$out) {
  187. // [-]?{nmstart}{nmchar}*
  188. // nmstart: [_a-z]|{nonascii}|{escape}
  189. // nmchar: [_a-z0-9-]|{nonascii}|{escape}
  190. if ($this->match('(-?[_a-z][_\w]*)', $m)) {
  191. $out = $m[1];
  192. return true;
  193. }
  194. return false;
  195. }
  196. function value(&$out) {
  197. if ($this->string($str)) {
  198. $out = $this->compileString($str);
  199. return true;
  200. } elseif ($this->ident($id)) {
  201. $out = $id;
  202. return true;
  203. }
  204. return false;
  205. }
  206. function combinator(&$op) {
  207. if ($this->match(self::$combinators, $m)) {
  208. $op = $m[1];
  209. return true;
  210. }
  211. return false;
  212. }
  213. }
  214. class nodecounter {
  215. var $count = 0;
  216. var $children = array();
  217. var $name;
  218. var $child_blocks;
  219. var $the_block;
  220. function __construct($name) {
  221. $this->name = $name;
  222. }
  223. function dump($stack = null) {
  224. if (is_null($stack)) $stack = array();
  225. $stack[] = $this->getName();
  226. echo implode(' -> ', $stack)." ($this->count)\n";
  227. foreach ($this->children as $child) {
  228. $child->dump($stack);
  229. }
  230. }
  231. static function compileProperties($c, $block) {
  232. foreach($block as $name => $value) {
  233. if ($c->isProperty($name, $value)) {
  234. echo $c->compileProperty($name, $value)."\n";
  235. }
  236. }
  237. }
  238. function compile($c, $path = null) {
  239. if (is_null($path)) $path = array();
  240. $path[] = $this->name;
  241. $isVisible = !is_null($this->the_block) || !is_null($this->child_blocks);
  242. if ($isVisible) {
  243. echo $c->indent(implode(' ', $path).' {');
  244. $c->indentLevel++;
  245. $path = array();
  246. if ($this->the_block) {
  247. $this->compileProperties($c, $this->the_block);
  248. }
  249. if ($this->child_blocks) {
  250. foreach ($this->child_blocks as $block) {
  251. echo $c->indent(tagparse::compilePaths($block['__tags']).' {');
  252. $c->indentLevel++;
  253. $this->compileProperties($c, $block);
  254. $c->indentLevel--;
  255. echo $c->indent('}');
  256. }
  257. }
  258. }
  259. // compile child nodes
  260. foreach($this->children as $node) {
  261. $node->compile($c, $path);
  262. }
  263. if ($isVisible) {
  264. $c->indentLevel--;
  265. echo $c->indent('}');
  266. }
  267. }
  268. function getName() {
  269. if (is_null($this->name)) return "[root]";
  270. else return $this->name;
  271. }
  272. function getNode($name) {
  273. if (!isset($this->children[$name])) {
  274. $this->children[$name] = new nodecounter($name);
  275. }
  276. return $this->children[$name];
  277. }
  278. function findNode($path) {
  279. $current = $this;
  280. for ($i = 0; $i < count($path); $i++) {
  281. $t = tagparse::compileTag($path[$i]);
  282. $current = $current->getNode($t);
  283. }
  284. return $current;
  285. }
  286. function addBlock($path, $block) {
  287. $node = $this->findNode($path);
  288. if (!is_null($node->the_block)) throw new exception("can this happen?");
  289. unset($block['__tags']);
  290. $node->the_block = $block;
  291. }
  292. function addToNode($path, $block) {
  293. $node = $this->findNode($path);
  294. $node->child_blocks[] = $block;
  295. }
  296. }
  297. /**
  298. * create a less file from a css file by combining blocks where appropriate
  299. */
  300. class lessify extends lessc {
  301. public function dump() {
  302. print_r($this->env);
  303. }
  304. public function parse($str = null) {
  305. $this->prepareParser($str ? $str : $this->buffer);
  306. while (false !== $this->parseChunk());
  307. $root = new nodecounter(null);
  308. // attempt to preserve some of the block order
  309. $order = array();
  310. $visitedTags = array();
  311. foreach (end($this->env) as $name => $block) {
  312. if (!$this->isBlock($name, $block)) continue;
  313. if (isset($visitedTags[$name])) continue;
  314. foreach ($block['__tags'] as $t) {
  315. $visitedTags[$t] = true;
  316. }
  317. // skip those with more than 1
  318. if (count($block['__tags']) == 1) {
  319. $p = new tagparse(end($block['__tags']));
  320. $path = $p->parse();
  321. $root->addBlock($path, $block);
  322. $order[] = array('compressed', $path, $block);
  323. continue;
  324. } else {
  325. $common = null;
  326. $paths = array();
  327. foreach ($block['__tags'] as $rawtag) {
  328. $p = new tagparse($rawtag);
  329. $paths[] = $path = $p->parse();
  330. if (is_null($common)) $common = $path;
  331. else {
  332. $new_common = array();
  333. foreach ($path as $tag) {
  334. $head = array_shift($common);
  335. if ($tag == $head) {
  336. $new_common[] = $head;
  337. } else break;
  338. }
  339. $common = $new_common;
  340. if (empty($common)) {
  341. // nothing in common
  342. break;
  343. }
  344. }
  345. }
  346. if (!empty($common)) {
  347. $new_paths = array();
  348. foreach ($paths as $p) $new_paths[] = array_slice($p, count($common));
  349. $block['__tags'] = $new_paths;
  350. $root->addToNode($common, $block);
  351. $order[] = array('compressed', $common, $block);
  352. continue;
  353. }
  354. }
  355. $order[] = array('none', $block['__tags'], $block);
  356. }
  357. $compressed = $root->children;
  358. foreach ($order as $item) {
  359. list($type, $tags, $block) = $item;
  360. if ($type == 'compressed') {
  361. $top = tagparse::compileTag(reset($tags));
  362. if (isset($compressed[$top])) {
  363. $compressed[$top]->compile($this);
  364. unset($compressed[$top]);
  365. }
  366. } else {
  367. echo $this->indent(implode(', ', $tags).' {');
  368. $this->indentLevel++;
  369. nodecounter::compileProperties($this, $block);
  370. $this->indentLevel--;
  371. echo $this->indent('}');
  372. }
  373. }
  374. }
  375. }