<?
########## Confixx(R) 3.0 Professional ############
####### Copyright SWsoft, Inc. 2004-2005 ##########
#### http://www.sw-soft.com - info@sw-soft.com ####


/*
 *  Confixx(R) 2.0 Professional
 *  Copyright Yippi-Yeah! E-Business GmbH 2001-2003
 *  http://www.confixx.de - info@yippi-yeah.com
 *
 *  This module contains classes for parsing CSS files
 */

class CssParser 
{
	var $parser = NULL;
	var $css_struct = NULL;	
	
	function CssParser() {
		$this->css    = new Css;
		$this->parser = new CssParser_RegexpImpl($this->css);
	}

	/*
	 * Parse specified string. String should contain CSS.
	 */
	function parseString($string) {
		return $this->parser->parse_string($string);
	}

	/*
	 * Get an array of errors.
	 */
	function getErrors() {
		return $this->parser->get_errors();
	}

	/*
	 * Return a string of CSS.
	 */
	function outputString() {
		return $this->css->output_string();	
	}
	
	/*
	 * Get a value of a specified property.
	 */
	function getPropertyValue($selector, $property) {
		$selector_obj =& $this->css->get_selector($selector);
		return (is_object($selector_obj))
			? $selector_obj->get_property($property)
			: '';
		
	}

	/*
	 * Set a value of a specified property.
	 */
	function setPropertyValue($selector, $property, $value) {
		$selector_obj =& $this->css->get_selector($selector);
		if (!is_object($selector_obj)) 
			$selector_obj = new CssSelector($selector);
		$selector_obj->set_property($property, $value);	
		$this->css->set_selector($selector_obj);
		return 1;
	}
}

/*
 * CSS Parser implementation based on Perl Regular Expressions. 
 */
 
class CssParser_RegexpImpl
{
	var $errors = array();
	var $css_struct = NULL;
	
	function CssParser_RegexpImpl(&$css_struct)
	{
		$this->css_struct =& $css_struct;
	}
	
	function parse_string($string)
	{
		// Flatten (collapse) whitespace and  /* comment */ style comments
		$string = preg_replace("/\r\n|\r|\n/", " ", $string);
		$string = preg_replace("!/\*.*?\*\/!", "",  $string);

		// Split into styles
		$styles = preg_grep("/\S/", preg_split("/(?<=\})/", $string));
		foreach ($styles as $style_str) {
			if (!preg_match("/^\s*([^{]+?)\s*\{(.*)\}\s*$/", $style_str, $matches)) {
				return $this->parse_error("Invalid or unexpected style data '$_'");
			}
			$this->add_style($matches[1], $matches[2]);
		}
		return 1;
	}

	function add_style($style, $content) 
	{
		$style_obj = new CssStyle;

		// parse the selectors
		$selectors = preg_split("/\s*,\s*/", $style);
		foreach ($selectors as $selector){
			$style_obj->add_selector($selector);
		}

		// parse the properties
		$properties = preg_grep("/\S/", preg_split("/\;/", $content));
		foreach ($properties as $property) {
			if (!preg_match("/^\s*([\w._-]+)\s*:\s*(.*?)\s*$/", $property, $matches)) {
				return $this->parse_error(
						"Invalid or unexpected property '$matches[0]' in style '$style'" );
			}
			$style_obj->add_property($matches[1], $matches[2]);
		}

		$this->css_struct->add_style($style_obj);
		return 1;
	}

	function parse_error($message) 	{
		array_push($this->errors, $message);
		return 0;	// error mark
	}
	
	function get_errors() {
		return $this->errors;
	}
}

/*
 * Container for CSS styles
 */
 
class CssStyle
{
	var $selectors  = array();
	var $properties = array();
	
	function add_selector($selector) {
        array_push($this->selectors, $selector);
	}
	
	function add_property($property, $value) {
    	$this->properties[$property] = $value;
	}

	function get_selectors() {
		return $this->selectors;
	}

	function get_properties() {
		return $this->properties;
	}
}

/*
 * Container for CSS selector
 */
 
 class CssSelector
{
	var $name = '';
	var $properties = array();
	
	function CssSelector($name, $properties = array()) {
		$this->name = "$name";
		$this->properties = $properties;
	}
	
	function set_property($property, $value) {
		if ("$value" != '')
    		$this->properties[$property] = $value;
		else 
			$this->del_property($property);
		return 1;
	}

	function del_property($property_name) {
		unset($this->properties[$property_name]);
		return 1;
	}
	
	function get_property($property_name) {
    	return array_key_exists($property_name, $this->properties)
			? $this->properties[$property_name]
			: NULL;
	}
	
	function get_name() {
		return $this->name;
	}

	function output_string() {
		$strings = array();
		foreach ($this->properties as $prop => $value) 
			array_push($strings, "\t$prop:\t$value;");
		return $this->name." {\n".implode("\n", $strings)."\n}\n";
	}
}

/*
 * Container for CSS tree structure
 */
 
class Css
{
	var $selectors = array();	# of CssSelector objects

	function add_style($style) 
	{
		$properties = $style->get_properties();
		foreach ($style->get_selectors() as $selector_name) {
			$selector_obj = new CssSelector($selector_name, $properties);
			$this->set_selector($selector_obj);
		}
	}

	function set_selector($selector_obj) {
		$this->selectors[$selector_obj->get_name()] = $selector_obj;
	}
	
	function &get_selector($selector_name) {
    	return array_key_exists($selector_name, $this->selectors)
			? $this->selectors[$selector_name]
			: NULL;
	}

	function output_string()
	{
		$strings = array();
		foreach ($this->selectors as $sel_name => $sel_obj) {
			array_push($strings, $sel_obj->output_string());
		}
		return implode("\n", $strings);
	}
}

?>
