Location: PHPKode > scripts > xCSS > xcss-class.php
<?php defined('XCSSCLASS') OR die('No direct access allowed.');
/**
 * xCSS class
 *
 * @author     Anton Pawlik
 * @version    1.0.1
 * @see        http://xcss.antpaw.org/docs/
 * @copyright  (c) 2010 Anton Pawlik
 * @license    http://xcss.antpaw.org/about/
 */

class xCSS
{
	// config vars
	public $path_css_dir;
	public $master_file;
	public $xcss_files;
	public $reset_files;
	public $hook_files;
	public $css_files;
	public $construct;
	public $compress_output_to_master;
	public $minify_output;
	public $master_content;
	public $debugmode;
	
	// hole content of the xCSS file
	public $filecont;
	
	// an array of keys(selectors) and values(propertys)
	public $parts;
	
	// nodes that will be extended some levels later
	public $levelparts;
	
	// final css nodes as an array
	public $css;
	
	// vars declared in xCSS files
	public $xcss_vars;
	
	// output string for each CSS file
	public $final_file;
	
	// relevant to debugging
	public $debug;
	
	public function __construct(array $cfg)
	{
		if(isset($cfg['disable_xCSS']) && $cfg['disable_xCSS'] === TRUE)
		{
			throw new xCSS_Exception('xcss_disabled');
		}
		
		$this->levelparts = array();
		$this->path_css_dir = isset($cfg['path_to_css_dir']) ? $cfg['path_to_css_dir'] : '../';
		
		if(isset($cfg['xCSS_files']))
		{
			$this->xcss_files = array();
			$this->css_files = array();
			foreach($cfg['xCSS_files'] as $xcss_file => $css_file)
			{
				if(strpos($xcss_file, '*') !== FALSE)
				{
					$xcss_dir = glob($this->path_css_dir . $xcss_file);
					foreach($xcss_dir as $glob_xcss_file)
					{
						$glob_xcss_file = str_replace($this->path_css_dir, NULL, $glob_xcss_file);
						array_push($this->xcss_files, $glob_xcss_file);
						
						$glob_css_file = dirname($css_file).'/'.basename(str_replace('.xcss', '.css', $glob_xcss_file));
						// get rid of the media properties
						$file = explode(':', $glob_css_file);
						array_push($this->css_files, trim($file[0]));
						$cfg['xCSS_files'][$glob_xcss_file] = $glob_css_file;
					}
					unset($cfg['xCSS_files'][$xcss_file]);
				}
				else
				{
					array_push($this->xcss_files, $xcss_file);
					// get rid of the media properties
					$file = explode(':', $css_file);
					array_push($this->css_files, trim($file[0]));
				}
			}
		}
		else
		{
			$this->xcss_files = array('xcss.xcss');
			$this->css_files = array('xcss_generated.css');
		}
		
		// CSS master file
		$this->compress_output_to_master = (isset($cfg['compress_output_to_master']) && $cfg['compress_output_to_master'] === TRUE);
		
		if($this->compress_output_to_master || (isset($cfg['use_master_file']) && $cfg['use_master_file'] === TRUE))
		{
			$this->master_file = isset($cfg['master_filename']) ? $cfg['master_filename'] : 'master.css';
			$this->reset_files = isset($cfg['reset_files']) ? $cfg['reset_files'] : NULL;
			$this->hook_files = isset($cfg['hook_files']) ? $cfg['hook_files'] : NULL;
			
			if( ! $this->compress_output_to_master)
			{
				$xcssf = isset($cfg['xCSS_files']) ? $cfg['xCSS_files'] : NULL;
				$this->create_master_file($this->reset_files, $xcssf, $this->hook_files);
			}
		}
		
		$this->construct = isset($cfg['construct_name']) ? $cfg['construct_name'] : 'self';
		
		$this->minify_output = isset($cfg['minify_output']) ? $cfg['minify_output'] : FALSE;
		
		$this->debugmode = isset($cfg['debugmode']) ? $cfg['debugmode'] : FALSE;
		
		if($this->debugmode)
		{
			$this->debug['xcss_time_start'] = $this->microtime_float();
			$this->debug['xcss_output'] = NULL;
		}
		
		// this is needed to be able to extend selectors across mulitple xCSS files
		$this->xcss_files = array_reverse($this->xcss_files);
		$this->css_files = array_reverse($this->css_files);
		
		$this->xcss_vars = array(
			// unsafe chars will be hidden as vars
			'$__doubleslash'			=> '//',
			'$__bigcopen'				=> '/*',
			'$__bigcclose'				=> '*/',
			'$__doubledot'				=> ':',
			'$__semicolon'				=> ';',
			'$__curlybracketopen'		=> '{',
			'$__curlybracketclosed'		=> '}',
			// shortcuts 
			// it's "a hidden feature" for now
			'bg:'						=> 'background:',
			'bgc:'						=> 'background-color:',
		);
	}
	
	public function create_master_file(array $reset = array(), array $main = array(), array $hook = array())
	{
		$all_files = array_merge($reset, $main, $hook);
		
		$master_file_content = NULL;
		foreach($all_files as $file)
		{
			$file = explode(':', $file);
			$props = isset($file[1]) ? ' '.trim($file[1]) : NULL;
			$master_file_content .= '@import url("'.trim($file[0]).'")'.$props.';'."\n";
		}
		
		$this->create_file($master_file_content, $this->master_file);
	}
	
	public function compile($input_xcss = FALSE)
	{
		if($input_xcss === FALSE)
		{
			$count_xcss_files = count($this->xcss_files);
			for($i = 0; $i < $count_xcss_files; $i++)
			{
				$this->parts = NULL;
				$this->filecont = NULL;
				$this->css = NULL;
				
				$filename = $this->path_css_dir.$this->xcss_files[$i];
				$this->filecont = $this->read_file($filename);
				
				if($this->parse_xcss_string())
				{
					$this->final_parse($this->css_files[$i]);
				}
			}
			
			if( ! empty($this->final_file))
			{
				if($this->compress_output_to_master)
				{
					$master_content = NULL;
					foreach($this->reset_files as $fname)
					{
						$fname = explode(':', $fname);
						$master_content .= $this->read_file($this->path_css_dir.$fname[0])."\n";
					}
					if($this->minify_output && strpos($master_content, '/*') !== FALSE)
					{
						$master_content = preg_replace("/\/\*(.*)?\*\//Usi", NULL, $master_content);				
					}
					$this->final_file = array_reverse($this->final_file);
					foreach($this->final_file as $fcont)
					{
						$master_content .= $this->use_vars($fcont);
					}
					foreach($this->hook_files as $fname)
					{
						$fname = explode(':', $fname);
						$tmp_file = $this->read_file($this->path_css_dir.$fname[0]);
						if($this->minify_output && strpos($tmp_file, '/*') !== FALSE)
						{
							$tmp_file = preg_replace("/\/\*(.*)?\*\//Usi", NULL, $tmp_file);				
						}
						$master_content .= $tmp_file;
					}
					$master_content = $this->do_math($master_content);
					$this->create_file($master_content, $this->master_file);
				}
				else
				{
					foreach($this->final_file as $fname => $fcont)
					{
						$fcont = $this->do_math($this->use_vars($fcont));
						$this->create_file($fcont, $fname);
					}
				}
			}
		}
		else
		{
			$this->filecont = $input_xcss;
			if($this->parse_xcss_string())
			{
				$this->final_parse('string');
				$fcont = $this->use_vars($this->final_file['string']);
				$fcont = $this->do_math($fcont);
				return $this->create_file($fcont, 'string');
			}
		}
	}
	
	public function parse_xcss_string()
	{		
		foreach($this->xcss_vars as $var => $unsafe_char)
		{
			$masked_unsafe_char = str_replace(array('*', '/'), array('\*', '\/'), $unsafe_char);
			$patterns[] = '/content(.*:.*(\'|").*)('.$masked_unsafe_char.')(.*(\'|"))/';
			$replacements[] = 'content$1'.$var.'$4';
		}
		
		$this->filecont = preg_replace($patterns, $replacements, $this->filecont);
		
		if(strlen($this->filecont) > 1)
		{
			$this->split_content();
			
			if( ! empty($this->parts))
			{
				$this->parse_level();
				
				$this->parts = $this->manage_order($this->parts);
				
				if( ! empty($this->levelparts))
				{
					$this->manage_global_extends();
				}
				
				return TRUE;
			}
		}
		
		return FALSE;
	}
	
	public function calc_string($math)
	{
		if(@eval('$result = '.$math.';') === FALSE)
		{
			throw new xCSS_Exception('xcss_math_error', array('math' => $math));
		}
		return $result;
	}
	
	public function do_math($content)
	{
		$units = array('px', '%', 'em', 'pt', 'cm', 'mm');
		$units_count = count($units);
		preg_match_all('/(\[(?:[^\[\]]|(?R))*\])((?:(?: |	)|;)|.+?\S)/', $content, $result);
		
		$count_results = count($result[0]);
		for($i = 0; $i < $count_results; $i++)
		{
			$better_math_str = strtr($result[1][$i], array('[' => '(', ']' => ')'));
			if (strpos($better_math_str, '=') !== FALSE)
			{
				continue;
			}
			if (strpos($better_math_str, '#') !== FALSE)
			{
				preg_match_all('/#(\w{6}|\w{3})/', $better_math_str, $colors);
				for($y = 0; $y < count($colors[1]); $y++)
				{
					$color = $colors[1][$y];
					if(strlen($color) === 6)
					{
						$r = $color[0].$color[1];
						$g = $color[2].$color[3];
						$b = $color[4].$color[5];
					}
					else
					{
						$r = $color[0].$color[0];
						$g = $color[1].$color[1];
						$b = $color[2].$color[2];
					}
					
					if($y === 0)
					{
						$rgb = array(
							str_replace('#'.$color, '0x'.$r, $better_math_str),
							str_replace('#'.$color, '0x'.$g, $better_math_str),
							str_replace('#'.$color, '0x'.$b, $better_math_str),
						);
					}
					else
					{
						$rgb = array(
							str_replace('#'.$color, '0x'.$r, $rgb[0]),
							str_replace('#'.$color, '0x'.$g, $rgb[1]),
							str_replace('#'.$color, '0x'.$b, $rgb[2]),
						);
					}
				}
				$better_math_str = '#';
				$c = $this->calc_string($rgb[0]);
				$better_math_str .= str_pad(dechex($c<0?0:($c>255?255:$c)), 2, 0, STR_PAD_LEFT);
				$c = $this->calc_string($rgb[1]);
				$better_math_str .= str_pad(dechex($c<0?0:($c>255?255:$c)), 2, 0, STR_PAD_LEFT);
				$c = $this->calc_string($rgb[2]);
				$better_math_str .= str_pad(dechex($c<0?0:($c>255?255:$c)), 2, 0, STR_PAD_LEFT);
			}
			else
			{
				$better_math_str = preg_replace("/[^\d\*+-\/\(\)]/", NULL, $better_math_str);
				if ($better_math_str === '()' || $better_math_str === '')
				{
					continue;
				}
				$new_unit = NULL;
				if($result[2][$i] === ';' || $result[2][$i] === ' ' || $result[2][$i] === '	')
				{
					$all_units_count = 0;
					for($x = 0; $x < $units_count; $x++)
					{
						$this_unit_count = count(explode($units[$x], $result[1][$i]))-1;
						if($all_units_count < $this_unit_count)
						{
							$new_unit = $units[$x];
							$all_units_count = $this_unit_count;
						}
					}
					if($all_units_count === 0)
					{
						$new_unit = 'px';
					}
				}
				
				$better_math_str = $this->calc_string($better_math_str) . $new_unit;
			}
			
			$content = str_replace(array('#'.$result[1][$i], $result[1][$i]), $better_math_str, $content);
		}
		
		return $content;
	}
	
	public function read_file($filepath)
	{
		$filecontent = NULL;
		
		if(file_exists($filepath))
		{
			$filecontent = str_replace('', NULL, utf8_encode(file_get_contents($filepath)));
		}
		else
		{
			throw new xCSS_Exception('xcss_file_does_not_exist', array('file' => $filepath));
		}
		
		return $filecontent;
	}
	
	public function split_content()
	{
		// removes multiple line comments
		$this->filecont = preg_replace("/\/\*(.*)?\*\//Usi", NULL, $this->filecont);
		// removes inline comments, but not :// (protocol)
		$this->filecont .= "\n";
		$this->filecont = preg_replace("/[^:]\/\/.+/", NULL, $this->filecont);
		$this->filecont = str_replace(array('	extends', 'extends	'), array(' extends', 'extends '), $this->filecont);
		
		$this->filecont = $this->change_braces($this->filecont);
		
		$this->filecont = explode('#c]}', $this->filecont);
		
		foreach($this->filecont as $i => $part)
		{
			$part = trim($part);
			if($part !== '')
			{
				list($keystr, $codestr) = explode('{[o#', $part);
				// adding new line to all (,) in selectors, to be able to find them for 'extends' later
				$keystr = str_replace(',', ",\n", trim($keystr));
				if($keystr === 'vars')
				{
					$this->setup_vars($codestr);
					unset($this->filecont[$i]);
				}
				else if($keystr !== '')
				{
					$this->parts[$keystr] = $codestr;
				}
			}
		}
	}
	
	public function setup_vars($codestr)
	{
		$codes = explode(';', $codestr);
		if( ! empty($codes))
		{
			foreach($codes as $code)
			{
				$code = trim($code);
				if( ! empty($code))
				{
					list($varkey, $varcode) = explode('=', $code);
					$varkey = trim($varkey);
					$varcode = trim($varcode);
					if(strlen($varkey) > 0)
					{
						$this->xcss_vars[$varkey] = $this->use_vars($varcode);
					}
				}
			}
			$this->xcss_vars[': var_rule'] = NULL;
		}
	}
	
	public function use_vars($cont)
	{
		return strtr($cont, $this->xcss_vars);
	}
	
	public function parse_level()
	{
		// this will manage xCSS rule: 'extends'
		$this->parse_extends();

		// this will manage xCSS rule: child objects inside of a node
		$this->parse_children();
	}
	
	public function regex_extend($keystr)
	{
		preg_match_all('/((\S|\s)+?) extends ((\S|\s|\n)[^,]+)/', $keystr, $result);
		return $result;
	}
	
	public function manage_global_extends()
	{
		// helps to find all the extenders of the global extended selector
		foreach($this->levelparts as $keystr => $codestr)
		{
			if(strpos($keystr, 'extends') !== FALSE)
			{
				$result = $this->regex_extend($keystr);
				
				$child = trim($result[1][0]);
				$parent = trim($result[3][0]);
				
				foreach($this->parts as $p_keystr => $p_codestr)
				{
					// to be sure we get all the children we need to find the parent selector
					// this must be the one that has no , after his name
					if(strpos($p_keystr, ",\n".$child) !== FALSE && strpos($p_keystr, $child.',') === FALSE)
					{
						$p_keys = explode(",\n", $p_keystr);
						foreach($p_keys as $p_key)
						{
							$this->levelparts[$p_key.' extends '.$parent] = NULL;
						}
					}
				}
			}
		}
	}
	
	public function manageMultipleExtends()
	{
		// To be able to manage multiple extends, you need to
		// destroy the actual node and creat many nodes that have
		// mono extend. the first one gets all the css rules
		foreach($this->parts as $keystr => $codestr)
		{
			if(strpos($keystr, 'extends') !== FALSE)
			{
				$result = $this->regex_extend($keystr);
				
				$parent = trim($result[3][0]);
				$child = trim($result[1][0]);
				
				if(strpos($parent, '&') !== FALSE)
				{
					$kill_this = $child.' extends '.$parent;
					
					$parents = explode(' & ', $parent);
					$with_this_key = $child.' extends '.$parents[0];
					
					$add_keys = array();
					$count_parents = count($parents);
					for($i = 1; $i < $count_parents; $i++)
					{
						array_push($add_keys, $child.' extends '.$parents[$i]);
					}
					
					$this->parts = $this->add_node_at_order($kill_this, $with_this_key, $codestr, $add_keys);
				}
			}
		}
	}
	
	public function add_node_at_order($kill_this, $with_this_key, $and_this_value, $additional_key = array())
	{
		foreach($this->parts as $keystr => $codestr)
		{
			if($keystr === $kill_this)
			{
				$temp[$with_this_key] = $and_this_value;
				
				if( ! empty($additional_key))
				{
					foreach($additional_key as $empty_key)
					{
						$temp[$empty_key] = NULL;
					}
				}
			}
			else
			{
				$temp[$keystr] = $codestr;
			}
		}
		return $temp;
	}
	
	public function parse_extends()
	{
		// this will manage xCSS rule: 'extends &'
		$this->manageMultipleExtends();
		
		foreach($this->levelparts as $keystr => $codestr)
		{
			if(strpos($keystr, 'extends') !== FALSE)
			{
				$result = $this->regex_extend($keystr);
				
				$parent = trim($result[3][0]);
				$child = trim($result[1][0]);
				
				// TRUE means that the parent node was in the same file
				if($this->search_for_parent($child, $parent))
				{
					// remove extended rule
					unset($this->levelparts[$keystr]);
				}
			}
		}

		foreach($this->parts as $keystr => $codestr)
		{
			if(strpos($keystr, 'extends') !== FALSE)
			{
				$result = $this->regex_extend($keystr);
				if(count($result[3]) > 1)
				{
					unset($this->parts[$keystr]);
					$keystr = str_replace(' extends '.$result[3][0], NULL, $keystr);
					$keystr .= ' extends '.$result[3][0];
					$this->parts[$keystr] = $codestr;
					$this->parse_extends();
					break;
				}
				
				$parent = trim($result[3][0]);
				$child = trim($result[1][0]);
				// TRUE means that the parent node was in the same file
				if($this->search_for_parent($child, $parent))
				{
					// if not empty, create own node with extended code
					$codestr = trim($codestr);
					if($codestr !== '')
					{
						$this->parts[$child] = $codestr;
					}
					
					unset($this->parts[$keystr]);
				}
				else
				{
					$codestr = trim($codestr);
					if($codestr !== '')
					{
						$this->parts[$child] = $codestr;
					}
					unset($this->parts[$keystr]);
					// add this node to levelparts to find it later
					$this->levelparts[$keystr] = $codestr;
				}
			}
		}
	}
	
	public function search_for_parent($child, $parent)
	{
		$parent_found = FALSE;
		foreach ($this->parts as $keystr => $codestr)
		{
			$sep_keys = explode(",\n", $keystr);
			foreach ($sep_keys as $s_key)
			{
				if($parent === $s_key)
				{
					$this->parts = $this->add_node_at_order($keystr, $child.",\n".$keystr, $codestr);
					// finds all the parent selectors with another bind selectors behind
					foreach ($this->parts as $keystr => $codestr)
					{
						$sep_keys = explode(",\n", $keystr);
						foreach ($sep_keys as $s_key)
						{
							if($parent !== $s_key && strpos($s_key, $parent) !== FALSE)
							{
								$childextra = str_replace($parent, $child, $s_key);
								
								if(strpos($childextra, 'extends') === FALSE)
								{
									// get rid off not extended parent node
									$this->parts = $this->add_node_at_order($keystr, $childextra.",\n".$keystr, $codestr);
								}
							}
						}
					}
					$parent_found = TRUE;
				}
			}
		}
		return $parent_found;
	}
	
	public function parse_children()
	{
		$children_left = FALSE;
		foreach($this->parts as $keystr => $codestr)
		{
			if(strpos($codestr, '{') !== FALSE)
			{
				$keystr = trim($keystr);
				unset($this->parts[$keystr]);
				unset($this->levelparts[$keystr]);
				$this->manage_children($keystr, $this->construct."{}\n".$codestr);
				$children_left = TRUE; // maybe
			}
		}
		if($children_left)
		{
			$this->parse_level();
		}
	}
	
	public function manage_children($keystr, $codestr)
	{
		$codestr = $this->change_braces($codestr);
		
		$c_parts = explode('#c]}', $codestr);
		foreach ($c_parts as $c_part)
		{
			$c_part = trim($c_part);
			if($c_part !== '')
			{
				list($c_keystr, $c_codestr) = explode('{[o#', $c_part);
				$c_keystr = trim($c_keystr);
				
				if($c_keystr !== '')
				{
					$better_key = NULL;
					
					$better_strkey = explode(',', $keystr);
					$c_keystr = explode(',', $c_keystr);
					foreach($c_keystr as $child_coma_keystr)
					{
						foreach($better_strkey as $parent_coma_keystr)
						{
							$better_key .= trim($parent_coma_keystr).' '.trim($child_coma_keystr).",\n";
						}
					}
					
					if(strpos($better_key, $this->construct) !== FALSE)
					{
						$better_key = str_replace(' '.$this->construct, NULL, $better_key);
					}
					$this->parts[substr($better_key, 0, -2)] = $c_codestr;
				}
			}
		}
	}
	
	public function change_braces($str)
	{
		/*
			This function was writen by Gumbo
			http://www.tutorials.de/forum/members/gumbo.html
			Thank you very much!
		
			finds the very outer braces and changes them to {[o# code #c]}
		*/
		$buffer = NULL;
		$depth = 0;
		$strlen_str = strlen($str);
		for($i = 0; $i < $strlen_str; $i++)
		{
			$char = $str[$i];
			switch ($char)
			{
				case '{':
					$depth++;
					$buffer .= ($depth === 1) ? '{[o#' : $char;
				break;
				case '}':
					$depth--;
					$buffer .= ($depth === 0) ? '#c]}' : $char;
				break;
				default:
					$buffer .= $char;
			}
		}
		return $buffer;
	}
	
	public function manage_order(array $parts)
	{
		/*
			this function brings the CSS nodes in the right order
			because the last value always wins
		*/
		foreach ($parts as $keystr => $codestr)
		{
			// ok let's find out who has the most 'extends' in his key
			// the more the higher this node will go
			$sep_keys = explode(",\n", $keystr);
			$order[$keystr] = count($sep_keys) * -1;
		}
		asort($order);
		foreach ($order as $keystr => $order_nr)
		{
			// with the sorted order we can now redeclare the values
			$sorted[$keystr] = $parts[$keystr];
		}
		// and give it back
		return $sorted;
	}
	
	public function final_parse($filename)
	{
		foreach($this->parts as $keystr => $codestr)
		{
			$codestr = trim($codestr);
			if($codestr !== '')
			{
				if( ! isset($this->css[$keystr]))
				{
					$this->css[$keystr] = array();
				}
				$codes = explode(';', $codestr);
				foreach($codes as $code)
				{
					$code = trim($code);
					if($code !== '')
					{
						$codeval = explode(':', $code);
						if(isset($codeval[1]))
						{
							$this->css[$keystr][trim($codeval[0])] = trim($codeval[1]);
						}
						else
						{
							$this->css[$keystr][trim($codeval[0])] = 'var_rule';
						}
					}
				}
			}
		}
		$this->final_file[$filename] = $this->create_css();
	}
	
	public function create_css()
	{
		$result = NULL;
		if(is_array($this->css))
		{
			foreach($this->css as $selector => $properties)
			{
				// feel free to modifie the indentations the way you like it
				$result .= "$selector {\n";
				foreach($properties as $property => $value)
				{
					$result .= "	$property: $value;\n";
				}
				$result .= "}\n";
			}
			$result = preg_replace('/\n+/', "\n", $result);
		}
		return $result;
	}
	
	public function create_file($content, $filename)
	{
		if($this->debugmode)
		{
			$this->debug['xcss_output'] .= "/*\nFILENAME:\n".$filename."\nCONTENT:\n".$content."*/\n//------------------------------------\n";
		}
		
		if($this->minify_output)
		{
			$content = str_replace(array("\n ", "\n", "\t", '  ', '   '), NULL, $content);
			$content = str_replace(array(' {', ';}', ': ', ', '), array('{', '}', ':', ','), $content);
		}
		
		if($filename === 'string')
		{
			return $content;
		}
		
		$filepath = $this->path_css_dir.$filename;
		if( ! file_exists($filepath))
		{
			if(is_dir(dirname($filepath)))
			{
				if( ! fopen($filepath, 'w'))
				{
					throw new xCSS_Exception('css_file_unwritable', array('file' => $filepath));
				}
			}
			else
			{
				throw new xCSS_Exception('css_dir_unwritable', array('file' => $filepath));
			}
		}
		else if( ! is_writable($filepath))
		{
			throw new xCSS_Exception('css_file_unwritable', array('file' => $filepath));
		}
		
		file_put_contents($filepath, utf8_decode($content));
	}
	
	public function microtime_float()
	{
		list($usec, $sec) = explode(' ', microtime());
		return ((float) $usec + (float) $sec);
	}
	
	public function __destruct()
	{
		if($this->debugmode)
		{
			$time = $this->microtime_float() - $this->debug['xcss_time_start'];
			echo '// Parsed xCSS in: '.round($time, 6).' seconds'."\n//------------------------------------\n".$this->debug['xcss_output'];
		}
	}
}

class xCSS_Exception extends Exception
{
	public function __construct($message, array $variables = NULL, $code = 0)
	{
		switch ($message)
		{
			case 'xcss_math_error':
				$message = 'xCSS Parse error: unable to solve this math operation: "'.$variables['math'].'"';
			break;
			case 'xcss_file_does_not_exist':
				$message = 'Cannot find "'.$variables['file'].'"';
			break;
			case 'css_file_unwritable':
			case 'css_dir_unwritable':
				$message = 'Cannot write to the output file "'.$variables['file'].'", check CHMOD permissions';
			break;
			case 'xcss_disabled':
				echo '// xCSS was disabled via "config.php"! Remove the xCSS <script> tag from your HMTL <head> tag';
				die();
			break;
			default:
				$message = 'xCSS Parse error: check the syntax of your xCSS files';
			break;
		}
		
		parent::__construct($message, $code);
	}
	
	public function __tostring()
	{
		echo sprintf("// %s\nalert(\"%s\");\n// in %s [ %d ]\n", get_class($this), addslashes($this->getMessage()), $this->getFile(), $this->getLine());
		die();
	}
}
Return current item: xCSS