[code]<?php

/** CSS parser class
  * used to parse normal css
  * @author 		Ruud Verbij
  * @start_date 	4 Oct 2007
  * @version 	    0.1a
  * @documentation	www.ruudverbij.nl/css/documentation.doc (very little)
  */

class CSSParser {

    // Class instance variables
    /*  Array with names of the css files to be parsed
      * initiated by the constructor and files added by addCSSFile()
      * @invariant cssNames != null && count(cssNames) > 0
      */
    private $cssNames = Array();

    /*  Array with content of the css files to be parsed (raw)
      * iniated by the constructor and updated by addCSSFile()
      * @invariant cssContents != null && count(cssContents) > 0
      */
    private $cssContents = Array();

    /*  Boolean that says if the output should be combined
      * if set true; setCombinedFileName() should be called
      * iniated by the constructor or updated by setCombinedFileName()
      * @invariant if(combined) getCombinedFileName != null
      */
    private $combine;

    /*  String containing the filename for the output if combine is set true
      * @invariant if(getCombined() == true) combinedFileName != null
      */
    private $combinedFileName = null;

    /*  Boolean that says if the parser should delete comments
      * initiated by the constructor and updated by cutComments()
      */
    private $cutComments;


    // Class constructor
    /** Constructor used for getting an instance of this class
      * @param Array - contains all files to be parsed (numeric)
      * @param Boolean - says if the fileoutput should be combined
      * @param Boolean - says if the parser should cut out comments
      * @require setCombinedFileName() must be called if combine is set true
      * @require count(files) > 0
      * @require file_exists(each file from $files)
      * @ensure getFileNames() == files
      * @ensure getCombined() == combine
      */
    public function __construct($files,$combine = false, $cutComments = true) {
        // set all instance variables
        $this->cssNames    = $files;
        $this->combine     = $combine;
        $this->cutComments = $cutComments;

        // get content from filenames and place them in the instance variable
        for($i = 0; $i < count($this->getFileNames()); $i++) {
            $this->cssContents[$i] = file_get_contents($this->cssNames[$i]);
        }
    }


    // Query functions
    /** function to check if combined has been set true
      * @return Boolean
      */
    public function getCombined() {
        return $this->combine;
    }

    /** function to check the filename of the combinedFileName
      * @return String or NULL
      */
    public function getCombinedFileName() {
        return $this->combinedFileName;
    }

    /** function to get all filenames that will be parsed
      * @return Array
      */
    public function getFileNames() {
        return $this->cssNames;
    }

    /** function to check if the parser will cut out comments
      * @return Boolean
      */
    public function getCutComments() {
        return $this->cutComments;
    }


    // Command functions
    /** function to set the combinedFileName
      * @param String
      * @ensure getCombined() == true
      * @ensure getCombinedFileName() == $filename
      */
    public function setCombinedFileName($filename) {
        $this->combine = true;
        $this->combinedFileName = (substr($filename,strlen($filename)-3) == 'css') ? substr($filename,0,strlen($filename)-1) : $filename;
    }

    /** function to add a cssfile that needs to be parsed
      * @param String
      * @ensure getFileNames()[i] == $filename
      */
    public function addCSSFile($filename) {
        $this->cssNames[] = $filename;
        $this->cssContents[] = file_get_contents($filename);
    }

    /** function to set the parser to cut comments or not
      * @param Boolean
      */
    public function setCutComments($cutComments) {
        $this->cutComments = $cutComments;
    }


    // Class functions
    /** function to parse the files added by addCSSFile and the constructor
      * @ensure if(getCombined()) then getCombinedFileName() contains all parsed css
      *         else getFileNames()[i] contains parsed css for file_get_contents(getFileNames()[i])
      */
    public function parseFiles() {
        $this->writeOutput($this->parse());
    }
    
    /*  function to write all output
      * @param Array - containing arrays per file (numeric) and per file (characters) 'css' and 'filename'
      * @ensure files will be written
      */
    private function writeOutput($output) {
        // then you can combine them
        if($this->getCombined()) {
            // combine all css
            $sub_output;
            for($i = 0; $i < count($output); $i++) {
                $sub_output .= '\n' . $output[$i]['css'];
            }

            // write sub_output to the getCombinedFileName() and build the file if it does not exists
            $handle = fopen($this->getCombinedFileName(), 'w');
            fwrite($handle, $sub_output);
            fclose($handle);
        } else {
            // write output to each file
            for($i = 0; $i < count($output); $i++) {
                $handle = fopen($output[$i]['filename'], 'w');
                fwrite($handle, $output[$i]['css']);
                fclose($handle);
            }
        }
    }
    
    /** echo the parsed files */
    public function echoParsedFiles() {
        $output = $this->parse();
        $sub_output;
        for($i = 0; $i < count($output); $i++) {
            $sub_output .= $output[$i]['css'];
        }
        echo $sub_output;
    }
    
    /* parse the filenames as prepared */
    private function parse() {
        $output = array();

        // first parse all files seperate
        for($i = 0; $i < count($this->getFileNames()); $i++) {
            $filenames = $this->getFileNames();
            $contents  = $this->cssContents[$i];
            $css = $this->splitFile($contents);
            $sub_output = ($this->getCutComments()) ? $this->cutComments($css[1]) : $css[1];
            $sub_output = $this->extendClasses($css[0],$sub_output);
            $sub_output = $this->parseVariables($css[0],$sub_output);
            $output[] = array('css'=> $sub_output,'filename'=>substr($filenames[$i],0,strlen($filenames[$i])-1));
        }
        
        return $output;
    }

    /*  function to split the file in 2 parts: the extended part and the actual css part
      * @param String - file to be split
      * @require $file != null
      * @return Array - return[0] = extended, return[1] = actual
    */
    static private function splitFile($file) {
        $filebrokencontent = preg_split('/\*\*\*[\s]?(ACTUAL|EXTENDED)[\s]?CSS[\s]?\*\*\*/i',$file);
        return array($filebrokencontent[1],$filebrokencontent[2]);
    }

    /*  function to cut out comments
      * @param String - containing the actual-part of the css file where comments should be cut out
      * @require $actual != null
      * @ensure return does not contain css-comments
      * @return String - the actual-css part without comments
      */
    static private function cutComments($actual) {
        return preg_replace('/\/\\*.*\*\//s','',$actual);
    }

    /*  function to parse variables declared in the extended part within the actual part
      * @param String - the extended part of the css
      * @param String - the actual part of the css
      * @require ($extended && $actual) != null
      * @return String - the actual part of the css file parsed with the variables declared in the extended part
      */
    static private function parseVariables($extended,$actual) {
        preg_match_all('/(\$[a-zA-Z]+[a-zA-Z0-9_]*)[\s]*:[\s]*([#0-9a-zA-Z\']+)/',$extended,$matches);
        $variables = $matches[1];
        $variables_content = $matches[2];
        for($i = 0; $i < count($variables); $i++)
            $actual = preg_replace('/\\'.$variables[$i].'/i',$variables_content[$i],$actual);
        return $actual;
    }

    /*  function to extend classes in the actual part with classes from the extended part
      * @param String - the extended part of the css
      * @param String - the actual part of the css
      * @require ($extended && $actual) != null
      * @return String - the actual part of the css file extended by the classes in the extended part
      */
    static private function extendClasses($extended,$actual) {
        preg_match_all('/([\.|#][a-zA-Z_]+)[\s]*{[\s]*([^}.]*)[\s]*}/s',$extended,$matches);
        $classes = $matches[1];
        $classes_style = $matches[2];
        for($i = 0; $i < count($classes); $i++)
           $actual = preg_replace('/extend-from: \''.$classes[$i].'\';/i',$classes_style[$i],$actual);
        $actual = preg_replace('/extended-from:[\s]*;/i','',$actual);
        return $actual;
    }
}

$file = $_GET['file'];
if(preg_match('/([a-zA-Z]+\.css)/',$file,$match)) {
    $cssparser = new CSSParser(array($file));
    $cssparser->parseFiles();
    echo file_get_contents(substr($file,0,strlen($file)-1));
}

?>[/code]