File

advagg_css_compress/css-compressor-3.x/src/CSSCompression.inc

View source
<?php


/**
 * CSS Compressor [VERSION]
 * [DATE]
 * Corey Hart @ http://www.codenothing.com
 */
// Static dependencies, Subclasses loaded ondemand
require DRUPAL_ROOT . '/' . dirname(__FILE__) . '/lib/Exception.inc';
require DRUPAL_ROOT . '/' . dirname(__FILE__) . '/lib/Control.inc';
class CSSCompression {
    
    /**
     * CSSCompression Info
     *
     * @const (string) VERSION: Release version
     * @const (string) DATE: Release date
     */
    const VERSION = "[VERSION]";
    const DATE = "[DATE]";
    
    /**
     * WARNING: This should ALWAYS BE FALSE in production
     * When DEV is true, backdoor access to private methods is opened.
     * Only used for unit testing and development.
     */
    const DEV = true;
    
    /**
     * TOKEN is a special string that gets used as a marker within
     * the compressor, and is removed before final output. Make sure
     * this token is unique to your stylsheets.
     *
     * NOTE: This string gets used in regular expressions, and escaping
     * won't help, so don't pick a complicated token.
     */
    const TOKEN = "@____CSSCOMPRESSION_TOKEN____@";
    
    /**
     * The default set of options for every instance.
     */
    public static $defaults = array(
        // Converts long color names to short hex names
        // (aliceblue -> #f0f8ff)
'color-long2hex' => true,
        // Converts rgb colors to hex
        // (rgb(159,80,98) -> #9F5062, rgb(100%) -> #FFFFFF)
'color-rgb2hex' => true,
        // Converts long hex codes to short color names (#f5f5dc -> beige)
        // Only works on latest browsers, careful when using
'color-hex2shortcolor' => false,
        // Converts long hex codes to short hex codes
        // (#44ff11 -> #4f1)
'color-hex2shorthex' => true,
        // Converts hex codes to safe CSS Level 1 color names
        // (#F00 -> red)
'color-hex2safe' => true,
        // Converts font-weight names to numbers
        // (bold -> 700)
'fontweight2num' => true,
        // Removes zero decimals and 0 units
        // (15.0px -> 15px || 0px -> 0)
'format-units' => true,
        // Lowercases html tags from list
        // (BODY -> body)
'lowercase-selectors' => true,
        // Converts id and class attribute selectors, to their short selector counterpart
        // (div[id=blah][class=moreblah] -> div#blah.moreblah)
'attr2selector' => true,
        // Promotes nested id's to the front of the selector
        // (body>div#elem p -> $elem p)
'strict-id' => false,
        // Add space after pseudo selectors, for ie6
        // (a:first-child{ -> a:first-child {)
'pseudo-space' => false,
        // Compresses single defined multi-directional properties
        // (margin: 15px 25px 15px 25px -> margin:15px 25px)
'directional-compress' => true,
        // Combines multiply defined selectors and details
        // (p{color:blue;} p{font-size:12pt} -> p{color:blue;font-size:12pt;})
        // (p{color:blue;} a{color:blue;} -> p,a{color:blue;})
'organize' => true,
        // Combines color/style/width properties
        // (border-style:dashed;border-color:black;border-width:4px; -> border:4px dashed black)
'csw-combine' => true,
        // Combines cue/pause properties
        // (cue-before: url(before.au); cue-after: url(after.au) -> cue:url(before.au) url(after.au))
'auralcp-combine' => true,
        // Combines margin/padding directionals
        // (margin-top:10px;margin-right:5px;margin-bottom:4px;margin-left:1px; -> margin:10px 5px 4px 1px;)
'mp-combine' => true,
        // Combines border directionals
        // (border-top|right|bottom|left:1px solid black -> border:1px solid black)
'border-combine' => true,
        // Combines font properties
        // (font-size:12pt; font-family: arial; -> font:12pt arial)
'font-combine' => true,
        // Combines background properties
        // (background-color: black; background-image: url(bgimg.jpeg); -> background:black url(bgimg.jpeg))
'background-combine' => true,
        // Combines list-style properties
        // (list-style-type: round; list-style-position: outside -> list-style:round outside)
'list-combine' => true,
        // Combines border-radius properties
        // {
        //  border-top-left-radius: 10px;
        //  border-top-right-radius: 10px;
        //  border-bottom-right-radius: 10px;
        //  border-bottom-left-radius: 10px;
        // }
        // -> { border-radius: 10px; }
'border-radius-combine' => true,
        // Removes the last semicolon of a property set
        // ({margin: 2px; color: blue;} -> {margin: 2px; color: blue})
'unnecessary-semicolons' => true,
        // Removes multiple declarations within the same rule set
'rm-multi-define' => true,
        // Adds all unknown blocks to the top of the output in a comment strip
        // Purely for bug reporting, but also useful to know what isn't being handled
'add-unknown' => true,
        // Readibility of Compressed Output, Defaults to none
'readability' => 0,
    );
    
    /**
     * Modes are predefined sets of configuration for referencing. When creating a mode, all options are set to true,
     * and the mode array defines which options are to be false
     *
     * @mode safe: Safe mode does zero combinations or organizing. It's the best mode if you use a lot of hacks
     * @mode sane: Sane mode does most combinations(multiple long hand notations to single shorthand),
     * --- but still keeps most declarations in their place
     * @mode small: Small mode reorganizes the whole sheet, combines as much as it can, and will break most comment hacks
     * @mode full: Full mode does everything small does, but also converts hex codes to their short color name alternatives
     */
    private static $modes = array(
        'safe' => array(
            'color-hex2shortcolor' => false,
            'attr2selector' => false,
            'strict-id' => false,
            'organize' => false,
            'csw-combine' => false,
            'auralcp-combine' => false,
            'mp-combine' => false,
            'border-combine' => false,
            'font-combine' => false,
            'background-combine' => false,
            'list-combine' => false,
            'border-radius-combine' => false,
            'rm-multi-define' => false,
        ),
        'sane' => array(
            'color-hex2shortcolor' => false,
            'strict-id' => false,
            'organize' => false,
            'font-combine' => false,
            'background-combine' => false,
            'list-combine' => false,
            'rm-multi-define' => false,
        ),
        'small' => array(
            'color-hex2shortcolor' => false,
            'pseudo-space' => false,
        ),
        'full' => array(
            'pseudo-space' => false,
        ),
    );
    
    /**
     * Readability Constants
     *
     * @param (int) READ_MAX: Maximum readability of output
     * @param (int) READ_MED: Medium readability of output
     * @param (int) READ_MIN: Minimal readability of output
     * @param (int) READ_NONE: No readability of output (full compression into single line)
     */
    const READ_MAX = 3;
    const READ_MED = 2;
    const READ_MIN = 1;
    const READ_NONE = 0;
    
    /**
     * Static Helpers
     *
     * @instance express: Use a separate instance from singleton access
     * @instance instance: Saved instance of CSSCompression
     * @param (array) instances: Array of stored instances
     * @param (array) rjson: Comment removal before json decoding
     */
    private static $express;
    private static $instance;
    private static $instances = array();
    private static $rjson = array(
        'patterns' => array(
            "/^(.*?){/s",
            "/(\t|\\s)+\\/\\/.*/",
        ),
        'replacements' => array(
            '{',
            '',
        ),
    );
    
    /**
     * Controller Instance
     */
    private $Control;
    
    /**
     * Builds the subclasses, runs the compression if css passed, and merges options
     *
     * @param (string) css: CSS to compress on initialization if needed
     * @param (array) options: Array of preferences to override the defaults
     */
    public function __construct($css = NULL, $options = NULL) {
        $this->Control = new CSSCompression_Control($this);
        // Autorun against css passed
        if ($css) {
            // Allow passing options/mode only
            if (is_array($css) || array_key_exists($css, self::$modes)) {
                $this->Control->Option
                    ->merge($css);
            }
            else {
                $this->Control
                    ->compress($css, $options);
            }
        }
        else {
            if ($options) {
                $this->Control->Option
                    ->merge($options);
            }
        }
    }
    
    /**
     * (Proxy function) Control access to properties
     *
     *	- Getting stats/_mode/css returns the current value of that property
     *	- Getting options will return the current full options array
     *	- Getting anything else returns that current value in the options array or NULL
     *
     * @param (string) name: Name of property that you want to access
     */
    public function __get($name) {
        return $this->Control
            ->get($name);
    }
    
    /**
     * (Proxy function) The setter method only allows
     * access to setting values in the options array
     *
     * @param (string) name: Key name of the option you want to set
     * @param (mixed) value: Value of the option you want to set
     */
    public function __set($name, $value) {
        return $this->Control
            ->set($name, $value);
    }
    
    /**
     * (Proxy function) Merges a predefined set options
     *
     * @param (string) mode: Name of mode to use.
     */
    public function mode($mode = NULL) {
        return $this->Control->Option
            ->merge($mode);
    }
    
    /**
     * Creates a new mode, or overwrites existing mode
     *
     * @param (mixed) mode: Name of the mode, or array of modes
     * @param (array) config: Configuration of the mode
     */
    public static function modes($mode = NULL, $config = NULL) {
        if ($mode === NULL) {
            return self::$modes;
        }
        else {
            if (is_array($mode)) {
                return array_merge(self::$modes, $mode);
            }
            else {
                if ($config === NULL) {
                    return isset(self::$modes[$mode]) ? self::$modes[$mode] : NULL;
                }
                else {
                    return self::$modes[$mode] = $config;
                }
            }
        }
    }
    
    /**
     * (Proxy function) Maintainable access to the options array
     *
     *	- Passing no arguments returns the entire options array
     *	- Passing a single name argument returns the value for the option
     *	- Passing an array will merge the options with the array passed, for object like extension
     * 	- Passing both a name and value, sets the value to the name key, and returns the value
     *
     * @param (mixed) name: The key name of the option
     * @param (mixed) value: Value to set the option
     */
    public function option($name = NULL, $value = NULL) {
        return $this->Control->Option
            ->option($name, $value);
    }
    
    /**
     * (Proxy function) Run compression on the sheet passed.
     *
     * @param (string) css: Stylesheet to be compressed
     * @param (mixed) options: Array of options or mode to use.
     */
    public function compress($css = NULL, $options = NULL) {
        return $this->Control
            ->compress($css, $options);
    }
    
    /**
     * Static access for direct compression
     *
     * @param (string) css: Stylesheet to be compressed
     * @param (mixed) options: Array of options or mode to use.
     */
    public static function express($css = NULL, $options = NULL) {
        if (!self::$express) {
            self::$express = new CSSCompression();
        }
        self::$express->reset();
        return self::$express->compress($css, $options);
    }
    
    /**
     * (Proxy Function) Cleans out compressor and it's subclasses to defaults
     *
     * @params none
     */
    public function reset() {
        return $this->Control
            ->reset();
    }
    
    /**
     * (Proxy Function) Cleans out class variables for next run
     *
     * @params none
     */
    public function flush() {
        return $this->Control
            ->flush();
    }
    
    /**
     * The Singleton access method (for those that want it)
     *
     * @param (string) name: Name of the stored instance
     */
    public static function getInstance($name = NULL) {
        if ($name !== NULL) {
            if (!isset(self::$instances[$name])) {
                self::$instances[$name] = new self();
            }
            return self::$instances[$name];
        }
        else {
            if (!self::$instance) {
                self::$instance = new self();
            }
        }
        return self::$instance;
    }
    
    /**
     * Reads JOSN based files, strips comments and converts to array
     *
     * @param (string) file: Filename
     */
    public static function getJSON($file) {
        // Assume helper file if full path not given
        $file = $file[0] == '/' ? $file : dirname(__FILE__) . '/helpers/' . $file;
        // Strip comments
        $json = preg_replace(self::$rjson['patterns'], self::$rjson['replacements'], file_get_contents($file));
        // Decode json
        $json = json_decode($json, true);
        // Check for errors
        if ($json === NULL) {
            $e = '';
            // JSON Errors, taken directly from http://php.net/manual/en/function.json-last-error.php
            switch (json_last_error()) {
                case JSON_ERROR_NONE:
                    $e = 'No error has occurred';
                    break;
                case JSON_ERROR_DEPTH:
                    $e = 'The maximum stack depth has been exceeded';
                    break;
                case JSON_ERROR_CTRL_CHAR:
                    $e = 'Control character error, possibly incorrectly encoded';
                    break;
                case JSON_ERROR_STATE_MISMATCH:
                    $e = 'Invalid or malformed JSON';
                    break;
                case JSON_ERROR_SYNTAX:
                    $e = 'Syntax error';
                    break;
                case JSON_ERROR_UTF8:
                    $e = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                    break;
                default:
                    $e = 'Unknown JSON Error';
                    break;
            }
            throw new CSSCompression_Exception("JSON Error in {$file}: {$e}");
        }
        // Good to go
        return $json;
    }
    
    /**
     * Backdoor access to subclasses
     * ONLY FOR DEVELOPMENT/TESTING.
     *
     * @param (string) class: Name of the focus class
     * @param (string) method: Method function to call
     * @param (array) args: Array of arguments to pass in
     */
    public function access($class = NULL, $method = NULL, $args = NULL) {
        if (!self::DEV) {
            throw new CSSCompression_Exception("CSSCompression is not in development mode.");
        }
        else {
            if ($class === NULL || $method === NULL || $args === NULL) {
                throw new CSSCompression_Exception("Invalid Access Call.");
            }
            else {
                if (!is_array($args)) {
                    throw new CSSCompression_Exception("Expecting array of arguments.");
                }
            }
        }
        return $this->Control
            ->access($class, $method, $args);
    }

}

Classes

Title Deprecated Summary
CSSCompression