1 <?php
  2   3   4   5   6   7   8   9  10  11 
 12 
 13 require "lessc.inc.php";
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 class easyparse {
 24     var $buffer;
 25     var $count;
 26 
 27     function __construct($str) {
 28         $this->count = 0;
 29         $this->buffer = trim($str);
 30     }
 31 
 32     function seek($where = null) {
 33         if ($where === null) return $this->count;
 34         else $this->count = $where;
 35         return true;
 36     }
 37 
 38     function preg_quote($what) {
 39         return preg_quote($what, '/');
 40     }
 41 
 42     function match($regex, &$out, $eatWhitespace = true) {
 43         $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
 44         if (preg_match($r, $this->buffer, $out, null, $this->count)) {
 45             $this->count += strlen($out[0]);
 46             return true;
 47         }
 48         return false;
 49     }
 50 
 51     function literal($what, $eatWhitespace = true) {
 52         
 53         if ($this->count >= strlen($this->buffer)) return false;
 54 
 55         
 56         if (!$eatWhitespace and strlen($what) == 1) {
 57             if ($this->buffer{$this->count} == $what) {
 58                 $this->count++;
 59                 return true;
 60             }
 61             else return false;
 62         }
 63 
 64         return $this->match($this->preg_quote($what), $m, $eatWhitespace);
 65     }
 66 
 67 }
 68 
 69 class tagparse extends easyparse {
 70     static private $combinators = null;
 71     static private $match_opts = null;
 72 
 73     function parse() {
 74         if (empty(self::$combinators)) {
 75             self::$combinators = '('.implode('|', array_map(array($this, 'preg_quote'),
 76                 array('+', '>', '~'))).')';
 77             self::$match_opts = '('.implode('|', array_map(array($this, 'preg_quote'),
 78                 array('=', '~=', '|=', '$=', '*='))).')';
 79         }
 80 
 81         
 82         $this->buffer = preg_replace('/\s+/', ' ', $this->buffer).' ';
 83 
 84         $tags = array();
 85         while ($this->tag($t)) $tags[] = $t;
 86 
 87         return $tags;
 88     }
 89 
 90     static function compileString($string) {
 91         list(, $delim, $str) = $string;
 92         $str = str_replace($delim, "\\".$delim, $str);
 93         $str = str_replace("\n", "\\\n", $str);
 94         return $delim.$str.$delim;
 95     }
 96 
 97     static function compilePaths($paths) {
 98         return implode(', ', array_map(array('self', 'compilePath'), $paths));
 99     }
100 
101     
102     static function compilePath($path) {
103         return implode(' ', array_map(array('self', 'compileTag'), $path));
104     }
105 
106 
107     static function compileTag($tag) {
108         ob_start();
109         if (isset($tag['comb'])) echo $tag['comb']." ";
110         if (isset($tag['front'])) echo $tag['front'];
111         if (isset($tag['attr'])) {
112             echo '['.$tag['attr'];
113             if (isset($tag['op'])) {
114                 echo $tag['op'].$tag['op_value'];
115             }
116             echo ']';
117         }
118         return ob_get_clean();
119     }
120 
121     function string(&$out) {
122         $s = $this->seek();
123 
124         if ($this->literal('"')) {
125             $delim = '"';
126         } elseif ($this->literal("'")) {
127             $delim = "'";
128         } else {
129             return false;
130         }
131 
132         while (true) {
133             
134             $buff = "";
135             $escapeNext = false;
136             $finished = false;
137             for ($i = $this->count; $i < strlen($this->buffer); $i++) {
138                 $char = $this->buffer[$i];
139                 switch ($char) {
140                 case $delim:
141                     if ($escapeNext) {
142                         $buff .= $char;
143                         $escapeNext = false;
144                         break;
145                     }
146                     $finished = true;
147                     break 2;
148                 case "\\":
149                     if ($escapeNext) {
150                         $buff .= $char;
151                         $escapeNext = false;
152                     } else {
153                         $escapeNext = true;
154                     }
155                     break;
156                 case "\n":
157                     if (!$escapeNext) {
158                         break 3;
159                     }
160 
161                     $buff .= $char;
162                     $escapeNext = false;
163                     break;
164                 default:
165                     if ($escapeNext) {
166                         $buff .= "\\";
167                         $escapeNext = false;
168                     }
169                     $buff .= $char;
170                 }
171             }
172             if (!$finished) break;
173             $out = array('string', $delim, $buff);
174             $this->seek($i+1);
175             return true;
176         }
177 
178         $this->seek($s);
179         return false;
180     }
181 
182     function tag(&$out) {
183         $s = $this->seek();
184         $tag = array();
185         if ($this->combinator($op)) $tag['comb'] = $op;
186 
187         if (!$this->match('(.*?)( |$|\[|'.self::$combinators.')', $match)) {
188             $this->seek($s);
189             return false;
190         }
191 
192         if (!empty($match[3])) {
193             
194             $this->count-=strlen($match[3]);
195         }
196 
197         if (!empty($match[1])) $tag['front'] = $match[1];
198 
199         if ($match[2] == '[') {
200             if ($this->ident($i)) {
201                 $tag['attr'] = $i;
202 
203                 if ($this->match(self::$match_opts, $m) && $this->value($v)) {
204                     $tag['op'] = $m[1];
205                     $tag['op_value'] = $v;
206                 }
207 
208                 if ($this->literal(']')) {
209                     $out = $tag;
210                     return true;
211                 }
212             }
213         } elseif (isset($tag['front'])) {
214             $out = $tag;
215             return true;
216         }
217 
218         $this->seek($s);
219         return false;
220     }
221 
222     function ident(&$out) {
223         
224         
225         
226         if ($this->match('(-?[_a-z][_\w]*)', $m)) {
227             $out = $m[1];
228             return true;
229         }
230         return false;
231     }
232 
233     function value(&$out) {
234         if ($this->string($str)) {
235             $out = $this->compileString($str);
236             return true;
237         } elseif ($this->ident($id)) {
238             $out = $id;
239             return true;
240         }
241         return false;
242     }
243 
244 
245     function combinator(&$op) {
246         if ($this->match(self::$combinators, $m)) {
247             $op = $m[1];
248             return true;
249         }
250         return false;
251     }
252 }
253 
254 class nodecounter {
255     var $count = 0;
256     var $children = array();
257 
258     var $name;
259     var $child_blocks;
260     var $the_block;
261 
262     function __construct($name) {
263         $this->name = $name;
264     }
265 
266     function dump($stack = null) {
267         if (is_null($stack)) $stack = array();
268         $stack[] = $this->getName();
269         echo implode(' -> ', $stack)." ($this->count)\n";
270         foreach ($this->children as $child) {
271             $child->dump($stack);
272         }
273     }
274 
275     static function compileProperties($c, $block) {
276         foreach($block as $name => $value) {
277             if ($c->isProperty($name, $value)) {
278                 echo $c->compileProperty($name, $value)."\n";
279             }
280         }
281     }
282 
283     function compile($c, $path = null) {
284         if (is_null($path)) $path = array();
285         $path[] = $this->name;
286 
287         $isVisible = !is_null($this->the_block) || !is_null($this->child_blocks);
288 
289         if ($isVisible) {
290             echo $c->indent(implode(' ', $path).' {');
291             $c->indentLevel++;
292             $path = array();
293 
294             if ($this->the_block) {
295                 $this->compileProperties($c, $this->the_block);
296             }
297 
298             if ($this->child_blocks) {
299                 foreach ($this->child_blocks as $block) {
300                     echo $c->indent(tagparse::compilePaths($block['__tags']).' {');
301                     $c->indentLevel++;
302                     $this->compileProperties($c, $block);
303                     $c->indentLevel--;
304                     echo $c->indent('}');
305                 }
306             }
307         }
308 
309         
310         foreach($this->children as $node) {
311             $node->compile($c, $path);
312         }
313 
314         if ($isVisible) {
315             $c->indentLevel--;
316             echo $c->indent('}');
317         }
318 
319     }
320 
321     function getName() {
322         if (is_null($this->name)) return "[root]";
323         else return $this->name;
324     }
325 
326     function getNode($name) {
327         if (!isset($this->children[$name])) {
328             $this->children[$name] = new nodecounter($name);
329         }
330 
331         return $this->children[$name];
332     }
333 
334     function findNode($path) {
335         $current = $this;
336         for ($i = 0; $i < count($path); $i++) {
337             $t = tagparse::compileTag($path[$i]);
338             $current = $current->getNode($t);
339         }
340 
341         return $current;
342     }
343 
344     function addBlock($path, $block) {
345         $node = $this->findNode($path);
346         if (!is_null($node->the_block)) throw new exception("can this happen?");
347 
348         unset($block['__tags']);
349         $node->the_block = $block;
350     }
351 
352     function addToNode($path, $block) {
353         $node = $this->findNode($path);
354         $node->child_blocks[] = $block;
355     }
356 }
357 
358 359 360 
361 class lessify extends lessc {
362     public function dump() {
363         print_r($this->env);
364     }
365 
366     public function parse($str = null) {
367         $this->prepareParser($str ? $str : $this->buffer);
368         while (false !== $this->parseChunk());
369 
370         $root = new nodecounter(null);
371 
372         
373         $order = array();
374 
375         $visitedTags = array();
376         foreach (end($this->env) as $name => $block) {
377             if (!$this->isBlock($name, $block)) continue;
378             if (isset($visitedTags[$name])) continue;
379 
380             foreach ($block['__tags'] as $t) {
381                 $visitedTags[$t] = true;
382             }
383 
384             
385             if (count($block['__tags']) == 1) {
386                 $p = new tagparse(end($block['__tags']));
387                 $path = $p->parse();
388                 $root->addBlock($path, $block);
389                 $order[] = array('compressed', $path, $block);
390                 continue;
391             } else {
392                 $common = null;
393                 $paths = array();
394                 foreach ($block['__tags'] as $rawtag) {
395                     $p = new tagparse($rawtag);
396                     $paths[] = $path = $p->parse();
397                     if (is_null($common)) $common = $path;
398                     else {
399                         $new_common = array();
400                         foreach ($path as $tag) {
401                             $head = array_shift($common);
402                             if ($tag == $head) {
403                                 $new_common[] = $head;
404                             } else break;
405                         }
406                         $common = $new_common;
407                         if (empty($common)) {
408                             
409                             break;
410                         }
411                     }
412                 }
413 
414                 if (!empty($common)) {
415                     $new_paths = array();
416                     foreach ($paths as $p) $new_paths[] = array_slice($p, count($common));
417                     $block['__tags'] = $new_paths;
418                     $root->addToNode($common, $block);
419                     $order[] = array('compressed', $common, $block);
420                     continue;
421                 }
422 
423             }
424 
425             $order[] = array('none', $block['__tags'], $block);
426         }
427 
428 
429         $compressed = $root->children;
430         foreach ($order as $item) {
431             list($type, $tags, $block) = $item;
432             if ($type == 'compressed') {
433                 $top = tagparse::compileTag(reset($tags));
434                 if (isset($compressed[$top])) {
435                     $compressed[$top]->compile($this);
436                     unset($compressed[$top]);
437                 }
438             } else {
439                 echo $this->indent(implode(', ', $tags).' {');
440                 $this->indentLevel++;
441                 nodecounter::compileProperties($this, $block);
442                 $this->indentLevel--;
443                 echo $this->indent('}');
444             }
445         }
446     }
447 }
448