* * WARNING: THIS DOES NOT WORK ANYMORE. NEEDS TO BE UPDATED FOR * LATEST VERSION OF LESSPHP. * */ require "lessc.inc.php"; // // check if the merge during mixin is overwriting values. should or should it not? // // // 1. split apart class tags // class easyparse { var $buffer; var $count; function __construct($str) { $this->count = 0; $this->buffer = trim($str); } function seek($where = null) { if ($where === null) return $this->count; else $this->count = $where; return true; } function preg_quote($what) { return preg_quote($what, '/'); } function match($regex, &$out, $eatWhitespace = true) { $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais'; if (preg_match($r, $this->buffer, $out, null, $this->count)) { $this->count += strlen($out[0]); return true; } return false; } function literal($what, $eatWhitespace = true) { // this is here mainly prevent notice from { } string accessor if ($this->count >= strlen($this->buffer)) return false; // shortcut on single letter if (!$eatWhitespace and strlen($what) == 1) { if ($this->buffer{$this->count} == $what) { $this->count++; return true; } else return false; } return $this->match($this->preg_quote($what), $m, $eatWhitespace); } } class tagparse extends easyparse { static private $combinators = null; static private $match_opts = null; function parse() { if (empty(self::$combinators)) { self::$combinators = '('.implode('|', array_map(array($this, 'preg_quote'), array('+', '>', '~'))).')'; self::$match_opts = '('.implode('|', array_map(array($this, 'preg_quote'), array('=', '~=', '|=', '$=', '*='))).')'; } // crush whitespace $this->buffer = preg_replace('/\s+/', ' ', $this->buffer).' '; $tags = array(); while ($this->tag($t)) $tags[] = $t; return $tags; } static function compileString($string) { list(, $delim, $str) = $string; $str = str_replace($delim, "\\".$delim, $str); $str = str_replace("\n", "\\\n", $str); return $delim.$str.$delim; } static function compilePaths($paths) { return implode(', ', array_map(array('self', 'compilePath'), $paths)); } // array of tags static function compilePath($path) { return implode(' ', array_map(array('self', 'compileTag'), $path)); } static function compileTag($tag) { ob_start(); if (isset($tag['comb'])) echo $tag['comb']." "; if (isset($tag['front'])) echo $tag['front']; if (isset($tag['attr'])) { echo '['.$tag['attr']; if (isset($tag['op'])) { echo $tag['op'].$tag['op_value']; } echo ']'; } return ob_get_clean(); } function string(&$out) { $s = $this->seek(); if ($this->literal('"')) { $delim = '"'; } elseif ($this->literal("'")) { $delim = "'"; } else { return false; } while (true) { // step through letters looking for either end or escape $buff = ""; $escapeNext = false; $finished = false; for ($i = $this->count; $i < strlen($this->buffer); $i++) { $char = $this->buffer[$i]; switch ($char) { case $delim: if ($escapeNext) { $buff .= $char; $escapeNext = false; break; } $finished = true; break 2; case "\\": if ($escapeNext) { $buff .= $char; $escapeNext = false; } else { $escapeNext = true; } break; case "\n": if (!$escapeNext) { break 3; } $buff .= $char; $escapeNext = false; break; default: if ($escapeNext) { $buff .= "\\"; $escapeNext = false; } $buff .= $char; } } if (!$finished) break; $out = array('string', $delim, $buff); $this->seek($i+1); return true; } $this->seek($s); return false; } function tag(&$out) { $s = $this->seek(); $tag = array(); if ($this->combinator($op)) $tag['comb'] = $op; if (!$this->match('(.*?)( |$|\[|'.self::$combinators.')', $match)) { $this->seek($s); return false; } if (!empty($match[3])) { // give back combinator $this->count-=strlen($match[3]); } if (!empty($match[1])) $tag['front'] = $match[1]; if ($match[2] == '[') { if ($this->ident($i)) { $tag['attr'] = $i; if ($this->match(self::$match_opts, $m) && $this->value($v)) { $tag['op'] = $m[1]; $tag['op_value'] = $v; } if ($this->literal(']')) { $out = $tag; return true; } } } elseif (isset($tag['front'])) { $out = $tag; return true; } $this->seek($s); return false; } function ident(&$out) { // [-]?{nmstart}{nmchar}* // nmstart: [_a-z]|{nonascii}|{escape} // nmchar: [_a-z0-9-]|{nonascii}|{escape} if ($this->match('(-?[_a-z][_\w]*)', $m)) { $out = $m[1]; return true; } return false; } function value(&$out) { if ($this->string($str)) { $out = $this->compileString($str); return true; } elseif ($this->ident($id)) { $out = $id; return true; } return false; } function combinator(&$op) { if ($this->match(self::$combinators, $m)) { $op = $m[1]; return true; } return false; } } class nodecounter { var $count = 0; var $children = array(); var $name; var $child_blocks; var $the_block; function __construct($name) { $this->name = $name; } function dump($stack = null) { if (is_null($stack)) $stack = array(); $stack[] = $this->getName(); echo implode(' -> ', $stack)." ($this->count)\n"; foreach ($this->children as $child) { $child->dump($stack); } } static function compileProperties($c, $block) { foreach($block as $name => $value) { if ($c->isProperty($name, $value)) { echo $c->compileProperty($name, $value)."\n"; } } } function compile($c, $path = null) { if (is_null($path)) $path = array(); $path[] = $this->name; $isVisible = !is_null($this->the_block) || !is_null($this->child_blocks); if ($isVisible) { echo $c->indent(implode(' ', $path).' {'); $c->indentLevel++; $path = array(); if ($this->the_block) { $this->compileProperties($c, $this->the_block); } if ($this->child_blocks) { foreach ($this->child_blocks as $block) { echo $c->indent(tagparse::compilePaths($block['__tags']).' {'); $c->indentLevel++; $this->compileProperties($c, $block); $c->indentLevel--; echo $c->indent('}'); } } } // compile child nodes foreach($this->children as $node) { $node->compile($c, $path); } if ($isVisible) { $c->indentLevel--; echo $c->indent('}'); } } function getName() { if (is_null($this->name)) return "[root]"; else return $this->name; } function getNode($name) { if (!isset($this->children[$name])) { $this->children[$name] = new nodecounter($name); } return $this->children[$name]; } function findNode($path) { $current = $this; for ($i = 0; $i < count($path); $i++) { $t = tagparse::compileTag($path[$i]); $current = $current->getNode($t); } return $current; } function addBlock($path, $block) { $node = $this->findNode($path); if (!is_null($node->the_block)) throw new exception("can this happen?"); unset($block['__tags']); $node->the_block = $block; } function addToNode($path, $block) { $node = $this->findNode($path); $node->child_blocks[] = $block; } } /** * create a less file from a css file by combining blocks where appropriate */ class lessify extends lessc { public function dump() { print_r($this->env); } public function parse($str = null) { $this->prepareParser($str ? $str : $this->buffer); while (false !== $this->parseChunk()); $root = new nodecounter(null); // attempt to preserve some of the block order $order = array(); $visitedTags = array(); foreach (end($this->env) as $name => $block) { if (!$this->isBlock($name, $block)) continue; if (isset($visitedTags[$name])) continue; foreach ($block['__tags'] as $t) { $visitedTags[$t] = true; } // skip those with more than 1 if (count($block['__tags']) == 1) { $p = new tagparse(end($block['__tags'])); $path = $p->parse(); $root->addBlock($path, $block); $order[] = array('compressed', $path, $block); continue; } else { $common = null; $paths = array(); foreach ($block['__tags'] as $rawtag) { $p = new tagparse($rawtag); $paths[] = $path = $p->parse(); if (is_null($common)) $common = $path; else { $new_common = array(); foreach ($path as $tag) { $head = array_shift($common); if ($tag == $head) { $new_common[] = $head; } else break; } $common = $new_common; if (empty($common)) { // nothing in common break; } } } if (!empty($common)) { $new_paths = array(); foreach ($paths as $p) $new_paths[] = array_slice($p, count($common)); $block['__tags'] = $new_paths; $root->addToNode($common, $block); $order[] = array('compressed', $common, $block); continue; } } $order[] = array('none', $block['__tags'], $block); } $compressed = $root->children; foreach ($order as $item) { list($type, $tags, $block) = $item; if ($type == 'compressed') { $top = tagparse::compileTag(reset($tags)); if (isset($compressed[$top])) { $compressed[$top]->compile($this); unset($compressed[$top]); } } else { echo $this->indent(implode(', ', $tags).' {'); $this->indentLevel++; nodecounter::compileProperties($this, $block); $this->indentLevel--; echo $this->indent('}'); } } } }