From 7ad9f272a482802da2d43fe83841adbe9bcd8cb4 Mon Sep 17 00:00:00 2001 From: derchris Date: Sat, 2 Feb 2019 15:35:30 +0100 Subject: update less.php to PHP 7.x compatible fork --- lib/less.php/Less.php | 10527 ------------------------------------------------ 1 file changed, 10527 deletions(-) delete mode 100644 lib/less.php/Less.php (limited to 'lib/less.php/Less.php') diff --git a/lib/less.php/Less.php b/lib/less.php/Less.php deleted file mode 100644 index 44cdffc..0000000 --- a/lib/less.php/Less.php +++ /dev/null @@ -1,10527 +0,0 @@ - false, // option - whether to compress - 'strictUnits' => false, // whether units need to evaluate correctly - 'strictMath' => false, // whether math has to be within parenthesis - 'relativeUrls' => true, // option - whether to adjust URL's to be relative - 'urlArgs' => array(), // whether to add args into url tokens - 'numPrecision' => 8, - - 'import_dirs' => array(), - 'import_callback' => null, - 'cache_dir' => null, - 'cache_method' => 'php', // false, 'serialize', 'php', 'var_export', 'callback'; - 'cache_callback_get' => null, - 'cache_callback_set' => null, - - 'sourceMap' => false, // whether to output a source map - 'sourceMapBasepath' => null, - 'sourceMapWriteTo' => null, - 'sourceMapURL' => null, - - 'plugins' => array(), - - ); - - public static $options = array(); - - - private $input; // Less input string - private $input_len; // input string length - private $pos; // current index in `input` - private $saveStack = array(); // holds state for backtracking - private $furthest; - private $mb_internal_encoding = ''; // for remember exists value of mbstring.internal_encoding - - /** - * @var Less_Environment - */ - private $env; - - protected $rules = array(); - - private static $imports = array(); - - public static $has_extends = false; - - public static $next_id = 0; - - /** - * Filename to contents of all parsed the files - * - * @var array - */ - public static $contentsMap = array(); - - - /** - * @param Less_Environment|array|null $env - */ - public function __construct( $env = null ){ - - // Top parser on an import tree must be sure there is one "env" - // which will then be passed around by reference. - if( $env instanceof Less_Environment ){ - $this->env = $env; - }else{ - $this->SetOptions(Less_Parser::$default_options); - $this->Reset( $env ); - } - - // mbstring.func_overload > 1 bugfix - // The encoding value must be set for each source file, - // therefore, to conserve resources and improve the speed of this design is taken here - if (ini_get('mbstring.func_overload')) { - $this->mb_internal_encoding = ini_get('mbstring.internal_encoding'); - @ini_set('mbstring.internal_encoding', 'ascii'); - } - - } - - - /** - * Reset the parser state completely - * - */ - public function Reset( $options = null ){ - $this->rules = array(); - self::$imports = array(); - self::$has_extends = false; - self::$imports = array(); - self::$contentsMap = array(); - - $this->env = new Less_Environment($options); - $this->env->Init(); - - //set new options - if( is_array($options) ){ - $this->SetOptions(Less_Parser::$default_options); - $this->SetOptions($options); - } - } - - /** - * Set one or more compiler options - * options: import_dirs, cache_dir, cache_method - * - */ - public function SetOptions( $options ){ - foreach($options as $option => $value){ - $this->SetOption($option,$value); - } - } - - /** - * Set one compiler option - * - */ - public function SetOption($option,$value){ - - switch($option){ - - case 'import_dirs': - $this->SetImportDirs($value); - return; - - case 'cache_dir': - if( is_string($value) ){ - Less_Cache::SetCacheDir($value); - Less_Cache::CheckCacheDir(); - } - return; - } - - Less_Parser::$options[$option] = $value; - } - - /** - * Registers a new custom function - * - * @param string $name function name - * @param callable $callback callback - */ - public function registerFunction($name, $callback) { - $this->env->functions[$name] = $callback; - } - - /** - * Removed an already registered function - * - * @param string $name function name - */ - public function unregisterFunction($name) { - if( isset($this->env->functions[$name]) ) - unset($this->env->functions[$name]); - } - - - /** - * Get the current css buffer - * - * @return string - */ - public function getCss(){ - - $precision = ini_get('precision'); - @ini_set('precision',16); - $locale = setlocale(LC_NUMERIC, 0); - setlocale(LC_NUMERIC, "C"); - - try { - - $root = new Less_Tree_Ruleset(array(), $this->rules ); - $root->root = true; - $root->firstRoot = true; - - - $this->PreVisitors($root); - - self::$has_extends = false; - $evaldRoot = $root->compile($this->env); - - - - $this->PostVisitors($evaldRoot); - - if( Less_Parser::$options['sourceMap'] ){ - $generator = new Less_SourceMap_Generator($evaldRoot, Less_Parser::$contentsMap, Less_Parser::$options ); - // will also save file - // FIXME: should happen somewhere else? - $css = $generator->generateCSS(); - }else{ - $css = $evaldRoot->toCSS(); - } - - if( Less_Parser::$options['compress'] ){ - $css = preg_replace('/(^(\s)+)|((\s)+$)/', '', $css); - } - - } catch (Exception $exc) { - // Intentional fall-through so we can reset environment - } - - //reset php settings - @ini_set('precision',$precision); - setlocale(LC_NUMERIC, $locale); - - // If you previously defined $this->mb_internal_encoding - // is required to return the encoding as it was before - if ($this->mb_internal_encoding != '') { - @ini_set("mbstring.internal_encoding", $this->mb_internal_encoding); - $this->mb_internal_encoding = ''; - } - - // Rethrow exception after we handled resetting the environment - if (!empty($exc)) { - throw $exc; - } - - - - return $css; - } - - /** - * Run pre-compile visitors - * - */ - private function PreVisitors($root){ - - if( Less_Parser::$options['plugins'] ){ - foreach(Less_Parser::$options['plugins'] as $plugin){ - if( !empty($plugin->isPreEvalVisitor) ){ - $plugin->run($root); - } - } - } - } - - - /** - * Run post-compile visitors - * - */ - private function PostVisitors($evaldRoot){ - - $visitors = array(); - $visitors[] = new Less_Visitor_joinSelector(); - if( self::$has_extends ){ - $visitors[] = new Less_Visitor_processExtends(); - } - $visitors[] = new Less_Visitor_toCSS(); - - - if( Less_Parser::$options['plugins'] ){ - foreach(Less_Parser::$options['plugins'] as $plugin){ - if( property_exists($plugin,'isPreEvalVisitor') && $plugin->isPreEvalVisitor ){ - continue; - } - - if( property_exists($plugin,'isPreVisitor') && $plugin->isPreVisitor ){ - array_unshift( $visitors, $plugin); - }else{ - $visitors[] = $plugin; - } - } - } - - - for($i = 0; $i < count_if_countable($visitors); $i++ ){ - $visitors[$i]->run($evaldRoot); - } - - } - - - /** - * Parse a Less string into css - * - * @param string $str The string to convert - * @param string $uri_root The url of the file - * @return Less_Tree_Ruleset|Less_Parser - */ - public function parse( $str, $file_uri = null ){ - - if( !$file_uri ){ - $uri_root = ''; - $filename = 'anonymous-file-'.Less_Parser::$next_id++.'.less'; - }else{ - $file_uri = self::WinPath($file_uri); - $filename = $file_uri; - $uri_root = dirname($file_uri); - } - - $previousFileInfo = $this->env->currentFileInfo; - $uri_root = self::WinPath($uri_root); - $this->SetFileInfo($filename, $uri_root); - - $this->input = $str; - $this->_parse(); - - if( $previousFileInfo ){ - $this->env->currentFileInfo = $previousFileInfo; - } - - return $this; - } - - - /** - * Parse a Less string from a given file - * - * @throws Less_Exception_Parser - * @param string $filename The file to parse - * @param string $uri_root The url of the file - * @param bool $returnRoot Indicates whether the return value should be a css string a root node - * @return Less_Tree_Ruleset|Less_Parser - */ - public function parseFile( $filename, $uri_root = '', $returnRoot = false){ - - if( !file_exists($filename) ){ - $this->Error(sprintf('File `%s` not found.', $filename)); - } - - - // fix uri_root? - // Instead of The mixture of file path for the first argument and directory path for the second argument has bee - if( !$returnRoot && !empty($uri_root) && basename($uri_root) == basename($filename) ){ - $uri_root = dirname($uri_root); - } - - - $previousFileInfo = $this->env->currentFileInfo; - - - if( $filename ){ - $filename = self::WinPath(realpath($filename)); - } - $uri_root = self::WinPath($uri_root); - - $this->SetFileInfo($filename, $uri_root); - - self::AddParsedFile($filename); - - if( $returnRoot ){ - $rules = $this->GetRules( $filename ); - $return = new Less_Tree_Ruleset(array(), $rules ); - }else{ - $this->_parse( $filename ); - $return = $this; - } - - if( $previousFileInfo ){ - $this->env->currentFileInfo = $previousFileInfo; - } - - return $return; - } - - - /** - * Allows a user to set variables values - * @param array $vars - * @return Less_Parser - */ - public function ModifyVars( $vars ){ - - $this->input = Less_Parser::serializeVars( $vars ); - $this->_parse(); - - return $this; - } - - - /** - * @param string $filename - */ - public function SetFileInfo( $filename, $uri_root = ''){ - - $filename = Less_Environment::normalizePath($filename); - $dirname = preg_replace('/[^\/\\\\]*$/','',$filename); - - if( !empty($uri_root) ){ - $uri_root = rtrim($uri_root,'/').'/'; - } - - $currentFileInfo = array(); - - //entry info - if( isset($this->env->currentFileInfo) ){ - $currentFileInfo['entryPath'] = $this->env->currentFileInfo['entryPath']; - $currentFileInfo['entryUri'] = $this->env->currentFileInfo['entryUri']; - $currentFileInfo['rootpath'] = $this->env->currentFileInfo['rootpath']; - - }else{ - $currentFileInfo['entryPath'] = $dirname; - $currentFileInfo['entryUri'] = $uri_root; - $currentFileInfo['rootpath'] = $dirname; - } - - $currentFileInfo['currentDirectory'] = $dirname; - $currentFileInfo['currentUri'] = $uri_root.basename($filename); - $currentFileInfo['filename'] = $filename; - $currentFileInfo['uri_root'] = $uri_root; - - - //inherit reference - if( isset($this->env->currentFileInfo['reference']) && $this->env->currentFileInfo['reference'] ){ - $currentFileInfo['reference'] = true; - } - - $this->env->currentFileInfo = $currentFileInfo; - } - - - /** - * @deprecated 1.5.1.2 - * - */ - public function SetCacheDir( $dir ){ - - if( !file_exists($dir) ){ - if( mkdir($dir) ){ - return true; - } - throw new Less_Exception_Parser('Less.php cache directory couldn\'t be created: '.$dir); - - }elseif( !is_dir($dir) ){ - throw new Less_Exception_Parser('Less.php cache directory doesn\'t exist: '.$dir); - - }elseif( !is_writable($dir) ){ - throw new Less_Exception_Parser('Less.php cache directory isn\'t writable: '.$dir); - - }else{ - $dir = self::WinPath($dir); - Less_Cache::$cache_dir = rtrim($dir,'/').'/'; - return true; - } - } - - - /** - * Set a list of directories or callbacks the parser should use for determining import paths - * - * @param array $dirs - */ - public function SetImportDirs( $dirs ){ - Less_Parser::$options['import_dirs'] = array(); - - foreach($dirs as $path => $uri_root){ - - $path = self::WinPath($path); - if( !empty($path) ){ - $path = rtrim($path,'/').'/'; - } - - if ( !is_callable($uri_root) ){ - $uri_root = self::WinPath($uri_root); - if( !empty($uri_root) ){ - $uri_root = rtrim($uri_root,'/').'/'; - } - } - - Less_Parser::$options['import_dirs'][$path] = $uri_root; - } - } - - /** - * @param string $file_path - */ - private function _parse( $file_path = null ){ - $this->rules = array_merge($this->rules, $this->GetRules( $file_path )); - } - - - /** - * Return the results of parsePrimary for $file_path - * Use cache and save cached results if possible - * - * @param string|null $file_path - */ - private function GetRules( $file_path ){ - - $this->SetInput($file_path); - - $cache_file = $this->CacheFile( $file_path ); - if( $cache_file ){ - if( Less_Parser::$options['cache_method'] == 'callback' ){ - if( is_callable(Less_Parser::$options['cache_callback_get']) ){ - $cache = call_user_func_array( - Less_Parser::$options['cache_callback_get'], - array($this, $file_path, $cache_file) - ); - - if( $cache ){ - $this->UnsetInput(); - return $cache; - } - } - - }elseif( file_exists($cache_file) ){ - switch(Less_Parser::$options['cache_method']){ - - // Using serialize - // Faster but uses more memory - case 'serialize': - $cache = unserialize(file_get_contents($cache_file)); - if( $cache ){ - touch($cache_file); - $this->UnsetInput(); - return $cache; - } - break; - - - // Using generated php code - case 'var_export': - case 'php': - $this->UnsetInput(); - return include($cache_file); - } - } - } - - $rules = $this->parsePrimary(); - - if( $this->pos < $this->input_len ){ - throw new Less_Exception_Chunk($this->input, null, $this->furthest, $this->env->currentFileInfo); - } - - $this->UnsetInput(); - - - //save the cache - if( $cache_file ){ - if( Less_Parser::$options['cache_method'] == 'callback' ){ - if( is_callable(Less_Parser::$options['cache_callback_set']) ){ - call_user_func_array( - Less_Parser::$options['cache_callback_set'], - array($this, $file_path, $cache_file, $rules) - ); - } - - }else{ - //msg('write cache file'); - switch(Less_Parser::$options['cache_method']){ - case 'serialize': - file_put_contents( $cache_file, serialize($rules) ); - break; - case 'php': - file_put_contents( $cache_file, '' ); - break; - case 'var_export': - //Requires __set_state() - file_put_contents( $cache_file, '' ); - break; - } - - Less_Cache::CleanCache(); - } - } - - return $rules; - } - - - /** - * Set up the input buffer - * - */ - public function SetInput( $file_path ){ - - if( $file_path ){ - $this->input = file_get_contents( $file_path ); - } - - $this->pos = $this->furthest = 0; - - // Remove potential UTF Byte Order Mark - $this->input = preg_replace('/\\G\xEF\xBB\xBF/', '', $this->input); - $this->input_len = strlen($this->input); - - - if( Less_Parser::$options['sourceMap'] && $this->env->currentFileInfo ){ - $uri = $this->env->currentFileInfo['currentUri']; - Less_Parser::$contentsMap[$uri] = $this->input; - } - - } - - - /** - * Free up some memory - * - */ - public function UnsetInput(){ - unset($this->input, $this->pos, $this->input_len, $this->furthest); - $this->saveStack = array(); - } - - - public function CacheFile( $file_path ){ - - if( $file_path && $this->CacheEnabled() ){ - - $env = get_object_vars($this->env); - unset($env['frames']); - - $parts = array(); - $parts[] = $file_path; - $parts[] = filesize( $file_path ); - $parts[] = filemtime( $file_path ); - $parts[] = $env; - $parts[] = Less_Version::cache_version; - $parts[] = Less_Parser::$options['cache_method']; - return Less_Cache::$cache_dir . Less_Cache::$prefix . base_convert( sha1(json_encode($parts) ), 16, 36) . '.lesscache'; - } - } - - - static function AddParsedFile($file){ - self::$imports[] = $file; - } - - static function AllParsedFiles(){ - return self::$imports; - } - - /** - * @param string $file - */ - static function FileParsed($file){ - return in_array($file,self::$imports); - } - - - function save() { - $this->saveStack[] = $this->pos; - } - - private function restore() { - $this->pos = array_pop($this->saveStack); - } - - private function forget(){ - array_pop($this->saveStack); - } - - - private function isWhitespace($offset = 0) { - return preg_match('/\s/',$this->input[ $this->pos + $offset]); - } - - /** - * Parse from a token, regexp or string, and move forward if match - * - * @param array $toks - * @return array - */ - private function match($toks){ - - // The match is confirmed, add the match length to `this::pos`, - // and consume any extra white-space characters (' ' || '\n') - // which come after that. The reason for this is that LeSS's - // grammar is mostly white-space insensitive. - // - - foreach($toks as $tok){ - - $char = $tok[0]; - - if( $char === '/' ){ - $match = $this->MatchReg($tok); - - if( $match ){ - return count_if_countable($match) === 1 ? $match[0] : $match; - } - - }elseif( $char === '#' ){ - $match = $this->MatchChar($tok[1]); - - }else{ - // Non-terminal, match using a function call - $match = $this->$tok(); - - } - - if( $match ){ - return $match; - } - } - } - - /** - * @param string[] $toks - * - * @return string - */ - private function MatchFuncs($toks){ - - if( $this->pos < $this->input_len ){ - foreach($toks as $tok){ - $match = $this->$tok(); - if( $match ){ - return $match; - } - } - } - - } - - // Match a single character in the input, - private function MatchChar($tok){ - if( ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok) ){ - $this->skipWhitespace(1); - return $tok; - } - } - - // Match a regexp from the current start point - private function MatchReg($tok){ - - if( preg_match($tok, $this->input, $match, 0, $this->pos) ){ - $this->skipWhitespace(strlen($match[0])); - return $match; - } - } - - - /** - * Same as match(), but don't change the state of the parser, - * just return the match. - * - * @param string $tok - * @return integer - */ - public function PeekReg($tok){ - return preg_match($tok, $this->input, $match, 0, $this->pos); - } - - /** - * @param string $tok - */ - public function PeekChar($tok){ - //return ($this->input[$this->pos] === $tok ); - return ($this->pos < $this->input_len) && ($this->input[$this->pos] === $tok ); - } - - - /** - * @param integer $length - */ - public function skipWhitespace($length){ - - $this->pos += $length; - - for(; $this->pos < $this->input_len; $this->pos++ ){ - $c = $this->input[$this->pos]; - - if( ($c !== "\n") && ($c !== "\r") && ($c !== "\t") && ($c !== ' ') ){ - break; - } - } - } - - - /** - * @param string $tok - * @param string|null $msg - */ - public function expect($tok, $msg = NULL) { - $result = $this->match( array($tok) ); - if (!$result) { - $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg ); - } else { - return $result; - } - } - - /** - * @param string $tok - */ - public function expectChar($tok, $msg = null ){ - $result = $this->MatchChar($tok); - if( !$result ){ - $this->Error( $msg ? "Expected '" . $tok . "' got '" . $this->input[$this->pos] . "'" : $msg ); - }else{ - return $result; - } - } - - // - // Here in, the parsing rules/functions - // - // The basic structure of the syntax tree generated is as follows: - // - // Ruleset -> Rule -> Value -> Expression -> Entity - // - // Here's some LESS code: - // - // .class { - // color: #fff; - // border: 1px solid #000; - // width: @w + 4px; - // > .child {...} - // } - // - // And here's what the parse tree might look like: - // - // Ruleset (Selector '.class', [ - // Rule ("color", Value ([Expression [Color #fff]])) - // Rule ("border", Value ([Expression [Dimension 1px][Keyword "solid"][Color #000]])) - // Rule ("width", Value ([Expression [Operation "+" [Variable "@w"][Dimension 4px]]])) - // Ruleset (Selector [Element '>', '.child'], [...]) - // ]) - // - // In general, most rules will try to parse a token with the `$()` function, and if the return - // value is truly, will return a new node, of the relevant type. Sometimes, we need to check - // first, before parsing, that's when we use `peek()`. - // - - // - // The `primary` rule is the *entry* and *exit* point of the parser. - // The rules here can appear at any level of the parse tree. - // - // The recursive nature of the grammar is an interplay between the `block` - // rule, which represents `{ ... }`, the `ruleset` rule, and this `primary` rule, - // as represented by this simplified grammar: - // - // primary → (ruleset | rule)+ - // ruleset → selector+ block - // block → '{' primary '}' - // - // Only at one point is the primary rule not called from the - // block rule: at the root level. - // - private function parsePrimary(){ - $root = array(); - - while( true ){ - - if( $this->pos >= $this->input_len ){ - break; - } - - $node = $this->parseExtend(true); - if( $node ){ - $root = array_merge($root,$node); - continue; - } - - //$node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseDirective')); - $node = $this->MatchFuncs( array( 'parseMixinDefinition', 'parseNameValue', 'parseRule', 'parseRuleset', 'parseMixinCall', 'parseComment', 'parseRulesetCall', 'parseDirective')); - - if( $node ){ - $root[] = $node; - }elseif( !$this->MatchReg('/\\G[\s\n;]+/') ){ - break; - } - - if( $this->PeekChar('}') ){ - break; - } - } - - return $root; - } - - - - // We create a Comment node for CSS comments `/* */`, - // but keep the LeSS comments `//` silent, by just skipping - // over them. - private function parseComment(){ - - if( $this->input[$this->pos] !== '/' ){ - return; - } - - if( $this->input[$this->pos+1] === '/' ){ - $match = $this->MatchReg('/\\G\/\/.*/'); - return $this->NewObj4('Less_Tree_Comment',array($match[0], true, $this->pos, $this->env->currentFileInfo)); - } - - //$comment = $this->MatchReg('/\\G\/\*(?:[^*]|\*+[^\/*])*\*+\/\n?/'); - $comment = $this->MatchReg('/\\G\/\*(?s).*?\*+\/\n?/');//not the same as less.js to prevent fatal errors - if( $comment ){ - return $this->NewObj4('Less_Tree_Comment',array($comment[0], false, $this->pos, $this->env->currentFileInfo)); - } - } - - private function parseComments(){ - $comments = array(); - - while( $this->pos < $this->input_len ){ - $comment = $this->parseComment(); - if( !$comment ){ - break; - } - - $comments[] = $comment; - } - - return $comments; - } - - - - // - // A string, which supports escaping " and ' - // - // "milky way" 'he\'s the one!' - // - private function parseEntitiesQuoted() { - $j = $this->pos; - $e = false; - $index = $this->pos; - - if( $this->input[$this->pos] === '~' ){ - $j++; - $e = true; // Escaped strings - } - - if( $this->input[$j] != '"' && $this->input[$j] !== "'" ){ - return; - } - - if ($e) { - $this->MatchChar('~'); - } - - // Fix for #124: match escaped newlines - //$str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.)*)"|\'((?:[^\'\\\\\r\n]|\\\\.)*)\'/'); - $str = $this->MatchReg('/\\G"((?:[^"\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)"|\'((?:[^\'\\\\\r\n]|\\\\.|\\\\\r\n|\\\\[\n\r\f])*)\'/'); - - if( $str ){ - $result = $str[0][0] == '"' ? $str[1] : $str[2]; - return $this->NewObj5('Less_Tree_Quoted',array($str[0], $result, $e, $index, $this->env->currentFileInfo) ); - } - return; - } - - - // - // A catch-all word, such as: - // - // black border-collapse - // - private function parseEntitiesKeyword(){ - - //$k = $this->MatchReg('/\\G[_A-Za-z-][_A-Za-z0-9-]*/'); - $k = $this->MatchReg('/\\G%|\\G[_A-Za-z-][_A-Za-z0-9-]*/'); - if( $k ){ - $k = $k[0]; - $color = $this->fromKeyword($k); - if( $color ){ - return $color; - } - return $this->NewObj1('Less_Tree_Keyword',$k); - } - } - - // duplicate of Less_Tree_Color::FromKeyword - private function FromKeyword( $keyword ){ - $keyword = strtolower($keyword); - - if( Less_Colors::hasOwnProperty($keyword) ){ - // detect named color - return $this->NewObj1('Less_Tree_Color',substr(Less_Colors::color($keyword), 1)); - } - - if( $keyword === 'transparent' ){ - return $this->NewObj3('Less_Tree_Color', array( array(0, 0, 0), 0, true)); - } - } - - // - // A function call - // - // rgb(255, 0, 255) - // - // We also try to catch IE's `alpha()`, but let the `alpha` parser - // deal with the details. - // - // The arguments are parsed with the `entities.arguments` parser. - // - private function parseEntitiesCall(){ - $index = $this->pos; - - if( !preg_match('/\\G([\w-]+|%|progid:[\w\.]+)\(/', $this->input, $name,0,$this->pos) ){ - return; - } - $name = $name[1]; - $nameLC = strtolower($name); - - if ($nameLC === 'url') { - return null; - } - - $this->pos += strlen($name); - - if( $nameLC === 'alpha' ){ - $alpha_ret = $this->parseAlpha(); - if( $alpha_ret ){ - return $alpha_ret; - } - } - - $this->MatchChar('('); // Parse the '(' and consume whitespace. - - $args = $this->parseEntitiesArguments(); - - if( !$this->MatchChar(')') ){ - return; - } - - if ($name) { - return $this->NewObj4('Less_Tree_Call',array($name, $args, $index, $this->env->currentFileInfo) ); - } - } - - /** - * Parse a list of arguments - * - * @return array - */ - private function parseEntitiesArguments(){ - - $args = array(); - while( true ){ - $arg = $this->MatchFuncs( array('parseEntitiesAssignment','parseExpression') ); - if( !$arg ){ - break; - } - - $args[] = $arg; - if( !$this->MatchChar(',') ){ - break; - } - } - return $args; - } - - private function parseEntitiesLiteral(){ - return $this->MatchFuncs( array('parseEntitiesDimension','parseEntitiesColor','parseEntitiesQuoted','parseUnicodeDescriptor') ); - } - - // Assignments are argument entities for calls. - // They are present in ie filter properties as shown below. - // - // filter: progid:DXImageTransform.Microsoft.Alpha( *opacity=50* ) - // - private function parseEntitiesAssignment() { - - $key = $this->MatchReg('/\\G\w+(?=\s?=)/'); - if( !$key ){ - return; - } - - if( !$this->MatchChar('=') ){ - return; - } - - $value = $this->parseEntity(); - if( $value ){ - return $this->NewObj2('Less_Tree_Assignment',array($key[0], $value)); - } - } - - // - // Parse url() tokens - // - // We use a specific rule for urls, because they don't really behave like - // standard function calls. The difference is that the argument doesn't have - // to be enclosed within a string, so it can't be parsed as an Expression. - // - private function parseEntitiesUrl(){ - - - if( $this->input[$this->pos] !== 'u' || !$this->matchReg('/\\Gurl\(/') ){ - return; - } - - $value = $this->match( array('parseEntitiesQuoted','parseEntitiesVariable','/\\Gdata\:.*?[^\)]+/','/\\G(?:(?:\\\\[\(\)\'"])|[^\(\)\'"])+/') ); - if( !$value ){ - $value = ''; - } - - - $this->expectChar(')'); - - - if( isset($value->value) || $value instanceof Less_Tree_Variable ){ - return $this->NewObj2('Less_Tree_Url',array($value, $this->env->currentFileInfo)); - } - - return $this->NewObj2('Less_Tree_Url', array( $this->NewObj1('Less_Tree_Anonymous',$value), $this->env->currentFileInfo) ); - } - - - // - // A Variable entity, such as `@fink`, in - // - // width: @fink + 2px - // - // We use a different parser for variable definitions, - // see `parsers.variable`. - // - private function parseEntitiesVariable(){ - $index = $this->pos; - if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G@@?[\w-]+/'))) { - return $this->NewObj3('Less_Tree_Variable', array( $name[0], $index, $this->env->currentFileInfo)); - } - } - - - // A variable entity useing the protective {} e.g. @{var} - private function parseEntitiesVariableCurly() { - $index = $this->pos; - - if( $this->input_len > ($this->pos+1) && $this->input[$this->pos] === '@' && ($curly = $this->MatchReg('/\\G@\{([\w-]+)\}/')) ){ - return $this->NewObj3('Less_Tree_Variable',array('@'.$curly[1], $index, $this->env->currentFileInfo)); - } - } - - // - // A Hexadecimal color - // - // #4F3C2F - // - // `rgb` and `hsl` colors are parsed through the `entities.call` parser. - // - private function parseEntitiesColor(){ - if ($this->PeekChar('#') && ($rgb = $this->MatchReg('/\\G#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/'))) { - return $this->NewObj1('Less_Tree_Color',$rgb[1]); - } - } - - // - // A Dimension, that is, a number and a unit - // - // 0.5em 95% - // - private function parseEntitiesDimension(){ - - $c = @ord($this->input[$this->pos]); - - //Is the first char of the dimension 0-9, '.', '+' or '-' - if (($c > 57 || $c < 43) || $c === 47 || $c == 44){ - return; - } - - $value = $this->MatchReg('/\\G([+-]?\d*\.?\d+)(%|[a-z]+)?/'); - if( $value ){ - - if( isset($value[2]) ){ - return $this->NewObj2('Less_Tree_Dimension', array($value[1],$value[2])); - } - return $this->NewObj1('Less_Tree_Dimension',$value[1]); - } - } - - - // - // A unicode descriptor, as is used in unicode-range - // - // U+0?? or U+00A1-00A9 - // - function parseUnicodeDescriptor() { - $ud = $this->MatchReg('/\\G(U\+[0-9a-fA-F?]+)(\-[0-9a-fA-F?]+)?/'); - if( $ud ){ - return $this->NewObj1('Less_Tree_UnicodeDescriptor', $ud[0]); - } - } - - - // - // JavaScript code to be evaluated - // - // `window.location.href` - // - private function parseEntitiesJavascript(){ - $e = false; - $j = $this->pos; - if( $this->input[$j] === '~' ){ - $j++; - $e = true; - } - if( $this->input[$j] !== '`' ){ - return; - } - if( $e ){ - $this->MatchChar('~'); - } - $str = $this->MatchReg('/\\G`([^`]*)`/'); - if( $str ){ - return $this->NewObj3('Less_Tree_Javascript', array($str[1], $this->pos, $e)); - } - } - - - // - // The variable part of a variable definition. Used in the `rule` parser - // - // @fink: - // - private function parseVariable(){ - if ($this->PeekChar('@') && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*:/'))) { - return $name[1]; - } - } - - - // - // The variable part of a variable definition. Used in the `rule` parser - // - // @fink(); - // - private function parseRulesetCall(){ - - if( $this->input[$this->pos] === '@' && ($name = $this->MatchReg('/\\G(@[\w-]+)\s*\(\s*\)\s*;/')) ){ - return $this->NewObj1('Less_Tree_RulesetCall', $name[1] ); - } - } - - - // - // extend syntax - used to extend selectors - // - function parseExtend($isRule = false){ - - $index = $this->pos; - $extendList = array(); - - - if( !$this->MatchReg( $isRule ? '/\\G&:extend\(/' : '/\\G:extend\(/' ) ){ return; } - - do{ - $option = null; - $elements = array(); - while( true ){ - $option = $this->MatchReg('/\\G(all)(?=\s*(\)|,))/'); - if( $option ){ break; } - $e = $this->parseElement(); - if( !$e ){ break; } - $elements[] = $e; - } - - if( $option ){ - $option = $option[1]; - } - - $extendList[] = $this->NewObj3('Less_Tree_Extend', array( $this->NewObj1('Less_Tree_Selector',$elements), $option, $index )); - - }while( $this->MatchChar(",") ); - - $this->expect('/\\G\)/'); - - if( $isRule ){ - $this->expect('/\\G;/'); - } - - return $extendList; - } - - - // - // A Mixin call, with an optional argument list - // - // #mixins > .square(#fff); - // .rounded(4px, black); - // .button; - // - // The `while` loop is there because mixins can be - // namespaced, but we only support the child and descendant - // selector for now. - // - private function parseMixinCall(){ - - $char = $this->input[$this->pos]; - if( $char !== '.' && $char !== '#' ){ - return; - } - - $index = $this->pos; - $this->save(); // stop us absorbing part of an invalid selector - - $elements = $this->parseMixinCallElements(); - - if( $elements ){ - - if( $this->MatchChar('(') ){ - $returned = $this->parseMixinArgs(true); - $args = $returned['args']; - $this->expectChar(')'); - }else{ - $args = array(); - } - - $important = $this->parseImportant(); - - if( $this->parseEnd() ){ - $this->forget(); - return $this->NewObj5('Less_Tree_Mixin_Call', array( $elements, $args, $index, $this->env->currentFileInfo, $important)); - } - } - - $this->restore(); - } - - - private function parseMixinCallElements(){ - $elements = array(); - $c = null; - - while( true ){ - $elemIndex = $this->pos; - $e = $this->MatchReg('/\\G[#.](?:[\w-]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/'); - if( !$e ){ - break; - } - $elements[] = $this->NewObj4('Less_Tree_Element', array($c, $e[0], $elemIndex, $this->env->currentFileInfo)); - $c = $this->MatchChar('>'); - } - - return $elements; - } - - - - /** - * @param boolean $isCall - */ - private function parseMixinArgs( $isCall ){ - $expressions = array(); - $argsSemiColon = array(); - $isSemiColonSeperated = null; - $argsComma = array(); - $expressionContainsNamed = null; - $name = null; - $returner = array('args'=>array(), 'variadic'=> false); - - $this->save(); - - while( true ){ - if( $isCall ){ - $arg = $this->MatchFuncs( array( 'parseDetachedRuleset','parseExpression' ) ); - } else { - $this->parseComments(); - if( $this->input[ $this->pos ] === '.' && $this->MatchReg('/\\G\.{3}/') ){ - $returner['variadic'] = true; - if( $this->MatchChar(";") && !$isSemiColonSeperated ){ - $isSemiColonSeperated = true; - } - - if( $isSemiColonSeperated ){ - $argsSemiColon[] = array('variadic'=>true); - }else{ - $argsComma[] = array('variadic'=>true); - } - break; - } - $arg = $this->MatchFuncs( array('parseEntitiesVariable','parseEntitiesLiteral','parseEntitiesKeyword') ); - } - - if( !$arg ){ - break; - } - - - $nameLoop = null; - if( $arg instanceof Less_Tree_Expression ){ - $arg->throwAwayComments(); - } - $value = $arg; - $val = null; - - if( $isCall ){ - // Variable - if( property_exists($arg,'value') && count_if_countable($arg->value) == 1 ){ - $val = $arg->value[0]; - } - } else { - $val = $arg; - } - - - if( $val instanceof Less_Tree_Variable ){ - - if( $this->MatchChar(':') ){ - if( $expressions ){ - if( $isSemiColonSeperated ){ - $this->Error('Cannot mix ; and , as delimiter types'); - } - $expressionContainsNamed = true; - } - - // we do not support setting a ruleset as a default variable - it doesn't make sense - // However if we do want to add it, there is nothing blocking it, just don't error - // and remove isCall dependency below - $value = null; - if( $isCall ){ - $value = $this->parseDetachedRuleset(); - } - if( !$value ){ - $value = $this->parseExpression(); - } - - if( !$value ){ - if( $isCall ){ - $this->Error('could not understand value for named argument'); - } else { - $this->restore(); - $returner['args'] = array(); - return $returner; - } - } - - $nameLoop = ($name = $val->name); - }elseif( !$isCall && $this->MatchReg('/\\G\.{3}/') ){ - $returner['variadic'] = true; - if( $this->MatchChar(";") && !$isSemiColonSeperated ){ - $isSemiColonSeperated = true; - } - if( $isSemiColonSeperated ){ - $argsSemiColon[] = array('name'=> $arg->name, 'variadic' => true); - }else{ - $argsComma[] = array('name'=> $arg->name, 'variadic' => true); - } - break; - }elseif( !$isCall ){ - $name = $nameLoop = $val->name; - $value = null; - } - } - - if( $value ){ - $expressions[] = $value; - } - - $argsComma[] = array('name'=>$nameLoop, 'value'=>$value ); - - if( $this->MatchChar(',') ){ - continue; - } - - if( $this->MatchChar(';') || $isSemiColonSeperated ){ - - if( $expressionContainsNamed ){ - $this->Error('Cannot mix ; and , as delimiter types'); - } - - $isSemiColonSeperated = true; - - if( count_if_countable($expressions) > 1 ){ - $value = $this->NewObj1('Less_Tree_Value', $expressions); - } - $argsSemiColon[] = array('name'=>$name, 'value'=>$value ); - - $name = null; - $expressions = array(); - $expressionContainsNamed = false; - } - } - - $this->forget(); - $returner['args'] = ($isSemiColonSeperated ? $argsSemiColon : $argsComma); - return $returner; - } - - - - // - // A Mixin definition, with a list of parameters - // - // .rounded (@radius: 2px, @color) { - // ... - // } - // - // Until we have a finer grained state-machine, we have to - // do a look-ahead, to make sure we don't have a mixin call. - // See the `rule` function for more information. - // - // We start by matching `.rounded (`, and then proceed on to - // the argument list, which has optional default values. - // We store the parameters in `params`, with a `value` key, - // if there is a value, such as in the case of `@radius`. - // - // Once we've got our params list, and a closing `)`, we parse - // the `{...}` block. - // - private function parseMixinDefinition(){ - $cond = null; - - $char = $this->input[$this->pos]; - if( ($char !== '.' && $char !== '#') || ($char === '{' && $this->PeekReg('/\\G[^{]*\}/')) ){ - return; - } - - $this->save(); - - $match = $this->MatchReg('/\\G([#.](?:[\w-]|\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+)\s*\(/'); - if( $match ){ - $name = $match[1]; - - $argInfo = $this->parseMixinArgs( false ); - $params = $argInfo['args']; - $variadic = $argInfo['variadic']; - - - // .mixincall("@{a}"); - // looks a bit like a mixin definition.. - // also - // .mixincall(@a: {rule: set;}); - // so we have to be nice and restore - if( !$this->MatchChar(')') ){ - $this->furthest = $this->pos; - $this->restore(); - return; - } - - - $this->parseComments(); - - if ($this->MatchReg('/\\Gwhen/')) { // Guard - $cond = $this->expect('parseConditions', 'Expected conditions'); - } - - $ruleset = $this->parseBlock(); - - if( is_array($ruleset) ){ - $this->forget(); - return $this->NewObj5('Less_Tree_Mixin_Definition', array( $name, $params, $ruleset, $cond, $variadic)); - } - - $this->restore(); - }else{ - $this->forget(); - } - } - - // - // Entities are the smallest recognized token, - // and can be found inside a rule's value. - // - private function parseEntity(){ - - return $this->MatchFuncs( array('parseEntitiesLiteral','parseEntitiesVariable','parseEntitiesUrl','parseEntitiesCall','parseEntitiesKeyword','parseEntitiesJavascript','parseComment') ); - } - - // - // A Rule terminator. Note that we use `peek()` to check for '}', - // because the `block` rule will be expecting it, but we still need to make sure - // it's there, if ';' was ommitted. - // - private function parseEnd(){ - return $this->MatchChar(';') || $this->PeekChar('}'); - } - - // - // IE's alpha function - // - // alpha(opacity=88) - // - private function parseAlpha(){ - - if ( ! $this->MatchReg('/\\G\(opacity=/i')) { - return; - } - - $value = $this->MatchReg('/\\G[0-9]+/'); - if( $value ){ - $value = $value[0]; - }else{ - $value = $this->parseEntitiesVariable(); - if( !$value ){ - return; - } - } - - $this->expectChar(')'); - return $this->NewObj1('Less_Tree_Alpha',$value); - } - - - // - // A Selector Element - // - // div - // + h1 - // #socks - // input[type="text"] - // - // Elements are the building blocks for Selectors, - // they are made out of a `Combinator` (see combinator rule), - // and an element name, such as a tag a class, or `*`. - // - private function parseElement(){ - $c = $this->parseCombinator(); - $index = $this->pos; - - $e = $this->match( array('/\\G(?:\d+\.\d+|\d+)%/', '/\\G(?:[.#]?|:*)(?:[\w-]|[^\x00-\x9f]|\\\\(?:[A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9]))+/', - '#*', '#&', 'parseAttribute', '/\\G\([^()@]+\)/', '/\\G[\.#](?=@)/', 'parseEntitiesVariableCurly') ); - - if( is_null($e) ){ - $this->save(); - if( $this->MatchChar('(') ){ - if( ($v = $this->parseSelector()) && $this->MatchChar(')') ){ - $e = $this->NewObj1('Less_Tree_Paren',$v); - $this->forget(); - }else{ - $this->restore(); - } - }else{ - $this->forget(); - } - } - - if( !is_null($e) ){ - return $this->NewObj4('Less_Tree_Element',array( $c, $e, $index, $this->env->currentFileInfo)); - } - } - - // - // Combinators combine elements together, in a Selector. - // - // Because our parser isn't white-space sensitive, special care - // has to be taken, when parsing the descendant combinator, ` `, - // as it's an empty space. We have to check the previous character - // in the input, to see if it's a ` ` character. - // - private function parseCombinator(){ - if( $this->pos < $this->input_len ){ - $c = $this->input[$this->pos]; - if ($c === '>' || $c === '+' || $c === '~' || $c === '|' || $c === '^' ){ - - $this->pos++; - if( $this->input[$this->pos] === '^' ){ - $c = '^^'; - $this->pos++; - } - - $this->skipWhitespace(0); - - return $c; - } - - if( $this->pos > 0 && $this->isWhitespace(-1) ){ - return ' '; - } - } - } - - // - // A CSS selector (see selector below) - // with less extensions e.g. the ability to extend and guard - // - private function parseLessSelector(){ - return $this->parseSelector(true); - } - - // - // A CSS Selector - // - // .class > div + h1 - // li a:hover - // - // Selectors are made out of one or more Elements, see above. - // - private function parseSelector( $isLess = false ){ - $elements = array(); - $extendList = array(); - $condition = null; - $when = false; - $extend = false; - $e = null; - $c = null; - $index = $this->pos; - - while( ($isLess && ($extend = $this->parseExtend())) || ($isLess && ($when = $this->MatchReg('/\\Gwhen/') )) || ($e = $this->parseElement()) ){ - if( $when ){ - $condition = $this->expect('parseConditions', 'expected condition'); - }elseif( $condition ){ - //error("CSS guard can only be used at the end of selector"); - }elseif( $extend ){ - $extendList = array_merge($extendList,$extend); - }else{ - //if( count($extendList) ){ - //error("Extend can only be used at the end of selector"); - //} - if( $this->pos < $this->input_len ){ - $c = $this->input[ $this->pos ]; - } - $elements[] = $e; - $e = null; - } - - if( $c === '{' || $c === '}' || $c === ';' || $c === ',' || $c === ')') { break; } - } - - if( $elements ){ - return $this->NewObj5('Less_Tree_Selector',array($elements, $extendList, $condition, $index, $this->env->currentFileInfo)); - } - if( $extendList ) { - $this->Error('Extend must be used to extend a selector, it cannot be used on its own'); - } - } - - private function parseTag(){ - return ( $tag = $this->MatchReg('/\\G[A-Za-z][A-Za-z-]*[0-9]?/') ) ? $tag : $this->MatchChar('*'); - } - - private function parseAttribute(){ - - $val = null; - - if( !$this->MatchChar('[') ){ - return; - } - - $key = $this->parseEntitiesVariableCurly(); - if( !$key ){ - $key = $this->expect('/\\G(?:[_A-Za-z0-9-\*]*\|)?(?:[_A-Za-z0-9-]|\\\\.)+/'); - } - - $op = $this->MatchReg('/\\G[|~*$^]?=/'); - if( $op ){ - $val = $this->match( array('parseEntitiesQuoted','/\\G[0-9]+%/','/\\G[\w-]+/','parseEntitiesVariableCurly') ); - } - - $this->expectChar(']'); - - return $this->NewObj3('Less_Tree_Attribute',array( $key, $op[0], $val)); - } - - // - // The `block` rule is used by `ruleset` and `mixin.definition`. - // It's a wrapper around the `primary` rule, with added `{}`. - // - private function parseBlock(){ - if( $this->MatchChar('{') ){ - $content = $this->parsePrimary(); - if( $this->MatchChar('}') ){ - return $content; - } - } - } - - private function parseBlockRuleset(){ - $block = $this->parseBlock(); - - if( $block ){ - $block = $this->NewObj2('Less_Tree_Ruleset',array( null, $block)); - } - - return $block; - } - - private function parseDetachedRuleset(){ - $blockRuleset = $this->parseBlockRuleset(); - if( $blockRuleset ){ - return $this->NewObj1('Less_Tree_DetachedRuleset',$blockRuleset); - } - } - - // - // div, .class, body > p {...} - // - private function parseRuleset(){ - $selectors = array(); - - $this->save(); - - while( true ){ - $s = $this->parseLessSelector(); - if( !$s ){ - break; - } - $selectors[] = $s; - $this->parseComments(); - - if( $s->condition && count_if_countable($selectors) > 1 ){ - $this->Error('Guards are only currently allowed on a single selector.'); - } - - if( !$this->MatchChar(',') ){ - break; - } - if( $s->condition ){ - $this->Error('Guards are only currently allowed on a single selector.'); - } - $this->parseComments(); - } - - - if( $selectors ){ - $rules = $this->parseBlock(); - if( is_array($rules) ){ - $this->forget(); - return $this->NewObj2('Less_Tree_Ruleset',array( $selectors, $rules)); //Less_Environment::$strictImports - } - } - - // Backtrack - $this->furthest = $this->pos; - $this->restore(); - } - - /** - * Custom less.php parse function for finding simple name-value css pairs - * ex: width:100px; - * - */ - private function parseNameValue(){ - - $index = $this->pos; - $this->save(); - - - //$match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*((?:\'")?[a-zA-Z0-9\-% \.,!]+?(?:\'")?)\s*([;}])/'); - $match = $this->MatchReg('/\\G([a-zA-Z\-]+)\s*:\s*([\'"]?[#a-zA-Z0-9\-%\.,]+?[\'"]?) *(! *important)?\s*([;}])/'); - if( $match ){ - - if( $match[4] == '}' ){ - $this->pos = $index + strlen($match[0])-1; - } - - if( $match[3] ){ - $match[2] .= ' !important'; - } - - return $this->NewObj4('Less_Tree_NameValue',array( $match[1], $match[2], $index, $this->env->currentFileInfo)); - } - - $this->restore(); - } - - - private function parseRule( $tryAnonymous = null ){ - - $merge = false; - $startOfRule = $this->pos; - - $c = $this->input[$this->pos]; - if( $c === '.' || $c === '#' || $c === '&' ){ - return; - } - - $this->save(); - $name = $this->MatchFuncs( array('parseVariable','parseRuleProperty')); - - if( $name ){ - - $isVariable = is_string($name); - - $value = null; - if( $isVariable ){ - $value = $this->parseDetachedRuleset(); - } - - $important = null; - if( !$value ){ - - // prefer to try to parse first if its a variable or we are compressing - // but always fallback on the other one - //if( !$tryAnonymous && is_string($name) && $name[0] === '@' ){ - if( !$tryAnonymous && (Less_Parser::$options['compress'] || $isVariable) ){ - $value = $this->MatchFuncs( array('parseValue','parseAnonymousValue')); - }else{ - $value = $this->MatchFuncs( array('parseAnonymousValue','parseValue')); - } - - $important = $this->parseImportant(); - - // a name returned by this.ruleProperty() is always an array of the form: - // [string-1, ..., string-n, ""] or [string-1, ..., string-n, "+"] - // where each item is a tree.Keyword or tree.Variable - if( !$isVariable && is_array($name) ){ - $nm = array_pop($name); - if( $nm->value ){ - $merge = $nm->value; - } - } - } - - - if( $value && $this->parseEnd() ){ - $this->forget(); - return $this->NewObj6('Less_Tree_Rule',array( $name, $value, $important, $merge, $startOfRule, $this->env->currentFileInfo)); - }else{ - $this->furthest = $this->pos; - $this->restore(); - if( $value && !$tryAnonymous ){ - return $this->parseRule(true); - } - } - }else{ - $this->forget(); - } - } - - function parseAnonymousValue(){ - - if( preg_match('/\\G([^@+\/\'"*`(;{}-]*);/',$this->input, $match, 0, $this->pos) ){ - $this->pos += strlen($match[1]); - return $this->NewObj1('Less_Tree_Anonymous',$match[1]); - } - } - - // - // An @import directive - // - // @import "lib"; - // - // Depending on our environment, importing is done differently: - // In the browser, it's an XHR request, in Node, it would be a - // file-system operation. The function used for importing is - // stored in `import`, which we pass to the Import constructor. - // - private function parseImport(){ - - $this->save(); - - $dir = $this->MatchReg('/\\G@import?\s+/'); - - if( $dir ){ - $options = $this->parseImportOptions(); - $path = $this->MatchFuncs( array('parseEntitiesQuoted','parseEntitiesUrl')); - - if( $path ){ - $features = $this->parseMediaFeatures(); - if( $this->MatchChar(';') ){ - if( $features ){ - $features = $this->NewObj1('Less_Tree_Value',$features); - } - - $this->forget(); - return $this->NewObj5('Less_Tree_Import',array( $path, $features, $options, $this->pos, $this->env->currentFileInfo)); - } - } - } - - $this->restore(); - } - - private function parseImportOptions(){ - - $options = array(); - - // list of options, surrounded by parens - if( !$this->MatchChar('(') ){ - return $options; - } - do{ - $optionName = $this->parseImportOption(); - if( $optionName ){ - $value = true; - switch( $optionName ){ - case "css": - $optionName = "less"; - $value = false; - break; - case "once": - $optionName = "multiple"; - $value = false; - break; - } - $options[$optionName] = $value; - if( !$this->MatchChar(',') ){ break; } - } - }while( $optionName ); - $this->expectChar(')'); - return $options; - } - - private function parseImportOption(){ - $opt = $this->MatchReg('/\\G(less|css|multiple|once|inline|reference)/'); - if( $opt ){ - return $opt[1]; - } - } - - private function parseMediaFeature() { - $nodes = array(); - - do{ - $e = $this->MatchFuncs(array('parseEntitiesKeyword','parseEntitiesVariable')); - if( $e ){ - $nodes[] = $e; - } elseif ($this->MatchChar('(')) { - $p = $this->parseProperty(); - $e = $this->parseValue(); - if ($this->MatchChar(')')) { - if ($p && $e) { - $r = $this->NewObj7('Less_Tree_Rule', array( $p, $e, null, null, $this->pos, $this->env->currentFileInfo, true)); - $nodes[] = $this->NewObj1('Less_Tree_Paren',$r); - } elseif ($e) { - $nodes[] = $this->NewObj1('Less_Tree_Paren',$e); - } else { - return null; - } - } else - return null; - } - } while ($e); - - if ($nodes) { - return $this->NewObj1('Less_Tree_Expression',$nodes); - } - } - - private function parseMediaFeatures() { - $features = array(); - - do{ - $e = $this->parseMediaFeature(); - if( $e ){ - $features[] = $e; - if (!$this->MatchChar(',')) break; - }else{ - $e = $this->parseEntitiesVariable(); - if( $e ){ - $features[] = $e; - if (!$this->MatchChar(',')) break; - } - } - } while ($e); - - return $features ? $features : null; - } - - private function parseMedia() { - if( $this->MatchReg('/\\G@media/') ){ - $features = $this->parseMediaFeatures(); - $rules = $this->parseBlock(); - - if( is_array($rules) ){ - return $this->NewObj4('Less_Tree_Media',array( $rules, $features, $this->pos, $this->env->currentFileInfo)); - } - } - } - - - // - // A CSS Directive - // - // @charset "utf-8"; - // - private function parseDirective(){ - - if( !$this->PeekChar('@') ){ - return; - } - - $rules = null; - $index = $this->pos; - $hasBlock = true; - $hasIdentifier = false; - $hasExpression = false; - $hasUnknown = false; - - - $value = $this->MatchFuncs(array('parseImport','parseMedia')); - if( $value ){ - return $value; - } - - $this->save(); - - $name = $this->MatchReg('/\\G@[a-z-]+/'); - - if( !$name ) return; - $name = $name[0]; - - - $nonVendorSpecificName = $name; - $pos = strpos($name,'-', 2); - if( $name[1] == '-' && $pos > 0 ){ - $nonVendorSpecificName = "@" . substr($name, $pos + 1); - } - - - switch( $nonVendorSpecificName ){ - /* - case "@font-face": - case "@viewport": - case "@top-left": - case "@top-left-corner": - case "@top-center": - case "@top-right": - case "@top-right-corner": - case "@bottom-left": - case "@bottom-left-corner": - case "@bottom-center": - case "@bottom-right": - case "@bottom-right-corner": - case "@left-top": - case "@left-middle": - case "@left-bottom": - case "@right-top": - case "@right-middle": - case "@right-bottom": - hasBlock = true; - break; - */ - case "@charset": - $hasIdentifier = true; - $hasBlock = false; - break; - case "@namespace": - $hasExpression = true; - $hasBlock = false; - break; - case "@keyframes": - $hasIdentifier = true; - break; - case "@host": - case "@page": - case "@document": - case "@supports": - $hasUnknown = true; - break; - } - - if( $hasIdentifier ){ - $value = $this->parseEntity(); - if( !$value ){ - $this->error("expected " . $name . " identifier"); - } - } else if( $hasExpression ){ - $value = $this->parseExpression(); - if( !$value ){ - $this->error("expected " . $name. " expression"); - } - } else if ($hasUnknown) { - - $value = $this->MatchReg('/\\G[^{;]+/'); - if( $value ){ - $value = $this->NewObj1('Less_Tree_Anonymous',trim($value[0])); - } - } - - if( $hasBlock ){ - $rules = $this->parseBlockRuleset(); - } - - if( $rules || (!$hasBlock && $value && $this->MatchChar(';'))) { - $this->forget(); - return $this->NewObj5('Less_Tree_Directive',array($name, $value, $rules, $index, $this->env->currentFileInfo)); - } - - $this->restore(); - } - - - // - // A Value is a comma-delimited list of Expressions - // - // font-family: Baskerville, Georgia, serif; - // - // In a Rule, a Value represents everything after the `:`, - // and before the `;`. - // - private function parseValue(){ - $expressions = array(); - - do{ - $e = $this->parseExpression(); - if( $e ){ - $expressions[] = $e; - if (! $this->MatchChar(',')) { - break; - } - } - }while($e); - - if( $expressions ){ - return $this->NewObj1('Less_Tree_Value',$expressions); - } - } - - private function parseImportant (){ - if( $this->PeekChar('!') && $this->MatchReg('/\\G! *important/') ){ - return ' !important'; - } - } - - private function parseSub (){ - - if( $this->MatchChar('(') ){ - $a = $this->parseAddition(); - if( $a ){ - $this->expectChar(')'); - return $this->NewObj2('Less_Tree_Expression',array( array($a), true) ); //instead of $e->parens = true so the value is cached - } - } - } - - - /** - * Parses multiplication operation - * - * @return Less_Tree_Operation|null - */ - function parseMultiplication(){ - - $return = $m = $this->parseOperand(); - if( $return ){ - while( true ){ - - $isSpaced = $this->isWhitespace( -1 ); - - if( $this->PeekReg('/\\G\/[*\/]/') ){ - break; - } - - $op = $this->MatchChar('/'); - if( !$op ){ - $op = $this->MatchChar('*'); - if( !$op ){ - break; - } - } - - $a = $this->parseOperand(); - - if(!$a) { break; } - - $m->parensInOp = true; - $a->parensInOp = true; - $return = $this->NewObj3('Less_Tree_Operation',array( $op, array( $return, $a ), $isSpaced) ); - } - } - return $return; - - } - - - /** - * Parses an addition operation - * - * @return Less_Tree_Operation|null - */ - private function parseAddition (){ - - $return = $m = $this->parseMultiplication(); - if( $return ){ - while( true ){ - - $isSpaced = $this->isWhitespace( -1 ); - - $op = $this->MatchReg('/\\G[-+]\s+/'); - if( $op ){ - $op = $op[0]; - }else{ - if( !$isSpaced ){ - $op = $this->match(array('#+','#-')); - } - if( !$op ){ - break; - } - } - - $a = $this->parseMultiplication(); - if( !$a ){ - break; - } - - $m->parensInOp = true; - $a->parensInOp = true; - $return = $this->NewObj3('Less_Tree_Operation',array($op, array($return, $a), $isSpaced)); - } - } - - return $return; - } - - - /** - * Parses the conditions - * - * @return Less_Tree_Condition|null - */ - private function parseConditions() { - $index = $this->pos; - $return = $a = $this->parseCondition(); - if( $a ){ - while( true ){ - if( !$this->PeekReg('/\\G,\s*(not\s*)?\(/') || !$this->MatchChar(',') ){ - break; - } - $b = $this->parseCondition(); - if( !$b ){ - break; - } - - $return = $this->NewObj4('Less_Tree_Condition',array('or', $return, $b, $index)); - } - return $return; - } - } - - private function parseCondition() { - $index = $this->pos; - $negate = false; - $c = null; - - if ($this->MatchReg('/\\Gnot/')) $negate = true; - $this->expectChar('('); - $a = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted')); - - if( $a ){ - $op = $this->MatchReg('/\\G(?:>=|<=|=<|[<=>])/'); - if( $op ){ - $b = $this->MatchFuncs(array('parseAddition','parseEntitiesKeyword','parseEntitiesQuoted')); - if( $b ){ - $c = $this->NewObj5('Less_Tree_Condition',array($op[0], $a, $b, $index, $negate)); - } else { - $this->Error('Unexpected expression'); - } - } else { - $k = $this->NewObj1('Less_Tree_Keyword','true'); - $c = $this->NewObj5('Less_Tree_Condition',array('=', $a, $k, $index, $negate)); - } - $this->expectChar(')'); - return $this->MatchReg('/\\Gand/') ? $this->NewObj3('Less_Tree_Condition',array('and', $c, $this->parseCondition())) : $c; - } - } - - /** - * An operand is anything that can be part of an operation, - * such as a Color, or a Variable - * - */ - private function parseOperand (){ - - $negate = false; - $offset = $this->pos+1; - if( $offset >= $this->input_len ){ - return; - } - $char = $this->input[$offset]; - if( $char === '@' || $char === '(' ){ - $negate = $this->MatchChar('-'); - } - - $o = $this->MatchFuncs(array('parseSub','parseEntitiesDimension','parseEntitiesColor','parseEntitiesVariable','parseEntitiesCall')); - - if( $negate ){ - $o->parensInOp = true; - $o = $this->NewObj1('Less_Tree_Negative',$o); - } - - return $o; - } - - - /** - * Expressions either represent mathematical operations, - * or white-space delimited Entities. - * - * 1px solid black - * @var * 2 - * - * @return Less_Tree_Expression|null - */ - private function parseExpression (){ - $entities = array(); - - do{ - $e = $this->MatchFuncs(array('parseAddition','parseEntity')); - if( $e ){ - $entities[] = $e; - // operations do not allow keyword "/" dimension (e.g. small/20px) so we support that here - if( !$this->PeekReg('/\\G\/[\/*]/') ){ - $delim = $this->MatchChar('/'); - if( $delim ){ - $entities[] = $this->NewObj1('Less_Tree_Anonymous',$delim); - } - } - } - }while($e); - - if( $entities ){ - return $this->NewObj1('Less_Tree_Expression',$entities); - } - } - - - /** - * Parse a property - * eg: 'min-width', 'orientation', etc - * - * @return string - */ - private function parseProperty (){ - $name = $this->MatchReg('/\\G(\*?-?[_a-zA-Z0-9-]+)\s*:/'); - if( $name ){ - return $name[1]; - } - } - - - /** - * Parse a rule property - * eg: 'color', 'width', 'height', etc - * - * @return string - */ - private function parseRuleProperty(){ - $offset = $this->pos; - $name = array(); - $index = array(); - $length = 0; - - - $this->rulePropertyMatch('/\\G(\*?)/', $offset, $length, $index, $name ); - while( $this->rulePropertyMatch('/\\G((?:[\w-]+)|(?:@\{[\w-]+\}))/', $offset, $length, $index, $name )); // ! - - if( (count_if_countable($name) > 1) && $this->rulePropertyMatch('/\\G\s*((?:\+_|\+)?)\s*:/', $offset, $length, $index, $name) ){ - // at last, we have the complete match now. move forward, - // convert name particles to tree objects and return: - $this->skipWhitespace($length); - - if( $name[0] === '' ){ - array_shift($name); - array_shift($index); - } - foreach($name as $k => $s ){ - if( !$s || $s[0] !== '@' ){ - $name[$k] = $this->NewObj1('Less_Tree_Keyword',$s); - }else{ - $name[$k] = $this->NewObj3('Less_Tree_Variable',array('@' . substr($s,2,-1), $index[$k], $this->env->currentFileInfo)); - } - } - return $name; - } - - - } - - private function rulePropertyMatch( $re, &$offset, &$length, &$index, &$name ){ - preg_match($re, $this->input, $a, 0, $offset); - if( $a ){ - $index[] = $this->pos + $length; - $length += strlen($a[0]); - $offset += strlen($a[0]); - $name[] = $a[1]; - return true; - } - } - - public static function serializeVars( $vars ){ - $s = ''; - - foreach($vars as $name => $value){ - $s .= (($name[0] === '@') ? '' : '@') . $name .': '. $value . ((substr($value,-1) === ';') ? '' : ';'); - } - - return $s; - } - - - /** - * Some versions of php have trouble with method_exists($a,$b) if $a is not an object - * - * @param string $b - */ - public static function is_method($a,$b){ - return is_object($a) && method_exists($a,$b); - } - - - /** - * Round numbers similarly to javascript - * eg: 1.499999 to 1 instead of 2 - * - */ - public static function round($i, $precision = 0){ - - $precision = pow(10,$precision); - $i = $i*$precision; - - $ceil = ceil($i); - $floor = floor($i); - if( ($ceil - $i) <= ($i - $floor) ){ - return $ceil/$precision; - }else{ - return $floor/$precision; - } - } - - - /** - * Create Less_Tree_* objects and optionally generate a cache string - * - * @return mixed - */ - public function NewObj0($class){ - $obj = new $class(); - if( $this->CacheEnabled() ){ - $obj->cache_string = ' new '.$class.'()'; - } - return $obj; - } - - public function NewObj1($class, $arg){ - $obj = new $class( $arg ); - if( $this->CacheEnabled() ){ - $obj->cache_string = ' new '.$class.'('.Less_Parser::ArgString($arg).')'; - } - return $obj; - } - - public function NewObj2($class, $args){ - $obj = new $class( $args[0], $args[1] ); - if( $this->CacheEnabled() ){ - $this->ObjCache( $obj, $class, $args); - } - return $obj; - } - - public function NewObj3($class, $args){ - $obj = new $class( $args[0], $args[1], $args[2] ); - if( $this->CacheEnabled() ){ - $this->ObjCache( $obj, $class, $args); - } - return $obj; - } - - public function NewObj4($class, $args){ - $obj = new $class( $args[0], $args[1], $args[2], $args[3] ); - if( $this->CacheEnabled() ){ - $this->ObjCache( $obj, $class, $args); - } - return $obj; - } - - public function NewObj5($class, $args){ - $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4] ); - if( $this->CacheEnabled() ){ - $this->ObjCache( $obj, $class, $args); - } - return $obj; - } - - public function NewObj6($class, $args){ - $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5] ); - if( $this->CacheEnabled() ){ - $this->ObjCache( $obj, $class, $args); - } - return $obj; - } - - public function NewObj7($class, $args){ - $obj = new $class( $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6] ); - if( $this->CacheEnabled() ){ - $this->ObjCache( $obj, $class, $args); - } - return $obj; - } - - //caching - public function ObjCache($obj, $class, $args=array()){ - $obj->cache_string = ' new '.$class.'('. self::ArgCache($args).')'; - } - - public function ArgCache($args){ - return implode(',',array_map( array('Less_Parser','ArgString'),$args)); - } - - - /** - * Convert an argument to a string for use in the parser cache - * - * @return string - */ - public static function ArgString($arg){ - - $type = gettype($arg); - - if( $type === 'object'){ - $string = $arg->cache_string; - unset($arg->cache_string); - return $string; - - }elseif( $type === 'array' ){ - $string = ' Array('; - foreach($arg as $k => $a){ - $string .= var_export($k,true).' => '.self::ArgString($a).','; - } - return $string . ')'; - } - - return var_export($arg,true); - } - - public function Error($msg){ - throw new Less_Exception_Parser($msg, null, $this->furthest, $this->env->currentFileInfo); - } - - public static function WinPath($path){ - return str_replace('\\', '/', $path); - } - - public function CacheEnabled(){ - return (Less_Parser::$options['cache_method'] && (Less_Cache::$cache_dir || (Less_Parser::$options['cache_method'] == 'callback'))); - } - -} - - - - -/** - * Utility for css colors - * - * @package Less - * @subpackage color - */ -class Less_Colors { - - public static $colors = array( - 'aliceblue'=>'#f0f8ff', - 'antiquewhite'=>'#faebd7', - 'aqua'=>'#00ffff', - 'aquamarine'=>'#7fffd4', - 'azure'=>'#f0ffff', - 'beige'=>'#f5f5dc', - 'bisque'=>'#ffe4c4', - 'black'=>'#000000', - 'blanchedalmond'=>'#ffebcd', - 'blue'=>'#0000ff', - 'blueviolet'=>'#8a2be2', - 'brown'=>'#a52a2a', - 'burlywood'=>'#deb887', - 'cadetblue'=>'#5f9ea0', - 'chartreuse'=>'#7fff00', - 'chocolate'=>'#d2691e', - 'coral'=>'#ff7f50', - 'cornflowerblue'=>'#6495ed', - 'cornsilk'=>'#fff8dc', - 'crimson'=>'#dc143c', - 'cyan'=>'#00ffff', - 'darkblue'=>'#00008b', - 'darkcyan'=>'#008b8b', - 'darkgoldenrod'=>'#b8860b', - 'darkgray'=>'#a9a9a9', - 'darkgrey'=>'#a9a9a9', - 'darkgreen'=>'#006400', - 'darkkhaki'=>'#bdb76b', - 'darkmagenta'=>'#8b008b', - 'darkolivegreen'=>'#556b2f', - 'darkorange'=>'#ff8c00', - 'darkorchid'=>'#9932cc', - 'darkred'=>'#8b0000', - 'darksalmon'=>'#e9967a', - 'darkseagreen'=>'#8fbc8f', - 'darkslateblue'=>'#483d8b', - 'darkslategray'=>'#2f4f4f', - 'darkslategrey'=>'#2f4f4f', - 'darkturquoise'=>'#00ced1', - 'darkviolet'=>'#9400d3', - 'deeppink'=>'#ff1493', - 'deepskyblue'=>'#00bfff', - 'dimgray'=>'#696969', - 'dimgrey'=>'#696969', - 'dodgerblue'=>'#1e90ff', - 'firebrick'=>'#b22222', - 'floralwhite'=>'#fffaf0', - 'forestgreen'=>'#228b22', - 'fuchsia'=>'#ff00ff', - 'gainsboro'=>'#dcdcdc', - 'ghostwhite'=>'#f8f8ff', - 'gold'=>'#ffd700', - 'goldenrod'=>'#daa520', - 'gray'=>'#808080', - 'grey'=>'#808080', - 'green'=>'#008000', - 'greenyellow'=>'#adff2f', - 'honeydew'=>'#f0fff0', - 'hotpink'=>'#ff69b4', - 'indianred'=>'#cd5c5c', - 'indigo'=>'#4b0082', - 'ivory'=>'#fffff0', - 'khaki'=>'#f0e68c', - 'lavender'=>'#e6e6fa', - 'lavenderblush'=>'#fff0f5', - 'lawngreen'=>'#7cfc00', - 'lemonchiffon'=>'#fffacd', - 'lightblue'=>'#add8e6', - 'lightcoral'=>'#f08080', - 'lightcyan'=>'#e0ffff', - 'lightgoldenrodyellow'=>'#fafad2', - 'lightgray'=>'#d3d3d3', - 'lightgrey'=>'#d3d3d3', - 'lightgreen'=>'#90ee90', - 'lightpink'=>'#ffb6c1', - 'lightsalmon'=>'#ffa07a', - 'lightseagreen'=>'#20b2aa', - 'lightskyblue'=>'#87cefa', - 'lightslategray'=>'#778899', - 'lightslategrey'=>'#778899', - 'lightsteelblue'=>'#b0c4de', - 'lightyellow'=>'#ffffe0', - 'lime'=>'#00ff00', - 'limegreen'=>'#32cd32', - 'linen'=>'#faf0e6', - 'magenta'=>'#ff00ff', - 'maroon'=>'#800000', - 'mediumaquamarine'=>'#66cdaa', - 'mediumblue'=>'#0000cd', - 'mediumorchid'=>'#ba55d3', - 'mediumpurple'=>'#9370d8', - 'mediumseagreen'=>'#3cb371', - 'mediumslateblue'=>'#7b68ee', - 'mediumspringgreen'=>'#00fa9a', - 'mediumturquoise'=>'#48d1cc', - 'mediumvioletred'=>'#c71585', - 'midnightblue'=>'#191970', - 'mintcream'=>'#f5fffa', - 'mistyrose'=>'#ffe4e1', - 'moccasin'=>'#ffe4b5', - 'navajowhite'=>'#ffdead', - 'navy'=>'#000080', - 'oldlace'=>'#fdf5e6', - 'olive'=>'#808000', - 'olivedrab'=>'#6b8e23', - 'orange'=>'#ffa500', - 'orangered'=>'#ff4500', - 'orchid'=>'#da70d6', - 'palegoldenrod'=>'#eee8aa', - 'palegreen'=>'#98fb98', - 'paleturquoise'=>'#afeeee', - 'palevioletred'=>'#d87093', - 'papayawhip'=>'#ffefd5', - 'peachpuff'=>'#ffdab9', - 'peru'=>'#cd853f', - 'pink'=>'#ffc0cb', - 'plum'=>'#dda0dd', - 'powderblue'=>'#b0e0e6', - 'purple'=>'#800080', - 'red'=>'#ff0000', - 'rosybrown'=>'#bc8f8f', - 'royalblue'=>'#4169e1', - 'saddlebrown'=>'#8b4513', - 'salmon'=>'#fa8072', - 'sandybrown'=>'#f4a460', - 'seagreen'=>'#2e8b57', - 'seashell'=>'#fff5ee', - 'sienna'=>'#a0522d', - 'silver'=>'#c0c0c0', - 'skyblue'=>'#87ceeb', - 'slateblue'=>'#6a5acd', - 'slategray'=>'#708090', - 'slategrey'=>'#708090', - 'snow'=>'#fffafa', - 'springgreen'=>'#00ff7f', - 'steelblue'=>'#4682b4', - 'tan'=>'#d2b48c', - 'teal'=>'#008080', - 'thistle'=>'#d8bfd8', - 'tomato'=>'#ff6347', - 'turquoise'=>'#40e0d0', - 'violet'=>'#ee82ee', - 'wheat'=>'#f5deb3', - 'white'=>'#ffffff', - 'whitesmoke'=>'#f5f5f5', - 'yellow'=>'#ffff00', - 'yellowgreen'=>'#9acd32' - ); - - public static function hasOwnProperty($color) { - return isset(self::$colors[$color]); - } - - - public static function color($color) { - return self::$colors[$color]; - } - -} - - - -/** - * Environment - * - * @package Less - * @subpackage environment - */ -class Less_Environment{ - - //public $paths = array(); // option - unmodified - paths to search for imports on - //public static $files = array(); // list of files that have been imported, used for import-once - //public $rootpath; // option - rootpath to append to URL's - //public static $strictImports = null; // option - - //public $insecure; // option - whether to allow imports from insecure ssl hosts - //public $processImports; // option - whether to process imports. if false then imports will not be imported - //public $javascriptEnabled; // option - whether JavaScript is enabled. if undefined, defaults to true - //public $useFileCache; // browser only - whether to use the per file session cache - public $currentFileInfo; // information about the current file - for error reporting and importing and making urls relative etc. - - public $importMultiple = false; // whether we are currently importing multiple copies - - - /** - * @var array - */ - public $frames = array(); - - /** - * @var array - */ - public $mediaBlocks = array(); - - /** - * @var array - */ - public $mediaPath = array(); - - public static $parensStack = 0; - - public static $tabLevel = 0; - - public static $lastRule = false; - - public static $_outputMap; - - public static $mixin_stack = 0; - - /** - * @var array - */ - public $functions = array(); - - - public function Init(){ - - self::$parensStack = 0; - self::$tabLevel = 0; - self::$lastRule = false; - self::$mixin_stack = 0; - - if( Less_Parser::$options['compress'] ){ - - Less_Environment::$_outputMap = array( - ',' => ',', - ': ' => ':', - '' => '', - ' ' => ' ', - ':' => ' :', - '+' => '+', - '~' => '~', - '>' => '>', - '|' => '|', - '^' => '^', - '^^' => '^^' - ); - - }else{ - - Less_Environment::$_outputMap = array( - ',' => ', ', - ': ' => ': ', - '' => '', - ' ' => ' ', - ':' => ' :', - '+' => ' + ', - '~' => ' ~ ', - '>' => ' > ', - '|' => '|', - '^' => ' ^ ', - '^^' => ' ^^ ' - ); - - } - } - - - public function copyEvalEnv($frames = array() ){ - $new_env = new Less_Environment(); - $new_env->frames = $frames; - return $new_env; - } - - - public static function isMathOn(){ - return !Less_Parser::$options['strictMath'] || Less_Environment::$parensStack; - } - - public static function isPathRelative($path){ - return !preg_match('/^(?:[a-z-]+:|\/)/',$path); - } - - - /** - * Canonicalize a path by resolving references to '/./', '/../' - * Does not remove leading "../" - * @param string path or url - * @return string Canonicalized path - * - */ - public static function normalizePath($path){ - - $segments = explode('/',$path); - $segments = array_reverse($segments); - - $path = array(); - $path_len = 0; - - while( $segments ){ - $segment = array_pop($segments); - switch( $segment ) { - - case '.': - break; - - case '..': - if( !$path_len || ( $path[$path_len-1] === '..') ){ - $path[] = $segment; - $path_len++; - }else{ - array_pop($path); - $path_len--; - } - break; - - default: - $path[] = $segment; - $path_len++; - break; - } - } - - return implode('/',$path); - } - - - public function unshiftFrame($frame){ - array_unshift($this->frames, $frame); - } - - public function shiftFrame(){ - return array_shift($this->frames); - } - -} - - -/** - * Builtin functions - * - * @package Less - * @subpackage function - * @see http://lesscss.org/functions/ - */ -class Less_Functions{ - - public $env; - public $currentFileInfo; - - function __construct($env, $currentFileInfo = null ){ - $this->env = $env; - $this->currentFileInfo = $currentFileInfo; - } - - /** - * @param string $op - */ - public static function operate( $op, $a, $b ){ - switch ($op) { - case '+': return $a + $b; - case '-': return $a - $b; - case '*': return $a * $b; - case '/': return $a / $b; - } - } - - public static function clamp($val, $max = 1){ - return min( max($val, 0), $max); - } - - public static function fround( $value ){ - - if( $value === 0 ){ - return $value; - } - - if( Less_Parser::$options['numPrecision'] ){ - $p = pow(10, Less_Parser::$options['numPrecision']); - return round( $value * $p) / $p; - } - return $value; - } - - public static function number($n){ - - if ($n instanceof Less_Tree_Dimension) { - return floatval( $n->unit->is('%') ? $n->value / 100 : $n->value); - } else if (is_numeric($n)) { - return $n; - } else { - throw new Less_Exception_Compiler("color functions take numbers as parameters"); - } - } - - public static function scaled($n, $size = 255 ){ - if( $n instanceof Less_Tree_Dimension && $n->unit->is('%') ){ - return (float)$n->value * $size / 100; - } else { - return Less_Functions::number($n); - } - } - - public function rgb ($r = null, $g = null, $b = null){ - if (is_null($r) || is_null($g) || is_null($b)) { - throw new Less_Exception_Compiler("rgb expects three parameters"); - } - return $this->rgba($r, $g, $b, 1.0); - } - - public function rgba($r = null, $g = null, $b = null, $a = null){ - $rgb = array($r, $g, $b); - $rgb = array_map(array('Less_Functions','scaled'),$rgb); - - $a = self::number($a); - return new Less_Tree_Color($rgb, $a); - } - - public function hsl($h, $s, $l){ - return $this->hsla($h, $s, $l, 1.0); - } - - public function hsla($h, $s, $l, $a){ - - $h = fmod(self::number($h), 360) / 360; // Classic % operator will change float to int - $s = self::clamp(self::number($s)); - $l = self::clamp(self::number($l)); - $a = self::clamp(self::number($a)); - - $m2 = $l <= 0.5 ? $l * ($s + 1) : $l + $s - $l * $s; - - $m1 = $l * 2 - $m2; - - return $this->rgba( self::hsla_hue($h + 1/3, $m1, $m2) * 255, - self::hsla_hue($h, $m1, $m2) * 255, - self::hsla_hue($h - 1/3, $m1, $m2) * 255, - $a); - } - - /** - * @param double $h - */ - public function hsla_hue($h, $m1, $m2){ - $h = $h < 0 ? $h + 1 : ($h > 1 ? $h - 1 : $h); - if ($h * 6 < 1) return $m1 + ($m2 - $m1) * $h * 6; - else if ($h * 2 < 1) return $m2; - else if ($h * 3 < 2) return $m1 + ($m2 - $m1) * (2/3 - $h) * 6; - else return $m1; - } - - public function hsv($h, $s, $v) { - return $this->hsva($h, $s, $v, 1.0); - } - - /** - * @param double $a - */ - public function hsva($h, $s, $v, $a) { - $h = ((Less_Functions::number($h) % 360) / 360 ) * 360; - $s = Less_Functions::number($s); - $v = Less_Functions::number($v); - $a = Less_Functions::number($a); - - $i = floor(($h / 60) % 6); - $f = ($h / 60) - $i; - - $vs = array( $v, - $v * (1 - $s), - $v * (1 - $f * $s), - $v * (1 - (1 - $f) * $s)); - - $perm = array(array(0, 3, 1), - array(2, 0, 1), - array(1, 0, 3), - array(1, 2, 0), - array(3, 1, 0), - array(0, 1, 2)); - - return $this->rgba($vs[$perm[$i][0]] * 255, - $vs[$perm[$i][1]] * 255, - $vs[$perm[$i][2]] * 255, - $a); - } - - public function hue($color = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to hue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $c = $color->toHSL(); - return new Less_Tree_Dimension(Less_Parser::round($c['h'])); - } - - public function saturation($color = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to saturation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $c = $color->toHSL(); - return new Less_Tree_Dimension(Less_Parser::round($c['s'] * 100), '%'); - } - - public function lightness($color = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to lightness must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $c = $color->toHSL(); - return new Less_Tree_Dimension(Less_Parser::round($c['l'] * 100), '%'); - } - - public function hsvhue( $color = null ){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to hsvhue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsv = $color->toHSV(); - return new Less_Tree_Dimension( Less_Parser::round($hsv['h']) ); - } - - - public function hsvsaturation( $color = null ){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to hsvsaturation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsv = $color->toHSV(); - return new Less_Tree_Dimension( Less_Parser::round($hsv['s'] * 100), '%' ); - } - - public function hsvvalue( $color = null ){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to hsvvalue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsv = $color->toHSV(); - return new Less_Tree_Dimension( Less_Parser::round($hsv['v'] * 100), '%' ); - } - - public function red($color = null) { - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to red must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return new Less_Tree_Dimension( $color->rgb[0] ); - } - - public function green($color = null) { - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to green must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return new Less_Tree_Dimension( $color->rgb[1] ); - } - - public function blue($color = null) { - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to blue must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return new Less_Tree_Dimension( $color->rgb[2] ); - } - - public function alpha($color = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to alpha must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $c = $color->toHSL(); - return new Less_Tree_Dimension($c['a']); - } - - public function luma ($color = null) { - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to luma must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return new Less_Tree_Dimension(Less_Parser::round( $color->luma() * $color->alpha * 100), '%'); - } - - public function luminance( $color = null ){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to luminance must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $luminance = - (0.2126 * $color->rgb[0] / 255) - + (0.7152 * $color->rgb[1] / 255) - + (0.0722 * $color->rgb[2] / 255); - - return new Less_Tree_Dimension(Less_Parser::round( $luminance * $color->alpha * 100), '%'); - } - - public function saturate($color = null, $amount = null){ - // filter: saturate(3.2); - // should be kept as is, so check for color - if ($color instanceof Less_Tree_Dimension) { - return null; - } - - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to saturate must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$amount instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The second argument to saturate must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsl = $color->toHSL(); - - $hsl['s'] += $amount->value / 100; - $hsl['s'] = self::clamp($hsl['s']); - - return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); - } - - /** - * @param Less_Tree_Dimension $amount - */ - public function desaturate($color = null, $amount = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to desaturate must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$amount instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The second argument to desaturate must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsl = $color->toHSL(); - - $hsl['s'] -= $amount->value / 100; - $hsl['s'] = self::clamp($hsl['s']); - - return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); - } - - - - public function lighten($color = null, $amount=null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to lighten must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$amount instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The second argument to lighten must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsl = $color->toHSL(); - - $hsl['l'] += $amount->value / 100; - $hsl['l'] = self::clamp($hsl['l']); - - return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); - } - - public function darken($color = null, $amount = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to darken must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$amount instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The second argument to darken must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsl = $color->toHSL(); - $hsl['l'] -= $amount->value / 100; - $hsl['l'] = self::clamp($hsl['l']); - - return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); - } - - public function fadein($color = null, $amount = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to fadein must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$amount instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The second argument to fadein must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsl = $color->toHSL(); - $hsl['a'] += $amount->value / 100; - $hsl['a'] = self::clamp($hsl['a']); - return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); - } - - public function fadeout($color = null, $amount = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to fadeout must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$amount instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The second argument to fadeout must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsl = $color->toHSL(); - $hsl['a'] -= $amount->value / 100; - $hsl['a'] = self::clamp($hsl['a']); - return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); - } - - public function fade($color = null, $amount = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to fade must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$amount instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The second argument to fade must be a percentage' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsl = $color->toHSL(); - - $hsl['a'] = $amount->value / 100; - $hsl['a'] = self::clamp($hsl['a']); - return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); - } - - - - public function spin($color = null, $amount = null){ - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to spin must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$amount instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The second argument to spin must be a number' . ($amount instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $hsl = $color->toHSL(); - $hue = fmod($hsl['h'] + $amount->value, 360); - - $hsl['h'] = $hue < 0 ? 360 + $hue : $hue; - - return $this->hsla($hsl['h'], $hsl['s'], $hsl['l'], $hsl['a']); - } - - // - // Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein - // http://sass-lang.com - // - - /** - * @param Less_Tree_Color $color1 - */ - public function mix($color1 = null, $color2 = null, $weight = null){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to mix must be a color' . ($color1 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to mix must be a color' . ($color2 instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$weight) { - $weight = new Less_Tree_Dimension('50', '%'); - } - if (!$weight instanceof Less_Tree_Dimension) { - throw new Less_Exception_Compiler('The third argument to contrast must be a percentage' . ($weight instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - $p = $weight->value / 100.0; - $w = $p * 2 - 1; - $hsl1 = $color1->toHSL(); - $hsl2 = $color2->toHSL(); - $a = $hsl1['a'] - $hsl2['a']; - - $w1 = (((($w * $a) == -1) ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2; - $w2 = 1 - $w1; - - $rgb = array($color1->rgb[0] * $w1 + $color2->rgb[0] * $w2, - $color1->rgb[1] * $w1 + $color2->rgb[1] * $w2, - $color1->rgb[2] * $w1 + $color2->rgb[2] * $w2); - - $alpha = $color1->alpha * $p + $color2->alpha * (1 - $p); - - return new Less_Tree_Color($rgb, $alpha); - } - - public function greyscale($color){ - return $this->desaturate($color, new Less_Tree_Dimension(100,'%')); - } - - - public function contrast( $color, $dark = null, $light = null, $threshold = null){ - // filter: contrast(3.2); - // should be kept as is, so check for color - if (!$color instanceof Less_Tree_Color) { - return null; - } - if( !$light ){ - $light = $this->rgba(255, 255, 255, 1.0); - } - if( !$dark ){ - $dark = $this->rgba(0, 0, 0, 1.0); - } - - if (!$dark instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to contrast must be a color' . ($dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$light instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The third argument to contrast must be a color' . ($light instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - //Figure out which is actually light and dark! - if( $dark->luma() > $light->luma() ){ - $t = $light; - $light = $dark; - $dark = $t; - } - if( !$threshold ){ - $threshold = 0.43; - } else { - $threshold = Less_Functions::number($threshold); - } - - if( $color->luma() < $threshold ){ - return $light; - } else { - return $dark; - } - } - - public function e ($str){ - if( is_string($str) ){ - return new Less_Tree_Anonymous($str); - } - return new Less_Tree_Anonymous($str instanceof Less_Tree_JavaScript ? $str->expression : $str->value); - } - - public function escape ($str){ - - $revert = array('%21'=>'!', '%2A'=>'*', '%27'=>"'",'%3F'=>'?','%26'=>'&','%2C'=>',','%2F'=>'/','%40'=>'@','%2B'=>'+','%24'=>'$'); - - return new Less_Tree_Anonymous(strtr(rawurlencode($str->value), $revert)); - } - - - /** - * todo: This function will need some additional work to make it work the same as less.js - * - */ - public function replace( $string, $pattern, $replacement, $flags = null ){ - $result = $string->value; - - $expr = '/'.str_replace('/','\\/',$pattern->value).'/'; - if( $flags && $flags->value){ - $expr .= self::replace_flags($flags->value); - } - - $result = preg_replace($expr,$replacement->value,$result); - - - if( property_exists($string,'quote') ){ - return new Less_Tree_Quoted( $string->quote, $result, $string->escaped); - } - return new Less_Tree_Quoted( '', $result ); - } - - public static function replace_flags($flags){ - $flags = str_split($flags,1); - $new_flags = ''; - - foreach($flags as $flag){ - switch($flag){ - case 'e': - case 'g': - break; - - default: - $new_flags .= $flag; - break; - } - } - - return $new_flags; - } - - public function _percent(){ - $string = func_get_arg(0); - - $args = func_get_args(); - array_shift($args); - $result = $string->value; - - foreach($args as $arg){ - if( preg_match('/%[sda]/i',$result, $token) ){ - $token = $token[0]; - $value = stristr($token, 's') ? $arg->value : $arg->toCSS(); - $value = preg_match('/[A-Z]$/', $token) ? urlencode($value) : $value; - $result = preg_replace('/%[sda]/i',$value, $result, 1); - } - } - $result = str_replace('%%', '%', $result); - - return new Less_Tree_Quoted( $string->quote , $result, $string->escaped); - } - - public function unit( $val, $unit = null) { - if( !($val instanceof Less_Tree_Dimension) ){ - throw new Less_Exception_Compiler('The first argument to unit must be a number' . ($val instanceof Less_Tree_Operation ? '. Have you forgotten parenthesis?' : '.') ); - } - - if( $unit ){ - if( $unit instanceof Less_Tree_Keyword ){ - $unit = $unit->value; - } else { - $unit = $unit->toCSS(); - } - } else { - $unit = ""; - } - return new Less_Tree_Dimension($val->value, $unit ); - } - - public function convert($val, $unit){ - return $val->convertTo($unit->value); - } - - public function round($n, $f = false) { - - $fraction = 0; - if( $f !== false ){ - $fraction = $f->value; - } - - return $this->_math('Less_Parser::round',null, $n, $fraction); - } - - public function pi(){ - return new Less_Tree_Dimension(M_PI); - } - - public function mod($a, $b) { - return new Less_Tree_Dimension( $a->value % $b->value, $a->unit); - } - - - - public function pow($x, $y) { - if( is_numeric($x) && is_numeric($y) ){ - $x = new Less_Tree_Dimension($x); - $y = new Less_Tree_Dimension($y); - }elseif( !($x instanceof Less_Tree_Dimension) || !($y instanceof Less_Tree_Dimension) ){ - throw new Less_Exception_Compiler('Arguments must be numbers'); - } - - return new Less_Tree_Dimension( pow($x->value, $y->value), $x->unit ); - } - - // var mathFunctions = [{name:"ce ... - public function ceil( $n ){ return $this->_math('ceil', null, $n); } - public function floor( $n ){ return $this->_math('floor', null, $n); } - public function sqrt( $n ){ return $this->_math('sqrt', null, $n); } - public function abs( $n ){ return $this->_math('abs', null, $n); } - - public function tan( $n ){ return $this->_math('tan', '', $n); } - public function sin( $n ){ return $this->_math('sin', '', $n); } - public function cos( $n ){ return $this->_math('cos', '', $n); } - - public function atan( $n ){ return $this->_math('atan', 'rad', $n); } - public function asin( $n ){ return $this->_math('asin', 'rad', $n); } - public function acos( $n ){ return $this->_math('acos', 'rad', $n); } - - private function _math() { - $args = func_get_args(); - $fn = array_shift($args); - $unit = array_shift($args); - - if ($args[0] instanceof Less_Tree_Dimension) { - - if( $unit === null ){ - $unit = $args[0]->unit; - }else{ - $args[0] = $args[0]->unify(); - } - $args[0] = (float)$args[0]->value; - return new Less_Tree_Dimension( call_user_func_array($fn, $args), $unit); - } else if (is_numeric($args[0])) { - return call_user_func_array($fn,$args); - } else { - throw new Less_Exception_Compiler("math functions take numbers as parameters"); - } - } - - /** - * @param boolean $isMin - */ - private function _minmax( $isMin, $args ){ - - $arg_count = count_if_countable($args); - - if( $arg_count < 1 ){ - throw new Less_Exception_Compiler( 'one or more arguments required'); - } - - $j = null; - $unitClone = null; - $unitStatic = null; - - - $order = array(); // elems only contains original argument values. - $values = array(); // key is the unit.toString() for unified tree.Dimension values, - // value is the index into the order array. - - - for( $i = 0; $i < $arg_count; $i++ ){ - $current = $args[$i]; - if( !($current instanceof Less_Tree_Dimension) ){ - if( is_array($args[$i]->value) ){ - $args[] = $args[$i]->value; - } - continue; - } - - if( $current->unit->toString() === '' && !$unitClone ){ - $temp = new Less_Tree_Dimension($current->value, $unitClone); - $currentUnified = $temp->unify(); - }else{ - $currentUnified = $current->unify(); - } - - if( $currentUnified->unit->toString() === "" && !$unitStatic ){ - $unit = $unitStatic; - }else{ - $unit = $currentUnified->unit->toString(); - } - - if( $unit !== '' && !$unitStatic || $unit !== '' && $order[0]->unify()->unit->toString() === "" ){ - $unitStatic = $unit; - } - - if( $unit != '' && !$unitClone ){ - $unitClone = $current->unit->toString(); - } - - if( isset($values['']) && $unit !== '' && $unit === $unitStatic ){ - $j = $values['']; - }elseif( isset($values[$unit]) ){ - $j = $values[$unit]; - }else{ - - if( $unitStatic && $unit !== $unitStatic ){ - throw new Less_Exception_Compiler( 'incompatible types'); - } - $values[$unit] = count_if_countable($order); - $order[] = $current; - continue; - } - - - if( $order[$j]->unit->toString() === "" && $unitClone ){ - $temp = new Less_Tree_Dimension( $order[$j]->value, $unitClone); - $referenceUnified = $temp->unify(); - }else{ - $referenceUnified = $order[$j]->unify(); - } - if( ($isMin && $currentUnified->value < $referenceUnified->value) || (!$isMin && $currentUnified->value > $referenceUnified->value) ){ - $order[$j] = $current; - } - } - - if( count_if_countable($order) == 1 ){ - return $order[0]; - } - $args = array(); - foreach($order as $a){ - $args[] = $a->toCSS($this->env); - } - return new Less_Tree_Anonymous( ($isMin?'min(':'max(') . implode(Less_Environment::$_outputMap[','],$args).')'); - } - - public function min(){ - $args = func_get_args(); - return $this->_minmax( true, $args ); - } - - public function max(){ - $args = func_get_args(); - return $this->_minmax( false, $args ); - } - - public function getunit($n){ - return new Less_Tree_Anonymous($n->unit); - } - - public function argb($color) { - if (!$color instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to argb must be a color' . ($dark instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return new Less_Tree_Anonymous($color->toARGB()); - } - - public function percentage($n) { - return new Less_Tree_Dimension($n->value * 100, '%'); - } - - public function color($n) { - - if( $n instanceof Less_Tree_Quoted ){ - $colorCandidate = $n->value; - $returnColor = Less_Tree_Color::fromKeyword($colorCandidate); - if( $returnColor ){ - return $returnColor; - } - if( preg_match('/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})/',$colorCandidate) ){ - return new Less_Tree_Color(substr($colorCandidate, 1)); - } - throw new Less_Exception_Compiler("argument must be a color keyword or 3/6 digit hex e.g. #FFF"); - } else { - throw new Less_Exception_Compiler("argument must be a string"); - } - } - - - public function iscolor($n) { - return $this->_isa($n, 'Less_Tree_Color'); - } - - public function isnumber($n) { - return $this->_isa($n, 'Less_Tree_Dimension'); - } - - public function isstring($n) { - return $this->_isa($n, 'Less_Tree_Quoted'); - } - - public function iskeyword($n) { - return $this->_isa($n, 'Less_Tree_Keyword'); - } - - public function isurl($n) { - return $this->_isa($n, 'Less_Tree_Url'); - } - - public function ispixel($n) { - return $this->isunit($n, 'px'); - } - - public function ispercentage($n) { - return $this->isunit($n, '%'); - } - - public function isem($n) { - return $this->isunit($n, 'em'); - } - - /** - * @param string $unit - */ - public function isunit( $n, $unit ){ - return ($n instanceof Less_Tree_Dimension) && $n->unit->is( ( property_exists($unit,'value') ? $unit->value : $unit) ) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false'); - } - - /** - * @param string $type - */ - private function _isa($n, $type) { - return is_a($n, $type) ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false'); - } - - public function tint($color, $amount) { - return $this->mix( $this->rgb(255,255,255), $color, $amount); - } - - public function shade($color, $amount) { - return $this->mix($this->rgb(0, 0, 0), $color, $amount); - } - - public function extract($values, $index ){ - $index = (int)$index->value - 1; // (1-based index) - // handle non-array values as an array of length 1 - // return 'undefined' if index is invalid - if( property_exists($values,'value') && is_array($values->value) ){ - if( isset($values->value[$index]) ){ - return $values->value[$index]; - } - return null; - - }elseif( (int)$index === 0 ){ - return $values; - } - - return null; - } - - public function length($values){ - $n = (property_exists($values,'value') && is_array($values->value)) ? count_if_countable($values->value) : 1; - return new Less_Tree_Dimension($n); - } - - public function datauri($mimetypeNode, $filePathNode = null ) { - - $filePath = ( $filePathNode ? $filePathNode->value : null ); - $mimetype = $mimetypeNode->value; - - $args = 2; - if( !$filePath ){ - $filePath = $mimetype; - $args = 1; - } - - $filePath = str_replace('\\','/',$filePath); - if( Less_Environment::isPathRelative($filePath) ){ - - if( Less_Parser::$options['relativeUrls'] ){ - $temp = $this->currentFileInfo['currentDirectory']; - } else { - $temp = $this->currentFileInfo['entryPath']; - } - - if( !empty($temp) ){ - $filePath = Less_Environment::normalizePath(rtrim($temp,'/').'/'.$filePath); - } - - } - - - // detect the mimetype if not given - if( $args < 2 ){ - - /* incomplete - $mime = require('mime'); - mimetype = mime.lookup(path); - - // use base 64 unless it's an ASCII or UTF-8 format - var charset = mime.charsets.lookup(mimetype); - useBase64 = ['US-ASCII', 'UTF-8'].indexOf(charset) < 0; - if (useBase64) mimetype += ';base64'; - */ - - $mimetype = Less_Mime::lookup($filePath); - - $charset = Less_Mime::charsets_lookup($mimetype); - $useBase64 = !in_array($charset,array('US-ASCII', 'UTF-8')); - if( $useBase64 ){ $mimetype .= ';base64'; } - - }else{ - $useBase64 = preg_match('/;base64$/',$mimetype); - } - - - if( file_exists($filePath) ){ - $buf = @file_get_contents($filePath); - }else{ - $buf = false; - } - - - // IE8 cannot handle a data-uri larger than 32KB. If this is exceeded - // and the --ieCompat flag is enabled, return a normal url() instead. - $DATA_URI_MAX_KB = 32; - $fileSizeInKB = round( strlen($buf) / 1024 ); - if( $fileSizeInKB >= $DATA_URI_MAX_KB ){ - $url = new Less_Tree_Url( ($filePathNode ? $filePathNode : $mimetypeNode), $this->currentFileInfo); - return $url->compile($this); - } - - if( $buf ){ - $buf = $useBase64 ? base64_encode($buf) : rawurlencode($buf); - $filePath = '"data:' . $mimetype . ',' . $buf . '"'; - } - - return new Less_Tree_Url( new Less_Tree_Anonymous($filePath) ); - } - - //svg-gradient - public function svggradient( $direction ){ - - $throw_message = 'svg-gradient expects direction, start_color [start_position], [color position,]..., end_color [end_position]'; - $arguments = func_get_args(); - - if( count_if_countable($arguments) < 3 ){ - throw new Less_Exception_Compiler( $throw_message ); - } - - $stops = array_slice($arguments,1); - $gradientType = 'linear'; - $rectangleDimension = 'x="0" y="0" width="1" height="1"'; - $useBase64 = true; - $directionValue = $direction->toCSS(); - - - switch( $directionValue ){ - case "to bottom": - $gradientDirectionSvg = 'x1="0%" y1="0%" x2="0%" y2="100%"'; - break; - case "to right": - $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="0%"'; - break; - case "to bottom right": - $gradientDirectionSvg = 'x1="0%" y1="0%" x2="100%" y2="100%"'; - break; - case "to top right": - $gradientDirectionSvg = 'x1="0%" y1="100%" x2="100%" y2="0%"'; - break; - case "ellipse": - case "ellipse at center": - $gradientType = "radial"; - $gradientDirectionSvg = 'cx="50%" cy="50%" r="75%"'; - $rectangleDimension = 'x="-50" y="-50" width="101" height="101"'; - break; - default: - throw new Less_Exception_Compiler( "svg-gradient direction must be 'to bottom', 'to right', 'to bottom right', 'to top right' or 'ellipse at center'" ); - } - - $returner = '' . - '' . - '<' . $gradientType . 'Gradient id="gradient" gradientUnits="userSpaceOnUse" ' . $gradientDirectionSvg . '>'; - - for( $i = 0; $i < count_if_countable($stops); $i++ ){ - if( is_object($stops[$i]) && property_exists($stops[$i],'value') ){ - $color = $stops[$i]->value[0]; - $position = $stops[$i]->value[1]; - }else{ - $color = $stops[$i]; - $position = null; - } - - if( !($color instanceof Less_Tree_Color) || (!(($i === 0 || $i+1 === count_if_countable($stops)) && $position === null) && !($position instanceof Less_Tree_Dimension)) ){ - throw new Less_Exception_Compiler( $throw_message ); - } - if( $position ){ - $positionValue = $position->toCSS(); - }elseif( $i === 0 ){ - $positionValue = '0%'; - }else{ - $positionValue = '100%'; - } - $alpha = $color->alpha; - $returner .= ''; - } - - $returner .= ''; - - - if( $useBase64 ){ - $returner = "'data:image/svg+xml;base64,".base64_encode($returner)."'"; - }else{ - $returner = "'data:image/svg+xml,".$returner."'"; - } - - return new Less_Tree_URL( new Less_Tree_Anonymous( $returner ) ); - } - - - /** - * Php version of javascript's `encodeURIComponent` function - * - * @param string $string The string to encode - * @return string The encoded string - */ - public static function encodeURIComponent($string){ - $revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')'); - return strtr(rawurlencode($string), $revert); - } - - - // Color Blending - // ref: http://www.w3.org/TR/compositing-1 - - public function colorBlend( $mode, $color1, $color2 ){ - $ab = $color1->alpha; // backdrop - $as = $color2->alpha; // source - $r = array(); // result - - $ar = $as + $ab * (1 - $as); - for( $i = 0; $i < 3; $i++ ){ - $cb = $color1->rgb[$i] / 255; - $cs = $color2->rgb[$i] / 255; - $cr = call_user_func( $mode, $cb, $cs ); - if( $ar ){ - $cr = ($as * $cs + $ab * ($cb - $as * ($cb + $cs - $cr))) / $ar; - } - $r[$i] = $cr * 255; - } - - return new Less_Tree_Color($r, $ar); - } - - public function multiply($color1 = null, $color2 = null ){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to multiply must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to multiply must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendMultiply'), $color1, $color2 ); - } - - private function colorBlendMultiply($cb, $cs){ - return $cb * $cs; - } - - public function screen($color1 = null, $color2 = null ){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to screen must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to screen must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendScreen'), $color1, $color2 ); - } - - private function colorBlendScreen( $cb, $cs){ - return $cb + $cs - $cb * $cs; - } - - public function overlay($color1 = null, $color2 = null){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to overlay must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to overlay must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendOverlay'), $color1, $color2 ); - } - - private function colorBlendOverlay($cb, $cs ){ - $cb *= 2; - return ($cb <= 1) - ? $this->colorBlendMultiply($cb, $cs) - : $this->colorBlendScreen($cb - 1, $cs); - } - - public function softlight($color1 = null, $color2 = null){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to softlight must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to softlight must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendSoftlight'), $color1, $color2 ); - } - - private function colorBlendSoftlight($cb, $cs ){ - $d = 1; - $e = $cb; - if( $cs > 0.5 ){ - $e = 1; - $d = ($cb > 0.25) ? sqrt($cb) - : ((16 * $cb - 12) * $cb + 4) * $cb; - } - return $cb - (1 - 2 * $cs) * $e * ($d - $cb); - } - - public function hardlight($color1 = null, $color2 = null){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to hardlight must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to hardlight must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendHardlight'), $color1, $color2 ); - } - - private function colorBlendHardlight( $cb, $cs ){ - return $this->colorBlendOverlay($cs, $cb); - } - - public function difference($color1 = null, $color2 = null) { - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to difference must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to difference must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendDifference'), $color1, $color2 ); - } - - private function colorBlendDifference( $cb, $cs ){ - return abs($cb - $cs); - } - - public function exclusion( $color1 = null, $color2 = null ){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to exclusion must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to exclusion must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendExclusion'), $color1, $color2 ); - } - - private function colorBlendExclusion( $cb, $cs ){ - return $cb + $cs - 2 * $cb * $cs; - } - - public function average($color1 = null, $color2 = null){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to average must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to average must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendAverage'), $color1, $color2 ); - } - - // non-w3c functions: - public function colorBlendAverage($cb, $cs ){ - return ($cb + $cs) / 2; - } - - public function negation($color1 = null, $color2 = null ){ - if (!$color1 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The first argument to negation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - if (!$color2 instanceof Less_Tree_Color) { - throw new Less_Exception_Compiler('The second argument to negation must be a color' . ($color instanceof Less_Tree_Expression ? ' (did you forgot commas?)' : '') ); - } - - return $this->colorBlend( array($this,'colorBlendNegation'), $color1, $color2 ); - } - - public function colorBlendNegation($cb, $cs){ - return 1 - abs($cb + $cs - 1); - } - - // ~ End of Color Blending - -} - - -/** - * Mime lookup - * - * @package Less - * @subpackage node - */ -class Less_Mime{ - - // this map is intentionally incomplete - // if you want more, install 'mime' dep - static $_types = array( - '.htm' => 'text/html', - '.html'=> 'text/html', - '.gif' => 'image/gif', - '.jpg' => 'image/jpeg', - '.jpeg'=> 'image/jpeg', - '.png' => 'image/png', - '.ttf' => 'application/x-font-ttf', - '.otf' => 'application/x-font-otf', - '.eot' => 'application/vnd.ms-fontobject', - '.woff' => 'application/x-font-woff', - '.svg' => 'image/svg+xml', - ); - - public static function lookup( $filepath ){ - $parts = explode('.',$filepath); - $ext = '.'.strtolower(array_pop($parts)); - - if( !isset(self::$_types[$ext]) ){ - return null; - } - return self::$_types[$ext]; - } - - public static function charsets_lookup( $type = null ){ - // assumes all text types are UTF-8 - return $type && preg_match('/^text\//',$type) ? 'UTF-8' : ''; - } -} - - -/** - * Tree - * - * @package Less - * @subpackage tree - */ -class Less_Tree{ - - public $cache_string; - - public function toCSS(){ - $output = new Less_Output(); - $this->genCSS($output); - return $output->toString(); - } - - - /** - * Generate CSS by adding it to the output object - * - * @param Less_Output $output The output - * @return void - */ - public function genCSS($output){} - - - /** - * @param Less_Tree_Ruleset[] $rules - */ - public static function outputRuleset( $output, $rules ){ - - $ruleCnt = count_if_countable($rules); - Less_Environment::$tabLevel++; - - - // Compressed - if( Less_Parser::$options['compress'] ){ - $output->add('{'); - for( $i = 0; $i < $ruleCnt; $i++ ){ - $rules[$i]->genCSS( $output ); - } - - $output->add( '}' ); - Less_Environment::$tabLevel--; - return; - } - - - // Non-compressed - $tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 ); - $tabRuleStr = $tabSetStr.' '; - - $output->add( " {" ); - for($i = 0; $i < $ruleCnt; $i++ ){ - $output->add( $tabRuleStr ); - $rules[$i]->genCSS( $output ); - } - Less_Environment::$tabLevel--; - $output->add( $tabSetStr.'}' ); - - } - - public function accept($visitor){} - - - public static function ReferencedArray($rules){ - foreach($rules as $rule){ - if( method_exists($rule, 'markReferenced') ){ - $rule->markReferenced(); - } - } - } - - - /** - * Requires php 5.3+ - */ - public static function __set_state($args){ - - $class = get_called_class(); - $obj = new $class(null,null,null,null); - foreach($args as $key => $val){ - $obj->$key = $val; - } - return $obj; - } - -} - -/** - * Parser output - * - * @package Less - * @subpackage output - */ -class Less_Output{ - - /** - * Output holder - * - * @var string - */ - protected $strs = array(); - - /** - * Adds a chunk to the stack - * - * @param string $chunk The chunk to output - * @param Less_FileInfo $fileInfo The file information - * @param integer $index The index - * @param mixed $mapLines - */ - public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){ - $this->strs[] = $chunk; - } - - /** - * Is the output empty? - * - * @return boolean - */ - public function isEmpty(){ - return count_if_countable($this->strs) === 0; - } - - - /** - * Converts the output to string - * - * @return string - */ - public function toString(){ - return implode('',$this->strs); - } - -} - -/** - * Visitor - * - * @package Less - * @subpackage visitor - */ -class Less_Visitor{ - - protected $methods = array(); - protected $_visitFnCache = array(); - - public function __construct(){ - $this->_visitFnCache = get_class_methods(get_class($this)); - $this->_visitFnCache = array_flip($this->_visitFnCache); - } - - public function visitObj( $node ){ - - $funcName = 'visit'.$node->type; - if( isset($this->_visitFnCache[$funcName]) ){ - - $visitDeeper = true; - $this->$funcName( $node, $visitDeeper ); - - if( $visitDeeper ){ - $node->accept($this); - } - - $funcName = $funcName . "Out"; - if( isset($this->_visitFnCache[$funcName]) ){ - $this->$funcName( $node ); - } - - }else{ - $node->accept($this); - } - - return $node; - } - - public function visitArray( $nodes ){ - - array_map( array($this,'visitObj'), $nodes); - return $nodes; - } -} - - - -/** - * Replacing Visitor - * - * @package Less - * @subpackage visitor - */ -class Less_VisitorReplacing extends Less_Visitor{ - - public function visitObj( $node ){ - - $funcName = 'visit'.$node->type; - if( isset($this->_visitFnCache[$funcName]) ){ - - $visitDeeper = true; - $node = $this->$funcName( $node, $visitDeeper ); - - if( $node ){ - if( $visitDeeper && is_object($node) ){ - $node->accept($this); - } - - $funcName = $funcName . "Out"; - if( isset($this->_visitFnCache[$funcName]) ){ - $this->$funcName( $node ); - } - } - - }else{ - $node->accept($this); - } - - return $node; - } - - public function visitArray( $nodes ){ - - $newNodes = array(); - foreach($nodes as $node){ - $evald = $this->visitObj($node); - if( $evald ){ - if( is_array($evald) ){ - self::flatten($evald,$newNodes); - }else{ - $newNodes[] = $evald; - } - } - } - return $newNodes; - } - - public function flatten( $arr, &$out ){ - - foreach($arr as $item){ - if( !is_array($item) ){ - $out[] = $item; - continue; - } - - foreach($item as $nestedItem){ - if( is_array($nestedItem) ){ - self::flatten( $nestedItem, $out); - }else{ - $out[] = $nestedItem; - } - } - } - - return $out; - } - -} - - - - -/** - * Configurable - * - * @package Less - * @subpackage Core - */ -abstract class Less_Configurable { - - /** - * Array of options - * - * @var array - */ - protected $options = array(); - - /** - * Array of default options - * - * @var array - */ - protected $defaultOptions = array(); - - - /** - * Set options - * - * If $options is an object it will be converted into an array by called - * it's toArray method. - * - * @throws Exception - * @param array|object $options - * - */ - public function setOptions($options){ - $options = array_intersect_key($options,$this->defaultOptions); - $this->options = array_merge($this->defaultOptions, $this->options, $options); - } - - - /** - * Get an option value by name - * - * If the option is empty or not set a NULL value will be returned. - * - * @param string $name - * @param mixed $default Default value if confiuration of $name is not present - * @return mixed - */ - public function getOption($name, $default = null){ - if(isset($this->options[$name])){ - return $this->options[$name]; - } - return $default; - } - - - /** - * Set an option - * - * @param string $name - * @param mixed $value - */ - public function setOption($name, $value){ - $this->options[$name] = $value; - } - -} - -/** - * Alpha - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Alpha extends Less_Tree{ - public $value; - public $type = 'Alpha'; - - public function __construct($val){ - $this->value = $val; - } - - //function accept( $visitor ){ - // $this->value = $visitor->visit( $this->value ); - //} - - public function compile($env){ - - if( is_object($this->value) ){ - $this->value = $this->value->compile($env); - } - - return $this; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - - $output->add( "alpha(opacity=" ); - - if( is_string($this->value) ){ - $output->add( $this->value ); - }else{ - $this->value->genCSS( $output); - } - - $output->add( ')' ); - } - - public function toCSS(){ - return "alpha(opacity=" . (is_string($this->value) ? $this->value : $this->value->toCSS()) . ")"; - } - - -} - -/** - * Anonymous - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Anonymous extends Less_Tree{ - public $value; - public $quote; - public $index; - public $mapLines; - public $currentFileInfo; - public $type = 'Anonymous'; - - /** - * @param integer $index - * @param boolean $mapLines - */ - public function __construct($value, $index = null, $currentFileInfo = null, $mapLines = null ){ - $this->value = $value; - $this->index = $index; - $this->mapLines = $mapLines; - $this->currentFileInfo = $currentFileInfo; - } - - public function compile(){ - return new Less_Tree_Anonymous($this->value, $this->index, $this->currentFileInfo, $this->mapLines); - } - - public function compare($x){ - if( !is_object($x) ){ - return -1; - } - - $left = $this->toCSS(); - $right = $x->toCSS(); - - if( $left === $right ){ - return 0; - } - - return $left < $right ? -1 : 1; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( $this->value, $this->currentFileInfo, $this->index, $this->mapLines ); - } - - public function toCSS(){ - return $this->value; - } - -} - - -/** - * Assignment - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Assignment extends Less_Tree{ - - public $key; - public $value; - public $type = 'Assignment'; - - public function __construct($key, $val) { - $this->key = $key; - $this->value = $val; - } - - public function accept( $visitor ){ - $this->value = $visitor->visitObj( $this->value ); - } - - public function compile($env) { - return new Less_Tree_Assignment( $this->key, $this->value->compile($env)); - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( $this->key . '=' ); - $this->value->genCSS( $output ); - } - - public function toCss(){ - return $this->key . '=' . $this->value->toCSS(); - } -} - - -/** - * Attribute - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Attribute extends Less_Tree{ - - public $key; - public $op; - public $value; - public $type = 'Attribute'; - - public function __construct($key, $op, $value){ - $this->key = $key; - $this->op = $op; - $this->value = $value; - } - - public function compile($env){ - - $key_obj = is_object($this->key); - $val_obj = is_object($this->value); - - if( !$key_obj && !$val_obj ){ - return $this; - } - - return new Less_Tree_Attribute( - $key_obj ? $this->key->compile($env) : $this->key , - $this->op, - $val_obj ? $this->value->compile($env) : $this->value); - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( $this->toCSS() ); - } - - public function toCSS(){ - $value = $this->key; - - if( $this->op ){ - $value .= $this->op; - $value .= (is_object($this->value) ? $this->value->toCSS() : $this->value); - } - - return '[' . $value . ']'; - } -} - - -/** - * Call - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Call extends Less_Tree{ - public $value; - - protected $name; - protected $args; - protected $index; - protected $currentFileInfo; - public $type = 'Call'; - - public function __construct($name, $args, $index, $currentFileInfo = null ){ - $this->name = $name; - $this->args = $args; - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - } - - public function accept( $visitor ){ - $this->args = $visitor->visitArray( $this->args ); - } - - // - // When evaluating a function call, - // we either find the function in `tree.functions` [1], - // in which case we call it, passing the evaluated arguments, - // or we simply print it out as it appeared originally [2]. - // - // The *functions.js* file contains the built-in functions. - // - // The reason why we evaluate the arguments, is in the case where - // we try to pass a variable to a function, like: `saturate(@color)`. - // The function should receive the value, not the variable. - // - public function compile($env=null){ - $args = array(); - foreach($this->args as $a){ - $args[] = $a->compile($env); - } - - $nameLC = strtolower($this->name); - switch($nameLC){ - case '%': - $nameLC = '_percent'; - break; - - case 'get-unit': - $nameLC = 'getunit'; - break; - - case 'data-uri': - $nameLC = 'datauri'; - break; - - case 'svg-gradient': - $nameLC = 'svggradient'; - break; - } - - $result = null; - if( $nameLC === 'default' ){ - $result = Less_Tree_DefaultFunc::compile(); - - }else{ - - if( method_exists('Less_Functions',$nameLC) ){ // 1. - try { - - $func = new Less_Functions($env, $this->currentFileInfo); - $result = call_user_func_array( array($func,$nameLC),$args); - - } catch (Exception $e) { - throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index); - } - } elseif( isset( $env->functions[$nameLC] ) && is_callable( $env->functions[$nameLC] ) ) { - try { - $result = call_user_func_array( $env->functions[$nameLC], $args ); - } catch (Exception $e) { - throw new Less_Exception_Compiler('error evaluating function `' . $this->name . '` '.$e->getMessage().' index: '. $this->index); - } - } - } - - if( $result !== null ){ - return $result; - } - - - return new Less_Tree_Call( $this->name, $args, $this->index, $this->currentFileInfo ); - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - - $output->add( $this->name . '(', $this->currentFileInfo, $this->index ); - $args_len = count_if_countable($this->args); - for($i = 0; $i < $args_len; $i++ ){ - $this->args[$i]->genCSS( $output ); - if( $i + 1 < $args_len ){ - $output->add( ', ' ); - } - } - - $output->add( ')' ); - } - - - //public function toCSS(){ - // return $this->compile()->toCSS(); - //} - -} - - -/** - * Color - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Color extends Less_Tree{ - public $rgb; - public $alpha; - public $isTransparentKeyword; - public $type = 'Color'; - - public function __construct($rgb, $a = 1, $isTransparentKeyword = null ){ - - if( $isTransparentKeyword ){ - $this->rgb = $rgb; - $this->alpha = $a; - $this->isTransparentKeyword = true; - return; - } - - $this->rgb = array(); - if( is_array($rgb) ){ - $this->rgb = $rgb; - }else if( strlen($rgb) == 6 ){ - foreach(str_split($rgb, 2) as $c){ - $this->rgb[] = hexdec($c); - } - }else{ - foreach(str_split($rgb, 1) as $c){ - $this->rgb[] = hexdec($c.$c); - } - } - $this->alpha = is_numeric($a) ? $a : 1; - } - - public function compile(){ - return $this; - } - - public function luma(){ - $r = $this->rgb[0] / 255; - $g = $this->rgb[1] / 255; - $b = $this->rgb[2] / 255; - - $r = ($r <= 0.03928) ? $r / 12.92 : pow((($r + 0.055) / 1.055), 2.4); - $g = ($g <= 0.03928) ? $g / 12.92 : pow((($g + 0.055) / 1.055), 2.4); - $b = ($b <= 0.03928) ? $b / 12.92 : pow((($b + 0.055) / 1.055), 2.4); - - return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( $this->toCSS() ); - } - - public function toCSS( $doNotCompress = false ){ - $compress = Less_Parser::$options['compress'] && !$doNotCompress; - $alpha = Less_Functions::fround( $this->alpha ); - - - // - // If we have some transparency, the only way to represent it - // is via `rgba`. Otherwise, we use the hex representation, - // which has better compatibility with older browsers. - // Values are capped between `0` and `255`, rounded and zero-padded. - // - if( $alpha < 1 ){ - if( ( $alpha === 0 || $alpha === 0.0 ) && isset($this->isTransparentKeyword) && $this->isTransparentKeyword ){ - return 'transparent'; - } - - $values = array(); - foreach($this->rgb as $c){ - $values[] = Less_Functions::clamp( round($c), 255); - } - $values[] = $alpha; - - $glue = ($compress ? ',' : ', '); - return "rgba(" . implode($glue, $values) . ")"; - }else{ - - $color = $this->toRGB(); - - if( $compress ){ - - // Convert color to short format - if( $color[1] === $color[2] && $color[3] === $color[4] && $color[5] === $color[6]) { - $color = '#'.$color[1] . $color[3] . $color[5]; - } - } - - return $color; - } - } - - // - // Operations have to be done per-channel, if not, - // channels will spill onto each other. Once we have - // our result, in the form of an integer triplet, - // we create a new Color node to hold the result. - // - - /** - * @param string $op - */ - public function operate( $op, $other) { - $rgb = array(); - $alpha = $this->alpha * (1 - $other->alpha) + $other->alpha; - for ($c = 0; $c < 3; $c++) { - $rgb[$c] = Less_Functions::operate( $op, $this->rgb[$c], $other->rgb[$c]); - } - return new Less_Tree_Color($rgb, $alpha); - } - - public function toRGB(){ - return $this->toHex($this->rgb); - } - - public function toHSL(){ - $r = $this->rgb[0] / 255; - $g = $this->rgb[1] / 255; - $b = $this->rgb[2] / 255; - $a = $this->alpha; - - $max = max($r, $g, $b); - $min = min($r, $g, $b); - $l = ($max + $min) / 2; - $d = $max - $min; - - $h = $s = 0; - if( $max !== $min ){ - $s = $l > 0.5 ? $d / (2 - $max - $min) : $d / ($max + $min); - - switch ($max) { - case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break; - case $g: $h = ($b - $r) / $d + 2; break; - case $b: $h = ($r - $g) / $d + 4; break; - } - $h /= 6; - } - return array('h' => $h * 360, 's' => $s, 'l' => $l, 'a' => $a ); - } - - //Adapted from http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript - public function toHSV() { - $r = $this->rgb[0] / 255; - $g = $this->rgb[1] / 255; - $b = $this->rgb[2] / 255; - $a = $this->alpha; - - $max = max($r, $g, $b); - $min = min($r, $g, $b); - - $v = $max; - - $d = $max - $min; - if ($max === 0) { - $s = 0; - } else { - $s = $d / $max; - } - - $h = 0; - if( $max !== $min ){ - switch($max){ - case $r: $h = ($g - $b) / $d + ($g < $b ? 6 : 0); break; - case $g: $h = ($b - $r) / $d + 2; break; - case $b: $h = ($r - $g) / $d + 4; break; - } - $h /= 6; - } - return array('h'=> $h * 360, 's'=> $s, 'v'=> $v, 'a' => $a ); - } - - public function toARGB(){ - $argb = array_merge( (array) Less_Parser::round($this->alpha * 255), $this->rgb); - return $this->toHex( $argb ); - } - - public function compare($x){ - - if( !property_exists( $x, 'rgb' ) ){ - return -1; - } - - - return ($x->rgb[0] === $this->rgb[0] && - $x->rgb[1] === $this->rgb[1] && - $x->rgb[2] === $this->rgb[2] && - $x->alpha === $this->alpha) ? 0 : -1; - } - - public function toHex( $v ){ - - $ret = '#'; - foreach($v as $c){ - $c = Less_Functions::clamp( Less_Parser::round($c), 255); - if( $c < 16 ){ - $ret .= '0'; - } - $ret .= dechex($c); - } - - return $ret; - } - - - /** - * @param string $keyword - */ - public static function fromKeyword( $keyword ){ - $keyword = strtolower($keyword); - - if( Less_Colors::hasOwnProperty($keyword) ){ - // detect named color - return new Less_Tree_Color(substr(Less_Colors::color($keyword), 1)); - } - - if( $keyword === 'transparent' ){ - return new Less_Tree_Color( array(0, 0, 0), 0, true); - } - } - -} - - -/** - * Comment - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Comment extends Less_Tree{ - - public $value; - public $silent; - public $isReferenced; - public $currentFileInfo; - public $type = 'Comment'; - - public function __construct($value, $silent, $index = null, $currentFileInfo = null ){ - $this->value = $value; - $this->silent = !! $silent; - $this->currentFileInfo = $currentFileInfo; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - //if( $this->debugInfo ){ - //$output->add( tree.debugInfo($env, $this), $this->currentFileInfo, $this->index); - //} - $output->add( trim($this->value) );//TODO shouldn't need to trim, we shouldn't grab the \n - } - - public function toCSS(){ - return Less_Parser::$options['compress'] ? '' : $this->value; - } - - public function isSilent(){ - $isReference = ($this->currentFileInfo && isset($this->currentFileInfo['reference']) && (!isset($this->isReferenced) || !$this->isReferenced) ); - $isCompressed = Less_Parser::$options['compress'] && !preg_match('/^\/\*!/', $this->value); - return $this->silent || $isReference || $isCompressed; - } - - public function compile(){ - return $this; - } - - public function markReferenced(){ - $this->isReferenced = true; - } - -} - - -/** - * Condition - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Condition extends Less_Tree{ - - public $op; - public $lvalue; - public $rvalue; - public $index; - public $negate; - public $type = 'Condition'; - - public function __construct($op, $l, $r, $i = 0, $negate = false) { - $this->op = trim($op); - $this->lvalue = $l; - $this->rvalue = $r; - $this->index = $i; - $this->negate = $negate; - } - - public function accept($visitor){ - $this->lvalue = $visitor->visitObj( $this->lvalue ); - $this->rvalue = $visitor->visitObj( $this->rvalue ); - } - - public function compile($env) { - $a = $this->lvalue->compile($env); - $b = $this->rvalue->compile($env); - - switch( $this->op ){ - case 'and': - $result = $a && $b; - break; - - case 'or': - $result = $a || $b; - break; - - default: - if( Less_Parser::is_method($a, 'compare') ){ - $result = $a->compare($b); - }elseif( Less_Parser::is_method($b, 'compare') ){ - $result = $b->compare($a); - }else{ - throw new Less_Exception_Compiler('Unable to perform comparison', null, $this->index); - } - - switch ($result) { - case -1: - $result = $this->op === '<' || $this->op === '=<' || $this->op === '<='; - break; - - case 0: - $result = $this->op === '=' || $this->op === '>=' || $this->op === '=<' || $this->op === '<='; - break; - - case 1: - $result = $this->op === '>' || $this->op === '>='; - break; - } - break; - } - - return $this->negate ? !$result : $result; - } - -} - - -/** - * DefaultFunc - * - * @package Less - * @subpackage tree - */ -class Less_Tree_DefaultFunc{ - - static $error_; - static $value_; - - public static function compile(){ - if( self::$error_ ){ - throw new Exception(self::$error_); - } - if( self::$value_ !== null ){ - return self::$value_ ? new Less_Tree_Keyword('true') : new Less_Tree_Keyword('false'); - } - } - - public static function value( $v ){ - self::$value_ = $v; - } - - public static function error( $e ){ - self::$error_ = $e; - } - - public static function reset(){ - self::$value_ = self::$error_ = null; - } -} - -/** - * DetachedRuleset - * - * @package Less - * @subpackage tree - */ -class Less_Tree_DetachedRuleset extends Less_Tree{ - - public $ruleset; - public $frames; - public $type = 'DetachedRuleset'; - - public function __construct( $ruleset, $frames = null ){ - $this->ruleset = $ruleset; - $this->frames = $frames; - } - - public function accept($visitor) { - $this->ruleset = $visitor->visitObj($this->ruleset); - } - - public function compile($env){ - if( $this->frames ){ - $frames = $this->frames; - }else{ - $frames = $env->frames; - } - return new Less_Tree_DetachedRuleset($this->ruleset, $frames); - } - - public function callEval($env) { - if( $this->frames ){ - return $this->ruleset->compile( $env->copyEvalEnv( array_merge($this->frames,$env->frames) ) ); - } - return $this->ruleset->compile( $env ); - } -} - - - -/** - * Dimension - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Dimension extends Less_Tree{ - - public $value; - public $unit; - public $type = 'Dimension'; - - public function __construct($value, $unit = null){ - $this->value = floatval($value); - - if( $unit && ($unit instanceof Less_Tree_Unit) ){ - $this->unit = $unit; - }elseif( $unit ){ - $this->unit = new Less_Tree_Unit( array($unit) ); - }else{ - $this->unit = new Less_Tree_Unit( ); - } - } - - public function accept( $visitor ){ - $this->unit = $visitor->visitObj( $this->unit ); - } - - public function compile(){ - return $this; - } - - public function toColor() { - return new Less_Tree_Color(array($this->value, $this->value, $this->value)); - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - - if( Less_Parser::$options['strictUnits'] && !$this->unit->isSingular() ){ - throw new Less_Exception_Compiler("Multiple units in dimension. Correct the units or use the unit function. Bad unit: ".$this->unit->toString()); - } - - $value = Less_Functions::fround( $this->value ); - $strValue = (string)$value; - - if( $value !== 0 && $value < 0.000001 && $value > -0.000001 ){ - // would be output 1e-6 etc. - $strValue = number_format($strValue,10); - $strValue = preg_replace('/\.?0+$/','', $strValue); - } - - if( Less_Parser::$options['compress'] ){ - // Zero values doesn't need a unit - if( $value === 0 && $this->unit->isLength() ){ - $output->add( $strValue ); - return $strValue; - } - - // Float values doesn't need a leading zero - if( $value > 0 && $value < 1 && $strValue[0] === '0' ){ - $strValue = substr($strValue,1); - } - } - - $output->add( $strValue ); - $this->unit->genCSS( $output ); - } - - public function __toString(){ - return $this->toCSS(); - } - - // In an operation between two Dimensions, - // we default to the first Dimension's unit, - // so `1px + 2em` will yield `3px`. - - /** - * @param string $op - */ - public function operate( $op, $other){ - - $value = Less_Functions::operate( $op, $this->value, $other->value); - $unit = clone $this->unit; - - if( $op === '+' || $op === '-' ){ - - if( !$unit->numerator && !$unit->denominator ){ - $unit->numerator = $other->unit->numerator; - $unit->denominator = $other->unit->denominator; - }elseif( !$other->unit->numerator && !$other->unit->denominator ){ - // do nothing - }else{ - $other = $other->convertTo( $this->unit->usedUnits()); - - if( Less_Parser::$options['strictUnits'] && $other->unit->toString() !== $unit->toCSS() ){ - throw new Less_Exception_Compiler("Incompatible units. Change the units or use the unit function. Bad units: '".$unit->toString() . "' and ".$other->unit->toString()+"'."); - } - - $value = Less_Functions::operate( $op, $this->value, $other->value); - } - }elseif( $op === '*' ){ - $unit->numerator = array_merge($unit->numerator, $other->unit->numerator); - $unit->denominator = array_merge($unit->denominator, $other->unit->denominator); - sort($unit->numerator); - sort($unit->denominator); - $unit->cancel(); - }elseif( $op === '/' ){ - $unit->numerator = array_merge($unit->numerator, $other->unit->denominator); - $unit->denominator = array_merge($unit->denominator, $other->unit->numerator); - sort($unit->numerator); - sort($unit->denominator); - $unit->cancel(); - } - return new Less_Tree_Dimension( $value, $unit); - } - - public function compare($other) { - if ($other instanceof Less_Tree_Dimension) { - - if( $this->unit->isEmpty() || $other->unit->isEmpty() ){ - $a = $this; - $b = $other; - } else { - $a = $this->unify(); - $b = $other->unify(); - if( $a->unit->compare($b->unit) !== 0 ){ - return -1; - } - } - $aValue = $a->value; - $bValue = $b->value; - - if ($bValue > $aValue) { - return -1; - } elseif ($bValue < $aValue) { - return 1; - } else { - return 0; - } - } else { - return -1; - } - } - - public function unify() { - return $this->convertTo(array('length'=> 'px', 'duration'=> 's', 'angle' => 'rad' )); - } - - public function convertTo($conversions) { - $value = $this->value; - $unit = clone $this->unit; - - if( is_string($conversions) ){ - $derivedConversions = array(); - foreach( Less_Tree_UnitConversions::$groups as $i ){ - if( isset(Less_Tree_UnitConversions::${$i}[$conversions]) ){ - $derivedConversions = array( $i => $conversions); - } - } - $conversions = $derivedConversions; - } - - - foreach($conversions as $groupName => $targetUnit){ - $group = Less_Tree_UnitConversions::${$groupName}; - - //numerator - foreach($unit->numerator as $i => $atomicUnit){ - $atomicUnit = $unit->numerator[$i]; - if( !isset($group[$atomicUnit]) ){ - continue; - } - - $value = $value * ($group[$atomicUnit] / $group[$targetUnit]); - - $unit->numerator[$i] = $targetUnit; - } - - //denominator - foreach($unit->denominator as $i => $atomicUnit){ - $atomicUnit = $unit->denominator[$i]; - if( !isset($group[$atomicUnit]) ){ - continue; - } - - $value = $value / ($group[$atomicUnit] / $group[$targetUnit]); - - $unit->denominator[$i] = $targetUnit; - } - } - - $unit->cancel(); - - return new Less_Tree_Dimension( $value, $unit); - } -} - - -/** - * Directive - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Directive extends Less_Tree{ - - public $name; - public $value; - public $rules; - public $index; - public $isReferenced; - public $currentFileInfo; - public $debugInfo; - public $type = 'Directive'; - - public function __construct($name, $value = null, $rules, $index = null, $currentFileInfo = null, $debugInfo = null ){ - $this->name = $name; - $this->value = $value; - if( $rules ){ - $this->rules = $rules; - $this->rules->allowImports = true; - } - - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - $this->debugInfo = $debugInfo; - } - - - public function accept( $visitor ){ - if( $this->rules ){ - $this->rules = $visitor->visitObj( $this->rules ); - } - if( $this->value ){ - $this->value = $visitor->visitObj( $this->value ); - } - } - - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $value = $this->value; - $rules = $this->rules; - $output->add( $this->name, $this->currentFileInfo, $this->index ); - if( $this->value ){ - $output->add(' '); - $this->value->genCSS($output); - } - if( $this->rules ){ - Less_Tree::outputRuleset( $output, array($this->rules)); - } else { - $output->add(';'); - } - } - - public function compile($env){ - - $value = $this->value; - $rules = $this->rules; - if( $value ){ - $value = $value->compile($env); - } - - if( $rules ){ - $rules = $rules->compile($env); - $rules->root = true; - } - - return new Less_Tree_Directive( $this->name, $value, $rules, $this->index, $this->currentFileInfo, $this->debugInfo ); - } - - - public function variable($name){ - if( $this->rules ){ - return $this->rules->variable($name); - } - } - - public function find($selector){ - if( $this->rules ){ - return $this->rules->find($selector, $this); - } - } - - //rulesets: function () { if (this.rules) return tree.Ruleset.prototype.rulesets.apply(this.rules); }, - - public function markReferenced(){ - $this->isReferenced = true; - if( $this->rules ){ - Less_Tree::ReferencedArray($this->rules->rules); - } - } - -} - - -/** - * Element - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Element extends Less_Tree{ - - public $combinator = ''; - public $value = ''; - public $index; - public $currentFileInfo; - public $type = 'Element'; - - public $value_is_object = false; - - public function __construct($combinator, $value, $index = null, $currentFileInfo = null ){ - - $this->value = $value; - $this->value_is_object = is_object($value); - - if( $combinator ){ - $this->combinator = $combinator; - } - - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - } - - public function accept( $visitor ){ - if( $this->value_is_object ){ //object or string - $this->value = $visitor->visitObj( $this->value ); - } - } - - public function compile($env){ - - if( Less_Environment::$mixin_stack ){ - return new Less_Tree_Element($this->combinator, ($this->value_is_object ? $this->value->compile($env) : $this->value), $this->index, $this->currentFileInfo ); - } - - if( $this->value_is_object ){ - $this->value = $this->value->compile($env); - } - - return $this; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( $this->toCSS(), $this->currentFileInfo, $this->index ); - } - - public function toCSS(){ - - if( $this->value_is_object ){ - $value = $this->value->toCSS(); - }else{ - $value = $this->value; - } - - - if( $value === '' && $this->combinator && $this->combinator === '&' ){ - return ''; - } - - - return Less_Environment::$_outputMap[$this->combinator] . $value; - } - -} - - -/** - * Expression - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Expression extends Less_Tree{ - - public $value = array(); - public $parens = false; - public $parensInOp = false; - public $type = 'Expression'; - - public function __construct( $value, $parens = null ){ - $this->value = $value; - $this->parens = $parens; - } - - public function accept( $visitor ){ - $this->value = $visitor->visitArray( $this->value ); - } - - public function compile($env) { - - $doubleParen = false; - - if( $this->parens && !$this->parensInOp ){ - Less_Environment::$parensStack++; - } - - $returnValue = null; - if( $this->value ){ - - $count = count_if_countable($this->value); - - if( $count > 1 ){ - - $ret = array(); - foreach($this->value as $e){ - $ret[] = $e->compile($env); - } - $returnValue = new Less_Tree_Expression($ret); - - }else{ - - if( ($this->value[0] instanceof Less_Tree_Expression) && $this->value[0]->parens && !$this->value[0]->parensInOp ){ - $doubleParen = true; - } - - $returnValue = $this->value[0]->compile($env); - } - - } else { - $returnValue = $this; - } - - if( $this->parens ){ - if( !$this->parensInOp ){ - Less_Environment::$parensStack--; - - }elseif( !Less_Environment::isMathOn() && !$doubleParen ){ - $returnValue = new Less_Tree_Paren($returnValue); - - } - } - return $returnValue; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $val_len = count_if_countable($this->value); - for( $i = 0; $i < $val_len; $i++ ){ - $this->value[$i]->genCSS( $output ); - if( $i + 1 < $val_len ){ - $output->add( ' ' ); - } - } - } - - public function throwAwayComments() { - - if( is_array($this->value) ){ - $new_value = array(); - foreach($this->value as $v){ - if( $v instanceof Less_Tree_Comment ){ - continue; - } - $new_value[] = $v; - } - $this->value = $new_value; - } - } -} - - -/** - * Extend - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Extend extends Less_Tree{ - - public $selector; - public $option; - public $index; - public $selfSelectors = array(); - public $allowBefore; - public $allowAfter; - public $firstExtendOnThisSelectorPath; - public $type = 'Extend'; - public $ruleset; - - - public $object_id; - public $parent_ids = array(); - - /** - * @param integer $index - */ - public function __construct($selector, $option, $index){ - static $i = 0; - $this->selector = $selector; - $this->option = $option; - $this->index = $index; - - switch($option){ - case "all": - $this->allowBefore = true; - $this->allowAfter = true; - break; - default: - $this->allowBefore = false; - $this->allowAfter = false; - break; - } - - $this->object_id = $i++; - $this->parent_ids = array($this->object_id); - } - - public function accept( $visitor ){ - $this->selector = $visitor->visitObj( $this->selector ); - } - - public function compile( $env ){ - Less_Parser::$has_extends = true; - $this->selector = $this->selector->compile($env); - return $this; - //return new Less_Tree_Extend( $this->selector->compile($env), $this->option, $this->index); - } - - public function findSelfSelectors( $selectors ){ - $selfElements = array(); - - - for( $i = 0, $selectors_len = count_if_countable($selectors); $i < $selectors_len; $i++ ){ - $selectorElements = $selectors[$i]->elements; - // duplicate the logic in genCSS function inside the selector node. - // future TODO - move both logics into the selector joiner visitor - if( $i && $selectorElements && $selectorElements[0]->combinator === "") { - $selectorElements[0]->combinator = ' '; - } - $selfElements = array_merge( $selfElements, $selectors[$i]->elements ); - } - - $this->selfSelectors = array(new Less_Tree_Selector($selfElements)); - } - -} - -/** - * CSS @import node - * - * The general strategy here is that we don't want to wait - * for the parsing to be completed, before we start importing - * the file. That's because in the context of a browser, - * most of the time will be spent waiting for the server to respond. - * - * On creation, we push the import path to our import queue, though - * `import,push`, we also pass it a callback, which it'll call once - * the file has been fetched, and parsed. - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Import extends Less_Tree{ - - public $options; - public $index; - public $path; - public $features; - public $currentFileInfo; - public $css; - public $skip; - public $root; - public $type = 'Import'; - - public function __construct($path, $features, $options, $index, $currentFileInfo = null ){ - $this->options = $options; - $this->index = $index; - $this->path = $path; - $this->features = $features; - $this->currentFileInfo = $currentFileInfo; - - if( is_array($options) ){ - $this->options += array('inline'=>false); - - if( isset($this->options['less']) || $this->options['inline'] ){ - $this->css = !isset($this->options['less']) || !$this->options['less'] || $this->options['inline']; - } else { - $pathValue = $this->getPath(); - if( $pathValue && preg_match('/css([\?;].*)?$/',$pathValue) ){ - $this->css = true; - } - } - } - } - -// -// The actual import node doesn't return anything, when converted to CSS. -// The reason is that it's used at the evaluation stage, so that the rules -// it imports can be treated like any other rules. -// -// In `eval`, we make sure all Import nodes get evaluated, recursively, so -// we end up with a flat structure, which can easily be imported in the parent -// ruleset. -// - - public function accept($visitor){ - - if( $this->features ){ - $this->features = $visitor->visitObj($this->features); - } - $this->path = $visitor->visitObj($this->path); - - if( !$this->options['inline'] && $this->root ){ - $this->root = $visitor->visit($this->root); - } - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - if( $this->css ){ - - $output->add( '@import ', $this->currentFileInfo, $this->index ); - - $this->path->genCSS( $output ); - if( $this->features ){ - $output->add( ' ' ); - $this->features->genCSS( $output ); - } - $output->add( ';' ); - } - } - - public function toCSS(){ - $features = $this->features ? ' ' . $this->features->toCSS() : ''; - - if ($this->css) { - return "@import " . $this->path->toCSS() . $features . ";\n"; - } else { - return ""; - } - } - - /** - * @return string - */ - public function getPath(){ - if ($this->path instanceof Less_Tree_Quoted) { - $path = $this->path->value; - $path = ( isset($this->css) || preg_match('/(\.[a-z]*$)|([\?;].*)$/',$path)) ? $path : $path . '.less'; - } else if ($this->path instanceof Less_Tree_URL) { - $path = $this->path->value->value; - }else{ - return null; - } - - //remove query string and fragment - return preg_replace('/[\?#][^\?]*$/','',$path); - } - - public function compileForImport( $env ){ - return new Less_Tree_Import( $this->path->compile($env), $this->features, $this->options, $this->index, $this->currentFileInfo); - } - - public function compilePath($env) { - $path = $this->path->compile($env); - $rootpath = ''; - if( $this->currentFileInfo && $this->currentFileInfo['rootpath'] ){ - $rootpath = $this->currentFileInfo['rootpath']; - } - - - if( !($path instanceof Less_Tree_URL) ){ - if( $rootpath ){ - $pathValue = $path->value; - // Add the base path if the import is relative - if( $pathValue && Less_Environment::isPathRelative($pathValue) ){ - $path->value = $this->currentFileInfo['uri_root'].$pathValue; - } - } - $path->value = Less_Environment::normalizePath($path->value); - } - - - - return $path; - } - - public function compile( $env ){ - - $evald = $this->compileForImport($env); - - //get path & uri - $path_and_uri = null; - if( is_callable(Less_Parser::$options['import_callback']) ){ - $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$evald); - } - - if( !$path_and_uri ){ - $path_and_uri = $evald->PathAndUri(); - } - - if( $path_and_uri ){ - list($full_path, $uri) = $path_and_uri; - }else{ - $full_path = $uri = $evald->getPath(); - } - - - //import once - if( $evald->skip( $full_path, $env) ){ - return array(); - } - - if( $this->options['inline'] ){ - //todo needs to reference css file not import - //$contents = new Less_Tree_Anonymous($this->root, 0, array('filename'=>$this->importedFilename), true ); - - Less_Parser::AddParsedFile($full_path); - $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true ); - - if( $this->features ){ - return new Less_Tree_Media( array($contents), $this->features->value ); - } - - return array( $contents ); - } - - - // css ? - if( $evald->css ){ - $features = ( $evald->features ? $evald->features->compile($env) : null ); - return new Less_Tree_Import( $this->compilePath( $env), $features, $this->options, $this->index); - } - - - return $this->ParseImport( $full_path, $uri, $env ); - } - - - /** - * Using the import directories, get the full absolute path and uri of the import - * - * @param Less_Tree_Import $evald - */ - public function PathAndUri(){ - - $evald_path = $this->getPath(); - - if( $evald_path ){ - - $import_dirs = array(); - - if( Less_Environment::isPathRelative($evald_path) ){ - //if the path is relative, the file should be in the current directory - $import_dirs[ $this->currentFileInfo['currentDirectory'] ] = $this->currentFileInfo['uri_root']; - - }else{ - //otherwise, the file should be relative to the server root - $import_dirs[ $this->currentFileInfo['entryPath'] ] = $this->currentFileInfo['entryUri']; - - //if the user supplied entryPath isn't the actual root - $import_dirs[ $_SERVER['DOCUMENT_ROOT'] ] = ''; - - } - - // always look in user supplied import directories - $import_dirs = array_merge( $import_dirs, Less_Parser::$options['import_dirs'] ); - - - foreach( $import_dirs as $rootpath => $rooturi){ - if( is_callable($rooturi) ){ - list($path, $uri) = call_user_func($rooturi, $evald_path); - if( is_string($path) ){ - $full_path = $path; - return array( $full_path, $uri ); - } - }elseif( !empty($rootpath) ){ - - - if( $rooturi ){ - if( strpos($evald_path,$rooturi) === 0 ){ - $evald_path = substr( $evald_path, strlen($rooturi) ); - } - } - - $path = rtrim($rootpath,'/\\').'/'.ltrim($evald_path,'/\\'); - - if( file_exists($path) ){ - $full_path = Less_Environment::normalizePath($path); - $uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path)); - return array( $full_path, $uri ); - } elseif( file_exists($path.'.less') ){ - $full_path = Less_Environment::normalizePath($path.'.less'); - $uri = Less_Environment::normalizePath(dirname($rooturi.$evald_path.'.less')); - return array( $full_path, $uri ); - } - } - } - } - } - - - /** - * Parse the import url and return the rules - * - * @return Less_Tree_Media|array - */ - public function ParseImport( $full_path, $uri, $env ){ - - $import_env = clone $env; - if( (isset($this->options['reference']) && $this->options['reference']) || isset($this->currentFileInfo['reference']) ){ - $import_env->currentFileInfo['reference'] = true; - } - - if( (isset($this->options['multiple']) && $this->options['multiple']) ){ - $import_env->importMultiple = true; - } - - $parser = new Less_Parser($import_env); - $root = $parser->parseFile($full_path, $uri, true); - - - $ruleset = new Less_Tree_Ruleset(array(), $root->rules ); - $ruleset->evalImports($import_env); - - return $this->features ? new Less_Tree_Media($ruleset->rules, $this->features->value) : $ruleset->rules; - } - - - /** - * Should the import be skipped? - * - * @return boolean|null - */ - private function Skip($path, $env){ - - $path = Less_Parser::winPath(realpath($path)); - - if( $path && Less_Parser::FileParsed($path) ){ - - if( isset($this->currentFileInfo['reference']) ){ - return true; - } - - return !isset($this->options['multiple']) && !$env->importMultiple; - } - - } -} - - - -/** - * Javascript - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Javascript extends Less_Tree{ - - public $type = 'Javascript'; - public $escaped; - public $expression; - public $index; - - /** - * @param boolean $index - * @param boolean $escaped - */ - public function __construct($string, $index, $escaped){ - $this->escaped = $escaped; - $this->expression = $string; - $this->index = $index; - } - - public function compile(){ - return new Less_Tree_Anonymous('/* Sorry, can not do JavaScript evaluation in PHP... :( */'); - } - -} - - -/** - * Keyword - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Keyword extends Less_Tree{ - - public $value; - public $type = 'Keyword'; - - /** - * @param string $value - */ - public function __construct($value){ - $this->value = $value; - } - - public function compile(){ - return $this; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - - if( $this->value === '%') { - throw new Less_Exception_Compiler("Invalid % without number"); - } - - $output->add( $this->value ); - } - - public function compare($other) { - if ($other instanceof Less_Tree_Keyword) { - return $other->value === $this->value ? 0 : 1; - } else { - return -1; - } - } -} - - -/** - * Media - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Media extends Less_Tree{ - - public $features; - public $rules; - public $index; - public $currentFileInfo; - public $isReferenced; - public $type = 'Media'; - - public function __construct($value = array(), $features = array(), $index = null, $currentFileInfo = null ){ - - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - - $selectors = $this->emptySelectors(); - - $this->features = new Less_Tree_Value($features); - - $this->rules = array(new Less_Tree_Ruleset($selectors, $value)); - $this->rules[0]->allowImports = true; - } - - public function accept( $visitor ){ - $this->features = $visitor->visitObj($this->features); - $this->rules = $visitor->visitArray($this->rules); - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - - $output->add( '@media ', $this->currentFileInfo, $this->index ); - $this->features->genCSS( $output ); - Less_Tree::outputRuleset( $output, $this->rules); - - } - - public function compile($env) { - - $media = new Less_Tree_Media(array(), array(), $this->index, $this->currentFileInfo ); - - $strictMathBypass = false; - if( Less_Parser::$options['strictMath'] === false) { - $strictMathBypass = true; - Less_Parser::$options['strictMath'] = true; - } - - $media->features = $this->features->compile($env); - - if( $strictMathBypass ){ - Less_Parser::$options['strictMath'] = false; - } - - $env->mediaPath[] = $media; - $env->mediaBlocks[] = $media; - - array_unshift($env->frames, $this->rules[0]); - $media->rules = array($this->rules[0]->compile($env)); - array_shift($env->frames); - - array_pop($env->mediaPath); - - return !$env->mediaPath ? $media->compileTop($env) : $media->compileNested($env); - } - - public function variable($name) { - return $this->rules[0]->variable($name); - } - - public function find($selector) { - return $this->rules[0]->find($selector, $this); - } - - public function emptySelectors(){ - $el = new Less_Tree_Element('','&', $this->index, $this->currentFileInfo ); - $sels = array( new Less_Tree_Selector(array($el), array(), null, $this->index, $this->currentFileInfo) ); - $sels[0]->mediaEmpty = true; - return $sels; - } - - public function markReferenced(){ - $this->rules[0]->markReferenced(); - $this->isReferenced = true; - Less_Tree::ReferencedArray($this->rules[0]->rules); - } - - // evaltop - public function compileTop($env) { - $result = $this; - - if (count_if_countable($env->mediaBlocks) > 1) { - $selectors = $this->emptySelectors(); - $result = new Less_Tree_Ruleset($selectors, $env->mediaBlocks); - $result->multiMedia = true; - } - - $env->mediaBlocks = array(); - $env->mediaPath = array(); - - return $result; - } - - public function compileNested($env) { - $path = array_merge($env->mediaPath, array($this)); - - // Extract the media-query conditions separated with `,` (OR). - foreach ($path as $key => $p) { - $value = $p->features instanceof Less_Tree_Value ? $p->features->value : $p->features; - $path[$key] = is_array($value) ? $value : array($value); - } - - // Trace all permutations to generate the resulting media-query. - // - // (a, b and c) with nested (d, e) -> - // a and d - // a and e - // b and c and d - // b and c and e - - $permuted = $this->permute($path); - $expressions = array(); - foreach($permuted as $path){ - - for( $i=0, $len=count_if_countable($path); $i < $len; $i++){ - $path[$i] = Less_Parser::is_method($path[$i], 'toCSS') ? $path[$i] : new Less_Tree_Anonymous($path[$i]); - } - - for( $i = count_if_countable($path) - 1; $i > 0; $i-- ){ - array_splice($path, $i, 0, array(new Less_Tree_Anonymous('and'))); - } - - $expressions[] = new Less_Tree_Expression($path); - } - $this->features = new Less_Tree_Value($expressions); - - - - // Fake a tree-node that doesn't output anything. - return new Less_Tree_Ruleset(array(), array()); - } - - public function permute($arr) { - if (!$arr) - return array(); - - if (count_if_countable($arr) == 1) - return $arr[0]; - - $result = array(); - $rest = $this->permute(array_slice($arr, 1)); - foreach ($rest as $r) { - foreach ($arr[0] as $a) { - $result[] = array_merge( - is_array($a) ? $a : array($a), - is_array($r) ? $r : array($r) - ); - } - } - - return $result; - } - - public function bubbleSelectors($selectors) { - - if( !$selectors) return; - - $this->rules = array(new Less_Tree_Ruleset( $selectors, array($this->rules[0]))); - } - -} - - -/** - * A simple css name-value pair - * ex: width:100px; - * - * In bootstrap, there are about 600-1,000 simple name-value pairs (depending on how forgiving the match is) -vs- 6,020 dynamic rules (Less_Tree_Rule) - * Using the name-value object can speed up bootstrap compilation slightly, but it breaks color keyword interpretation: color:red -> color:#FF0000; - * - * @package Less - * @subpackage tree - */ -class Less_Tree_NameValue extends Less_Tree{ - - public $name; - public $value; - public $index; - public $currentFileInfo; - public $type = 'NameValue'; - - public function __construct($name, $value = null, $index = null, $currentFileInfo = null ){ - $this->name = $name; - $this->value = $value; - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - } - - public function genCSS( $output ){ - - $output->add( - $this->name - . Less_Environment::$_outputMap[': '] - . $this->value - . (((Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";") - , $this->currentFileInfo, $this->index); - } - - public function compile ($env){ - return $this; - } -} - - -/** - * Negative - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Negative extends Less_Tree{ - - public $value; - public $type = 'Negative'; - - public function __construct($node){ - $this->value = $node; - } - - //function accept($visitor) { - // $this->value = $visitor->visit($this->value); - //} - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( '-' ); - $this->value->genCSS( $output ); - } - - public function compile($env) { - if( Less_Environment::isMathOn() ){ - $ret = new Less_Tree_Operation('*', array( new Less_Tree_Dimension(-1), $this->value ) ); - return $ret->compile($env); - } - return new Less_Tree_Negative( $this->value->compile($env) ); - } -} - -/** - * Operation - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Operation extends Less_Tree{ - - public $op; - public $operands; - public $isSpaced; - public $type = 'Operation'; - - /** - * @param string $op - */ - public function __construct($op, $operands, $isSpaced = false){ - $this->op = trim($op); - $this->operands = $operands; - $this->isSpaced = $isSpaced; - } - - public function accept($visitor) { - $this->operands = $visitor->visitArray($this->operands); - } - - public function compile($env){ - $a = $this->operands[0]->compile($env); - $b = $this->operands[1]->compile($env); - - - if( Less_Environment::isMathOn() ){ - - if( $a instanceof Less_Tree_Dimension && $b instanceof Less_Tree_Color ){ - $a = $a->toColor(); - - }elseif( $b instanceof Less_Tree_Dimension && $a instanceof Less_Tree_Color ){ - $b = $b->toColor(); - - } - - if( !method_exists($a,'operate') ){ - throw new Less_Exception_Compiler("Operation on an invalid type"); - } - - return $a->operate( $this->op, $b); - } - - return new Less_Tree_Operation($this->op, array($a, $b), $this->isSpaced ); - } - - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $this->operands[0]->genCSS( $output ); - if( $this->isSpaced ){ - $output->add( " " ); - } - $output->add( $this->op ); - if( $this->isSpaced ){ - $output->add( ' ' ); - } - $this->operands[1]->genCSS( $output ); - } - -} - - -/** - * Paren - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Paren extends Less_Tree{ - - public $value; - public $type = 'Paren'; - - public function __construct($value) { - $this->value = $value; - } - - public function accept($visitor){ - $this->value = $visitor->visitObj($this->value); - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( '(' ); - $this->value->genCSS( $output ); - $output->add( ')' ); - } - - public function compile($env) { - return new Less_Tree_Paren($this->value->compile($env)); - } - -} - - -/** - * Quoted - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Quoted extends Less_Tree{ - public $escaped; - public $value; - public $quote; - public $index; - public $currentFileInfo; - public $type = 'Quoted'; - - /** - * @param string $str - */ - public function __construct($str, $content = '', $escaped = false, $index = false, $currentFileInfo = null ){ - $this->escaped = $escaped; - $this->value = $content; - if( $str ){ - $this->quote = $str[0]; - } - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - if( !$this->escaped ){ - $output->add( $this->quote, $this->currentFileInfo, $this->index ); - } - $output->add( $this->value ); - if( !$this->escaped ){ - $output->add( $this->quote ); - } - } - - public function compile($env){ - - $value = $this->value; - if( preg_match_all('/`([^`]+)`/', $this->value, $matches) ){ - foreach($matches as $i => $match){ - $js = new Less_Tree_JavaScript($matches[1], $this->index, true); - $js = $js->compile()->value; - $value = str_replace($matches[0][$i], $js, $value); - } - } - - if( preg_match_all('/@\{([\w-]+)\}/',$value,$matches) ){ - foreach($matches[1] as $i => $match){ - $v = new Less_Tree_Variable('@' . $match, $this->index, $this->currentFileInfo ); - $v = $v->compile($env); - $v = ($v instanceof Less_Tree_Quoted) ? $v->value : $v->toCSS(); - $value = str_replace($matches[0][$i], $v, $value); - } - } - - return new Less_Tree_Quoted($this->quote . $value . $this->quote, $value, $this->escaped, $this->index, $this->currentFileInfo); - } - - public function compare($x) { - - if( !Less_Parser::is_method($x, 'toCSS') ){ - return -1; - } - - $left = $this->toCSS(); - $right = $x->toCSS(); - - if ($left === $right) { - return 0; - } - - return $left < $right ? -1 : 1; - } -} - - -/** - * Rule - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Rule extends Less_Tree{ - - public $name; - public $value; - public $important; - public $merge; - public $index; - public $inline; - public $variable; - public $currentFileInfo; - public $type = 'Rule'; - - /** - * @param string $important - */ - public function __construct($name, $value = null, $important = null, $merge = null, $index = null, $currentFileInfo = null, $inline = false){ - $this->name = $name; - $this->value = ($value instanceof Less_Tree_Value || $value instanceof Less_Tree_Ruleset) ? $value : new Less_Tree_Value(array($value)); - $this->important = $important ? ' ' . trim($important) : ''; - $this->merge = $merge; - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - $this->inline = $inline; - $this->variable = ( is_string($name) && $name[0] === '@'); - } - - public function accept($visitor) { - $this->value = $visitor->visitObj( $this->value ); - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - - $output->add( $this->name . Less_Environment::$_outputMap[': '], $this->currentFileInfo, $this->index); - try{ - $this->value->genCSS( $output); - - }catch( Less_Exception_Parser $e ){ - $e->index = $this->index; - $e->currentFile = $this->currentFileInfo; - throw $e; - } - $output->add( $this->important . (($this->inline || (Less_Environment::$lastRule && Less_Parser::$options['compress'])) ? "" : ";"), $this->currentFileInfo, $this->index); - } - - public function compile ($env){ - - $name = $this->name; - if( is_array($name) ){ - // expand 'primitive' name directly to get - // things faster (~10% for benchmark.less): - if( count_if_countable($name) === 1 && $name[0] instanceof Less_Tree_Keyword ){ - $name = $name[0]->value; - }else{ - $name = $this->CompileName($env,$name); - } - } - - $strictMathBypass = Less_Parser::$options['strictMath']; - if( $name === "font" && !Less_Parser::$options['strictMath'] ){ - Less_Parser::$options['strictMath'] = true; - } - - try { - $evaldValue = $this->value->compile($env); - - if( !$this->variable && $evaldValue->type === "DetachedRuleset") { - throw new Less_Exception_Compiler("Rulesets cannot be evaluated on a property.", null, $this->index, $this->currentFileInfo); - } - - if( Less_Environment::$mixin_stack ){ - $return = new Less_Tree_Rule($name, $evaldValue, $this->important, $this->merge, $this->index, $this->currentFileInfo, $this->inline); - }else{ - $this->name = $name; - $this->value = $evaldValue; - $return = $this; - } - - }catch( Less_Exception_Parser $e ){ - if( !is_numeric($e->index) ){ - $e->index = $this->index; - $e->currentFile = $this->currentFileInfo; - } - throw $e; - } - - Less_Parser::$options['strictMath'] = $strictMathBypass; - - return $return; - } - - - public function CompileName( $env, $name ){ - $output = new Less_Output(); - foreach($name as $n){ - $n->compile($env)->genCSS($output); - } - return $output->toString(); - } - - public function makeImportant(){ - return new Less_Tree_Rule($this->name, $this->value, '!important', $this->merge, $this->index, $this->currentFileInfo, $this->inline); - } - -} - - -/** - * Ruleset - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Ruleset extends Less_Tree{ - - protected $lookups; - public $_variables; - public $_rulesets; - - public $strictImports; - - public $selectors; - public $rules; - public $root; - public $allowImports; - public $paths; - public $firstRoot; - public $type = 'Ruleset'; - public $multiMedia; - public $allExtends; - - public $ruleset_id; - public $originalRuleset; - - public $first_oelements; - - public function SetRulesetIndex(){ - $this->ruleset_id = Less_Parser::$next_id++; - $this->originalRuleset = $this->ruleset_id; - - if( $this->selectors ){ - foreach($this->selectors as $sel){ - if( $sel->_oelements ){ - $this->first_oelements[$sel->_oelements[0]] = true; - } - } - } - } - - public function __construct($selectors, $rules, $strictImports = null){ - $this->selectors = $selectors; - $this->rules = $rules; - $this->lookups = array(); - $this->strictImports = $strictImports; - $this->SetRulesetIndex(); - } - - public function accept( $visitor ){ - if( $this->paths ){ - $paths_len = count_if_countable($this->paths); - for($i = 0,$paths_len; $i < $paths_len; $i++ ){ - $this->paths[$i] = $visitor->visitArray($this->paths[$i]); - } - }elseif( $this->selectors ){ - $this->selectors = $visitor->visitArray($this->selectors); - } - - if( $this->rules ){ - $this->rules = $visitor->visitArray($this->rules); - } - } - - public function compile($env){ - - $ruleset = $this->PrepareRuleset($env); - - - // Store the frames around mixin definitions, - // so they can be evaluated like closures when the time comes. - $rsRuleCnt = count_if_countable($ruleset->rules); - for( $i = 0; $i < $rsRuleCnt; $i++ ){ - if( $ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset ){ - $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env); - } - } - - $mediaBlockCount = 0; - if( $env instanceof Less_Environment ){ - $mediaBlockCount = count_if_countable($env->mediaBlocks); - } - - // Evaluate mixin calls. - $this->EvalMixinCalls( $ruleset, $env, $rsRuleCnt ); - - - // Evaluate everything else - for( $i=0; $i<$rsRuleCnt; $i++ ){ - if(! ($ruleset->rules[$i] instanceof Less_Tree_Mixin_Definition || $ruleset->rules[$i] instanceof Less_Tree_DetachedRuleset) ){ - $ruleset->rules[$i] = $ruleset->rules[$i]->compile($env); - } - } - - // Evaluate everything else - for( $i=0; $i<$rsRuleCnt; $i++ ){ - $rule = $ruleset->rules[$i]; - - // for rulesets, check if it is a css guard and can be removed - if( $rule instanceof Less_Tree_Ruleset && $rule->selectors && count_if_countable($rule->selectors) === 1 ){ - - // check if it can be folded in (e.g. & where) - if( $rule->selectors[0]->isJustParentSelector() ){ - array_splice($ruleset->rules,$i--,1); - $rsRuleCnt--; - - for($j = 0; $j < count_if_countable($rule->rules); $j++ ){ - $subRule = $rule->rules[$j]; - if( !($subRule instanceof Less_Tree_Rule) || !$subRule->variable ){ - array_splice($ruleset->rules, ++$i, 0, array($subRule)); - $rsRuleCnt++; - } - } - - } - } - } - - - // Pop the stack - $env->shiftFrame(); - - if ($mediaBlockCount) { - $len = count_if_countable($env->mediaBlocks); - for($i = $mediaBlockCount; $i < $len; $i++ ){ - $env->mediaBlocks[$i]->bubbleSelectors($ruleset->selectors); - } - } - - return $ruleset; - } - - /** - * Compile Less_Tree_Mixin_Call objects - * - * @param Less_Tree_Ruleset $ruleset - * @param integer $rsRuleCnt - */ - private function EvalMixinCalls( $ruleset, $env, &$rsRuleCnt ){ - for($i=0; $i < $rsRuleCnt; $i++){ - $rule = $ruleset->rules[$i]; - - if( $rule instanceof Less_Tree_Mixin_Call ){ - $rule = $rule->compile($env); - - $temp = array(); - foreach($rule as $r){ - if( ($r instanceof Less_Tree_Rule) && $r->variable ){ - // do not pollute the scope if the variable is - // already there. consider returning false here - // but we need a way to "return" variable from mixins - if( !$ruleset->variable($r->name) ){ - $temp[] = $r; - } - }else{ - $temp[] = $r; - } - } - $temp_count = count_if_countable($temp)-1; - array_splice($ruleset->rules, $i, 1, $temp); - $rsRuleCnt += $temp_count; - $i += $temp_count; - $ruleset->resetCache(); - - }elseif( $rule instanceof Less_Tree_RulesetCall ){ - - $rule = $rule->compile($env); - $rules = array(); - foreach($rule->rules as $r){ - if( ($r instanceof Less_Tree_Rule) && $r->variable ){ - continue; - } - $rules[] = $r; - } - - array_splice($ruleset->rules, $i, 1, $rules); - $temp_count = count_if_countable($rules); - $rsRuleCnt += $temp_count - 1; - $i += $temp_count-1; - $ruleset->resetCache(); - } - - } - } - - - /** - * Compile the selectors and create a new ruleset object for the compile() method - * - */ - private function PrepareRuleset($env){ - - $hasOnePassingSelector = false; - $selectors = array(); - if( $this->selectors ){ - Less_Tree_DefaultFunc::error("it is currently only allowed in parametric mixin guards,"); - - foreach($this->selectors as $s){ - $selector = $s->compile($env); - $selectors[] = $selector; - if( $selector->evaldCondition ){ - $hasOnePassingSelector = true; - } - } - - Less_Tree_DefaultFunc::reset(); - } else { - $hasOnePassingSelector = true; - } - - if( $this->rules && $hasOnePassingSelector ){ - $rules = $this->rules; - }else{ - $rules = array(); - } - - $ruleset = new Less_Tree_Ruleset($selectors, $rules, $this->strictImports); - - $ruleset->originalRuleset = $this->ruleset_id; - - $ruleset->root = $this->root; - $ruleset->firstRoot = $this->firstRoot; - $ruleset->allowImports = $this->allowImports; - - - // push the current ruleset to the frames stack - $env->unshiftFrame($ruleset); - - - // Evaluate imports - if( $ruleset->root || $ruleset->allowImports || !$ruleset->strictImports ){ - $ruleset->evalImports($env); - } - - return $ruleset; - } - - function evalImports($env) { - - $rules_len = count_if_countable($this->rules); - for($i=0; $i < $rules_len; $i++){ - $rule = $this->rules[$i]; - - if( $rule instanceof Less_Tree_Import ){ - $rules = $rule->compile($env); - if( is_array($rules) ){ - array_splice($this->rules, $i, 1, $rules); - $temp_count = count_if_countable($rules)-1; - $i += $temp_count; - $rules_len += $temp_count; - }else{ - array_splice($this->rules, $i, 1, array($rules)); - } - - $this->resetCache(); - } - } - } - - function makeImportant(){ - - $important_rules = array(); - foreach($this->rules as $rule){ - if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_Ruleset ){ - $important_rules[] = $rule->makeImportant(); - }else{ - $important_rules[] = $rule; - } - } - - return new Less_Tree_Ruleset($this->selectors, $important_rules, $this->strictImports ); - } - - public function matchArgs($args){ - return !$args; - } - - // lets you call a css selector with a guard - public function matchCondition( $args, $env ){ - $lastSelector = end($this->selectors); - - if( !$lastSelector->evaldCondition ){ - return false; - } - if( $lastSelector->condition && !$lastSelector->condition->compile( $env->copyEvalEnv( $env->frames ) ) ){ - return false; - } - return true; - } - - function resetCache(){ - $this->_rulesets = null; - $this->_variables = null; - $this->lookups = array(); - } - - public function variables(){ - $this->_variables = array(); - foreach( $this->rules as $r){ - if ($r instanceof Less_Tree_Rule && $r->variable === true) { - $this->_variables[$r->name] = $r; - } - } - } - - public function variable($name){ - - if( is_null($this->_variables) ){ - $this->variables(); - } - return isset($this->_variables[$name]) ? $this->_variables[$name] : null; - } - - public function find( $selector, $self = null ){ - - $key = implode(' ',$selector->_oelements); - - if( !isset($this->lookups[$key]) ){ - - if( !$self ){ - $self = $this->ruleset_id; - } - - $this->lookups[$key] = array(); - - $first_oelement = $selector->_oelements[0]; - - foreach($this->rules as $rule){ - if( $rule instanceof Less_Tree_Ruleset && $rule->ruleset_id != $self ){ - - if( isset($rule->first_oelements[$first_oelement]) ){ - - foreach( $rule->selectors as $ruleSelector ){ - $match = $selector->match($ruleSelector); - if( $match ){ - if( $selector->elements_len > $match ){ - $this->lookups[$key] = array_merge($this->lookups[$key], $rule->find( new Less_Tree_Selector(array_slice($selector->elements, $match)), $self)); - } else { - $this->lookups[$key][] = $rule; - } - break; - } - } - } - } - } - } - - return $this->lookups[$key]; - } - - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - - if( !$this->root ){ - Less_Environment::$tabLevel++; - } - - $tabRuleStr = $tabSetStr = ''; - if( !Less_Parser::$options['compress'] ){ - if( Less_Environment::$tabLevel ){ - $tabRuleStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel ); - $tabSetStr = "\n".str_repeat( ' ' , Less_Environment::$tabLevel-1 ); - }else{ - $tabSetStr = $tabRuleStr = "\n"; - } - } - - - $ruleNodes = array(); - $rulesetNodes = array(); - foreach($this->rules as $rule){ - - $class = get_class($rule); - if( ($class === 'Less_Tree_Media') || ($class === 'Less_Tree_Directive') || ($this->root && $class === 'Less_Tree_Comment') || ($class === 'Less_Tree_Ruleset' && $rule->rules) ){ - $rulesetNodes[] = $rule; - }else{ - $ruleNodes[] = $rule; - } - } - - // If this is the root node, we don't render - // a selector, or {}. - if( !$this->root ){ - - /* - debugInfo = tree.debugInfo(env, this, tabSetStr); - - if (debugInfo) { - output.add(debugInfo); - output.add(tabSetStr); - } - */ - - $paths_len = count_if_countable($this->paths); - for( $i = 0; $i < $paths_len; $i++ ){ - $path = $this->paths[$i]; - $firstSelector = true; - - foreach($path as $p){ - $p->genCSS( $output, $firstSelector ); - $firstSelector = false; - } - - if( $i + 1 < $paths_len ){ - $output->add( ',' . $tabSetStr ); - } - } - - $output->add( (Less_Parser::$options['compress'] ? '{' : " {") . $tabRuleStr ); - } - - // Compile rules and rulesets - $ruleNodes_len = count_if_countable($ruleNodes); - $rulesetNodes_len = count_if_countable($rulesetNodes); - for( $i = 0; $i < $ruleNodes_len; $i++ ){ - $rule = $ruleNodes[$i]; - - // @page{ directive ends up with root elements inside it, a mix of rules and rulesets - // In this instance we do not know whether it is the last property - if( $i + 1 === $ruleNodes_len && (!$this->root || $rulesetNodes_len === 0 || $this->firstRoot ) ){ - Less_Environment::$lastRule = true; - } - - $rule->genCSS( $output ); - - if( !Less_Environment::$lastRule ){ - $output->add( $tabRuleStr ); - }else{ - Less_Environment::$lastRule = false; - } - } - - if( !$this->root ){ - $output->add( $tabSetStr . '}' ); - Less_Environment::$tabLevel--; - } - - $firstRuleset = true; - $space = ($this->root ? $tabRuleStr : $tabSetStr); - for( $i = 0; $i < $rulesetNodes_len; $i++ ){ - - if( $ruleNodes_len && $firstRuleset ){ - $output->add( $space ); - }elseif( !$firstRuleset ){ - $output->add( $space ); - } - $firstRuleset = false; - $rulesetNodes[$i]->genCSS( $output); - } - - if( !Less_Parser::$options['compress'] && $this->firstRoot ){ - $output->add( "\n" ); - } - - } - - - function markReferenced(){ - if( !$this->selectors ){ - return; - } - foreach($this->selectors as $selector){ - $selector->markReferenced(); - } - } - - public function joinSelectors( $context, $selectors ){ - $paths = array(); - if( is_array($selectors) ){ - foreach($selectors as $selector) { - $this->joinSelector( $paths, $context, $selector); - } - } - return $paths; - } - - public function joinSelector( &$paths, $context, $selector){ - - $hasParentSelector = false; - - foreach($selector->elements as $el) { - if( $el->value === '&') { - $hasParentSelector = true; - } - } - - if( !$hasParentSelector ){ - if( $context ){ - foreach($context as $context_el){ - $paths[] = array_merge($context_el, array($selector) ); - } - }else { - $paths[] = array($selector); - } - return; - } - - - // The paths are [[Selector]] - // The first list is a list of comma seperated selectors - // The inner list is a list of inheritance seperated selectors - // e.g. - // .a, .b { - // .c { - // } - // } - // == [[.a] [.c]] [[.b] [.c]] - // - - // the elements from the current selector so far - $currentElements = array(); - // the current list of new selectors to add to the path. - // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors - // by the parents - $newSelectors = array(array()); - - - foreach( $selector->elements as $el){ - - // non parent reference elements just get added - if( $el->value !== '&' ){ - $currentElements[] = $el; - } else { - // the new list of selectors to add - $selectorsMultiplied = array(); - - // merge the current list of non parent selector elements - // on to the current list of selectors to add - if( $currentElements ){ - $this->mergeElementsOnToSelectors( $currentElements, $newSelectors); - } - - // loop through our current selectors - foreach($newSelectors as $sel){ - - // if we don't have any parent paths, the & might be in a mixin so that it can be used - // whether there are parents or not - if( !$context ){ - // the combinator used on el should now be applied to the next element instead so that - // it is not lost - if( $sel ){ - $sel[0]->elements = array_slice($sel[0]->elements,0); - $sel[0]->elements[] = new Less_Tree_Element($el->combinator, '', $el->index, $el->currentFileInfo ); - } - $selectorsMultiplied[] = $sel; - }else { - - // and the parent selectors - foreach($context as $parentSel){ - // We need to put the current selectors - // then join the last selector's elements on to the parents selectors - - // our new selector path - $newSelectorPath = array(); - // selectors from the parent after the join - $afterParentJoin = array(); - $newJoinedSelectorEmpty = true; - - //construct the joined selector - if & is the first thing this will be empty, - // if not newJoinedSelector will be the last set of elements in the selector - if( $sel ){ - $newSelectorPath = $sel; - $lastSelector = array_pop($newSelectorPath); - $newJoinedSelector = $selector->createDerived( array_slice($lastSelector->elements,0) ); - $newJoinedSelectorEmpty = false; - } - else { - $newJoinedSelector = $selector->createDerived(array()); - } - - //put together the parent selectors after the join - if ( count_if_countable($parentSel) > 1) { - $afterParentJoin = array_merge($afterParentJoin, array_slice($parentSel,1) ); - } - - if ( $parentSel ){ - $newJoinedSelectorEmpty = false; - - // join the elements so far with the first part of the parent - $newJoinedSelector->elements[] = new Less_Tree_Element( $el->combinator, $parentSel[0]->elements[0]->value, $el->index, $el->currentFileInfo); - - $newJoinedSelector->elements = array_merge( $newJoinedSelector->elements, array_slice($parentSel[0]->elements, 1) ); - } - - if (!$newJoinedSelectorEmpty) { - // now add the joined selector - $newSelectorPath[] = $newJoinedSelector; - } - - // and the rest of the parent - $newSelectorPath = array_merge($newSelectorPath, $afterParentJoin); - - // add that to our new set of selectors - $selectorsMultiplied[] = $newSelectorPath; - } - } - } - - // our new selectors has been multiplied, so reset the state - $newSelectors = $selectorsMultiplied; - $currentElements = array(); - } - } - - // if we have any elements left over (e.g. .a& .b == .b) - // add them on to all the current selectors - if( $currentElements ){ - $this->mergeElementsOnToSelectors($currentElements, $newSelectors); - } - foreach( $newSelectors as $new_sel){ - if( $new_sel ){ - $paths[] = $new_sel; - } - } - } - - function mergeElementsOnToSelectors( $elements, &$selectors){ - - if( !$selectors ){ - $selectors[] = array( new Less_Tree_Selector($elements) ); - return; - } - - - foreach( $selectors as &$sel){ - - // if the previous thing in sel is a parent this needs to join on to it - if( $sel ){ - $last = count_if_countable($sel)-1; - $sel[$last] = $sel[$last]->createDerived( array_merge($sel[$last]->elements, $elements) ); - }else{ - $sel[] = new Less_Tree_Selector( $elements ); - } - } - } -} - - -/** - * RulesetCall - * - * @package Less - * @subpackage tree - */ -class Less_Tree_RulesetCall extends Less_Tree{ - - public $variable; - public $type = "RulesetCall"; - - public function __construct($variable){ - $this->variable = $variable; - } - - public function accept($visitor) {} - - public function compile( $env ){ - $variable = new Less_Tree_Variable($this->variable); - $detachedRuleset = $variable->compile($env); - return $detachedRuleset->callEval($env); - } -} - - - -/** - * Selector - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Selector extends Less_Tree{ - - public $elements; - public $condition; - public $extendList = array(); - public $_css; - public $index; - public $evaldCondition = false; - public $type = 'Selector'; - public $currentFileInfo = array(); - public $isReferenced; - public $mediaEmpty; - - public $elements_len = 0; - - public $_oelements; - public $_oelements_len; - public $cacheable = true; - - /** - * @param boolean $isReferenced - */ - public function __construct( $elements, $extendList = array() , $condition = null, $index=null, $currentFileInfo=null, $isReferenced=null ){ - - $this->elements = $elements; - $this->elements_len = count_if_countable($elements); - $this->extendList = $extendList; - $this->condition = $condition; - if( $currentFileInfo ){ - $this->currentFileInfo = $currentFileInfo; - } - $this->isReferenced = $isReferenced; - if( !$condition ){ - $this->evaldCondition = true; - } - - $this->CacheElements(); - } - - public function accept($visitor) { - $this->elements = $visitor->visitArray($this->elements); - $this->extendList = $visitor->visitArray($this->extendList); - if( $this->condition ){ - $this->condition = $visitor->visitObj($this->condition); - } - - if( $visitor instanceof Less_Visitor_extendFinder ){ - $this->CacheElements(); - } - } - - public function createDerived( $elements, $extendList = null, $evaldCondition = null ){ - $newSelector = new Less_Tree_Selector( $elements, ($extendList ? $extendList : $this->extendList), null, $this->index, $this->currentFileInfo, $this->isReferenced); - $newSelector->evaldCondition = $evaldCondition ? $evaldCondition : $this->evaldCondition; - return $newSelector; - } - - - public function match( $other ){ - - if( !$other->_oelements || ($this->elements_len < $other->_oelements_len) ){ - return 0; - } - - for( $i = 0; $i < $other->_oelements_len; $i++ ){ - if( $this->elements[$i]->value !== $other->_oelements[$i]) { - return 0; - } - } - - return $other->_oelements_len; // return number of matched elements - } - - - public function CacheElements(){ - - $this->_oelements = array(); - $css = ''; - - foreach($this->elements as $v){ - - $css .= $v->combinator; - if( !$v->value_is_object ){ - $css .= $v->value; - continue; - } - - if( !property_exists($v->value,'value') || !is_string($v->value->value) ){ - $this->cacheable = false; - return; - } - $css .= $v->value->value; - } - - $this->_oelements_len = preg_match_all('/[,&#\.\w-](?:[\w-]|(?:\\\\.))*/', $css, $matches); - if( $this->_oelements_len ){ - $this->_oelements = $matches[0]; - - if( $this->_oelements[0] === '&' ){ - array_shift($this->_oelements); - $this->_oelements_len--; - } - } - } - - public function isJustParentSelector(){ - return !$this->mediaEmpty && - count_if_countable($this->elements) === 1 && - $this->elements[0]->value === '&' && - ($this->elements[0]->combinator === ' ' || $this->elements[0]->combinator === ''); - } - - public function compile($env) { - - $elements = array(); - foreach($this->elements as $el){ - $elements[] = $el->compile($env); - } - - $extendList = array(); - foreach($this->extendList as $el){ - $extendList[] = $el->compile($el); - } - - $evaldCondition = false; - if( $this->condition ){ - $evaldCondition = $this->condition->compile($env); - } - - return $this->createDerived( $elements, $extendList, $evaldCondition ); - } - - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output, $firstSelector = true ){ - - if( !$firstSelector && $this->elements[0]->combinator === "" ){ - $output->add(' ', $this->currentFileInfo, $this->index); - } - - foreach($this->elements as $element){ - $element->genCSS( $output ); - } - } - - public function markReferenced(){ - $this->isReferenced = true; - } - - public function getIsReferenced(){ - return !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] || $this->isReferenced; - } - - public function getIsOutput(){ - return $this->evaldCondition; - } - -} - - -/** - * UnicodeDescriptor - * - * @package Less - * @subpackage tree - */ -class Less_Tree_UnicodeDescriptor extends Less_Tree{ - - public $value; - public $type = 'UnicodeDescriptor'; - - public function __construct($value){ - $this->value = $value; - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( $this->value ); - } - - public function compile(){ - return $this; - } -} - - - -/** - * Unit - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Unit extends Less_Tree{ - - var $numerator = array(); - var $denominator = array(); - public $backupUnit; - public $type = 'Unit'; - - public function __construct($numerator = array(), $denominator = array(), $backupUnit = null ){ - $this->numerator = $numerator; - $this->denominator = $denominator; - $this->backupUnit = $backupUnit; - } - - public function __clone(){ - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - - if( $this->numerator ){ - $output->add( $this->numerator[0] ); - }elseif( $this->denominator ){ - $output->add( $this->denominator[0] ); - }elseif( !Less_Parser::$options['strictUnits'] && $this->backupUnit ){ - $output->add( $this->backupUnit ); - return ; - } - } - - public function toString(){ - $returnStr = implode('*',$this->numerator); - foreach($this->denominator as $d){ - $returnStr .= '/'.$d; - } - return $returnStr; - } - - public function __toString(){ - return $this->toString(); - } - - - /** - * @param Less_Tree_Unit $other - */ - public function compare($other) { - return $this->is( $other->toString() ) ? 0 : -1; - } - - public function is($unitString){ - return $this->toString() === $unitString; - } - - public function isLength(){ - $css = $this->toCSS(); - return !!preg_match('/px|em|%|in|cm|mm|pc|pt|ex/',$css); - } - - public function isAngle() { - return isset( Less_Tree_UnitConversions::$angle[$this->toCSS()] ); - } - - public function isEmpty(){ - return !$this->numerator && !$this->denominator; - } - - public function isSingular() { - return count_if_countable($this->numerator) <= 1 && !$this->denominator; - } - - - public function usedUnits(){ - $result = array(); - - foreach(Less_Tree_UnitConversions::$groups as $groupName){ - $group = Less_Tree_UnitConversions::${$groupName}; - - foreach($this->numerator as $atomicUnit){ - if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){ - $result[$groupName] = $atomicUnit; - } - } - - foreach($this->denominator as $atomicUnit){ - if( isset($group[$atomicUnit]) && !isset($result[$groupName]) ){ - $result[$groupName] = $atomicUnit; - } - } - } - - return $result; - } - - public function cancel(){ - $counter = array(); - $backup = null; - - foreach($this->numerator as $atomicUnit){ - if( !$backup ){ - $backup = $atomicUnit; - } - $counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) + 1; - } - - foreach($this->denominator as $atomicUnit){ - if( !$backup ){ - $backup = $atomicUnit; - } - $counter[$atomicUnit] = ( isset($counter[$atomicUnit]) ? $counter[$atomicUnit] : 0) - 1; - } - - $this->numerator = array(); - $this->denominator = array(); - - foreach($counter as $atomicUnit => $count){ - if( $count > 0 ){ - for( $i = 0; $i < $count; $i++ ){ - $this->numerator[] = $atomicUnit; - } - }elseif( $count < 0 ){ - for( $i = 0; $i < -$count; $i++ ){ - $this->denominator[] = $atomicUnit; - } - } - } - - if( !$this->numerator && !$this->denominator && $backup ){ - $this->backupUnit = $backup; - } - - sort($this->numerator); - sort($this->denominator); - } - - -} - - - -/** - * UnitConversions - * - * @package Less - * @subpackage tree - */ -class Less_Tree_UnitConversions{ - - public static $groups = array('length','duration','angle'); - - public static $length = array( - 'm'=> 1, - 'cm'=> 0.01, - 'mm'=> 0.001, - 'in'=> 0.0254, - 'px'=> 0.000264583, // 0.0254 / 96, - 'pt'=> 0.000352778, // 0.0254 / 72, - 'pc'=> 0.004233333, // 0.0254 / 72 * 12 - ); - - public static $duration = array( - 's'=> 1, - 'ms'=> 0.001 - ); - - public static $angle = array( - 'rad' => 0.1591549430919, // 1/(2*M_PI), - 'deg' => 0.002777778, // 1/360, - 'grad'=> 0.0025, // 1/400, - 'turn'=> 1 - ); - -} - -/** - * Url - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Url extends Less_Tree{ - - public $attrs; - public $value; - public $currentFileInfo; - public $isEvald; - public $type = 'Url'; - - public function __construct($value, $currentFileInfo = null, $isEvald = null){ - $this->value = $value; - $this->currentFileInfo = $currentFileInfo; - $this->isEvald = $isEvald; - } - - public function accept( $visitor ){ - $this->value = $visitor->visitObj($this->value); - } - - /** - * @see Less_Tree::genCSS - */ - public function genCSS( $output ){ - $output->add( 'url(' ); - $this->value->genCSS( $output ); - $output->add( ')' ); - } - - /** - * @param Less_Functions $ctx - */ - public function compile($ctx){ - $val = $this->value->compile($ctx); - - if( !$this->isEvald ){ - // Add the base path if the URL is relative - if( Less_Parser::$options['relativeUrls'] - && $this->currentFileInfo - && is_string($val->value) - && Less_Environment::isPathRelative($val->value) - ){ - $rootpath = $this->currentFileInfo['uri_root']; - if ( !$val->quote ){ - $rootpath = preg_replace('/[\(\)\'"\s]/', '\\$1', $rootpath ); - } - $val->value = $rootpath . $val->value; - } - - $val->value = Less_Environment::normalizePath( $val->value); - } - - // Add cache buster if enabled - if( Less_Parser::$options['urlArgs'] ){ - if( !preg_match('/^\s*data:/',$val->value) ){ - $delimiter = strpos($val->value,'?') === false ? '?' : '&'; - $urlArgs = $delimiter . Less_Parser::$options['urlArgs']; - $hash_pos = strpos($val->value,'#'); - if( $hash_pos !== false ){ - $val->value = substr_replace($val->value,$urlArgs, $hash_pos, 0); - } else { - $val->value .= $urlArgs; - } - } - } - - return new Less_Tree_URL($val, $this->currentFileInfo, true); - } - -} - - -/** - * Value - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Value extends Less_Tree{ - - public $type = 'Value'; - public $value; - - public function __construct($value){ - $this->value = $value; - } - - public function accept($visitor) { - $this->value = $visitor->visitArray($this->value); - } - - public function compile($env){ - - $ret = array(); - $i = 0; - foreach($this->value as $i => $v){ - $ret[] = $v->compile($env); - } - if( $i > 0 ){ - return new Less_Tree_Value($ret); - } - return $ret[0]; - } - - /** - * @see Less_Tree::genCSS - */ - function genCSS( $output ){ - $len = count_if_countable($this->value); - for($i = 0; $i < $len; $i++ ){ - $this->value[$i]->genCSS( $output ); - if( $i+1 < $len ){ - $output->add( Less_Environment::$_outputMap[','] ); - } - } - } - -} - - -/** - * Variable - * - * @package Less - * @subpackage tree - */ -class Less_Tree_Variable extends Less_Tree{ - - public $name; - public $index; - public $currentFileInfo; - public $evaluating = false; - public $type = 'Variable'; - - /** - * @param string $name - */ - public function __construct($name, $index = null, $currentFileInfo = null) { - $this->name = $name; - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - } - - public function compile($env) { - - if( $this->name[1] === '@' ){ - $v = new Less_Tree_Variable(substr($this->name, 1), $this->index + 1, $this->currentFileInfo); - $name = '@' . $v->compile($env)->value; - }else{ - $name = $this->name; - } - - if ($this->evaluating) { - throw new Less_Exception_Compiler("Recursive variable definition for " . $name, null, $this->index, $this->currentFileInfo); - } - - $this->evaluating = true; - - foreach($env->frames as $frame){ - if( $v = $frame->variable($name) ){ - $r = $v->value->compile($env); - $this->evaluating = false; - return $r; - } - } - - throw new Less_Exception_Compiler("variable " . $name . " is undefined in file ".$this->currentFileInfo["filename"], null, $this->index, $this->currentFileInfo); - } - -} - - - -class Less_Tree_Mixin_Call extends Less_Tree{ - - public $selector; - public $arguments; - public $index; - public $currentFileInfo; - - public $important; - public $type = 'MixinCall'; - - /** - * less.js: tree.mixin.Call - * - */ - public function __construct($elements, $args, $index, $currentFileInfo, $important = false){ - $this->selector = new Less_Tree_Selector($elements); - $this->arguments = $args; - $this->index = $index; - $this->currentFileInfo = $currentFileInfo; - $this->important = $important; - } - - //function accept($visitor){ - // $this->selector = $visitor->visit($this->selector); - // $this->arguments = $visitor->visit($this->arguments); - //} - - - public function compile($env){ - - $rules = array(); - $match = false; - $isOneFound = false; - $candidates = array(); - $defaultUsed = false; - $conditionResult = array(); - - $args = array(); - foreach($this->arguments as $a){ - $args[] = array('name'=> $a['name'], 'value' => $a['value']->compile($env) ); - } - - foreach($env->frames as $frame){ - - $mixins = $frame->find($this->selector); - - if( !$mixins ){ - continue; - } - - $isOneFound = true; - $defNone = 0; - $defTrue = 1; - $defFalse = 2; - - // To make `default()` function independent of definition order we have two "subpasses" here. - // At first we evaluate each guard *twice* (with `default() == true` and `default() == false`), - // and build candidate list with corresponding flags. Then, when we know all possible matches, - // we make a final decision. - - $mixins_len = count_if_countable($mixins); - for( $m = 0; $m < $mixins_len; $m++ ){ - $mixin = $mixins[$m]; - - if( $this->IsRecursive( $env, $mixin ) ){ - continue; - } - - if( $mixin->matchArgs($args, $env) ){ - - $candidate = array('mixin' => $mixin, 'group' => $defNone); - - if( $mixin instanceof Less_Tree_Ruleset ){ - - for( $f = 0; $f < 2; $f++ ){ - Less_Tree_DefaultFunc::value($f); - $conditionResult[$f] = $mixin->matchCondition( $args, $env); - } - if( $conditionResult[0] || $conditionResult[1] ){ - if( $conditionResult[0] != $conditionResult[1] ){ - $candidate['group'] = $conditionResult[1] ? $defTrue : $defFalse; - } - - $candidates[] = $candidate; - } - }else{ - $candidates[] = $candidate; - } - - $match = true; - } - } - - Less_Tree_DefaultFunc::reset(); - - - $count = array(0, 0, 0); - for( $m = 0; $m < count_if_countable($candidates); $m++ ){ - $count[ $candidates[$m]['group'] ]++; - } - - if( $count[$defNone] > 0 ){ - $defaultResult = $defFalse; - } else { - $defaultResult = $defTrue; - if( ($count[$defTrue] + $count[$defFalse]) > 1 ){ - throw new Exception( 'Ambiguous use of `default()` found when matching for `'. $this->format($args) + '`' ); - } - } - - - $candidates_length = count_if_countable($candidates); - $length_1 = ($candidates_length == 1); - - for( $m = 0; $m < $candidates_length; $m++){ - $candidate = $candidates[$m]['group']; - if( ($candidate === $defNone) || ($candidate === $defaultResult) ){ - try{ - $mixin = $candidates[$m]['mixin']; - if( !($mixin instanceof Less_Tree_Mixin_Definition) ){ - $mixin = new Less_Tree_Mixin_Definition('', array(), $mixin->rules, null, false); - $mixin->originalRuleset = $mixins[$m]->originalRuleset; - } - $rules = array_merge($rules, $mixin->evalCall($env, $args, $this->important)->rules); - } catch (Exception $e) { - //throw new Less_Exception_Compiler($e->getMessage(), $e->index, null, $this->currentFileInfo['filename']); - throw new Less_Exception_Compiler($e->getMessage(), null, null, $this->currentFileInfo); - } - } - } - - if( $match ){ - if( !$this->currentFileInfo || !isset($this->currentFileInfo['reference']) || !$this->currentFileInfo['reference'] ){ - Less_Tree::ReferencedArray($rules); - } - - return $rules; - } - } - - if( $isOneFound ){ - throw new Less_Exception_Compiler('No matching definition was found for `'.$this->Format( $args ).'`', null, $this->index, $this->currentFileInfo); - - }else{ - throw new Less_Exception_Compiler(trim($this->selector->toCSS()) . " is undefined in ".$this->currentFileInfo['filename'], null, $this->index); - } - - } - - /** - * Format the args for use in exception messages - * - */ - private function Format($args){ - $message = array(); - if( $args ){ - foreach($args as $a){ - $argValue = ''; - if( $a['name'] ){ - $argValue += $a['name']+':'; - } - if( is_object($a['value']) ){ - $argValue += $a['value']->toCSS(); - }else{ - $argValue += '???'; - } - $message[] = $argValue; - } - } - return implode(', ',$message); - } - - - /** - * Are we in a recursive mixin call? - * - * @return bool - */ - private function IsRecursive( $env, $mixin ){ - - foreach($env->frames as $recur_frame){ - if( !($mixin instanceof Less_Tree_Mixin_Definition) ){ - - if( $mixin === $recur_frame ){ - return true; - } - - if( isset($recur_frame->originalRuleset) && $mixin->ruleset_id === $recur_frame->originalRuleset ){ - return true; - } - } - } - - return false; - } - -} - - - - -class Less_Tree_Mixin_Definition extends Less_Tree_Ruleset{ - public $name; - public $selectors; - public $params; - public $arity = 0; - public $rules; - public $lookups = array(); - public $required = 0; - public $frames = array(); - public $condition; - public $variadic; - public $type = 'MixinDefinition'; - - - // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition - public function __construct($name, $params, $rules, $condition, $variadic = false, $frames = array() ){ - $this->name = $name; - $this->selectors = array(new Less_Tree_Selector(array( new Less_Tree_Element(null, $name)))); - - $this->params = $params; - $this->condition = $condition; - $this->variadic = $variadic; - $this->rules = $rules; - - if( $params ){ - $this->arity = count_if_countable($params); - foreach( $params as $p ){ - if (! isset($p['name']) || ($p['name'] && !isset($p['value']))) { - $this->required++; - } - } - } - - $this->frames = $frames; - $this->SetRulesetIndex(); - } - - - - //function accept( $visitor ){ - // $this->params = $visitor->visit($this->params); - // $this->rules = $visitor->visit($this->rules); - // $this->condition = $visitor->visit($this->condition); - //} - - - public function toCSS(){ - return ''; - } - - // less.js : /lib/less/tree/mixin.js : tree.mixin.Definition.evalParams - public function compileParams($env, $mixinFrames, $args = array() , &$evaldArguments = array() ){ - $frame = new Less_Tree_Ruleset(null, array()); - $params = $this->params; - $mixinEnv = null; - $argsLength = 0; - - if( $args ){ - $argsLength = count_if_countable($args); - for($i = 0; $i < $argsLength; $i++ ){ - $arg = $args[$i]; - - if( $arg && $arg['name'] ){ - $isNamedFound = false; - - foreach($params as $j => $param){ - if( !isset($evaldArguments[$j]) && $arg['name'] === $params[$j]['name']) { - $evaldArguments[$j] = $arg['value']->compile($env); - array_unshift($frame->rules, new Less_Tree_Rule( $arg['name'], $arg['value']->compile($env) ) ); - $isNamedFound = true; - break; - } - } - if ($isNamedFound) { - array_splice($args, $i, 1); - $i--; - $argsLength--; - continue; - } else { - throw new Less_Exception_Compiler("Named argument for " . $this->name .' '.$args[$i]['name'] . ' not found'); - } - } - } - } - - $argIndex = 0; - foreach($params as $i => $param){ - - if ( isset($evaldArguments[$i]) ){ continue; } - - $arg = null; - if( isset($args[$argIndex]) ){ - $arg = $args[$argIndex]; - } - - if (isset($param['name']) && $param['name']) { - - if( isset($param['variadic']) ){ - $varargs = array(); - for ($j = $argIndex; $j < $argsLength; $j++) { - $varargs[] = $args[$j]['value']->compile($env); - } - $expression = new Less_Tree_Expression($varargs); - array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $expression->compile($env))); - }else{ - $val = ($arg && $arg['value']) ? $arg['value'] : false; - - if ($val) { - $val = $val->compile($env); - } else if ( isset($param['value']) ) { - - if( !$mixinEnv ){ - $mixinEnv = new Less_Environment(); - $mixinEnv->frames = array_merge( array($frame), $mixinFrames); - } - - $val = $param['value']->compile($mixinEnv); - $frame->resetCache(); - } else { - throw new Less_Exception_Compiler("Wrong number of arguments for " . $this->name . " (" . $argsLength . ' for ' . $this->arity . ")"); - } - - array_unshift($frame->rules, new Less_Tree_Rule($param['name'], $val)); - $evaldArguments[$i] = $val; - } - } - - if ( isset($param['variadic']) && $args) { - for ($j = $argIndex; $j < $argsLength; $j++) { - $evaldArguments[$j] = $args[$j]['value']->compile($env); - } - } - $argIndex++; - } - - ksort($evaldArguments); - $evaldArguments = array_values($evaldArguments); - - return $frame; - } - - public function compile($env) { - if( $this->frames ){ - return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $this->frames ); - } - return new Less_Tree_Mixin_Definition($this->name, $this->params, $this->rules, $this->condition, $this->variadic, $env->frames ); - } - - public function evalCall($env, $args = NULL, $important = NULL) { - - Less_Environment::$mixin_stack++; - - $_arguments = array(); - - if( $this->frames ){ - $mixinFrames = array_merge($this->frames, $env->frames); - }else{ - $mixinFrames = $env->frames; - } - - $frame = $this->compileParams($env, $mixinFrames, $args, $_arguments); - - $ex = new Less_Tree_Expression($_arguments); - array_unshift($frame->rules, new Less_Tree_Rule('@arguments', $ex->compile($env))); - - - $ruleset = new Less_Tree_Ruleset(null, $this->rules); - $ruleset->originalRuleset = $this->ruleset_id; - - - $ruleSetEnv = new Less_Environment(); - $ruleSetEnv->frames = array_merge( array($this, $frame), $mixinFrames ); - $ruleset = $ruleset->compile( $ruleSetEnv ); - - if( $important ){ - $ruleset = $ruleset->makeImportant(); - } - - Less_Environment::$mixin_stack--; - - return $ruleset; - } - - - public function matchCondition($args, $env) { - - if( !$this->condition ){ - return true; - } - - // set array to prevent error on array_merge - if(!is_array($this->frames)) { - $this->frames = array(); - } - - $frame = $this->compileParams($env, array_merge($this->frames,$env->frames), $args ); - - $compile_env = new Less_Environment(); - $compile_env->frames = array_merge( - array($frame) // the parameter variables - , $this->frames // the parent namespace/mixin frames - , $env->frames // the current environment frames - ); - - $compile_env->functions = $env->functions; - - return (bool)$this->condition->compile($compile_env); - } - - public function matchArgs($args, $env = NULL){ - $argsLength = count_if_countable($args); - - if( !$this->variadic ){ - if( $argsLength < $this->required ){ - return false; - } - if( $argsLength > count_if_countable($this->params) ){ - return false; - } - }else{ - if( $argsLength < ($this->required - 1)){ - return false; - } - } - - $len = min($argsLength, $this->arity); - - for( $i = 0; $i < $len; $i++ ){ - if( !isset($this->params[$i]['name']) && !isset($this->params[$i]['variadic']) ){ - if( $args[$i]['value']->compile($env)->toCSS() != $this->params[$i]['value']->compile($env)->toCSS() ){ - return false; - } - } - } - - return true; - } - -} - - -/** - * Extend Finder Visitor - * - * @package Less - * @subpackage visitor - */ -class Less_Visitor_extendFinder extends Less_Visitor{ - - public $contexts = array(); - public $allExtendsStack; - public $foundExtends; - - public function __construct(){ - $this->contexts = array(); - $this->allExtendsStack = array(array()); - parent::__construct(); - } - - /** - * @param Less_Tree_Ruleset $root - */ - public function run($root){ - $root = $this->visitObj($root); - $root->allExtends =& $this->allExtendsStack[0]; - return $root; - } - - public function visitRule($ruleNode, &$visitDeeper ){ - $visitDeeper = false; - } - - public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){ - $visitDeeper = false; - } - - public function visitRuleset($rulesetNode){ - - if( $rulesetNode->root ){ - return; - } - - $allSelectorsExtendList = array(); - - // get &:extend(.a); rules which apply to all selectors in this ruleset - if( $rulesetNode->rules ){ - foreach($rulesetNode->rules as $rule){ - if( $rule instanceof Less_Tree_Extend ){ - $allSelectorsExtendList[] = $rule; - $rulesetNode->extendOnEveryPath = true; - } - } - } - - - // now find every selector and apply the extends that apply to all extends - // and the ones which apply to an individual extend - foreach($rulesetNode->paths as $selectorPath){ - $selector = end($selectorPath); //$selectorPath[ count($selectorPath)-1]; - - $j = 0; - foreach($selector->extendList as $extend){ - $this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j); - } - foreach($allSelectorsExtendList as $extend){ - $this->allExtendsStackPush($rulesetNode, $selectorPath, $extend, $j); - } - } - - $this->contexts[] = $rulesetNode->selectors; - } - - public function allExtendsStackPush($rulesetNode, $selectorPath, $extend, &$j){ - $this->foundExtends = true; - $extend = clone $extend; - $extend->findSelfSelectors( $selectorPath ); - $extend->ruleset = $rulesetNode; - if( $j === 0 ){ - $extend->firstExtendOnThisSelectorPath = true; - } - - $end_key = count_if_countable($this->allExtendsStack)-1; - $this->allExtendsStack[$end_key][] = $extend; - $j++; - } - - - public function visitRulesetOut( $rulesetNode ){ - if( !is_object($rulesetNode) || !$rulesetNode->root ){ - array_pop($this->contexts); - } - } - - public function visitMedia( $mediaNode ){ - $mediaNode->allExtends = array(); - $this->allExtendsStack[] =& $mediaNode->allExtends; - } - - public function visitMediaOut(){ - array_pop($this->allExtendsStack); - } - - public function visitDirective( $directiveNode ){ - $directiveNode->allExtends = array(); - $this->allExtendsStack[] =& $directiveNode->allExtends; - } - - public function visitDirectiveOut(){ - array_pop($this->allExtendsStack); - } -} - - - - -/* -class Less_Visitor_import extends Less_VisitorReplacing{ - - public $_visitor; - public $_importer; - public $importCount; - - function __construct( $evalEnv ){ - $this->env = $evalEnv; - $this->importCount = 0; - parent::__construct(); - } - - - function run( $root ){ - $root = $this->visitObj($root); - $this->isFinished = true; - - //if( $this->importCount === 0) { - // $this->_finish(); - //} - } - - function visitImport($importNode, &$visitDeeper ){ - $importVisitor = $this; - $inlineCSS = $importNode->options['inline']; - - if( !$importNode->css || $inlineCSS ){ - $evaldImportNode = $importNode->compileForImport($this->env); - - if( $evaldImportNode && (!$evaldImportNode->css || $inlineCSS) ){ - $importNode = $evaldImportNode; - $this->importCount++; - $env = clone $this->env; - - if( (isset($importNode->options['multiple']) && $importNode->options['multiple']) ){ - $env->importMultiple = true; - } - - //get path & uri - $path_and_uri = null; - if( is_callable(Less_Parser::$options['import_callback']) ){ - $path_and_uri = call_user_func(Less_Parser::$options['import_callback'],$importNode); - } - - if( !$path_and_uri ){ - $path_and_uri = $importNode->PathAndUri(); - } - - if( $path_and_uri ){ - list($full_path, $uri) = $path_and_uri; - }else{ - $full_path = $uri = $importNode->getPath(); - } - - - //import once - if( $importNode->skip( $full_path, $env) ){ - return array(); - } - - if( $importNode->options['inline'] ){ - //todo needs to reference css file not import - //$contents = new Less_Tree_Anonymous($importNode->root, 0, array('filename'=>$importNode->importedFilename), true ); - - Less_Parser::AddParsedFile($full_path); - $contents = new Less_Tree_Anonymous( file_get_contents($full_path), 0, array(), true ); - - if( $importNode->features ){ - return new Less_Tree_Media( array($contents), $importNode->features->value ); - } - - return array( $contents ); - } - - - // css ? - if( $importNode->css ){ - $features = ( $importNode->features ? $importNode->features->compile($env) : null ); - return new Less_Tree_Import( $importNode->compilePath( $env), $features, $importNode->options, $this->index); - } - - return $importNode->ParseImport( $full_path, $uri, $env ); - } - - } - - $visitDeeper = false; - return $importNode; - } - - - function visitRule( $ruleNode, &$visitDeeper ){ - $visitDeeper = false; - return $ruleNode; - } - - function visitDirective($directiveNode, $visitArgs){ - array_unshift($this->env->frames,$directiveNode); - return $directiveNode; - } - - function visitDirectiveOut($directiveNode) { - array_shift($this->env->frames); - } - - function visitMixinDefinition($mixinDefinitionNode, $visitArgs) { - array_unshift($this->env->frames,$mixinDefinitionNode); - return $mixinDefinitionNode; - } - - function visitMixinDefinitionOut($mixinDefinitionNode) { - array_shift($this->env->frames); - } - - function visitRuleset($rulesetNode, $visitArgs) { - array_unshift($this->env->frames,$rulesetNode); - return $rulesetNode; - } - - function visitRulesetOut($rulesetNode) { - array_shift($this->env->frames); - } - - function visitMedia($mediaNode, $visitArgs) { - array_unshift($this->env->frames, $mediaNode->ruleset); - return $mediaNode; - } - - function visitMediaOut($mediaNode) { - array_shift($this->env->frames); - } - -} -*/ - - - - -/** - * Join Selector Visitor - * - * @package Less - * @subpackage visitor - */ -class Less_Visitor_joinSelector extends Less_Visitor{ - - public $contexts = array( array() ); - - /** - * @param Less_Tree_Ruleset $root - */ - public function run( $root ){ - return $this->visitObj($root); - } - - public function visitRule( $ruleNode, &$visitDeeper ){ - $visitDeeper = false; - } - - public function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){ - $visitDeeper = false; - } - - public function visitRuleset( $rulesetNode ){ - - $paths = array(); - - if( !$rulesetNode->root ){ - $selectors = array(); - - if( $rulesetNode->selectors && $rulesetNode->selectors ){ - foreach($rulesetNode->selectors as $selector){ - if( $selector->getIsOutput() ){ - $selectors[] = $selector; - } - } - } - - if( !$selectors ){ - $rulesetNode->selectors = null; - $rulesetNode->rules = null; - }else{ - $context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1]; - $paths = $rulesetNode->joinSelectors( $context, $selectors); - } - - $rulesetNode->paths = $paths; - } - - $this->contexts[] = $paths; //different from less.js. Placed after joinSelectors() so that $this->contexts will get correct $paths - } - - public function visitRulesetOut(){ - array_pop($this->contexts); - } - - public function visitMedia($mediaNode) { - $context = end($this->contexts); //$context = $this->contexts[ count($this->contexts) - 1]; - - if( !count_if_countable($context) || (is_object($context[0]) && $context[0]->multiMedia) ){ - $mediaNode->rules[0]->root = true; - } - } - -} - - - -/** - * Process Extends Visitor - * - * @package Less - * @subpackage visitor - */ -class Less_Visitor_processExtends extends Less_Visitor{ - - public $allExtendsStack; - - /** - * @param Less_Tree_Ruleset $root - */ - public function run( $root ){ - $extendFinder = new Less_Visitor_extendFinder(); - $extendFinder->run( $root ); - if( !$extendFinder->foundExtends){ - return $root; - } - - $root->allExtends = $this->doExtendChaining( $root->allExtends, $root->allExtends); - - $this->allExtendsStack = array(); - $this->allExtendsStack[] = &$root->allExtends; - - return $this->visitObj( $root ); - } - - private function doExtendChaining( $extendsList, $extendsListTarget, $iterationCount = 0){ - // - // chaining is different from normal extension.. if we extend an extend then we are not just copying, altering and pasting - // the selector we would do normally, but we are also adding an extend with the same target selector - // this means this new extend can then go and alter other extends - // - // this method deals with all the chaining work - without it, extend is flat and doesn't work on other extend selectors - // this is also the most expensive.. and a match on one selector can cause an extension of a selector we had already processed if - // we look at each selector at a time, as is done in visitRuleset - - $extendsToAdd = array(); - - - //loop through comparing every extend with every target extend. - // a target extend is the one on the ruleset we are looking at copy/edit/pasting in place - // e.g. .a:extend(.b) {} and .b:extend(.c) {} then the first extend extends the second one - // and the second is the target. - // the seperation into two lists allows us to process a subset of chains with a bigger set, as is the - // case when processing media queries - for( $extendIndex = 0, $extendsList_len = count_if_countable($extendsList); $extendIndex < $extendsList_len; $extendIndex++ ){ - for( $targetExtendIndex = 0; $targetExtendIndex < count_if_countable($extendsListTarget); $targetExtendIndex++ ){ - - $extend = $extendsList[$extendIndex]; - $targetExtend = $extendsListTarget[$targetExtendIndex]; - - // look for circular references - if( in_array($targetExtend->object_id, $extend->parent_ids,true) ){ - continue; - } - - // find a match in the target extends self selector (the bit before :extend) - $selectorPath = array( $targetExtend->selfSelectors[0] ); - $matches = $this->findMatch( $extend, $selectorPath); - - - if( $matches ){ - - // we found a match, so for each self selector.. - foreach($extend->selfSelectors as $selfSelector ){ - - - // process the extend as usual - $newSelector = $this->extendSelector( $matches, $selectorPath, $selfSelector); - - // but now we create a new extend from it - $newExtend = new Less_Tree_Extend( $targetExtend->selector, $targetExtend->option, 0); - $newExtend->selfSelectors = $newSelector; - - // add the extend onto the list of extends for that selector - end($newSelector)->extendList = array($newExtend); - //$newSelector[ count($newSelector)-1]->extendList = array($newExtend); - - // record that we need to add it. - $extendsToAdd[] = $newExtend; - $newExtend->ruleset = $targetExtend->ruleset; - - //remember its parents for circular references - $newExtend->parent_ids = array_merge($newExtend->parent_ids,$targetExtend->parent_ids,$extend->parent_ids); - - // only process the selector once.. if we have :extend(.a,.b) then multiple - // extends will look at the same selector path, so when extending - // we know that any others will be duplicates in terms of what is added to the css - if( $targetExtend->firstExtendOnThisSelectorPath ){ - $newExtend->firstExtendOnThisSelectorPath = true; - $targetExtend->ruleset->paths[] = $newSelector; - } - } - } - } - } - - if( $extendsToAdd ){ - // try to detect circular references to stop a stack overflow. - // may no longer be needed. $this->extendChainCount++; - if( $iterationCount > 100) { - - try{ - $selectorOne = $extendsToAdd[0]->selfSelectors[0]->toCSS(); - $selectorTwo = $extendsToAdd[0]->selector->toCSS(); - }catch(Exception $e){ - $selectorOne = "{unable to calculate}"; - $selectorTwo = "{unable to calculate}"; - } - - throw new Less_Exception_Parser("extend circular reference detected. One of the circular extends is currently:"+$selectorOne+":extend(" + $selectorTwo+")"); - } - - // now process the new extends on the existing rules so that we can handle a extending b extending c ectending d extending e... - $extendsToAdd = $this->doExtendChaining( $extendsToAdd, $extendsListTarget, $iterationCount+1); - } - - return array_merge($extendsList, $extendsToAdd); - } - - - protected function visitRule( $ruleNode, &$visitDeeper ){ - $visitDeeper = false; - } - - protected function visitMixinDefinition( $mixinDefinitionNode, &$visitDeeper ){ - $visitDeeper = false; - } - - protected function visitSelector( $selectorNode, &$visitDeeper ){ - $visitDeeper = false; - } - - protected function visitRuleset($rulesetNode){ - - - if( $rulesetNode->root ){ - return; - } - - $allExtends = end($this->allExtendsStack); - $paths_len = count_if_countable($rulesetNode->paths); - - // look at each selector path in the ruleset, find any extend matches and then copy, find and replace - foreach($allExtends as $allExtend){ - for($pathIndex = 0; $pathIndex < $paths_len; $pathIndex++ ){ - - // extending extends happens initially, before the main pass - if( isset($rulesetNode->extendOnEveryPath) && $rulesetNode->extendOnEveryPath ){ - continue; - } - - $selectorPath = $rulesetNode->paths[$pathIndex]; - - if( end($selectorPath)->extendList ){ - continue; - } - - $this->ExtendMatch( $rulesetNode, $allExtend, $selectorPath); - - } - } - } - - - private function ExtendMatch( $rulesetNode, $extend, $selectorPath ){ - $matches = $this->findMatch($extend, $selectorPath); - - if( $matches ){ - foreach($extend->selfSelectors as $selfSelector ){ - $rulesetNode->paths[] = $this->extendSelector($matches, $selectorPath, $selfSelector); - } - } - } - - - - private function findMatch($extend, $haystackSelectorPath ){ - - - if( !$this->HasMatches($extend, $haystackSelectorPath) ){ - return false; - } - - - // - // look through the haystack selector path to try and find the needle - extend.selector - // returns an array of selector matches that can then be replaced - // - $needleElements = $extend->selector->elements; - $potentialMatches = array(); - $potentialMatches_len = 0; - $potentialMatch = null; - $matches = array(); - - - - // loop through the haystack elements - $haystack_path_len = count_if_countable($haystackSelectorPath); - for($haystackSelectorIndex = 0; $haystackSelectorIndex < $haystack_path_len; $haystackSelectorIndex++ ){ - $hackstackSelector = $haystackSelectorPath[$haystackSelectorIndex]; - - $haystack_elements_len = count_if_countable($hackstackSelector->elements); - for($hackstackElementIndex = 0; $hackstackElementIndex < $haystack_elements_len; $hackstackElementIndex++ ){ - - $haystackElement = $hackstackSelector->elements[$hackstackElementIndex]; - - // if we allow elements before our match we can add a potential match every time. otherwise only at the first element. - if( $extend->allowBefore || ($haystackSelectorIndex === 0 && $hackstackElementIndex === 0) ){ - $potentialMatches[] = array('pathIndex'=> $haystackSelectorIndex, 'index'=> $hackstackElementIndex, 'matched'=> 0, 'initialCombinator'=> $haystackElement->combinator); - $potentialMatches_len++; - } - - for($i = 0; $i < $potentialMatches_len; $i++ ){ - - $potentialMatch = &$potentialMatches[$i]; - $potentialMatch = $this->PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ); - - - // if we are still valid and have finished, test whether we have elements after and whether these are allowed - if( $potentialMatch && $potentialMatch['matched'] === $extend->selector->elements_len ){ - $potentialMatch['finished'] = true; - - if( !$extend->allowAfter && ($hackstackElementIndex+1 < $haystack_elements_len || $haystackSelectorIndex+1 < $haystack_path_len) ){ - $potentialMatch = null; - } - } - - // if null we remove, if not, we are still valid, so either push as a valid match or continue - if( $potentialMatch ){ - if( $potentialMatch['finished'] ){ - $potentialMatch['length'] = $extend->selector->elements_len; - $potentialMatch['endPathIndex'] = $haystackSelectorIndex; - $potentialMatch['endPathElementIndex'] = $hackstackElementIndex + 1; // index after end of match - $potentialMatches = array(); // we don't allow matches to overlap, so start matching again - $potentialMatches_len = 0; - $matches[] = $potentialMatch; - } - continue; - } - - array_splice($potentialMatches, $i, 1); - $potentialMatches_len--; - $i--; - } - } - } - - return $matches; - } - - - // Before going through all the nested loops, lets check to see if a match is possible - // Reduces Bootstrap 3.1 compile time from ~6.5s to ~5.6s - private function HasMatches($extend, $haystackSelectorPath){ - - if( !$extend->selector->cacheable ){ - return true; - } - - $first_el = $extend->selector->_oelements[0]; - - foreach($haystackSelectorPath as $hackstackSelector){ - if( !$hackstackSelector->cacheable ){ - return true; - } - - if( in_array($first_el, $hackstackSelector->_oelements) ){ - return true; - } - } - - return false; - } - - - /** - * @param integer $hackstackElementIndex - */ - private function PotentialMatch( $potentialMatch, $needleElements, $haystackElement, $hackstackElementIndex ){ - - - if( $potentialMatch['matched'] > 0 ){ - - // selectors add " " onto the first element. When we use & it joins the selectors together, but if we don't - // then each selector in haystackSelectorPath has a space before it added in the toCSS phase. so we need to work out - // what the resulting combinator will be - $targetCombinator = $haystackElement->combinator; - if( $targetCombinator === '' && $hackstackElementIndex === 0 ){ - $targetCombinator = ' '; - } - - if( $needleElements[ $potentialMatch['matched'] ]->combinator !== $targetCombinator ){ - return null; - } - } - - // if we don't match, null our match to indicate failure - if( !$this->isElementValuesEqual( $needleElements[$potentialMatch['matched'] ]->value, $haystackElement->value) ){ - return null; - } - - $potentialMatch['finished'] = false; - $potentialMatch['matched']++; - - return $potentialMatch; - } - - - private function isElementValuesEqual( $elementValue1, $elementValue2 ){ - - if( $elementValue1 === $elementValue2 ){ - return true; - } - - if( is_string($elementValue1) || is_string($elementValue2) ) { - return false; - } - - if( $elementValue1 instanceof Less_Tree_Attribute ){ - return $this->isAttributeValuesEqual( $elementValue1, $elementValue2 ); - } - - $elementValue1 = $elementValue1->value; - if( $elementValue1 instanceof Less_Tree_Selector ){ - return $this->isSelectorValuesEqual( $elementValue1, $elementValue2 ); - } - - return false; - } - - - /** - * @param Less_Tree_Selector $elementValue1 - */ - private function isSelectorValuesEqual( $elementValue1, $elementValue2 ){ - - $elementValue2 = $elementValue2->value; - if( !($elementValue2 instanceof Less_Tree_Selector) || $elementValue1->elements_len !== $elementValue2->elements_len ){ - return false; - } - - for( $i = 0; $i < $elementValue1->elements_len; $i++ ){ - - if( $elementValue1->elements[$i]->combinator !== $elementValue2->elements[$i]->combinator ){ - if( $i !== 0 || ($elementValue1->elements[$i]->combinator || ' ') !== ($elementValue2->elements[$i]->combinator || ' ') ){ - return false; - } - } - - if( !$this->isElementValuesEqual($elementValue1->elements[$i]->value, $elementValue2->elements[$i]->value) ){ - return false; - } - } - - return true; - } - - - /** - * @param Less_Tree_Attribute $elementValue1 - */ - private function isAttributeValuesEqual( $elementValue1, $elementValue2 ){ - - if( $elementValue1->op !== $elementValue2->op || $elementValue1->key !== $elementValue2->key ){ - return false; - } - - if( !$elementValue1->value || !$elementValue2->value ){ - if( $elementValue1->value || $elementValue2->value ) { - return false; - } - return true; - } - - $elementValue1 = ($elementValue1->value->value ? $elementValue1->value->value : $elementValue1->value ); - $elementValue2 = ($elementValue2->value->value ? $elementValue2->value->value : $elementValue2->value ); - - return $elementValue1 === $elementValue2; - } - - - private function extendSelector($matches, $selectorPath, $replacementSelector){ - - //for a set of matches, replace each match with the replacement selector - - $currentSelectorPathIndex = 0; - $currentSelectorPathElementIndex = 0; - $path = array(); - $selectorPath_len = count_if_countable($selectorPath); - - for($matchIndex = 0, $matches_len = count_if_countable($matches); $matchIndex < $matches_len; $matchIndex++ ){ - - - $match = $matches[$matchIndex]; - $selector = $selectorPath[ $match['pathIndex'] ]; - - $firstElement = new Less_Tree_Element( - $match['initialCombinator'], - $replacementSelector->elements[0]->value, - $replacementSelector->elements[0]->index, - $replacementSelector->elements[0]->currentFileInfo - ); - - if( $match['pathIndex'] > $currentSelectorPathIndex && $currentSelectorPathElementIndex > 0 ){ - $last_path = end($path); - $last_path->elements = array_merge( $last_path->elements, array_slice( $selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex)); - $currentSelectorPathElementIndex = 0; - $currentSelectorPathIndex++; - } - - $newElements = array_merge( - array_slice($selector->elements, $currentSelectorPathElementIndex, ($match['index'] - $currentSelectorPathElementIndex) ) // last parameter of array_slice is different than the last parameter of javascript's slice - , array($firstElement) - , array_slice($replacementSelector->elements,1) - ); - - if( $currentSelectorPathIndex === $match['pathIndex'] && $matchIndex > 0 ){ - $last_key = count_if_countable($path)-1; - $path[$last_key]->elements = array_merge($path[$last_key]->elements,$newElements); - }else{ - $path = array_merge( $path, array_slice( $selectorPath, $currentSelectorPathIndex, $match['pathIndex'] )); - $path[] = new Less_Tree_Selector( $newElements ); - } - - $currentSelectorPathIndex = $match['endPathIndex']; - $currentSelectorPathElementIndex = $match['endPathElementIndex']; - if( $currentSelectorPathElementIndex >= count_if_countable($selectorPath[$currentSelectorPathIndex]->elements) ){ - $currentSelectorPathElementIndex = 0; - $currentSelectorPathIndex++; - } - } - - if( $currentSelectorPathIndex < $selectorPath_len && $currentSelectorPathElementIndex > 0 ){ - $last_path = end($path); - $last_path->elements = array_merge( $last_path->elements, array_slice($selectorPath[$currentSelectorPathIndex]->elements, $currentSelectorPathElementIndex)); - $currentSelectorPathIndex++; - } - - $slice_len = $selectorPath_len - $currentSelectorPathIndex; - $path = array_merge($path, array_slice($selectorPath, $currentSelectorPathIndex, $slice_len)); - - return $path; - } - - - protected function visitMedia( $mediaNode ){ - $newAllExtends = array_merge( $mediaNode->allExtends, end($this->allExtendsStack) ); - $this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $mediaNode->allExtends); - } - - protected function visitMediaOut(){ - array_pop( $this->allExtendsStack ); - } - - protected function visitDirective( $directiveNode ){ - $newAllExtends = array_merge( $directiveNode->allExtends, end($this->allExtendsStack) ); - $this->allExtendsStack[] = $this->doExtendChaining($newAllExtends, $directiveNode->allExtends); - } - - protected function visitDirectiveOut(){ - array_pop($this->allExtendsStack); - } - -} - -/** - * toCSS Visitor - * - * @package Less - * @subpackage visitor - */ -class Less_Visitor_toCSS extends Less_VisitorReplacing{ - - private $charset; - - public function __construct(){ - parent::__construct(); - } - - /** - * @param Less_Tree_Ruleset $root - */ - public function run( $root ){ - return $this->visitObj($root); - } - - public function visitRule( $ruleNode ){ - if( $ruleNode->variable ){ - return array(); - } - return $ruleNode; - } - - public function visitMixinDefinition($mixinNode){ - // mixin definitions do not get eval'd - this means they keep state - // so we have to clear that state here so it isn't used if toCSS is called twice - $mixinNode->frames = array(); - return array(); - } - - public function visitExtend(){ - return array(); - } - - public function visitComment( $commentNode ){ - if( $commentNode->isSilent() ){ - return array(); - } - return $commentNode; - } - - public function visitMedia( $mediaNode, &$visitDeeper ){ - $mediaNode->accept($this); - $visitDeeper = false; - - if( !$mediaNode->rules ){ - return array(); - } - return $mediaNode; - } - - public function visitDirective( $directiveNode ){ - if( isset($directiveNode->currentFileInfo['reference']) && (!property_exists($directiveNode,'isReferenced') || !$directiveNode->isReferenced) ){ - return array(); - } - if( $directiveNode->name === '@charset' ){ - // Only output the debug info together with subsequent @charset definitions - // a comment (or @media statement) before the actual @charset directive would - // be considered illegal css as it has to be on the first line - if( isset($this->charset) && $this->charset ){ - - //if( $directiveNode->debugInfo ){ - // $comment = new Less_Tree_Comment('/* ' . str_replace("\n",'',$directiveNode->toCSS())." */\n"); - // $comment->debugInfo = $directiveNode->debugInfo; - // return $this->visit($comment); - //} - - - return array(); - } - $this->charset = true; - } - return $directiveNode; - } - - public function checkPropertiesInRoot( $rulesetNode ){ - - if( !$rulesetNode->firstRoot ){ - return; - } - - foreach($rulesetNode->rules as $ruleNode){ - if( $ruleNode instanceof Less_Tree_Rule && !$ruleNode->variable ){ - $msg = "properties must be inside selector blocks, they cannot be in the root. Index ".$ruleNode->index.($ruleNode->currentFileInfo ? (' Filename: '.$ruleNode->currentFileInfo['filename']) : null); - throw new Less_Exception_Compiler($msg); - } - } - } - - - public function visitRuleset( $rulesetNode, &$visitDeeper ){ - - $visitDeeper = false; - - $this->checkPropertiesInRoot( $rulesetNode ); - - if( $rulesetNode->root ){ - return $this->visitRulesetRoot( $rulesetNode ); - } - - $rulesets = array(); - $rulesetNode->paths = $this->visitRulesetPaths($rulesetNode); - - - // Compile rules and rulesets - $nodeRuleCnt = count_if_countable($rulesetNode->rules); - for( $i = 0; $i < $nodeRuleCnt; ){ - $rule = $rulesetNode->rules[$i]; - - if( property_exists($rule,'rules') ){ - // visit because we are moving them out from being a child - $rulesets[] = $this->visitObj($rule); - array_splice($rulesetNode->rules,$i,1); - $nodeRuleCnt--; - continue; - } - $i++; - } - - - // accept the visitor to remove rules and refactor itself - // then we can decide now whether we want it or not - if( $nodeRuleCnt > 0 ){ - $rulesetNode->accept($this); - - if( $rulesetNode->rules ){ - - if( count_if_countable($rulesetNode->rules) > 1 ){ - $this->_mergeRules( $rulesetNode->rules ); - $this->_removeDuplicateRules( $rulesetNode->rules ); - } - - // now decide whether we keep the ruleset - if( $rulesetNode->paths ){ - //array_unshift($rulesets, $rulesetNode); - array_splice($rulesets,0,0,array($rulesetNode)); - } - } - - } - - - if( count_if_countable($rulesets) === 1 ){ - return $rulesets[0]; - } - return $rulesets; - } - - - /** - * Helper function for visitiRuleset - * - * return array|Less_Tree_Ruleset - */ - private function visitRulesetRoot( $rulesetNode ){ - $rulesetNode->accept( $this ); - if( $rulesetNode->firstRoot || $rulesetNode->rules ){ - return $rulesetNode; - } - return array(); - } - - - /** - * Helper function for visitRuleset() - * - * @return array - */ - private function visitRulesetPaths($rulesetNode){ - - $paths = array(); - foreach($rulesetNode->paths as $p){ - if( $p[0]->elements[0]->combinator === ' ' ){ - $p[0]->elements[0]->combinator = ''; - } - - foreach($p as $pi){ - if( $pi->getIsReferenced() && $pi->getIsOutput() ){ - $paths[] = $p; - break; - } - } - } - - return $paths; - } - - protected function _removeDuplicateRules( &$rules ){ - // remove duplicates - $ruleCache = array(); - for( $i = count_if_countable($rules)-1; $i >= 0 ; $i-- ){ - $rule = $rules[$i]; - if( $rule instanceof Less_Tree_Rule || $rule instanceof Less_Tree_NameValue ){ - - if( !isset($ruleCache[$rule->name]) ){ - $ruleCache[$rule->name] = $rule; - }else{ - $ruleList =& $ruleCache[$rule->name]; - - if( $ruleList instanceof Less_Tree_Rule || $ruleList instanceof Less_Tree_NameValue ){ - $ruleList = $ruleCache[$rule->name] = array( $ruleCache[$rule->name]->toCSS() ); - } - - $ruleCSS = $rule->toCSS(); - if( array_search($ruleCSS,$ruleList) !== false ){ - array_splice($rules,$i,1); - }else{ - $ruleList[] = $ruleCSS; - } - } - } - } - } - - protected function _mergeRules( &$rules ){ - $groups = array(); - - //obj($rules); - - $rules_len = count_if_countable($rules); - for( $i = 0; $i < $rules_len; $i++ ){ - $rule = $rules[$i]; - - if( ($rule instanceof Less_Tree_Rule) && $rule->merge ){ - - $key = $rule->name; - if( $rule->important ){ - $key .= ',!'; - } - - if( !isset($groups[$key]) ){ - $groups[$key] = array(); - }else{ - array_splice($rules, $i--, 1); - $rules_len--; - } - - $groups[$key][] = $rule; - } - } - - - foreach($groups as $parts){ - - if( count_if_countable($parts) > 1 ){ - $rule = $parts[0]; - $spacedGroups = array(); - $lastSpacedGroup = array(); - $parts_mapped = array(); - foreach($parts as $p){ - if( $p->merge === '+' ){ - if( $lastSpacedGroup ){ - $spacedGroups[] = self::toExpression($lastSpacedGroup); - } - $lastSpacedGroup = array(); - } - $lastSpacedGroup[] = $p; - } - - $spacedGroups[] = self::toExpression($lastSpacedGroup); - $rule->value = self::toValue($spacedGroups); - } - } - - } - - public static function toExpression($values){ - $mapped = array(); - foreach($values as $p){ - $mapped[] = $p->value; - } - return new Less_Tree_Expression( $mapped ); - } - - public static function toValue($values){ - //return new Less_Tree_Value($values); ?? - - $mapped = array(); - foreach($values as $p){ - $mapped[] = $p; - } - return new Less_Tree_Value($mapped); - } -} - - - -/** - * Parser Exception - * - * @package Less - * @subpackage exception - */ -class Less_Exception_Parser extends Exception{ - - /** - * The current file - * - * @var Less_ImportedFile - */ - public $currentFile; - - /** - * The current parser index - * - * @var integer - */ - public $index; - - protected $input; - - protected $details = array(); - - - /** - * Constructor - * - * @param string $message - * @param Exception $previous Previous exception - * @param integer $index The current parser index - * @param Less_FileInfo|string $currentFile The file - * @param integer $code The exception code - */ - public function __construct($message = null, Exception $previous = null, $index = null, $currentFile = null, $code = 0){ - - if (PHP_VERSION_ID < 50300) { - $this->previous = $previous; - parent::__construct($message, $code); - } else { - parent::__construct($message, $code, $previous); - } - - $this->currentFile = $currentFile; - $this->index = $index; - - $this->genMessage(); - } - - - protected function getInput(){ - - if( !$this->input && $this->currentFile && $this->currentFile['filename'] && file_exists($this->currentFile['filename']) ){ - $this->input = file_get_contents( $this->currentFile['filename'] ); - } - } - - - - /** - * Converts the exception to string - * - * @return string - */ - public function genMessage(){ - - if( $this->currentFile && $this->currentFile['filename'] ){ - $this->message .= ' in '.basename($this->currentFile['filename']); - } - - if( $this->index !== null ){ - $this->getInput(); - if( $this->input ){ - $line = self::getLineNumber(); - $this->message .= ' on line '.$line.', column '.self::getColumn(); - - $lines = explode("\n",$this->input); - - $count = count_if_countable($lines); - $start_line = max(0, $line-3); - $last_line = min($count, $start_line+6); - $num_len = strlen($last_line); - for( $i = $start_line; $i < $last_line; $i++ ){ - $this->message .= "\n".str_pad($i+1,$num_len,'0',STR_PAD_LEFT).'| '.$lines[$i]; - } - } - } - - } - - /** - * Returns the line number the error was encountered - * - * @return integer - */ - public function getLineNumber(){ - if( $this->index ){ - // https://bugs.php.net/bug.php?id=49790 - if (ini_get("mbstring.func_overload")) { - return substr_count(substr($this->input, 0, $this->index), "\n") + 1; - } else { - return substr_count($this->input, "\n", 0, $this->index) + 1; - } - } - return 1; - } - - - /** - * Returns the column the error was encountered - * - * @return integer - */ - public function getColumn(){ - - $part = substr($this->input, 0, $this->index); - $pos = strrpos($part,"\n"); - return $this->index - $pos; - } - -} - - -/** - * Chunk Exception - * - * @package Less - * @subpackage exception - */ -class Less_Exception_Chunk extends Less_Exception_Parser{ - - - protected $parserCurrentIndex = 0; - - protected $emitFrom = 0; - - protected $input_len; - - - /** - * Constructor - * - * @param string $input - * @param Exception $previous Previous exception - * @param integer $index The current parser index - * @param Less_FileInfo|string $currentFile The file - * @param integer $code The exception code - */ - public function __construct($input, Exception $previous = null, $index = null, $currentFile = null, $code = 0){ - - $this->message = 'ParseError: Unexpected input'; //default message - - $this->index = $index; - - $this->currentFile = $currentFile; - - $this->input = $input; - $this->input_len = strlen($input); - - $this->Chunks(); - $this->genMessage(); - } - - - /** - * See less.js chunks() - * We don't actually need the chunks - * - */ - protected function Chunks(){ - $level = 0; - $parenLevel = 0; - $lastMultiCommentEndBrace = null; - $lastOpening = null; - $lastMultiComment = null; - $lastParen = null; - - for( $this->parserCurrentIndex = 0; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++ ){ - $cc = $this->CharCode($this->parserCurrentIndex); - if ((($cc >= 97) && ($cc <= 122)) || ($cc < 34)) { - // a-z or whitespace - break; - } - - switch ($cc) { - - // ( - case 40: - $parenLevel++; - $lastParen = $this->parserCurrentIndex; - break; - - // ) - case 41: - $parenLevel--; - if( $parenLevel < 0 ){ - return $this->fail("missing opening `(`"); - } - break; - - // ; - case 59: - //if (!$parenLevel) { $this->emitChunk(); } - break; - - // { - case 123: - $level++; - $lastOpening = $this->parserCurrentIndex; - break; - - // } - case 125: - $level--; - if( $level < 0 ){ - return $this->fail("missing opening `{`"); - - } - //if (!$level && !$parenLevel) { $this->emitChunk(); } - break; - // \ - case 92: - if ($this->parserCurrentIndex < $this->input_len - 1) { $this->parserCurrentIndex++; break; } - return $this->fail("unescaped `\\`"); - - // ", ' and ` - case 34: - case 39: - case 96: - $matched = 0; - $currentChunkStartIndex = $this->parserCurrentIndex; - for ($this->parserCurrentIndex = $this->parserCurrentIndex + 1; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) { - $cc2 = $this->CharCode($this->parserCurrentIndex); - if ($cc2 > 96) { continue; } - if ($cc2 == $cc) { $matched = 1; break; } - if ($cc2 == 92) { // \ - if ($this->parserCurrentIndex == $this->input_len - 1) { - return $this->fail("unescaped `\\`"); - } - $this->parserCurrentIndex++; - } - } - if ($matched) { break; } - return $this->fail("unmatched `" + chr($cc) + "`", $currentChunkStartIndex); - - // /, check for comment - case 47: - if ($parenLevel || ($this->parserCurrentIndex == $this->input_len - 1)) { break; } - $cc2 = $this->CharCode($this->parserCurrentIndex+1); - if ($cc2 == 47) { - // //, find lnfeed - for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len; $this->parserCurrentIndex++) { - $cc2 = $this->CharCode($this->parserCurrentIndex); - if (($cc2 <= 13) && (($cc2 == 10) || ($cc2 == 13))) { break; } - } - } else if ($cc2 == 42) { - // /*, find */ - $lastMultiComment = $currentChunkStartIndex = $this->parserCurrentIndex; - for ($this->parserCurrentIndex = $this->parserCurrentIndex + 2; $this->parserCurrentIndex < $this->input_len - 1; $this->parserCurrentIndex++) { - $cc2 = $this->CharCode($this->parserCurrentIndex); - if ($cc2 == 125) { $lastMultiCommentEndBrace = $this->parserCurrentIndex; } - if ($cc2 != 42) { continue; } - if ($this->CharCode($this->parserCurrentIndex+1) == 47) { break; } - } - if ($this->parserCurrentIndex == $this->input_len - 1) { - return $this->fail("missing closing `*/`", $currentChunkStartIndex); - } - } - break; - - // *, check for unmatched */ - case 42: - if (($this->parserCurrentIndex < $this->input_len - 1) && ($this->CharCode($this->parserCurrentIndex+1) == 47)) { - return $this->fail("unmatched `/*`"); - } - break; - } - } - - if( $level !== 0 ){ - if( ($lastMultiComment > $lastOpening) && ($lastMultiCommentEndBrace > $lastMultiComment) ){ - return $this->fail("missing closing `}` or `*/`", $lastOpening); - } else { - return $this->fail("missing closing `}`", $lastOpening); - } - } else if ( $parenLevel !== 0 ){ - return $this->fail("missing closing `)`", $lastParen); - } - - - //chunk didn't fail - - - //$this->emitChunk(true); - } - - public function CharCode($pos){ - return ord($this->input[$pos]); - } - - - public function fail( $msg, $index = null ){ - - if( !$index ){ - $this->index = $this->parserCurrentIndex; - }else{ - $this->index = $index; - } - $this->message = 'ParseError: '.$msg; - } - - - /* - function emitChunk( $force = false ){ - $len = $this->parserCurrentIndex - $this->emitFrom; - if ((($len < 512) && !$force) || !$len) { - return; - } - $chunks[] = substr($this->input, $this->emitFrom, $this->parserCurrentIndex + 1 - $this->emitFrom ); - $this->emitFrom = $this->parserCurrentIndex + 1; - } - */ - -} - - -/** - * Compiler Exception - * - * @package Less - * @subpackage exception - */ -class Less_Exception_Compiler extends Less_Exception_Parser{ - -} - -/** - * Parser output with source map - * - * @package Less - * @subpackage Output - */ -class Less_Output_Mapped extends Less_Output { - - /** - * The source map generator - * - * @var Less_SourceMap_Generator - */ - protected $generator; - - /** - * Current line - * - * @var integer - */ - protected $lineNumber = 0; - - /** - * Current column - * - * @var integer - */ - protected $column = 0; - - /** - * Array of contents map (file and its content) - * - * @var array - */ - protected $contentsMap = array(); - - /** - * Constructor - * - * @param array $contentsMap Array of filename to contents map - * @param Less_SourceMap_Generator $generator - */ - public function __construct(array $contentsMap, $generator){ - $this->contentsMap = $contentsMap; - $this->generator = $generator; - } - - /** - * Adds a chunk to the stack - * The $index for less.php may be different from less.js since less.php does not chunkify inputs - * - * @param string $chunk - * @param string $fileInfo - * @param integer $index - * @param mixed $mapLines - */ - public function add($chunk, $fileInfo = null, $index = 0, $mapLines = null){ - - //ignore adding empty strings - if( $chunk === '' ){ - return; - } - - - $sourceLines = array(); - $sourceColumns = ' '; - - - if( $fileInfo ){ - - $url = $fileInfo['currentUri']; - - if( isset($this->contentsMap[$url]) ){ - $inputSource = substr($this->contentsMap[$url], 0, $index); - $sourceLines = explode("\n", $inputSource); - $sourceColumns = end($sourceLines); - }else{ - throw new Exception('Filename '.$url.' not in contentsMap'); - } - - } - - $lines = explode("\n", $chunk); - $columns = end($lines); - - if($fileInfo){ - - if(!$mapLines){ - $this->generator->addMapping( - $this->lineNumber + 1, // generated_line - $this->column, // generated_column - count_if_countable($sourceLines), // original_line - strlen($sourceColumns), // original_column - $fileInfo - ); - }else{ - for($i = 0, $count = count_if_countable($lines); $i < $count; $i++){ - $this->generator->addMapping( - $this->lineNumber + $i + 1, // generated_line - $i === 0 ? $this->column : 0, // generated_column - count_if_countable($sourceLines) + $i, // original_line - $i === 0 ? strlen($sourceColumns) : 0, // original_column - $fileInfo - ); - } - } - } - - if(count_if_countable($lines) === 1){ - $this->column += strlen($columns); - }else{ - $this->lineNumber += count_if_countable($lines) - 1; - $this->column = strlen($columns); - } - - // add only chunk - parent::add($chunk); - } - -} - -/** - * Encode / Decode Base64 VLQ. - * - * @package Less - * @subpackage SourceMap - */ -class Less_SourceMap_Base64VLQ { - - /** - * Shift - * - * @var integer - */ - private $shift = 5; - - /** - * Mask - * - * @var integer - */ - private $mask = 0x1F; // == (1 << shift) == 0b00011111 - - /** - * Continuation bit - * - * @var integer - */ - private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000 - - /** - * Char to integer map - * - * @var array - */ - private $charToIntMap = array( - 'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, - 'H' => 7,'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, - 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, - 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27, - 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31, 'g' => 32, 'h' => 33, 'i' => 34, - 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39, 'o' => 40, 'p' => 41, - 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47, 'w' => 48, - 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55, 4 => 56, - 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63, - ); - - /** - * Integer to char map - * - * @var array - */ - private $intToCharMap = array( - 0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', - 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N', - 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U', - 21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b', - 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i', - 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o', 41 => 'p', - 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w', - 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3', - 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+', - 63 => '/', - ); - - /** - * Constructor - */ - public function __construct(){ - // I leave it here for future reference - // foreach(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char) - // { - // $this->charToIntMap[$char] = $i; - // $this->intToCharMap[$i] = $char; - // } - } - - /** - * Convert from a two-complement value to a value where the sign bit is - * is placed in the least significant bit. For example, as decimals: - * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) - * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) - * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297, - * even on a 64 bit machine. - * @param string $aValue - */ - public function toVLQSigned($aValue){ - return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0); - } - - /** - * Convert to a two-complement value from a value where the sign bit is - * is placed in the least significant bit. For example, as decimals: - * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 - * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 - * We assume that the value was generated with a 32 bit machine in mind. - * Hence - * 1 becomes -2147483648 - * even on a 64 bit machine. - * @param integer $aValue - */ - public function fromVLQSigned($aValue){ - return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1); - } - - /** - * Return the base 64 VLQ encoded value. - * - * @param string $aValue The value to encode - * @return string The encoded value - */ - public function encode($aValue){ - $encoded = ''; - $vlq = $this->toVLQSigned($aValue); - do - { - $digit = $vlq & $this->mask; - $vlq = $this->zeroFill($vlq, $this->shift); - if($vlq > 0){ - $digit |= $this->continuationBit; - } - $encoded .= $this->base64Encode($digit); - } while($vlq > 0); - - return $encoded; - } - - /** - * Return the value decoded from base 64 VLQ. - * - * @param string $encoded The encoded value to decode - * @return integer The decoded value - */ - public function decode($encoded){ - $vlq = 0; - $i = 0; - do - { - $digit = $this->base64Decode($encoded[$i]); - $vlq |= ($digit & $this->mask) << ($i * $this->shift); - $i++; - } while($digit & $this->continuationBit); - - return $this->fromVLQSigned($vlq); - } - - /** - * Right shift with zero fill. - * - * @param integer $a number to shift - * @param integer $b number of bits to shift - * @return integer - */ - public function zeroFill($a, $b){ - return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1)); - } - - /** - * Encode single 6-bit digit as base64. - * - * @param integer $number - * @return string - * @throws Exception If the number is invalid - */ - public function base64Encode($number){ - if($number < 0 || $number > 63){ - throw new Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number)); - } - return $this->intToCharMap[$number]; - } - - /** - * Decode single 6-bit digit from base64 - * - * @param string $char - * @return number - * @throws Exception If the number is invalid - */ - public function base64Decode($char){ - if(!array_key_exists($char, $this->charToIntMap)){ - throw new Exception(sprintf('Invalid base 64 digit "%s" given.', $char)); - } - return $this->charToIntMap[$char]; - } - -} - - -/** - * Source map generator - * - * @package Less - * @subpackage Output - */ -class Less_SourceMap_Generator extends Less_Configurable { - - /** - * What version of source map does the generator generate? - */ - const VERSION = 3; - - /** - * Array of default options - * - * @var array - */ - protected $defaultOptions = array( - // an optional source root, useful for relocating source files - // on a server or removing repeated values in the 'sources' entry. - // This value is prepended to the individual entries in the 'source' field. - 'sourceRoot' => '', - - // an optional name of the generated code that this source map is associated with. - 'sourceMapFilename' => null, - - // url of the map - 'sourceMapURL' => null, - - // absolute path to a file to write the map to - 'sourceMapWriteTo' => null, - - // output source contents? - 'outputSourceFiles' => false, - - // base path for filename normalization - 'sourceMapRootpath' => '', - - // base path for filename normalization - 'sourceMapBasepath' => '' - ); - - /** - * The base64 VLQ encoder - * - * @var Less_SourceMap_Base64VLQ - */ - protected $encoder; - - /** - * Array of mappings - * - * @var array - */ - protected $mappings = array(); - - /** - * The root node - * - * @var Less_Tree_Ruleset - */ - protected $root; - - /** - * Array of contents map - * - * @var array - */ - protected $contentsMap = array(); - - /** - * File to content map - * - * @var array - */ - protected $sources = array(); - protected $source_keys = array(); - - /** - * Constructor - * - * @param Less_Tree_Ruleset $root The root node - * @param array $options Array of options - */ - public function __construct(Less_Tree_Ruleset $root, $contentsMap, $options = array()){ - $this->root = $root; - $this->contentsMap = $contentsMap; - $this->encoder = new Less_SourceMap_Base64VLQ(); - - $this->SetOptions($options); - - $this->options['sourceMapRootpath'] = $this->fixWindowsPath($this->options['sourceMapRootpath'], true); - $this->options['sourceMapBasepath'] = $this->fixWindowsPath($this->options['sourceMapBasepath'], true); - } - - /** - * Generates the CSS - * - * @return string - */ - public function generateCSS(){ - $output = new Less_Output_Mapped($this->contentsMap, $this); - - // catch the output - $this->root->genCSS($output); - - - $sourceMapUrl = $this->getOption('sourceMapURL'); - $sourceMapFilename = $this->getOption('sourceMapFilename'); - $sourceMapContent = $this->generateJson(); - $sourceMapWriteTo = $this->getOption('sourceMapWriteTo'); - - if( !$sourceMapUrl && $sourceMapFilename ){ - $sourceMapUrl = $this->normalizeFilename($sourceMapFilename); - } - - // write map to a file - if( $sourceMapWriteTo ){ - $this->saveMap($sourceMapWriteTo, $sourceMapContent); - } - - // inline the map - if( !$sourceMapUrl ){ - $sourceMapUrl = sprintf('data:application/json,%s', Less_Functions::encodeURIComponent($sourceMapContent)); - } - - if( $sourceMapUrl ){ - $output->add( sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl) ); - } - - return $output->toString(); - } - - /** - * Saves the source map to a file - * - * @param string $file The absolute path to a file - * @param string $content The content to write - * @throws Exception If the file could not be saved - */ - protected function saveMap($file, $content){ - $dir = dirname($file); - // directory does not exist - if( !is_dir($dir) ){ - // FIXME: create the dir automatically? - throw new Exception(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir)); - } - // FIXME: proper saving, with dir write check! - if(file_put_contents($file, $content) === false){ - throw new Exception(sprintf('Cannot save the source map to "%s"', $file)); - } - return true; - } - - /** - * Normalizes the filename - * - * @param string $filename - * @return string - */ - protected function normalizeFilename($filename){ - - $filename = $this->fixWindowsPath($filename); - - $rootpath = $this->getOption('sourceMapRootpath'); - $basePath = $this->getOption('sourceMapBasepath'); - - // "Trim" the 'sourceMapBasepath' from the output filename. - if (! empty($basePath) && strpos($filename, (string) $basePath) === 0) { - $filename = substr($filename, strlen($basePath)); - } - - // Remove extra leading path separators. - if(strpos($filename, '\\') === 0 || strpos($filename, '/') === 0){ - $filename = substr($filename, 1); - } - - return $rootpath . $filename; - } - - /** - * Adds a mapping - * - * @param integer $generatedLine The line number in generated file - * @param integer $generatedColumn The column number in generated file - * @param integer $originalLine The line number in original file - * @param integer $originalColumn The column number in original file - * @param string $sourceFile The original source file - */ - public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $fileInfo ){ - - $this->mappings[] = array( - 'generated_line' => $generatedLine, - 'generated_column' => $generatedColumn, - 'original_line' => $originalLine, - 'original_column' => $originalColumn, - 'source_file' => $fileInfo['currentUri'] - ); - - $this->sources[$fileInfo['currentUri']] = $fileInfo['filename']; - } - - - /** - * Generates the JSON source map - * - * @return string - * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit# - */ - protected function generateJson(){ - - $sourceMap = array(); - $mappings = $this->generateMappings(); - - // File version (always the first entry in the object) and must be a positive integer. - $sourceMap['version'] = self::VERSION; - - - // An optional name of the generated code that this source map is associated with. - $file = $this->getOption('sourceMapFilename'); - if( $file ){ - $sourceMap['file'] = $file; - } - - - // An optional source root, useful for relocating source files on a server or removing repeated values in the 'sources' entry. This value is prepended to the individual entries in the 'source' field. - $root = $this->getOption('sourceRoot'); - if( $root ){ - $sourceMap['sourceRoot'] = $root; - } - - - // A list of original sources used by the 'mappings' entry. - $sourceMap['sources'] = array(); - foreach($this->sources as $source_uri => $source_filename){ - $sourceMap['sources'][] = $this->normalizeFilename($source_filename); - } - - - // A list of symbol names used by the 'mappings' entry. - $sourceMap['names'] = array(); - - // A string with the encoded mapping data. - $sourceMap['mappings'] = $mappings; - - if( $this->getOption('outputSourceFiles') ){ - // An optional list of source content, useful when the 'source' can't be hosted. - // The contents are listed in the same order as the sources above. - // 'null' may be used if some original sources should be retrieved by name. - $sourceMap['sourcesContent'] = $this->getSourcesContent(); - } - - // less.js compat fixes - if( count_if_countable($sourceMap['sources']) && empty($sourceMap['sourceRoot']) ){ - unset($sourceMap['sourceRoot']); - } - - return json_encode($sourceMap); - } - - /** - * Returns the sources contents - * - * @return array|null - */ - protected function getSourcesContent(){ - if(empty($this->sources)){ - return; - } - $content = array(); - foreach($this->sources as $sourceFile){ - $content[] = file_get_contents($sourceFile); - } - return $content; - } - - /** - * Generates the mappings string - * - * @return string - */ - public function generateMappings(){ - - if( !count_if_countable($this->mappings) ){ - return ''; - } - - $this->source_keys = array_flip(array_keys($this->sources)); - - - // group mappings by generated line number. - $groupedMap = $groupedMapEncoded = array(); - foreach($this->mappings as $m){ - $groupedMap[$m['generated_line']][] = $m; - } - ksort($groupedMap); - - $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0; - - foreach($groupedMap as $lineNumber => $line_map){ - while(++$lastGeneratedLine < $lineNumber){ - $groupedMapEncoded[] = ';'; - } - - $lineMapEncoded = array(); - $lastGeneratedColumn = 0; - - foreach($line_map as $m){ - $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn); - $lastGeneratedColumn = $m['generated_column']; - - // find the index - if( $m['source_file'] ){ - $index = $this->findFileIndex($m['source_file']); - if( $index !== false ){ - $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex); - $lastOriginalIndex = $index; - - // lines are stored 0-based in SourceMap spec version 3 - $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine); - $lastOriginalLine = $m['original_line'] - 1; - - $mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn); - $lastOriginalColumn = $m['original_column']; - } - } - - $lineMapEncoded[] = $mapEncoded; - } - - $groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';'; - } - - return rtrim(implode($groupedMapEncoded), ';'); - } - - /** - * Finds the index for the filename - * - * @param string $filename - * @return integer|false - */ - protected function findFileIndex($filename){ - return $this->source_keys[$filename]; - } - - /** - * fix windows paths - * @param string $path - * @return string - */ - public function fixWindowsPath($path, $addEndSlash = false){ - $slash = ($addEndSlash) ? '/' : ''; - if( !empty($path) ){ - $path = str_replace('\\', '/', $path); - $path = rtrim($path,'/') . $slash; - } - - return $path; - } - -} \ No newline at end of file -- cgit v1.2.3