You are here

CSSCompression.inc in Advanced CSS/JS Aggregation 7

Same filename and directory in other branches
  1. 6 advagg_css_compress/css-compressor-3.x/src/CSSCompression.inc

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

Namesort descending Description
CSSCompression