Now all modules are in core modules folder

This commit is contained in:
Manuel Cillero 2017-08-08 12:14:45 +02:00
parent 5ba1cdfa0b
commit 05b6a91b0c
1907 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,77 @@
<?php
/**
* @file
* Admin page callbacks for the advagg CSS compression module.
*/
/**
* Page generation function for admin/settings/css-compress
*/
function advagg_css_compress_admin_page() {
$output = '';
return $output . drupal_get_form('advagg_css_compress_admin_settings_form');
}
/**
* Form builder; Configure advagg settings.
*
* @ingroup forms
* @see system_settings_form()
*/
function advagg_css_compress_admin_settings_form() {
$form = array();
$form['advagg_css_compress_agg_files'] = array(
'#type' => 'checkbox',
'#title' => t('Compress CSS Files'),
'#default_value' => variable_get('advagg_css_compress_agg_files', ADVAGG_CSS_COMPRESS_AGG_FILES),
);
$form['advagg_css_compress_inline'] = array(
'#type' => 'checkbox',
'#title' => t('Compress Inline CSS'),
'#default_value' => variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE),
);
$advagg_css_compressor = variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR);
$form['advagg_css_compressor'] = array(
'#type' => 'radios',
'#title' => t('Select the compression library to use'),
'#default_value' => $advagg_css_compressor,
'#options' => array(
0 => 'CSSTidy',
1 => 'CSS Compressor',
2 => 'YUI CSSMin',
),
'#description' => t('<a href="@csstidy">CSSTidy</a> is a well known CSS compression library, but it has some known issues/limitations.<br \> <a href="@csscompressor">CSS Compressor</a> is a faster CSS compression alternative.<br \> <a href="@cssmin">YUI CSSMin</a> is a php port of the java library from yahoo and is fairly quick.<br \>',
array(
'@csstidy' => 'https://github.com/Cerdic/CSSTidy',
'@csscompressor' => 'http://www.codenothing.com/css-compressor/',
'@cssmin' => 'http://code.google.com/p/minify/',
)
),
);
if ($advagg_css_compressor == 0) {
$form['advagg_css_compress_preserve_css'] = array(
'#type' => 'checkbox',
'#title' => t('CSSTidy: Preserve CSS'),
'#default_value' => variable_get('advagg_css_compress_preserve_css', ADVAGG_CSS_COMPRESS_PRESERVE_CSS),
'#description' => t('If disabled CSS selectors will try to be merged together, significantly reducing the css file size. May result in broken layouts is disabled. This only applies to compression through the CSSTidy library.'),
);
}
elseif ($advagg_css_compressor == 1) {
$form['advagg_css_compress_compressor_level'] = array(
'#type' => 'radios',
'#title' => t('CSS Compressor: Select the CSS Compression to use'),
'#default_value' => variable_get('advagg_css_compress_compressor_level', ADVAGG_CSS_COMPRESS_COMPRESSOR_LEVEL),
'#options' => array(
'safe' => t('Safe mode (99% safe) does zero combinations or organizing. Its the best mode if you use a lot of CSS hacks.'),
'sane' => t('Sane mode (90% safe) does most combinations (multiple long hand notations to single shorthand), but still keeps most declarations in their place.'),
'small' => t('Small mode (65% safe) reorganizes the whole style sheet, combines as much as it can, and will break most comment hacks.'),
'full' => t('Full mode (64% safe) does everything small does, but also converts hex codes to their short color name alternatives.'),
),
'#description' => t('This only applies to compression through the CSS Compressor library.'),
);
}
return system_settings_form($form);
}

View file

@ -0,0 +1,13 @@
name = AdvAgg Compress CSS
description = Compress CSS with a 3rd party compressor, CSSTidy currently.
package = Advanced CSS/JS Aggregation
core = 6.x
dependencies[] = advagg
php = 5.0
; Information added by Drupal.org packaging script on 2017-03-18
version = "6.x-1.11"
core = "6.x"
project = "advagg"
datestamp = "1489800488"

View file

@ -0,0 +1,91 @@
<?php
/**
* @file
* Handles AdvAgg CSS compress installation and upgrade tasks.
*/
/**
* Implementation of hook_enable().
*/
function advagg_css_compress_enable() {
// Flush advagg caches.
$cache_tables = advagg_flush_caches();
foreach ($cache_tables as $table) {
cache_clear_all('*', $table, TRUE);
}
}
/**
* Implementation of hook_disable().
*/
function advagg_css_compress_disable() {
// Flush advagg caches.
$cache_tables = advagg_flush_caches();
foreach ($cache_tables as $table) {
cache_clear_all('*', $table, TRUE);
}
}
/**
* Implementation of hook_install().
*/
function advagg_css_compress_install() {
drupal_install_schema('advagg_css_compress');
}
/**
* Implementation of hook_uninstall().
*/
function advagg_css_compress_uninstall() {
// Remove variables.
variable_del('advagg_css_compress_compressor_level');
variable_del('advagg_css_compress_preserve_css');
variable_del('advagg_css_compress_inline_cache');
variable_del('advagg_css_compress_agg_files');
variable_del('advagg_css_compress_inline');
variable_del('advagg_css_compressor');
// Remove our cache table.
cache_clear_all('*', 'cache_advagg_css_compress_inline', TRUE);
drupal_uninstall_schema('advagg_css_compress');
}
/**
* Implementation of hook_requirements().
*/
function advagg_css_compress_requirements($phase) {
$requirements = array();
// Ensure translations don't break at install time
$t = get_t();
if ($phase == 'runtime') {
}
return $requirements;
}
/**
* Implementation of hook_schema().
*/
function advagg_css_compress_schema() {
$schema = array();
// Create cache tables.
$schema['cache_advagg_css_compress_inline'] = drupal_get_schema_unprocessed('system', 'cache');
$schema['cache_advagg_css_compress_inline']['description'] = t('Cache table for Advanced CSS/JS Aggregations CSS Compress module. Used to keep inline versions of compressed CSS.');
return $schema;
}
/**
* Update 6100 - Create the cache_advagg_css_compress_inline cache table.
*/
function advagg_css_compress_update_6100() {
$ret = array();
// Create cache table.
$schema = advagg_css_compress_schema();
db_create_table($ret, 'cache_advagg_css_compress_inline', $schema['cache_advagg_css_compress_inline']);
return $ret;
}

View file

@ -0,0 +1,180 @@
<?php
/**
* @file
* Advanced aggregation css compression module.
*
*/
/**
* Default value to see if CSSTidy will preserve CSS.
*/
define('ADVAGG_CSS_COMPRESS_PRESERVE_CSS', TRUE);
/**
* Default value to see if this will compress aggregated files.
*/
define('ADVAGG_CSS_COMPRESS_AGG_FILES', TRUE);
/**
* Default value to see if this will compress inline css.
*/
define('ADVAGG_CSS_COMPRESS_INLINE', TRUE);
/**
* Default value to see if this will cache the compressed inline css.
*/
define('ADVAGG_CSS_COMPRESS_INLINE_CACHE', TRUE);
/**
* Default value for which css compression library to use.
*/
define('ADVAGG_CSS_COMPRESSOR', 2);
/**
* Default value for which css compression library to use.
*/
define('ADVAGG_CSS_COMPRESS_COMPRESSOR_LEVEL', 'sane');
/**
* Implementation of hook_menu
*/
function advagg_css_compress_menu() {
$items = array();
$file_path = drupal_get_path('module', 'advagg_css_compress');
$items['admin/settings/advagg/css-compress'] = array(
'title' => 'CSS Compression',
'description' => 'Adjust CSS Compression settings.',
'page callback' => 'advagg_css_compress_admin_page',
'type' => MENU_LOCAL_TASK,
'access arguments' => array('administer site configuration'),
'file path' => $file_path,
'file' => 'advagg_css_compress.admin.inc',
'weight' => 10,
);
return $items;
}
/**
* Implement hook_advagg_css_alter.
*/
function advagg_css_compress_advagg_css_alter(&$contents, $files, $bundle_md5) {
if (!variable_get('advagg_css_compress_agg_files', ADVAGG_CSS_COMPRESS_AGG_FILES)) {
return;
}
$compressor = variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR);
if ($compressor == 0) {
advagg_css_compress_css_tidy($contents);
}
elseif ($compressor == 1) {
advagg_css_compress_css_compressor($contents);
}
elseif ($compressor == 2) {
advagg_css_compress_yui_cssmin($contents);
}
}
/**
* Implement hook_advagg_css_inline_alter.
*/
function advagg_css_compress_advagg_css_inline_alter(&$contents) {
if (!variable_get('advagg_css_compress_inline', ADVAGG_CSS_COMPRESS_INLINE)) {
return;
}
$compressor = variable_get('advagg_css_compressor', ADVAGG_CSS_COMPRESSOR);
// If using a cache, try to get the contents of it.
if (variable_get('advagg_css_compress_inline_cache', ADVAGG_CSS_COMPRESS_INLINE_CACHE)) {
$key = md5($contents) . $compressor;
$table = 'cache_advagg_css_compress_inline';
$data = cache_get($key, $table);
if (!empty($data->data)) {
$contents = $data->data;
return;
}
}
if ($compressor == 0) {
advagg_css_compress_css_tidy($contents);
}
if ($compressor == 1) {
advagg_css_compress_css_compressor($contents);
}
// If using a cache set it.
if (isset($key)) {
cache_set($key, $contents, $table, CACHE_TEMPORARY);
}
}
/**
* Use the CSS Tidy library to compress the CSS.
*
* TODO have set_cfg be fully configurable from GUI.
*/
function advagg_css_compress_css_tidy(&$contents) {
// Initialize CSSTidy.
$filename = drupal_get_path('module', 'advagg_css_compress') . '/csstidy/class.csstidy.inc';
include_once($filename);
$css = new csstidy();
// Try to allocate enough time to run CSSTidy.
if (function_exists('set_time_limit')) {
@set_time_limit(variable_get('advagg_set_time_limit', ADVAGG_SET_TIME_LIMIT));
}
// Set configuration.
$css->set_cfg('preserve_css', variable_get('advagg_css_compress_preserve_css', ADVAGG_CSS_COMPRESS_PRESERVE_CSS));
$css->set_cfg('remove_last_;', TRUE);
$css->set_cfg('merge_selectors', TRUE);
$css->set_cfg('optimise_shorthands', TRUE);
$css->set_cfg('silent', TRUE);
$css->set_cfg('compress_colors', TRUE);
$css->set_cfg('sort_selectors', FALSE);
$css->set_cfg('sort_properties', FALSE);
$css->set_cfg('discard_invalid_properties', FALSE);
$css->set_cfg('timestamp', FALSE);
$css->load_template("highest_compression");
// Compress CSS.
$css->parse($contents);
$contents = $css->print->plain();
}
/**
* Use the CSS Compressor library to compress the CSS.
*
* TODO have compression level be selectable from GUI.
*/
function advagg_css_compress_css_compressor(&$contents) {
// Initialize CSSTidy.
$filename = drupal_get_path('module', 'advagg_css_compress') . '/css-compressor-3.x/src/CSSCompression.inc';
include_once($filename);
$CSSC = new CSSCompression(variable_get('advagg_css_compress_compressor_level', ADVAGG_CSS_COMPRESS_COMPRESSOR_LEVEL));
$contents = $CSSC->compress($contents);
}
/**
* Use the CSSmin library from YUI to compress the CSS.
*/
function advagg_css_compress_yui_cssmin(&$contents) {
// Include CSSmin from YUI.
$filename = drupal_get_path('module', 'advagg_css_compress') . '/yui/CSSMin.inc';
include_once($filename);
$cssmin = new CSSmin();
// Compress the CSS splitting lines after 4k of text
$contents = $cssmin->run($contents, 4096);
}
/**
* Implementation of hook_flush_caches().
*/
function advagg_css_compress_flush_caches() {
return array('cache_advagg_css_compress_inline');
}

View file

@ -0,0 +1,34 @@
#
# CSS Compressor [VERSION]
# [DATE]
# Corey Hart @ http://www.codenothing.com
#
.PHONY: benchmark test
VERSION=temp
all:
@echo "\n\x1B[1;31mPC_LOAD_LETTER\x1B[0m\n"
test:
@php unit/start.inc
test-all:
@php unit/start.inc all
test-focus:
@php unit/focus.inc
test-file:
@php unit/file.inc
test-regression:
@php unit/benchmark/benchmark.inc $(VERSION)
test-cli:
@php cli/cli.inc -i cli/test/a.css > cli/test/result.css
benchmark:
@php unit/benchmark/benchmark.inc
clean:
@sh unit/clean.sh

View file

@ -0,0 +1,137 @@
[CSS Compressor](http://www.codenothing.com/css-compressor/)
============================================================
CSSCompression is a PHP based CSS minifier that analyzes stylesheets for various compressions.
It finds possible CSS shorthand techniques for combination of properties.
Usage
-----
require( 'src/CSSCompression.inc' );
$compressed = CSSCompression::express( $css, 'sane' );
Or, if you need to run it multiple times
$CSSC = new CSSCompression( 'sane' );
$compressed = $CSSC->compress( $css );
Modes
-----
Modes are pre-defined sets of options that can be set by passing in the mode name.
- **safe**: (99% safe) Safe mode does zero combinations or organizing. It's the best mode if you use a lot of hacks.
- **sane**: (90% safe) Sane mode does most combinations(multiple long hand notations to single shorthand), but still keeps most declarations in their place.
- **small**: (65% safe) Small mode reorganizes the whole sheet, combines as much as it can, and will break most comment hacks.
- **full**: (64% safe) Full mode does everything small does, but also converts hex codes to their short color name alternatives.
Here's a few different ways to initiate a mode.
// Express with safe mode
$compressed = CSSCompression::express( $css, 'safe' );
// Creating new instance with sane mode
$CSSC = new CSSCompression( 'sane' );
// Setting an instance with small mode
$CSSC->mode( 'small' );
// Or compressing with the current instance, and setting full mode
$compressed = $CSSC->compress( $css, 'full' );
Singleton Instances
-------------------
Yes the compressor provides singleton access(separate from express), but use it wisely.
$CSSC = CSSCompression::getInstance();
// Or, if you want to keep named instances
$rose_instance = CSSCompression::getInstance('rose');
$blue_instance = CSSCompression::getInstance('blue');
Option Handling
---------------
The compressor has an option function with multiple functionalities.
- If no arguments are passed in, the entire options array is returned.
- If a single name argument is passed, then the value of that key name in the options array is returned.
- If both a name and value are passed, then that value is set to it's corresponding key in the array.
Usage
// Returns entire options array
$options = $CSSC->option();
// Returns the readability option value
$readability = $CSSC->option( 'readability' );
// Sets the readability to non-readable
$CSSC->option( 'readability', CSSCompression::READ_NONE );
Alternatively, you can use direct access methods
// Direct access to options array
$options = $CSSC->options;
// Direct access to readability option value
$readability = $CSSC->readability;
// Direct setting of readability value
$CSSC->readability = CSSCompression::READ_NONE;
Additionally, a reset function is provided to revert back to base options (decided at runtime).
// Resets options to original values
$CSSC->reset();
Readability
-----------
The compressor class provides static integers that map to the internal readability values
CSSCompression::READ_MAX // Maximum Readability
CSSCompression::READ_MED // Medium readability of output
CSSCompression::READ_MIN // Minimal readability of output
CSSCompression::READ_NONE // No readability of output (full compression into single line)
// To set maximum readability (Assuming you have your own instance)
$CSSC->option( 'readability', CSSCompression::READ_MAX );
// Or, just pass it in as another option
$options = array(
'readability' => CSSCompression::READ_MAX,
// Other options ...
);
// Get full readability through express
$compressed = CSSCompression::express( $css, $options );
Contributors
------------
[Corey Hart](http://www.codenothing.com) - Creator
[Martin Zvarík](http://www.teplaky.net/) - Pointed out the url and empty definition bug.
[Phil DeJarnett](http://www.overzealous.com/) - Pointed out splitting(and numerous other) problems
[Stoyan Stefanov](http://www.phpied.com/) - [At rules writeup](http://www.phpied.com/css-railroad-diagrams/) and test suite help.
[Julien Deniau](http://www.jeuxvideo.fr/) - Pointed out escaped characters issue
[Olivier Gorzalka](http://clearideaz.com/) - Strict error warnings cleanup

View file

@ -0,0 +1,22 @@
#
# CSS Compression [VERSION] Change Log
# [DATE]
# Corey Hart @ http://www.codenothing.com
#
# Compressor Additions
- Configuration Modes
- pseduo-space, add space between pseduo selectors and comma/brace for ie6
- removing leading 0 of a decimal, 0.633px -> .633px
- handling all At Rules (charsets,media,imports,font-family,etc..)
- handling ms filter paths
- More css hacks support, including box-modal hack
- attr2selector, Convert id=blah and class=blah attributes to their selector counterparts
- strict-id, removes all selectors up to the last id
- add-unknown, adds unknown blocks to top of output in a comment
# Codebase Changes
- Converted to subclass architecture, more modular.
- Using JSON based sandbox specs for focused unit tests.
- Helper conversion definitions(long2hex, hex2short) are now in JSON based files.
- Added benchmark/regression testing

View file

@ -0,0 +1,263 @@
Class CSSCompression
====================
Below is a description of all public access points to the compressor.
const *bool* VERSION
--------------------
Release version
const *bool* DATE
-----------------
Release Date
const *bool* DEV
----------------
Signifies development mode. When true, back door access to all subclasses is enabled. Should always be false in production.
const *bool* TOKEN
------------------
Special marker that gets injected and removed into the stylesheet during compression. Change this if it exists in your sheet.
public static *array* defaults
------------------------------
Default settings for every instance.
const *int* READ_MAX, const *int* READ_MED, const *int* READ_MIN, const *int* READ_NONE
---------------------------------------------------------------------------------------
Readability constants. Tells what format to return the css back after compression.
Getters
=======
This is the list of readable vars on any given instance
- *string* **css**: Contains the compressed result of the last compression ran.
- *string* **mode**: Contains the current mode of the instance
- *array* **options**: Contains the complete array of currently defined options
- *array* **stats**: Contains the result stats of the last compression ran.
- *string* **(option-name)**: Contains the value of that option **name**.
Usage:
// Print out compressed css
echo $CSSC->css;
// Print out the current mode
echo $CSSC->mode;
// Print out list of options
print_r( $CSSC->options );
// Print out result stats
print_r( $CSSC->stats );
// Print out a single options value
echo $CSSC->readability;
Setters
=======
Currently, you can only directly set options
- *string* **options**, *array* **value**: Merge an array of options with the current defaults
- *string* **name**, *mixed* **value**: Set the option **name** with the **value**.
Usage:
// Merge a custom set of options into the defined set
// Remember that it doesn't set, just merges
$CSSC->options = array( 'readability' => CSSCompression::READ_MAX, 'organize' => true );
// Set a single options value
$CSSC->readability = CSSCompression::READ_MAX;
$CSSC->organize = true;
public function __construct( [ *mixed* $css = NULL, *mixed* $options = NULL ] )
-------------------------------------------------------------------------------
Builds the subclasses first, then does one of the following
- Passing a single string argument that is the name of a mode, sets the mode for this instance.
- Passing a single array of options argument will merge those with the defaults for this instance.
- Passing a single long string argument, that is not the name of a mode, will run compression with the defaults set.
- Passing a long string argument, and a mode name argument, sets the mode's options, and then runs compression on the css string.
- Passing a long string argument, and an array of options argument, merges those with default options, and runs compression on the css string.
Usage:
// Create an instance in 'sane' mode
$CSSC = new CSSCompression( 'sane' );
// Create an instance with a custom set of options
$CSSC = new CSSCompression( array( 'readability' => CSSCompression::READ_MAX ) );
// Creates a new instance, then runs compression on the css passed
$CSSC = new CSSCompression( $css );
echo $CSSC->css;
// Creates a new instance in 'sane' mode, then runs compression on the css passed
$CSSC = new CSSCompression( $css, 'sane' );
echo $CSSC->css;
// Creates a new instance with a custom set of options, then runs compression on the css passed
$CSSC = new CSSCompression( $css, array( 'readability' => CSSCompression::READ_MAX ) );
echo $CSSC->css;
*array* public function mode( *string* $mode = NULL )
-----------------------------------------------------
Sets the mode of the instance.
// Set this instance to 'sane' mode
$CSSC->mode( 'sane' );
*array* public static function modes( [ *mixed* $mode = NULL, *array* $config = NULL ] )
----------------------------------------------------------------------------------------
Mode configuration, any one of the following combination of arguments is allowed
- Passing no arguments returns the entire array of modes.
- Passing only a string mode argument returns that modes configuration.
- Passing a string mode argument, and an array config argument sets that config to the mode.
- Passing a single array argument merges a set of modes into the configured set
Usage:
// Returns the entire list of modes
$modes = CSSCompression::modes();
// Returns 'sane' mode configuration
$sane = CSSCompression::modes( 'sane' );
// Add 'rose' mode to the list of modes
CSSCompression::modes( 'rose', array( 'organize' => false, 'readability' => CSSCompression::READ_MAX ) );
// Add 'rose' and 'blue' mode configurations to set of modes
CSSCompression::modes(array(
'rose' => array( 'organize' => false, 'readability' => CSSCompression::READ_MAX ),
'blue' => array( 'rm-multi-define' => false, 'readability' => CSSCompression::READ_NONE )
));
**NOTE:** When an instance configures itself to a mode, it sets every option to true, and expects the mode configuration to tell it what is false.
*mixed* public function option( [ *mixed* $name = NULL, *mixed* $value = NULL ] )
---------------------------------------------------------------------------------
Custom option handling, any one of the following may happen
- Passing no arguments returns the entire array of options currently set.
- Passing only a string name argument returns the value for that option.
- Passing a single array argument merges those into the current options of the instance.
- Passing a string name argument, and a value argument sets the value to it's corresponding option name.
Usage:
// Get the entire options array for this instance
$options = $CSSC->option();
// Get the current readability value for this instance
$readability = $CSSC->option( 'readability' );
// Merge a set of options into the current instance
$CSSC->option( array( 'organize' => false, 'readability' => CSSCompression::READ_MAX ) );
// Set the readability of the current object to full
$CSSC->option( 'readability', CSSCompression::READ_MAX );
*string* public function compress( *string* $css = NULL, [ *mixed* $options = NULL ] )
--------------------------------------------------------------------------------------
Compresses the given string with the given options/mode. $options can be the name of a mode, or an array of options.
// Compress the css passed
$compressed = $CSSC->comrpess( $css );
// Compress the css in 'sane' mode
$compressed = $CSSC->comrpess( $css, 'sane' );
// Compress the css with a custom set of options
$compressed = $CSSC->comrpess( $css, array( 'readability' => CSSCompression::READ_MAX ) );
*string* public static function express( *string* $css = NULL, [ *mixed* $options = NULL ] )
--------------------------------------------------------------------------------------------
Use's it's own singleton instance to return compressed css sheets. $options can be the name of a mode, or an array of options.
// Compress the css passed
$compressed = CSSCompression::express( $css );
// Compress the css in 'sane' mode
$compressed = CSSCompression::express( $css, 'sane' );
// Compress the css with a custom set of options
$compressed = CSSCompression::express( $css, array( 'readability' => CSSCompression::READ_MAX ) );
*bool* public function reset()
------------------------------
Cleans out compression instance, all of it's subclasses, and resets options back to their defaults.
// Reset this instance to it's defaults
$CSSC->reset();
*bool* public function flush()
------------------------------
Cleans out class vars.
// Flush out compression variables
$CSSC->flush();
*object* public static function getInstance( [ *string* name = NULL ] )
-----------------------------------------------------------------------
Returns a singleton instance of the compressor
// Get a singleton instance
$CSSC = CSSCompression::getInstance();
// Get the stored 'rose' singleton instance
$CSSC = CSSCompression::getInstance( 'rose' );
*array* public static function getJSON( *string* $file )
--------------------------------------------------------
Pulls the contents of the $file, does some quick comment stripping, then returns a json decoded hash. Mainly for internal use.
$json = CSSCompression::getJSON( "/path/to/my/file.json" );

View file

@ -0,0 +1,63 @@
Contribute
==========
This project is always in dire need of help in any form, I only have 2 rules:
- All sandboxed tests must pass.
For nix users: From the root directory, run 'make test'
For windows users: Run 'php unit/start.inc'
- Tab character indentations
I don't care if you like 2,3,4,8,16 spaced indentation, just make sure it's a tab character and not spaces.
make test
---------
This command will run all sandboxed tests to check that most functions run the way they should.
make test-focus
---------------
This command runs a focused test on a single function for development. Open up unit/focus.inc for configuration.
make test-file
--------------
This command runs a focused test on a single file. Make sure the original resides in unit/sheets/original/ and the expected
output resides in unit/sheets/expected/. Open up unit/file.inc for configuration
make test-all
-------------
This command runs all sandboxed tests, as well as double compressions on any stylesheets in benchmark src. Doesn't always pass,
but is a helpful check to see that most compression is done the first time around.
make test-regression [ VERSION=temp ]
--------------------------------
The regression command takes an optional assignment which will do testing against the given version. Defaults is to the last run test.
make benchmark
--------------
This command runs the benchmark process. This will create a new temp directory for regression testing and comparison.
make clean
----------
This command removes all generated files used for comparisons and benchmarks.
unit/sandbox/
-------------
The sandbox directory contains json files for focused tests used in unit testing.

View file

@ -0,0 +1,234 @@
Options
=======
Here's a few different ways to set options.
// Set an array of options
$options = array( 'color-long2hex' => false, 'readability' => CSSCompression::READ_MAX );
// Pass directly into express compression
$compressed = CSSCompression::express( $css, $options );
// Create an instance based on the predefined set of options
$CSSC = new CSSCompression( $options );
// Set a batch of options on an instance
$CSSC->option( $options );
// Set a single option on an instance
$CSSC->option( 'readability', CSSCompression::READ_MAX );
// Or, if you just want to read an option
$readability = $CSSC->option( 'readability' );
// Also, you can look at the current options
$options = $CSSC->option();
color-long2hex
--------------
Converts long color names to short hex names
- *aliceblue -> #f0f8ff*
color-rgb2hex
-------------
Converts rgb colors to hex
- *rgb(159,80,98) -> #9F5062, rgb(100%) -> #FFFFFF*
color-hex2shortcolor
--------------------
Converts long hex codes to short color names, Only works on latest browsers, careful when using.
- *#f5f5dc -> beige*
color-hex2shorthex
------------------
Converts long hex codes to short hex codes
- *#44ff11 -> #4f1*
color-hex2safe
--------------------
Converts long hex codes to safe CSS Level 1 color names.
- *#f00 -> red*
fontweight2num
--------------
Converts font-weight names to numbers
- *bold -> 700*
format-units
------------
Removes zero decimals and 0 units
- *15.0px -> 15px || 0px -> 0*
lowercase-selectors
-------------------
Lowercases html tags from list
- *BODY -> body*
attr2selector
-------------
Converts class and id attributes to their shorthand counterparts
- *div[id=blah][class=blah] -> div#blah.blah*
strict-id
---------
Promotes nested id's to the front of the selector
- *body > div#elem p -> #elem p*
pseudo-space
------------
Add space after :first-letter and :first-line pseudo selectors, for ie6
- *a:first-line{ -> a:first-line {*
directional-compress
--------------------
Compresses single defined multi-directional properties
- *margin: 15px 25px 15px 25px -> margin:15px 25px*
organize
--------
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;}*
csw-combine
-----------
Combines color/style/width properties
- *border-style:dashed;border-color:black;border-width:4px; -> border:4px dashed black*
auralcp-combine
---------------
Combines cue/pause properties
- *cue-before: url(before.au); cue-after: url(after.au) -> cue:url(before.au) url(after.au)*
mp-combine
----------
Combines margin/padding directionals
- *margin-top:10px;margin-right:5px;margin-bottom:4px;margin-left:1px; -> margin:10px 5px 4px 1px;*
border-combine
--------------
Combines border directionals
- *border-top|right|bottom|left:1px solid black -> border:1px solid black*
font-combine
------------
Combines font properties
- *font-size:12pt; font-family: arial; -> font:12pt arial*
background-combine
------------------
Combines background properties
- *background-color: black; background-image: url(bgimg.jpeg); -> background:black url(bgimg.jpeg)*
list-combine
------------
Combines list-style properties
- *list-style-type: round; list-style-position: outside -> list-style:round outside*
border-radius-combine
---------------------
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; }
unnecessary-semicolons
----------------------
Removes the last semicolon of a rule set
- *{margin: 2px; color: blue;} -> {margin: 2px; color: blue}*
rm-multi-define
---------------
Removes multiple declarations within the same rule set
- *{color:black;font-size:12pt;color:red;} -> {color:red;font-size:12pt;}*
add-unknown
-----------
Adds unknown artifacts to a comment block at the top of output.
readability
-----------
Readability of Compressed Output.
CSSCompression::READ_MAX; // Maximum readability
CSSCompression::READ_MED; // Medium readability
CSSCompression::READ_MIN; // Minimum readability
CSSCompression::READ_NONE; // No readability

View file

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2011 Corey Hart
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,438 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
// Static dependencies, Subclasses loaded ondemand
require( dirname(__FILE__) . '/lib/Exception.inc' );
require( 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 );
}
}
// Merge passed 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 );
}
}

View file

@ -0,0 +1,39 @@
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
{
"#f0ffff":"azure",
"#f5f5dc":"beige",
"#ffe4c4":"bisque",
"#a52a2a":"brown",
"#ff7f50":"coral",
"#ffd700":"gold",
"#808080":"gray",
"#008000":"green",
"#4b0082":"indigo",
"#fffff0":"ivory",
"#f0e68c":"khaki",
"#faf0e6":"linen",
"#800000":"maroon",
"#000080":"navy",
"#808000":"olive",
"#ffa500":"orange",
"#da70d6":"orchid",
"#cd853f":"peru",
"#ffc0cb":"pink",
"#dda0dd":"plum",
"#800080":"purple",
"#ff0000":"red",
"#fa8072":"salmon",
"#a0522d":"sienna",
"#c0c0c0":"silver",
"#fffafa":"snow",
"#d2b48c":"tan",
"#008080":"teal",
"#ff6347":"tomato",
"#ee82ee":"violet",
"#f5deb3":"wheat",
"#f00":"red"
}

View file

@ -0,0 +1,16 @@
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
{
"#808080":"gray",
"#008000":"green",
"#800000":"maroon",
"#000080":"navy",
"#808000":"olive",
"#ff0000":"red",
"#c0c0c0":"silver",
"#008080":"teal",
"#f00":"red"
}

View file

@ -0,0 +1,126 @@
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
{
"aliceblue":"#f0f8ff",
"antiquewhite":"#faebd7",
"aquamarine":"#7fffd4",
"bisque":"#ffe4c4",
"black":"#000000",
"blanchedalmond":"#ffebcd",
"blueviolet":"#8a2be2",
"burlywood":"#deb887",
"cadetblue":"#5f9ea0",
"chartreuse":"#7fff00",
"chocolate":"#d2691e",
"coral":"#ff7f50",
"cornflowerblue":"#6495ed",
"cornsilk":"#fff8dc",
"crimson":"#dc143c",
"cyan":"#00ffff",
"darkblue":"#00008b",
"darkcyan":"#008b8b",
"darkgoldenrod":"#b8860b",
"darkgray":"#a9a9a9",
"darkgreen":"#006400",
"darkkhaki":"#bdb76b",
"darkmagenta":"#8b008b",
"darkolivegreen":"#556b2f",
"darkorange":"#ff8c00",
"darkorchid":"#9932cc",
"darkred":"#8b0000",
"darksalmon":"#e9967a",
"darkseagreen":"#8fbc8f",
"darkslateblue":"#483d8b",
"darkslategray":"#2f4f4f",
"darkturquoise":"#00ced1",
"darkviolet":"#9400d3",
"deeppink":"#ff1493",
"deepskyblue":"#00bfff",
"dimgray":"#696969",
"dodgerblue":"#1e90ff",
"firebrick":"#b22222",
"floralwhite":"#fffaf0",
"forestgreen":"#228b22",
"fuchsia":"#ff00ff",
"gainsboro":"#dcdcdc",
"ghostwhite":"#f8f8ff",
"goldenrod":"#daa520",
"greenyellow":"#adff2f",
"honeydew":"#f0fff0",
"hotpink":"#ff69b4",
"indianred ":"#cd5c5c",
"indigo ":"#4b0082",
"lavender":"#e6e6fa",
"lavenderblush":"#fff0f5",
"lawngreen":"#7cfc00",
"lemonchiffon":"#fffacd",
"lightblue":"#add8e6",
"lightcoral":"#f08080",
"lightcyan":"#e0ffff",
"lightgoldenrodyellow":"#fafad2",
"lightgrey":"#d3d3d3",
"lightgreen":"#90ee90",
"lightpink":"#ffb6c1",
"lightsalmon":"#ffa07a",
"lightseagreen":"#20b2aa",
"lightskyblue":"#87cefa",
"lightslategray":"#778899",
"lightsteelblue":"#b0c4de",
"lightyellow":"#ffffe0",
"lime":"#00ff00",
"limegreen":"#32cd32",
"magenta":"#ff00ff",
"maroon":"#800000",
"mediumaquamarine":"#66cdaa",
"mediumblue":"#0000cd",
"mediumorchid":"#ba55d3",
"mediumpurple":"#9370d8",
"mediumseagreen":"#3cb371",
"mediumslateblue":"#7b68ee",
"mediumspringgreen":"#00fa9a",
"mediumturquoise":"#48d1cc",
"mediumvioletred":"#c71585",
"midnightblue":"#191970",
"mintcream":"#f5fffa",
"mistyrose":"#ffe4e1",
"moccasin":"#ffe4b5",
"navajowhite":"#ffdead",
"oldlace":"#fdf5e6",
"olivedrab":"#6b8e23",
"orange":"#ffa500",
"orangered":"#ff4500",
"orchid":"#da70d6",
"palegoldenrod":"#eee8aa",
"palegreen":"#98fb98",
"paleturquoise":"#afeeee",
"palevioletred":"#d87093",
"papayawhip":"#ffefd5",
"peachpuff":"#ffdab9",
"powderblue":"#b0e0e6",
"purple":"#800080",
"rosybrown":"#bc8f8f",
"royalblue":"#4169e1",
"saddlebrown":"#8b4513",
"salmon":"#fa8072",
"sandybrown":"#f4a460",
"seagreen":"#2e8b57",
"seashell":"#fff5ee",
"sienna":"#a0522d",
"silver":"#c0c0c0",
"skyblue":"#87ceeb",
"slateblue":"#6a5acd",
"slategray":"#708090",
"springgreen":"#00ff7f",
"steelblue":"#4682b4",
"thistle":"#d8bfd8",
"tomato":"#ff6347",
"turquoise":"#40e0d0",
"violet":"#ee82ee",
"white":"#ffffff",
"whitesmoke":"#f5f5f5",
"yellow":"#ffff00",
"yellowgreen":"#9acd32"
}

View file

@ -0,0 +1,218 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Cleanup {
/**
* Cleanup patterns
*
* @class Control: Compression Controller
* @param (string) token: Copy of the injection token
* @param (regex) rtoken: Token regex built upon instantiation
* @param (array) options: Reference to options
* @param (regex) rsemi: Checks for last semit colon in details
* @param (regex) rsemicolon: Checks for semicolon without an escape '\' character before it
* @param (regex) rspace: Checks for space without an escape '\' character before it
* @param (regex) rcolon: Checks for colon without an escape '\' character before it
* @param (regex) rquote: Checks for quote (') without an escape '\' character before it
* @param (array) rescape: Array of patterns for groupings that should be escaped
* @param (array) escaped: Contains patterns and replacements for espaced characters
*/
private $Control;
private $token = '';
private $rtoken = '';
private $options = array();
private $rsemi = "/;$/";
private $rsemicolon = "/(?<!\\\);/";
private $rspace = "/(?<!\\\)\s/";
private $rcolon = "/(?<!\\\):/";
private $rquote = "/(?<!\\\)'/";
private $rescape = array(
"/((?<!\\\)\")(.*?)((?<!\\\)\")/",
"/((?<!\\\)')(.*?)((?<!\\\)')/",
"/(url\()(.*?)(\))/",
);
private $escaped = array(
'search' => array(
"\\:",
"\\;",
"\\}",
"\\{",
"\\@",
"\\!",
"\\,",
"\\>",
"\\+",
"\\~",
"\\/",
"\\*",
"\\.",
"\\=",
"\\#",
"\\r",
"\\n",
"\\t",
"\\ ",
),
'replace' => array(
":",
";",
"}",
"{",
"@",
"!",
",",
">",
"+",
"~",
"/",
"*",
".",
"=",
"#",
"\r",
"\n",
"\t",
" ",
),
);
/**
* Build the token regex based on defined token
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->token = CSSCompression::TOKEN;
$this->options = &$control->Option->options;
// Have to build the token regexs after initialization
$this->rtoken = "/($this->token)(.*?)($this->token)/";
array_push( $this->rescape, $this->rtoken );
}
/**
* Central cleanup process, removes all injections
*
* @param (array) selectors: Array of selectors
* @param (array) details: Array of details
*/
public function cleanup( &$selectors, &$details ) {
foreach ( $details as $i => &$value ) {
// Auto skip sections
if ( isset( $selectors[$i] ) && strpos( $selectors[$i], $this->token ) === 0 ) {
continue;
}
// Removing dupes
if ( $this->options['rm-multi-define'] ) {
$value = $this->removeMultipleDefinitions( $value );
}
$value = $this->removeUnnecessarySemicolon( $value );
}
return array($selectors, $details);
}
/**
* Removes '\' from possible splitter characters in URLs
*
* @param (string) css: Full css sheet
*/
public function removeInjections( $css ) {
// Remove escaped characters
foreach ( $this->rescape as $regex ) {
$pos = 0;
while ( preg_match( $regex, $css, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$value = $match[1][0]
. str_replace( $this->escaped['search'], $this->escaped['replace'], $match[2][0] )
. $match[3][0];
$css = substr_replace( $css, $value, $match[0][1], strlen( $match[0][0] ) );
$pos = $match[0][1] + strlen( $value ) + 1;
}
}
// Remove token injections
$pos = 0;
while ( preg_match( $this->rtoken, $css, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$value = $match[2][0];
$id = substr( $css, $match[0][1] - 4, 4 ) == '[id=' ? true : false;
$class = substr( $css, $match[0][1] - 7, 7 ) == '[class=' ? true : false;
if ( preg_match( $this->rspace, $value ) || ( ! $id && ! $class ) ) {
$quote = preg_match( $this->rquote, $value ) ? "\"" : "'";
$value = "$quote$value$quote";
$css = substr_replace( $css, $value, $match[0][1], strlen( $match[0][0] ) );
$pos = $match[0][1] + strlen( $value ) + 1;
}
else {
$css = substr_replace( $css, $value, $match[0][1], strlen( $match[0][0] ) );
$pos = $match[0][1] + strlen( $value ) + 1;
}
}
return $css;
}
/**
* Removes multiple definitions that were created during compression
*
* @param (string) val: CSS Selector Properties
*/
private function removeMultipleDefinitions( $val = '' ) {
$storage = array();
$arr = preg_split( $this->rsemicolon, $val );
foreach ( $arr as $x ) {
if ( $x ) {
list( $a, $b ) = preg_split( $this->rcolon, $x, 2 );
$storage[$a] = $b;
}
}
if ( $storage ) {
$val = '';
foreach ( $storage as $x => $y ) {
$val .= "$x:$y;";
}
}
// Return converted val
return $val;
}
/**
* Removes last semicolons on the final property of a set
*
* @param (string) value: rule set
*/
private function removeUnnecessarySemicolon( $value ) {
return preg_replace( $this->rsemi, '', $value );
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
if ( $method == 'cleanup' ) {
return $this->cleanup( $args[0], $args[1] );
}
else {
return call_user_func_array( array($this, $method), $args );
}
}
else {
throw new CSSCompression_Exception( "Unknown method in Cleanup Class - " . $method );
}
}
}

View file

@ -0,0 +1,196 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Color {
/**
* Color Patterns
*
* @class Control: Compression Controller
* @param (array) options: Reference to options array
* @param (regex) rrgb: Checks for rgb notation
* @param (regex) rhex: Checks for hex code
* @param (regex) rfullhex: Checks for full 6 character hex code
* @static (array) color2hex: Long color name to hex code conversions
* @static (array) hex2short: Hex code to short color name conversions
* @static (array) hex2short_safe: CSS Level 1 safe color names that are shorter than hex codes
* @static (array) files: List of static helpers with their class vars
*/
private $Control;
private $options = array();
private $rrgb = "/^rgb\((\d{1,3}\%?(,\d{1,3}\%?,\d{1,3}\%?)?)\)$/i";
private $rhex = "/^#([0-9a-f]{3}|[0-9a-f]{6})$/i";
private $rfullhex = "/^#([0-9a-f]{6})$/i";
private static $color2hex = array();
private static $hex2short = array();
private static $hex2short_safe = array();
private static $files = array(
'color2hex' => 'long2hex-colors.json',
'hex2short' => 'hex2short-colors.json',
'hex2short_safe' => 'hex2short-safe.json',
);
/**
* Stash a reference to the controller on each instantiation
* and install conversion helpers
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->options = &$control->Option->options;
if ( ! self::$color2hex ) {
foreach ( self::$files as $v => $file ) {
self::$$v = CSSCompression::getJSON( $file );
}
}
}
/**
* Central handler for all color conversions.
*
* @param (string) val: Color to be parsed
*/
public function color( $val ) {
// Converts rgb values to hex codes
if ( $this->options['color-rgb2hex'] ) {
$val = $this->rgb2hex( $val );
}
// Convert long color names to hex codes
if ( $this->options['color-long2hex'] ) {
$val = $this->color2hex( $val );
}
// Ensure all hex codes are lowercase
if ( preg_match( $this->rhex, $val ) ) {
$val = strtolower( $val );
}
// Convert large hex codes to small codes
if ( $this->options['color-hex2shorthex'] ) {
$val = $this->hex2short( $val );
}
// Convert 6 digit hex codes to short color names
if ( $this->options['color-hex2shortcolor'] ) {
$val = $this->hex2color( $val );
}
// Convert safe css level1 color names
if ( $this->options['color-hex2safe'] ) {
$val = $this->hex2safe( $val );
}
return $val;
}
/**
* Converts rgb values to hex codes
*
* @param (string) val: Color to be converted
*/
private function rgb2hex( $val ) {
if ( ! preg_match( $this->rrgb, $val, $match ) ) {
return $val;
}
// locals
$hex = '0123456789abcdef';
$str = explode( ',', $match[1] );
$new = '';
// Incase rgb was defined with single val
if ( ! $str ) {
$str = array($match[1]);
}
foreach ( $str as $x ) {
$x = strpos( $x, '%' ) !== false ? intval( ( intval( $x ) / 100 ) * 255 ) : intval( $x );
if ( $x > 255 ) {
$x = 255;
}
if ( $x < 0 ) {
$x = 0;
}
$new .= $hex[( $x - $x % 16 ) / 16];
$new .= $hex[$x % 16];
}
// Repeat hex code to complete 6 digit hex requirement for single definitions
if ( count( $str ) == 1 ) {
$new .= $new . $new;
}
// Replace with hex value
return "#$new";
}
/**
* Convert long color names to hex codes
*
* @param (string) val: Color to be converted
*/
private function color2hex( $val ) {
return isset( self::$color2hex[$val] ) ? self::$color2hex[$val] : $val;
}
/**
* Convert large hex codes to small codes
*
* @param (string) val: Hex to be shortened
*/
private function hex2short( $val ) {
if ( ! preg_match( $this->rfullhex, $val, $match ) ) {
return $val;
}
// See if we can convert to 3 char hex
$hex = $match[1];
if ( $hex[0] == $hex[1] && $hex[2] == $hex[3] && $hex[4] == $hex[5] ) {
$val = '#' . $hex[0] . $hex[2] . $hex[4];
}
return $val;
}
/**
* Convert large hex codes to small codes
*
* @param (string) val: Color to be converted
*/
private function hex2color( $val ) {
return isset( self::$hex2short[$val] ) ? self::$hex2short[$val] : $val;
}
/**
* Convert large hex codes to small codes
*
* @param (string) val: Color to be converted
*/
private function hex2safe( $val ) {
return isset( self::$hex2short_safe[$val] ) ? self::$hex2short_safe[$val] : $val;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Color Class - " . $method );
}
}
}

View file

@ -0,0 +1,188 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @param (string) token: Copy of the injection token
* @param (array) options: Reference to options
* @param (regex) rspace: Checks for space without an escape '\' character before it
* @param (regex) rslash: Checks for unescaped slash character
* @param (regex) rimportant: Checking props for uncombinables
* @param (array) methods: List of options with their corresponding class
*/
private $Control;
private $token = '';
private $options = array();
private $rspace = "/(?<!\\\)\s/";
private $rslash = "/(?<!\\\)\//";
private $rimportant = "/inherit|\!important|\!ie|(?<!\\\)\s/i";
private $methods = array(
'csw-combine' => 'BorderOutline',
'border-radius-combine' => 'BorderRadius',
'border-combine' => 'Border',
'mp-combine' => 'MarginPadding',
'background-combine' => 'Background',
'auralcp-combine' => 'Aural',
'font-combine' => 'Font',
'list-combine' => 'List',
);
/**
* Sub Comination Classes
*
* @class BorderOutline: Handles Color/Style/With combinations of border/outline properties
* @class BorderRadius: Handles border-radius combinations
* @class Border: Handles normal border combinations
* @class MarginPadding: Handles margin/padding combinations
* @class Background: Handles background combinations
* @class Aural: Handles aural combinations
* @class Font: Handles font combinations
* @class List: Handles list combinations
* @param (array) subcombines: Array holding all subcombination classes
*/
public $BorderOutline;
public $BorderRadius;
public $Border;
public $MarginPadding;
public $Background;
public $Aural;
public $Font;
public $List;
private $subcombines = array(
'BorderOutline',
'BorderRadius',
'Border',
'MarginPadding',
'Background',
'Aural',
'Font',
'List',
);
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->token = CSSCompression::TOKEN;
$this->options = &$control->Option->options;
// Include classes if not already done so
if ( ! class_exists( "CSSCompression_Combine_Border", false ) ) {
$path = dirname(__FILE__) . '/Combine/';
foreach ( $this->subcombines as $class ) {
require( $path . $class . '.inc' );
}
}
// Instantiate each sub combine
foreach ( $this->subcombines as $class ) {
$full = "CSSCompression_Combine_$class";
$this->$class = new $full( $control, $this );
}
}
/**
* Reads through each detailed package and checks for cross defn combinations
*
* @param (array) selectors: Array of selectors
* @param (array) details: Array of details
*/
public function combine( &$selectors = array(), &$details = array() ) {
foreach ( $details as $i => &$value ) {
if ( isset( $selectors[$i] ) && strpos( $selectors[$i], $this->token ) === 0 ) {
continue;
}
foreach ( $this->methods as $option => $class ) {
if ( $this->options[$option] ) {
$value = $this->$class->combine( $value );
}
}
}
return array($selectors, $details);
}
/**
* Helper function to ensure flagged words don't get
* overridden
*
* @param (mixed) obj: Array/String of definitions to be checked
*/
public function checkUncombinables( $obj ) {
if ( is_array( $obj ) ) {
foreach ( $obj as $item ) {
if ( preg_match( $this->rimportant, $item ) ) {
return true;
}
}
return false;
}
else {
return preg_match( $this->rimportant, $obj );
}
}
/**
* Helper function to ensure all values of search array
* exist within the storage array
*
* @param (string) prop: CSS Property
* @param (array) storage: Array of definitions found
* @param (array) search: Array of definitions requred
*/
public function searchDefinitions( $prop, $storage, $search ) {
// Return if storage & search don't match
if ( count( $storage ) != count( $search ) ) {
return false;
}
$str = "$prop:";
foreach ( $search as $value ) {
if ( ! isset( $storage[$value] ) || $this->checkUncombinables( $storage[$value] ) ) {
return false;
}
$str .= $storage[$value] . ' ';
}
return trim( $str ) . ';';
}
/**
* Access to private methods for testing
*
* @param (string) subclass: Name of subclass to focus on
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $subclass, $method, $args ) {
if ( $subclass == 'Combine' ) {
if ( method_exists( $this, $method ) ) {
if ( $method == 'combine' ) {
return $this->combine( $args[0], $args[1] );
}
else {
return call_user_func_array( array($this, $method), $args );
}
}
else {
throw new CSSCompression_Exception( "Unknown method in Combine Class - " . $method );
}
}
else if ( in_array( $subclass, $this->subcombines ) ) {
return $this->$subclass->access( $method, $args );
}
else {
throw new CSSCompression_Exception( "Unknown Sub Combine Class - " . $subclass );
}
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine_Aural {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @class Combine: Combine Controller
* @param (regex) raural: Aurual matching
*/
private $Control;
private $Combine;
private $raural = "/(^|(?<!\\\);)(cue|pause)-(before|after):(.*?)((?<!\\\);|$)/";
/**
* Stash a reference to the controller & combiner
*
* @param (class) control: CSSCompression Controller
* @param (class) combine: CSSCompression Combiner
*/
public function __construct( CSSCompression_Control $control, CSSCompression_Combine $combine ) {
$this->Control = $control;
$this->Combine = $combine;
}
/**
* Combines Aural properties (currently being depreciated in W3C Standards)
*
* @param (string) val: Rule Set
*/
public function combine( $val ) {
$storage = $this->storage( $val );
$pos = 0;
// Replace first occurance with it's prop, and remove all following occurances
while ( preg_match( $this->raural, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$prop = $match[2][0];
if ( isset( $storage[$prop] ) ) {
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $storage[$prop], $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $storage[$prop] ) - $colon - 1;
$storage[$prop] = '';
}
else {
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
}
// Return converted val
return $val;
}
/**
* Builds a storage object for iteration
*
* @param (string) val: Rule Set
*/
private function storage( $val ) {
$storage = array();
// Find all possible occurences and build the replacement
$pos = 0;
while ( preg_match( $this->raural, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
if ( ! isset( $storage[$match[2][0]] ) ) {
$storage[$match[2][0]] = array($match[3][0] => $match[4][0]);
}
// Override double written properties
$storage[$match[2][0]][$match[3][0]] = $match[4][0];
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
// Go through each tag for possible combination
foreach ( $storage as $tag => $arr ) {
// All three have to be defined
if ( count( $arr ) == 2 && ! $this->Combine->checkUncombinables( $arr ) ) {
$storage[$tag] = "$tag:" . $arr['before'] . ' ' . $arr['after'] . ';';
}
else {
unset( $storage[$tag] );
}
}
return $storage;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Aural Class - " . $method );
}
}
}

View file

@ -0,0 +1,104 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine_Background {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @class Combine: Combine Controller
* @param (regex) rbackground: Background matching
* @param (array) groupings: List of background combinations
*/
private $Control;
private $Combine;
private $rbackground = "/(^|(?<!\\\);)background-(color|image|repeat|attachment|position):(.*?)((?<!\\\);|$)/";
private $groupings = array(
// With color
array('color', 'image', 'repeat', 'attachment', 'position'),
array('color', 'image', 'attachment', 'position'),
array('color', 'image', 'repeat', 'position'),
array('color', 'image', 'repeat', 'attachment'),
array('color', 'image', 'repeat'),
array('color', 'image', 'attachment'),
array('color', 'image', 'position'),
array('color', 'image'),
// Without Color
array('image', 'attachment', 'position'),
array('image', 'repeat', 'position'),
array('image', 'repeat', 'attachment'),
array('image', 'repeat'),
array('image', 'attachment'),
array('image', 'position'),
// Just Color/Image
array('image'),
array('color'),
);
/**
* Stash a reference to the controller & combiner
*
* @param (class) control: CSSCompression Controller
* @param (class) combine: CSSCompression Combiner
*/
public function __construct( CSSCompression_Control $control, CSSCompression_Combine $combine ) {
$this->Control = $control;
$this->Combine = $combine;
}
/**
* Combines multiple background props into single definition
*
* @param (string) val: Rule Set
*/
public function combine( $val ) {
$storage = array();
// Find all possible occurences and build the replacement
$pos = 0;
while ( preg_match( $this->rbackground, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$storage[$match[2][0]] = $match[3][0];
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
// Run background checks and get replacement str
foreach ( $this->groupings as $props ) {
if ( $replace = $this->Combine->searchDefinitions( 'background', $storage, $props ) ) {
break;
}
}
// If replacement string found, run it on all declarations
if ( $replace ) {
$pos = 0;
while ( preg_match( $this->rbackground, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $replace ) - $colon - 1;
$replace = '';
}
}
// Return converted val
return $val;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Background Class - " . $method );
}
}
}

View file

@ -0,0 +1,95 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine_Border {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @class Combine: Combine Controller
* @param (regex) rborder: Border matching
*/
private $Control;
private $Combine;
private $rborder = "/(^|(?<!\\\);)border-(top|right|bottom|left):(.*?)((?<!\\\);|$)/";
/**
* Stash a reference to the controller & combiner
*
* @param (class) control: CSSCompression Controller
* @param (class) combine: CSSCompression Combiner
*/
public function __construct( CSSCompression_Control $control, CSSCompression_Combine $combine ) {
$this->Control = $control;
$this->Combine = $combine;
}
/**
* Combines multiple border properties into single definition
*
* @param (string) val: Rule Set
*/
public function combine( $val ) {
if ( ( $replace = $this->replace( $val ) ) === false ) {
return $val;
}
// Rebuild the rule set with the combinations found
$pos = 0;
while ( preg_match( $this->rborder, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $replace ) - $colon - 1;
$replace = '';
}
// Return converted val
return $val;
}
/**
* Builds a replacement string
*
* @param (string) val: Rule Set
*/
private function replace( $val ) {
$storage = array();
// Find all possible occurences and build the replacement
$pos = 0;
while ( preg_match( $this->rborder, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
// Override double written properties
$storage[$match[2][0]] = $match[3][0];
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
// All 4 have to be defined
if ( count( $storage ) == 4 &&
$storage['top'] == $storage['bottom'] &&
$storage['left'] == $storage['right'] &&
$storage['top'] == $storage['right'] ) {
return "border:" . $storage['top'] . ';';
}
return false;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Border Class - " . $method );
}
}
}

View file

@ -0,0 +1,106 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine_BorderOutline {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @class Combine: Combine Controller
* @param (regex) rcsw: Border/Outline matching
*/
private $Control;
private $Combine;
private $rcsw = "/(^|(?<!\\\);)(border|border-top|border-bottom|border-left|border-right|outline)-(color|style|width):(.*?)((?<!\\\);|$)/";
/**
* Stash a reference to the controller & combiner
*
* @param (class) control: CSSCompression Controller
* @param (class) combine: CSSCompression Combiner
*/
public function __construct( CSSCompression_Control $control, CSSCompression_Combine $combine ) {
$this->Control = $control;
$this->Combine = $combine;
}
/**
* Combines color/style/width of border/outline properties
*
* @param (string) val: Rule Set
*/
public function combine( $val ) {
$storage = $this->storage( $val );
$pos = 0;
// Now rebuild the string replacing all instances
while ( preg_match( $this->rcsw, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$prop = $match[2][0];
if ( isset( $storage[$prop] ) ) {
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $storage[$prop], $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $storage[$prop] ) - $colon - 1;
$storage[$prop] = '';
}
else {
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
}
// Return converted val
return $val;
}
/**
* Builds a storage object for iteration
*
* @param (string) val: Rule Set
*/
private function storage( $val ) {
$storage = array();
$pos = 0;
// Find all possible occurences and build the replacement
while ( preg_match( $this->rcsw, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
if ( ! isset( $storage[$match[2][0]] ) ) {
$storage[$match[2][0]] = array($match[3][0] => $match[4][0]);
}
// Override double written properties
$storage[$match[2][0]][$match[3][0]] = $match[4][0];
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
// Go through each tag for possible combination
foreach ( $storage as $tag => $arr ) {
// All three have to be defined
if ( count( $arr ) == 3 && ! $this->Combine->checkUncombinables( $arr ) ) {
$storage[$tag] = "$tag:" . $arr['width'] . ' ' . $arr['style'] . ' ' . $arr['color'] . ';';
}
else {
unset( $storage[$tag] );
}
}
return $storage;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in BorderOutline Class - " . $method );
}
}
}

View file

@ -0,0 +1,263 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine_BorderRadius {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @class Combine: Combine Controller
* @param (regex) rspace: Checks for space without an escape '\' character before it
* @param (regex) rslash: Checks for unescaped slash character
* @param (array) borderRadius: Various border radii components
*/
private $Control;
private $Combine;
private $rspace = "/(?<!\\\)\s/";
private $rslash = "/(?<!\\\)\//";
private $borderRadius = array(
'css3' => array(
'mod' => '',
'base' => "/(^|(?<!\\\);)border-radius:(.*?)((?<!\\\);|$)/",
'full' => "/(^|(?<!\\\);)border-(top|bottom)-(left|right)-radius:(.*?)((?<!\\\);|$)/",
),
'moz' => array(
'mod' => '-moz-',
'base' => "/(^|(?<!\\\);)-moz-border-radius:(.*?)((?<!\\\);|$)/",
'full' => "/(^|(?<!\\\);)-moz-border-radius-(top|bottom)(left|right):(.*?)((?<!\\\);|$)/",
),
'webkit' => array(
'mod' => '-webkit-',
'base' => "/(^|(?<!\\\);)-webkit-border-radius:(.*?)((?<!\\\);|$)/",
'full' => "/(^|(?<!\\\);)-webkit-border-(top|bottom)-(left|right)-radius:(.*?)((?<!\\\);|$)/",
),
);
/**
* Stash a reference to the controller & combiner
*
* @param (class) control: CSSCompression Controller
* @param (class) combine: CSSCompression Combiner
*/
public function __construct( CSSCompression_Control $control, CSSCompression_Combine $combine ) {
$this->Control = $control;
$this->Combine = $combine;
}
/**
* Main handler to combine border-radii into a single rule
*
* @param (string) val: Rule Set
*/
public function combine( $val ) {
foreach ( $this->borderRadius as $regex ) {
$val = $this->fix( $val, $regex );
}
return $val;
}
/**
* Does the actual combining
*
* @param (string) val: Rule Set
*/
private function fix( $val, $regex ) {
$val = $this->base( $val, $regex );
$replace = $regex['mod'];
// Storage builder
if ( ( $storage = $this->storage( $val, $regex ) ) === false ) {
return $val;
}
// Setup horizontal/vertical radii
foreach ( $storage as $dir => &$config ) {
// Verticals are optional
if ( $dir == 'vertical' && ! $config['keep'] ) {
break;
}
// All 4 are the same
else if ( $config['top-left'] == $config['top-right'] &&
$config['top-right'] == $config['bottom-right'] &&
$config['bottom-right'] == $config['bottom-left'] ) {
$config['replace'] .= $config['top-left'];
}
// Opposites are the same
else if ( $config['top-left'] == $config['bottom-right'] && $config['top-right'] == $config['bottom-left'] ) {
$config['replace'] .= $config['top-left'] . ' ' . $config['top-right'];
}
// 3-point directional
else if ( $config['top-right'] == $config['bottom-left'] ) {
$config['replace'] .= $config['top-left'] . ' ' . $config['top-right'] . ' ' . $config['bottom-right'];
}
// none are the same, but can still use shorthand notation
else {
$config['replace'] .= $config['top-left'] . ' ' . $config['top-right'] . ' '
. $config['bottom-right'] . ' ' . $config['bottom-left'];
}
}
// Now rebuild the string replacing all instances of margin/padding if shorthand exists
$pos = 0;
$replace = $regex['mod'] . "border-radius:" . $storage['horizontal']['replace'] . $storage['vertical']['replace'] . ';';
while ( preg_match( $regex['full'], $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $replace ) - $colon - 1;
$replace = '';
}
// Return converted val
return $val;
}
/**
* Expands short handed border radius props for combination
*
* @param (string) val: Rule Set
*/
private function base( $val, $regex ) {
$pos = 0;
while ( preg_match( $regex['base'], $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$replace = '';
$colon = strlen( $match[1][0] );
$parts = preg_split( $this->rslash, trim( $match[2][0] ), 2 );
$positions = array(
'top-left' => 0,
'top-right' => 0,
'bottom-right' => 0,
'bottom-left' => 0,
);
$base = array(
'horizontal' => array(
'parts' => preg_split( $this->rspace, trim( $parts[0] ) ),
'pos' => $positions,
),
'vertical' => array(
'parts' => isset( $parts[1] ) ? preg_split( $this->rspace, trim( $parts[1] ) ) : '',
'pos' => $positions,
),
);
foreach ( $base as &$config ) {
// Skip uncombinables
if ( $this->Combine->checkUncombinables( $config['parts'] ) ) {
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
continue 2;
}
// Might not have verticals
else if ( $config['parts'] === '' ) {
continue;
}
// Each position needs a value
switch ( count( $config['parts'] ) ) {
case 1:
$config['pos']['top-left'] = $config['pos']['top-right'] = $config['parts'][0];
$config['pos']['bottom-left'] = $config['pos']['bottom-right'] = $config['parts'][0];
break;
case 2:
$config['pos']['top-left'] = $config['pos']['bottom-right'] = $config['parts'][0];
$config['pos']['bottom-left'] = $config['pos']['top-right'] = $config['parts'][1];
break;
case 3:
$config['pos']['top-left'] = $config['parts'][0];
$config['pos']['bottom-left'] = $config['pos']['top-right'] = $config['parts'][1];
$config['pos']['bottom-right'] = $config['parts'][2];
break;
case 4:
$config['pos']['top-left'] = $config['parts'][0];
$config['pos']['top-right'] = $config['parts'][1];
$config['pos']['bottom-right'] = $config['parts'][2];
$config['pos']['bottom-left'] = $config['parts'][3];
break;
default:
continue 2;
}
}
// Build the replacement
foreach ( $positions as $p => $v ) {
if ( $regex['mod'] == '-moz-' ) {
$replace .= "-moz-border-radius-" . preg_replace( "/-/", '', $p ) . ":"
. $base['horizontal']['pos'][$p]
. ( $base['vertical']['parts'] === '' ? '' : ' ' . $base['vertical']['pos'][$p] )
. ';';
}
else {
$replace .= $regex['mod'] . "border-$p-radius:"
. $base['horizontal']['pos'][$p]
. ( $base['vertical']['parts'] === '' ? '' : ' ' . $base['vertical']['pos'][$p] )
. ';';
}
}
$pos += strlen( $replace );
$val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
}
return $val;
}
/**
* Builds the storage object for border radius props
*
* @param (string) val: Rule Set
* @param (array) regex: Current border radius type checking props
*/
private function storage( $val, $regex ) {
$storage = array(
'horizontal' => array('replace' => ''),
'vertical' => array(
'replace' => '',
'keep' => false,
),
);
// Find all possible occurences of this border-radius type and mark their directional value
$pos = 0;
while ( preg_match( $regex['full'], $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
$parts = preg_split( $this->rspace, $match[4][0], 2 );
$storage['horizontal'][$match[2][0] . '-' . $match[3][0]] = trim( $parts[0] );
if ( isset( $parts[1] ) ) {
$storage['vertical'][$match[2][0] . '-' . $match[3][0]] = trim( $parts[1] );
$storage['vertical']['keep'] = true;
$storage['vertical']['replace'] = '/';
}
else {
$storage['vertical'][$match[2][0] . '-' . $match[3][0]] = '0';
}
}
// Only combine if all 4 definitions are found (5 including replace)
if ( count( $storage['horizontal'] ) != 5 ||
$this->Combine->checkUncombinables( $storage['horizontal'] ) ||
$this->Combine->checkUncombinables( $storage['vertical'] ) ) {
return false;
}
return $storage;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in BorderRadius Class - " . $method );
}
}
}

View file

@ -0,0 +1,122 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine_Font {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @class Combine: Combine Controller
* @param (regex) rfont: Font matching
* @param (array) groupings: Set of font combinationals
*/
private $Control;
private $Combine;
private $rfont = "/(^|(?<!\\\);)(font|line)-(style|variant|weight|size|height|family):(.*?)((?<!\\\);|$)/";
private $groupings = array(
array('font-style', 'font-variant', 'font-weight', 'size/height', 'font-family'),
array('font-style', 'font-variant', 'font-weight', 'font-size', 'font-family'),
array('font-style', 'font-variant', 'size/height', 'font-family'),
array('font-style', 'font-variant', 'font-size', 'font-family'),
array('font-style', 'font-weight', 'size/height', 'font-family'),
array('font-style', 'font-weight', 'font-size', 'font-family'),
array('font-variant', 'font-weight', 'size/height', 'font-family'),
array('font-variant', 'font-weight', 'font-size', 'font-family'),
array('font-weight', 'size/height', 'font-family'),
array('font-weight', 'font-size', 'font-family'),
array('font-variant', 'size/height', 'font-family'),
array('font-variant', 'font-size', 'font-family'),
array('font-style', 'size/height', 'font-family'),
array('font-style', 'font-size', 'font-family'),
array('size/height', 'font-family'),
array('font-size', 'font-family'),
);
/**
* Stash a reference to the controller & combiner
*
* @param (class) control: CSSCompression Controller
* @param (class) combine: CSSCompression Combiner
*/
public function __construct( CSSCompression_Control $control, CSSCompression_Combine $combine ) {
$this->Control = $control;
$this->Combine = $combine;
}
/**
* Combines multiple font-definitions into single definition
*
* @param (string) val: Rule Set
*/
public function combine( $val ) {
$storage = $this->storage( $val );
// Loop through each property check and see if they can be replaced
foreach ( $this->groupings as $props ) {
if ( $replace = $this->Combine->searchDefinitions( 'font', $storage, $props ) ) {
break;
}
}
// If replacement string found, run it on all declarations
if ( $replace ) {
$pos = 0;
while ( preg_match( $this->rfont, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
if ( ! isset( $storage['line-height'] ) && stripos( $match[0][0], 'line-height') === 0 ) {
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
continue;
}
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $replace ) - $colon - 1;
$replace = '';
}
}
// Return converted val
return $val;
}
/**
* Builds a storage object for iteration
*
* @param (string) val: Rule Set
*/
private function storage( $val ) {
$storage = array();
// Find all possible occurences and build the replacement
$pos = 0;
while ( preg_match( $this->rfont, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$storage[$match[2][0] . '-' . $match[3][0]] = $match[4][0];
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
// Combine font-size & line-height if possible
if ( isset( $storage['font-size'] ) && isset( $storage['line-height'] ) ) {
$storage['size/height'] = $storage['font-size'] . '/' . $storage['line-height'];
unset( $storage['font-size'], $storage['line-height'] );
}
return $storage;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Font Class - " . $method );
}
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine_List {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @class Combine: Combine Controller
* @param (regex) rlist: List style matching
* @param (array) groupings: Group of list combinationals
*/
private $Control;
private $Combine;
private $rlist = "/(^|(?<!\\\);)list-style-(type|position|image):(.*?)((?<!\\\);|$)/";
private $groupings = array(
array('type', 'position', 'image'),
array('type', 'position'),
array('type', 'image'),
array('position', 'image'),
array('type'),
array('position'),
array('image'),
);
/**
* Stash a reference to the controller & combiner
*
* @param (class) control: CSSCompression Controller
* @param (class) combine: CSSCompression Combiner
*/
public function __construct( CSSCompression_Control $control, CSSCompression_Combine $combine ) {
$this->Control = $control;
$this->Combine = $combine;
}
/**
* Combines multiple list style props into single definition
*
* @param (string) val: Rule Set
*/
public function combine( $val ) {
// If replacement string found, run it on all declarations
if ( ( $replace = $this->replace( $val ) ) !== false ) {
$pos = 0;
while ( preg_match( $this->rlist, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $replace ) - $colon - 1;
$replace = '';
}
}
// Return converted val
return $val;
}
/**
* Build the replacement string for list props
*
* @param (string) val: Rule Set
*/
private function replace( $val ) {
$storage = array();
$pos = 0;
// Find all possible occurences and build the replacement
while ( preg_match( $this->rlist, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$storage[$match[2][0]] = $match[3][0];
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
// Run background checks and get replacement str
foreach ( $this->groupings as $props ) {
if ( $replace = $this->Combine->searchDefinitions( 'list-style', $storage, $props ) ) {
return $replace;
}
}
return false;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in List Class - " . $method );
}
}
}

View file

@ -0,0 +1,187 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Combine_MarginPadding {
/**
* Combine Patterns
*
* @class Control: Compression Controller
* @class Combine: Combine Controller
* @param (regex) rspace: Checks for space without an escape '\' character before it
* @param (regex) rmp: Margin/Padding matching
* @param (regex) rmpbase: Margin/Padding base match
*/
private $Control;
private $Combine;
private $rspace = "/(?<!\\\)\s/";
private $rmp = "/(^|(?<!\\\);)(margin|padding)-(top|right|bottom|left):(.*?)((?<!\\\);|$)/";
private $rmpbase = "/(^|(?<!\\\);)(margin|padding):(.*?)((?<!\\\);|$)/";
/**
* Stash a reference to the controller & combiner
*
* @param (class) control: CSSCompression Controller
* @param (class) combine: CSSCompression Combiner
*/
public function __construct( CSSCompression_Control $control, CSSCompression_Combine $combine ) {
$this->Control = $control;
$this->Combine = $combine;
}
/**
* Combines multiple directional properties of
* margin/padding into single definition.
*
* @param (string) val: Rule Set
*/
public function combine( $val ) {
$val = $this->expand( $val );
$storage = $this->storage( $val );
$pos = 0;
// Now rebuild the string replacing all instances of margin/padding if shorthand exists
while ( preg_match( $this->rmp, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$prop = $match[2][0];
if ( isset( $storage[$prop] ) ) {
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $storage[$prop], $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $storage[$prop] ) - $colon - 1;
$storage[$prop] = '';
}
else {
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
}
// Return converted val
return $val;
}
/**
* Build the storage object for iteration
*
* @param (string) val: Rule Set
*/
private function storage( $val ) {
$storage = array();
$pos = 0;
// Find all possible occurences of margin/padding and mark their directional value
while ( preg_match( $this->rmp, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
if ( ! isset( $storage[$match[2][0]] ) ) {
$storage[$match[2][0]] = array($match[3][0] => $match[4][0]);
}
// Override double written properties
$storage[$match[2][0]][$match[3][0]] = $match[4][0];
$pos = $match[0][1] + strlen( $match[0][0] ) - 1;
}
// Go through each tag for possible combination
foreach ( $storage as $tag => $arr ) {
// Only combine if all 4 definitions are found
if ( count( $arr ) == 4 && ! $this->Combine->checkUncombinables( $arr ) ) {
// All 4 are the same
if ( $arr['top'] == $arr['bottom'] && $arr['left'] == $arr['right'] && $arr['top'] == $arr['left'] ) {
$storage[$tag] = "$tag:" . $arr['top'] . ';';
}
// Opposites are the same
else if ( $arr['top'] == $arr['bottom'] && $arr['left'] == $arr['right'] ) {
$storage[$tag] = "$tag:" . $arr['top'] . ' ' . $arr['left'] . ';';
}
// 3-point directional
else if ( $arr['right'] == $arr['left'] ) {
$storage[$tag] = "$tag:" . $arr['top'] . ' ' . $arr['right'] . ' ' . $arr['bottom'] . ';';
}
// none are the same, but can still use shorthand notation
else {
$storage[$tag] = "$tag:" . $arr['top'] . ' ' . $arr['right'] . ' ' . $arr['bottom'] . ' ' . $arr['left'] . ';';
}
}
else {
unset( $storage[$tag] );
}
}
return $storage;
}
/**
* Explodes shorthanded margin/padding properties for later combination
*
* @param (string) val: Rule set
*/
private function expand( $val ) {
$pos = 0;
while ( preg_match( $this->rmpbase, $val, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$replace = '';
$prop = $match[2][0];
$value = preg_split( $this->rspace, trim( $match[3][0] ) );
$positions = array(
'top' => 0,
'right' => 0,
'bottom' => 0,
'left' => 0,
);
// Skip uncombinables
if ( $this->Combine->checkUncombinables( $value ) ) {
$pos = $match[0][1] + strlen( $match[0][0] );
continue;
}
// Each position needs a value
switch ( count( $value ) ) {
case 1:
$positions['top'] = $positions['right'] = $positions['bottom'] = $positions['left'] = $value[0];
break;
case 2:
$positions['top'] = $positions['bottom'] = $value[0];
$positions['right'] = $positions['left'] = $value[1];
break;
case 3:
$positions['top'] = $value[0];
$positions['right'] = $positions['left'] = $value[1];
$positions['bottom'] = $value[2];
break;
case 4:
$positions['top'] = $value[0];
$positions['right'] = $value[1];
$positions['bottom'] = $value[2];
$positions['left'] = $value[3];
break;
default:
continue;
}
// Build the replacement
foreach ( $positions as $p => $v ) {
$replace .= "$prop-$p:$v;";
}
$colon = strlen( $match[1][0] );
$val = substr_replace( $val, $replace, $match[0][1] + $colon, strlen( $match[0][0] ) - $colon );
$pos = $match[0][1] + strlen( $replace ) - $colon - 1;
}
return $val;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in MarginPadding Class - " . $method );
}
}
}

View file

@ -0,0 +1,209 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Compress {
/**
* Trim Patterns
*
* @param (array) options: Reference to options
* @param (array) stats: Reference to stats
* @param (regex) rsemicolon: Checks for semicolon without an escape '\' character before it
* @param (regex) rcolon: Checks for colon without an escape '\' character before it
* @param (regex) rspace: Checks for space without an escape '\' character before it
*/
private $options = array();
private $stats = array();
private $rsemicolon = "/(?<!\\\);/";
private $rcolon = "/(?<!\\\):/";
private $rspace = "/(?<!\\\)\s/";
/**
* Other inner classes that get used within compression
*
* @class Control: Compression Controller
* @class Trim: Trim Instance
* @class Setup: Setup Instance
* @class Format: Formatting Instance
* @class Combine: Combine Instance
* @class Cleanup: Cleanup Instance
* @class Organize: Organize Instance
* @class Selectors: Selectors Instance
* @param (array) others: List of above classes for copying
*/
private $Control;
private $Trim;
private $Setup;
private $Format;
private $Combine;
private $Cleanup;
private $Organize;
private $Selectors;
private $others = array(
'Trim',
'Setup',
'Format',
'Combine',
'Cleanup',
'Organize',
'Selectors',
);
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->options = &$control->Option->options;
$this->stats = &$control->stats;
foreach ( $this->others as $class ) {
$this->$class = $control->$class;
}
}
/**
* Centralized function to run css compression.
*
* @param (string) css: Stylesheet to compresss
*/
public function compress( $css ) {
$setup = $this->setup( $css );
$setup = $this->rulesets( $setup );
$css = $this->readability( $setup );
// Attach plea to top of page with unknown blocks
if ( $this->options['add-unknown'] && count( $setup['unknown'] ) ) {
$css = "/*\nThere are unknown blocks in the sheet, please please please open an issue with your sheet attached to it:\n"
. "https://github.com/codenothing/css-compressor/issues\n"
. "Thank You --\n\n"
. implode( "\n", $setup['unknown'] )
. "\n*/\n"
. $css;
}
// Mark final file size
$this->stats['after']['size'] = strlen( $css = trim( $css ) );
// Return compressed css
return $css;
}
/**
* Runs css through initial setup handlers
*
* @param (string) css: Sheet to compress
*/
private function setup( $css ) {
// Initial stats
$this->stats['before']['time'] = microtime( true );
$this->stats['before']['size'] = strlen( $css );
// Initial trimming
$css = $this->Trim->trim( $css );
// Do a little tokenizing, compress each property individually
$setup = $this->Setup->setup( $css );
// Mark number of selectors pre-combine
$this->stats['before']['selectors'] = count( $setup['selectors'] );
return $setup;
}
/**
* Focus compressions on each rule set
*
* @param (array) setup: Array containing selectors and rule sets
*/
private function rulesets( $setup ) {
// Do selector specific compressions
$this->Selectors->selectors( $setup['selectors'] );
// Look at each group of properties as a whole, and compress/combine similiar definitions
$this->Combine->combine( $setup['selectors'], $setup['details'] );
// If order isn't important, run comination functions before and after compressions to catch all instances
// Be sure to prune before hand for higher chance of matching
if ( $this->options['organize'] ) {
$this->Cleanup->cleanup( $setup['selectors'], $setup['details'] );
$this->Organize->organize( $setup['selectors'], $setup['details'] );
$this->Combine->combine( $setup['selectors'], $setup['details'] );
}
// Do final maintenace work, remove injected property/values
$this->Cleanup->cleanup( $setup['selectors'], $setup['details'] );
// Run final counters before full cleanup
$this->finalCount( $setup['selectors'], $setup['details'] );
return $setup;
}
/**
* Runs final counts on selectors and props
*
* @param (array) selectors: Selector rules
* @param (array) details: Rule sets
*/
private function finalCount( $selectors, $details ) {
// Selectors and props
$this->stats['after']['selectors'] = count( $selectors );
foreach ( $details as $item ) {
$props = preg_split( $this->rsemicolon, $item );
// Make sure count is true
foreach ( $props as $k => $v ) {
if ( ! isset( $v ) || $v == '' ) {
unset( $props[$k] );
}
}
$this->stats['after']['props'] += count( $props );
}
// Final count for stats
$this->stats['after']['time'] = microtime( true );
}
/**
* Formats the compressed rule sets into a stylesheet
*
* @param (array) setup: Array containing selectors and rule sets
*/
private function readability( $setup ) {
// Format css to users preference
$css = $this->Format->readability( $this->options['readability'], $setup['selectors'], $setup['details'] );
// Intros
foreach ( $setup as $value ) {
if ( $value && is_string( $value ) ) {
$css = $value . $css;
}
}
// Remove escapables
$css = $this->Cleanup->removeInjections( $css );
return $css;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Compress Class - " . $method );
}
}
}

View file

@ -0,0 +1,233 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Control {
/**
* Control Patterns
*
* @param (string) css: Holds compressed css string
* @param (string) mode: Current compression mode state
* @param (array) stats: Holds compression stats
* @param (array) getters: Array of accessible getters
*/
public $css = '';
public $mode = '';
public $stats = array();
private $getters = array(
'css',
'mode',
'stats',
);
/**
* Subclasses that do the ground work for this compressor
*
* @class CSSCompression: Public facing compression class
* @class Option: Option handling
* @class Trim: Does the initial trimming for the css
* @class Format: Formats the output
* @class Numeric: Handles numeric compression
* @class Color: Handles color compression
* @class Individuals: Runs compression algorithms on individual properties and values
* @class Selectors: Runs selector specific compressions
* @class Combine: Handles combining of various properties
* @class Organize: Reorganizes the sheet for futher compression
* @class Cleanup: Cleans out all injected characters during compression
* @class Compress: Central compression unit.
* @param (array) subclasses: Array holding all the subclasses for inlusion
*/
public $CSSCompression;
public $Option;
public $Trim;
public $Format;
public $Numeric;
public $Color;
public $Individuals;
public $Selectors;
public $Combine;
public $Organize;
public $Cleanup;
public $Setup;
public $Compress;
private $subclasses = array(
'Option',
'Trim',
'Format',
'Numeric',
'Color',
'Individuals',
'Selectors',
'Combine',
'Organize',
'Cleanup',
'Setup',
'Compress',
);
/**
* Pull in the Compression instance and build the subclasses
*
* @param (class) CSSCompression: CSSCompression Instance
*/
public function __construct( CSSCompression $CSSCompression ) {
$this->CSSCompression = $CSSCompression;
// Load all subclasses on demand
if ( ! class_exists( "CSSCompression_Option", false ) ) {
$path = dirname(__FILE__) . '/';
foreach ( $this->subclasses as $class ) {
require( $path . $class . '.inc' );
}
}
// Initialize each subclass
foreach ( $this->subclasses as $class ) {
$full = "CSSCompression_$class";
$this->$class = new $full( $this );
}
}
/**
* 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 ) {
if ( in_array( $name, $this->getters ) ) {
return $this->$name;
}
else if ( $name == 'options' ) {
return $this->Option->options;
}
else {
return $this->Option->option( $name );
}
}
/**
* 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 ) {
// Allow for passing array of options to merge into current ones
if ( $name === 'options' && is_array( $value ) ) {
return $this->Option->merge( $value );
}
else if ( isset( CSSCompression::$defaults[$name] ) ) {
return $this->Option->option( $name, $value );
}
else {
throw new CSSCompression_Exception( "Invalid Private Access to $name in CSSCompression." );
}
}
/**
* Merges a predefined set options
*
* @param (string) mode: Name of mode to use.
*/
public function mode( $mode = NULL ) {
return $this->Options->merge( $mode );
}
/**
* Resets options to their defaults, and flushes out variables
*
* @params none
*/
public function reset() {
$this->Option->reset();
return $this->flush();
}
/**
* Cleans out class variables for next run
*
* @params none
*/
public function flush() {
$this->css = '';
$this->stats = array(
'before' => array(
'props' => 0,
'selectors' => 0,
'size' => 0,
'time' => 0,
),
'after' => array(
'props' => 0,
'selectors' => 0,
'size' => 0,
'time' => 0,
),
);
return true;
}
/**
* Proxy to run Compression on the sheet passed. Handle options here.
*
* @param (string) css: Stylesheet to be compressed
* @param (mixed) options: Array of options or mode to use.
*/
public function compress( $css = NULL, $options = NULL ) {
// Flush out old stats and variables
$this->flush();
// If no additional options, just run compression
if ( $options === NULL ) {
return $this->css = $this->Compress->compress( $css );
}
// Store old params
$old = $this->mode == '__custom' ? $this->Option->option() : $this->mode;
$readability = $this->Option->option( 'readability' );
// Compress with new set of options
$this->Option->merge( $options );
$css = $this->Compress->compress( $css );
// Reset original options
$this->reset();
$this->Option->merge( $old );
// Return the compressed css
return $this->css = $css;
}
/**
* Backdoor access to subclasses
* ONLY FOR DEVELOPMENT/TESTING.
*
* @param (string) class: Name of the focus class
* @param (array) config: Contains name reference and test arguments
*/
public function access( $class, $method, $args ) {
if ( $class == 'Control' ) {
return call_user_func_array( array($class, $method), $args );
}
else if ( strpos( $class, '.' ) !== false ) {
$parts = explode( '.', $class );
$class = $parts[0];
$subclass = $parts[1];
return $this->$class->access( $subclass, $method, $args );
}
else if ( in_array( $class, $this->subclasses ) ) {
return $this->$class->access( $method, $args );
}
else {
throw new CSSCompression_Exception( "Unknown Class Access - " . $class );
}
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Exception extends Exception {
/**
* Custom exception handler
*
* @param (string) message: Error message
* @param (int) code: Error code
* @instance (Exception Instance) previous: Previous exception
*/
public function __construct( $message = 'Unknown Exception', $code = 0, Exception $previous = NULL ) {
parent::__construct( $message, $code, $previous );
}
/**
* String version of this custom exception
*
* @params none
*/
public function __toString() {
return "CSSCompression Exception: [" . $this->code . "] " . $this->message . "\n";
}
}

View file

@ -0,0 +1,184 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Format {
/**
* Format Patterns
*
* @class Control: Compression Controller
* @param (string) token: Copy of the injection token
* @param (array) options: Reference to options
* @param (regex) rsemicolon: Checks for semicolon without an escape '\' character before it
* @param (regex) rcolon: Checks for colon without an escape '\' character before it
* @param (array) readability: Mapping to readability functions
*/
private $Control;
private $token = '';
private $options = array();
private $rsemicolon = "/(?<!\\\);/";
private $rcolon = "/(?<!\\\):/";
private $readability = array(
CSSCompression::READ_MAX => 'maximum',
CSSCompression::READ_MED => 'medium',
CSSCompression::READ_MIN => 'minimum',
CSSCompression::READ_NONE => 'none',
);
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->token = CSSCompression::TOKEN;
$this->options = &$control->Option->options;
}
/**
* Reformats compressed CSS into specified format
*
* @param (int) readability: Readability level of compressed output
* @param (array) selectors: Array of selectors
* @param (array) details: Array of declarations
*/
public function readability( $readability = CSSCompression::READ_NONE, $selectors = array(), $details = array() ) {
if ( isset( $this->readability[$readability] ) ) {
$fn = $this->readability[$readability];
return trim( $this->$fn( $selectors, $details ) );
}
else {
return 'Invalid Readability Value';
}
}
/**
* Returns maxium readability, breaking on every selector, brace, and property
*
* @param (array) selectors: Array of selectors
* @param (array) details: Array of declarations
*/
private function maximum( $selectors, $details ) {
$css = '';
foreach ( $selectors as $k => $v ) {
if ( strpos( $v, $this->token ) === 0 ) {
$css .= substr( $v, strlen( $this->token ) );
$css .= $details[$k];
continue;
}
else if ( ! $details[$k] || trim( $details[$k] ) == '' ) {
continue;
}
$v = str_replace( '>', ' > ', $v );
$v = str_replace( '+', ' + ', $v );
$v = str_replace( ',', ', ', $v );
$css .= "$v {\n";
$arr = preg_split( $this->rsemicolon, $details[$k] );
foreach ( $arr as $item ) {
if ( ! $item ) {
continue;
}
list( $prop, $val ) = preg_split( $this->rcolon, $item, 2 );
$css .= "\t$prop: $val;\n";
}
// Kill that last semicolon at users request
if ( $this->options['unnecessary-semicolons'] ) {
$css = preg_replace( "/;\n$/", "\n", $css );
}
$css .= "}\n\n";
}
return $css;
}
/**
* Returns medium readability, putting selectors and rule sets on new lines
*
* @param (array) selectors: Array of selectors
* @param (array) details: Array of declarations
*/
private function medium( $selectors, $details ) {
$css = '';
foreach ( $selectors as $k => $v ) {
if ( strpos( $v, $this->token ) === 0 ) {
$css .= substr( $v, strlen( $this->token ) );
$css .= $details[$k];
continue;
}
else if ( $details[$k] && $details[$k] != '' ) {
$css .= "$v {\n\t" . $details[$k] . "\n}\n";
}
}
return $css;
}
/**
* Returns minimum readability, breaking after every selector and it's rule set
*
* @param (array) selectors: Array of selectors
* @param (array) details: Array of declarations
*/
private function minimum( $selectors, $details ) {
$css = '';
foreach ( $selectors as $k => $v ) {
if ( strpos( $v, $this->token ) === 0 ) {
$css .= substr( $v, strlen( $this->token ) );
$css .= $details[$k];
continue;
}
else if ( $details[$k] && $details[$k] != '' ) {
$css .= "$v{" . $details[$k] . "}\n";
}
}
return $css;
}
/**
* Returns an unreadable, but fully compressed script
*
* @param (array) selectors: Array of selectors
* @param (array) details: Array of declarations
*/
private function none( $selectors, $details ) {
$css = '';
foreach ( $selectors as $k => $v ) {
if ( strpos( $v, $this->token ) === 0 ) {
$css .= substr( $v, strlen( $this->token ) );
$css .= $details[$k];
continue;
}
else if ( $details[$k] && $details[$k] != '' ) {
$css .= trim( "$v{" . $details[$k] . "}" );
}
}
return $css;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Format Class - " . $method );
}
}
}

View file

@ -0,0 +1,304 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Individuals {
/**
* Individual patterns
*
* @class Control: Compression Controller
* @class Numeric: Numeric handler
* @class Color: Color Handler
* @param (array) options: Reference to options
* @param (regex) rdirectional: Properties that may have multiple directions
* @param (regex) rborderradius: Checks property for border-radius declaration
* @param (regex) rnoneprop: Properties that can have none as their value(will be converted to 0)
* @param (regex) rclip: Looks for rect grouping in clip declaration
* @param (regex) rsplitter: Checks font properties for font-size/line-height split
* @param (regex) rfilter: Special alpha filter for msie
* @param (regex) rspace: Checks for unescaped space
* @param (regex) rspace: Checks for unescaped slash
* @param (array) weights: Array of font-weight name conversions to their numeric counterpart
*/
private $Control;
private $Numeric;
private $Color;
private $options = array();
private $rdirectional = "/^(margin|padding|border-spacing)$/";
private $rborderradius = "/border[a-z-]*radius/";
private $rradiusfull = "/^(-moz-|-webkit-)?border-radius$/";
private $rnoneprop = "/^(border|background|border-(top|right|bottom|left))$/";
private $rclip = "/^rect\(\s*(\-?\d*\.?\d*?\w*)(,|\s)(\-?\d*\.?\d*?\w*)(,|\s)(\-?\d*\.?\d*?\w*)(,|\s)(\-?\d*\.?\d*?\w*)\s*\)$/";
private $rsplitter = "/(^|(?<!\\\)\s)([^\/ ]+)\/([^\/ ]+)((?<!\\\)\s|$)/";
private $rfilter = "/[\"']?PROGID\\\?:DXImageTransform\\\?.Microsoft\\\?.Alpha\(Opacity\\\?=(\d+\\\?\.?\d*)\)[\"']?/i";
private $rspace = "/(?<!\\\)\s/";
private $rslash = "/(?<!\\\)\//";
private $weights = array(
"normal" => 400,
"bold" => 700,
);
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->Numeric = $control->Numeric;
$this->Color = $control->Color;
$this->options = &$control->Option->options;
}
/**
* Runs special unit/directional compressions
*
* @param (string) prop: CSS Property
* @param (string) val: Value of CSS Property
*/
public function individuals( $prop, $val ) {
// Properties should always be lowercase
$prop = strtolower( $prop );
// Split up each definiton for color and numeric compressions
$parts = preg_split( $this->rspace, $val );
foreach ( $parts as &$v ) {
if ( ! $v || $v == '' ) {
continue;
}
// Remove uneeded decimals/units
if ( $this->options['format-units'] ) {
$v = $this->Numeric->numeric( $v );
}
// Color compression
$v = $this->Color->color( $v );
}
$val = trim( implode( ' ', $parts ) );
// Special border radius handling
if ( preg_match( $this->rborderradius, $prop ) ) {
$val = $this->borderRadius( $prop, $val );
}
// Remove uneeded side definitions if possible
else if ( $this->options['directional-compress'] && count( $parts ) > 1 && preg_match( $this->rdirectional, $prop ) ) {
$val = $this->directionals( strtolower( $val ) );
}
// Font-weight converter
if ( $this->options['fontweight2num'] && ( $prop == 'font-weight' || $prop == 'font' ) ) {
$val = $this->fontweight( $val );
}
// Special font value conversions
if ( $prop == 'font' ) {
$val = $this->font( $val );
}
// Special clip value compressions
if ( $prop == 'clip' ) {
$val = $this->clip( $val );
}
// None to 0 converter
$val = $this->none( $prop, $val );
// MSIE Filters
$val = $this->filter( $prop, $val );
// Return for list retrival
return array($prop, $val);
}
/**
* Preps border radius for directional compression
*
* @param (string) prop: Property Declaration
* @param (string) val: Declaration Value
*/
private function borderRadius( $prop, $val ) {
if ( preg_match( $this->rslash, $val ) ) {
$parts = preg_split( $this->rslash, $val, 2 );
// We have to redo numeric compression because the slash may hav intruded
foreach ( $parts as &$row ) {
$p = preg_split( $this->rspace, $row );
foreach ( $p as &$v ) {
if ( ! $v || $v == '' ) {
continue;
}
// Remove uneeded decimals/units
if ( $this->options['format-units'] ) {
$v = $this->Numeric->numeric( $v );
}
}
$row = implode( ' ', $p );
if ( $this->options['directional-compress'] ) {
$row = $this->directionals( strtolower( $row ) );
}
}
$val = implode( '/', $parts );
}
else if ( $this->options['directional-compress'] && preg_match( $this->rradiusfull, $prop ) ) {
$val = $this->directionals( strtolower( $val ) );
}
return $val;
}
/**
* Finds directional compression on methods like margin/padding
*
* @param (string) val: Value of CSS Property
*/
private function directionals( $val ) {
// Split up each definiton
$direction = preg_split( $this->rspace, $val );
// 4 Direction reduction
$count = count( $direction );
if ( $count == 4 ) {
// All 4 sides are the same, combine into 1 definition
if ( $direction[0] == $direction[1] && $direction[2] == $direction[3] && $direction[0] == $direction[3] ) {
$direction = array($direction[0]);
}
// top-bottom/left-right are the same, reduce definition
else if ( $direction[0] == $direction[2] && $direction[1] == $direction[3] ) {
$direction = array($direction[0], $direction[1]);
}
// Only left-right are the same
else if ( $direction[1] == $direction[3] ) {
$direction = array($direction[0], $direction[1], $direction[2]);
}
}
// 3 Direction reduction
else if ( $count == 3 ) {
// All directions are the same
if ( $direction[0] == $direction[1] && $direction[1] == $direction[2] ) {
$direction = array($direction[0]);
}
// Only top(first) and bottom(last) are the same
else if ( $direction[0] == $direction[2] ) {
$direction = array($direction[0], $direction[1]);
}
}
// 2 Direction reduction
// Both directions are the same, combine into single definition
else if ( $count == 2 && $direction[0] == $direction[1] ) {
$direction = array($direction[0]);
}
// Return the combined version of the directions
// Single entries will just return
return implode( ' ', $direction );
}
/**
* Converts font-weight names to numbers
*
* @param (string) val: font-weight prop value
*/
private function fontweight( $val ) {
if ( preg_match( $this->rspace, $val ) ) {
$parts = preg_split( $this->rspace, $val );
foreach ( $parts as &$item ) {
$lower = strtolower( $item );
if ( isset( $this->weights[$lower] ) && $lower != 'normal' ) {
$item = $this->weights[$lower];
}
}
$val = implode( ' ', $parts );
}
else if ( isset( $this->weights[strtolower( $val )] ) ) {
$val = $this->weights[strtolower( $val )];
}
return $val;
}
/**
* Special font conversions
*
* @param (string) val: property value
*/
private function font( $val ) {
// Split out the font-size/line-height split and run through numerical handlers
if ( preg_match( $this->rsplitter, $val, $match, PREG_OFFSET_CAPTURE ) ) {
$size = $this->Numeric->numeric( $match[2][0] );
$height = $this->Numeric->numeric( $match[3][0] );
$concat = $match[1][0] . $size . '/' . $height . $match[4][0];
$val = substr_replace( $val, $concat, $match[0][1], strlen( $match[0][0] ) );
}
return $val;
}
/**
* Special clip conversions
*
* @param (string) val: property value
*/
private function clip( $val ) {
if ( preg_match( $this->rclip, $val, $match ) ) {
$positions = array(1, 3, 5, 7);
$clean = 'rect(';
foreach ( $positions as $pos ) {
if ( ! isset( $match[$pos] ) ) {
return $val;
}
$clean .= $this->Numeric->numeric( $match[$pos] ) . ( isset( $match[$pos + 1] ) ? $match[$pos + 1] : '' );
}
$val = $clean . ')';
}
return $val;
}
/**
* Convert none vals to 0
*
* @param (string) prop: Current Property
* @param (string) val: property value
*/
private function none( $prop, $val ) {
if ( preg_match( $this->rnoneprop, $prop ) && $val == 'none' ) {
$val = '0';
}
return $val;
}
/**
* MSIE Filter Conversion
*
* @param (string) prop: Current Property
* @param (string) val: property value
*/
private function filter( $prop, $val ) {
if ( preg_match( "/filter/", $prop ) ) {
$val = preg_replace( $this->rfilter, "alpha(opacity=$1)", $val );
}
return $val;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Individuals Class - " . $method );
}
}
}

View file

@ -0,0 +1,100 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Numeric {
/**
* Numeric Patterns
*
* @class Control: Compression Controller
* @param (array) options: Reference to options
* @param (regex) rdecimal: Checks for zero decimal
* @param (regex) rzero: Checks for preceding 0 to decimal unit
* @param (regex) runit: Checks for suffix on 0 unit
*/
private $Control;
private $options = array();
private $rdecimal = "/^(\+|\-)?(\d*\.[1-9]*0*)(\%|[a-z]{2})$/i";
private $rzero = "/^(\+|\-)?0(\.\d+)(\%|[a-z]{2})?$/i";
private $runit = "/^0(\%|[a-z]{2})$/i";
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->options = &$control->Option->options;
}
/**
* Runs all numeric operations
*
* @param (string) str: Unit string
*/
public function numeric( $str ) {
$str = $this->decimal( $str );
$str = $this->zeroes( $str );
$str = $this->units( $str );
return $str;
}
/**
* Remove's unecessary decimal, ie 13.0px => 13px
*
* @param (string) str: Unit string
*/
private function decimal( $str ) {
if ( preg_match( $this->rdecimal, $str, $match ) ) {
$str = ( $match[1] == '-' ? '-' : '' ) . floatval( $match[2] ) . $match[3];
}
return $str;
}
/**
* Removes suffix from 0 unit, ie 0px; => 0;
*
* @param (string) str: Unit string
*/
private function units( $str ) {
if ( preg_match( $this->runit, $str, $match ) ) {
$str = '0';
}
return $str;
}
/**
* Removes leading zero in decimal, ie 0.33px => .33px
*
* @param (string) str: Unit string
*/
private function zeroes( $str ) {
if ( preg_match( $this->rzero, $str, $match ) ) {
$str = ( isset( $match[1] ) && $match[1] == '-' ? '-' : '' ) . $match[2] . ( isset( $match[3] ) ? $match[3] : '' );
}
return $str;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Numeric Class - " . $method );
}
}
}

View file

@ -0,0 +1,130 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Option {
/**
* Option Patterns
*
* @class Control: Compression Controller
* @param (string) custom: Name of the custom mode
* @param (array) options: Instance settings
*/
private $Control;
private $custom = '__custom';
public $options = array();
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->options = CSSCompression::$defaults;
$control->mode = $this->custom;
}
/**
* 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 both a name and value, sets the value to the name key, and returns the value
* - Passing an array will merge the options with the array passed, for object like extension
*
* @param (mixed) name: The key name of the option
* @param (mixed) value: Value to set the option
*/
public function option( $name = NULL, $value = NULL ) {
if ( $name === NULL ) {
return $this->options;
}
else if ( is_array( $name ) ) {
return $this->merge( $name );
}
else if ( $value === NULL ) {
return isset( $this->options[$name] ) ? $this->options[$name] : NULL;
}
else {
// Readability doesn't signify custom settings
if ( $name != 'readability' ) {
$this->Control->mode = $this->custom;
}
return ( $this->options[$name] = $value );
}
}
/**
* Reset's the default options
*
* @params none;
*/
public function reset() {
// Reset and return the new options
return $this->options = CSSCompression::$defaults;
}
/**
* Extend like function to merge an array of preferences into
* the options array.
*
* @param (mixed) options: Array of preferences to merge into options
*/
public function merge( $options = array() ) {
$modes = CSSCompression::modes();
if ( $options && is_array( $options ) && count( $options ) ) {
$this->Control->mode = $this->custom;
foreach ( $this->options as $key => $value ) {
if ( ! isset( $options[$key] ) ) {
continue;
}
else if ( strtolower( $options[$key] ) == 'on' ) {
$this->options[$key] = true;
}
else if ( strtolower( $options[$key] ) == 'off' ) {
$this->options[$key] = false;
}
else {
$this->options[$key] = intval( $options[$key] );
}
}
}
else if ( $options && is_string( $options ) && array_key_exists( $options, $modes ) ) {
$this->Control->mode = $options;
// Default all to true, the mode has to force false
foreach ( $this->options as $key => $value ) {
if ( $key != 'readability' ) {
$this->options[$key] = true;
}
}
// Merge mode into options
foreach ( $modes[$options] as $key => $value ) {
$this->options[$key] = $value;
}
}
return $this->options;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Option Class - " . $method );
}
}
}

View file

@ -0,0 +1,146 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Organize {
/**
* Organize Patterns
*
* @class Control: Compression Controller
* @param (array) options: Reference to options
* @param (regex) rsemicolon: Checks for semicolon without an escape '\' character before it
* @param (regex) rlastsemi: Checks for semicolon at the end of the string
*/
private $Control;
private $options = array();
private $rsemicolon = "/(?<!\\\);/";
private $rlastsemi = "/(?<!\\\);$/";
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->options = &$control->Option->options;
}
/**
* Look to see if we can combine selectors to reduce the number
* of definitions.
*
* @param (array) selectors: Array of selectors, map directly to details
* @param (array) details: Array of rule sets, map directly to selectors
*/
public function organize( &$selectors = array(), &$details = array() ) {
// Combining defns based on similar selectors
list( $selectors, $details ) = $this->reduceSelectors( $selectors, $details );
// Combining defns based on similar details
list( $selectors, $details ) = $this->reduceDetails( $selectors, $details );
// Return in package form
return array($selectors, $details);
}
/**
* Combines multiply defined selectors by merging the rule sets,
* latter declarations overide declaratins at top of file
*
* @param (array) selectors: Array of selectors broken down by setup
* @param (array) details: Array of rule sets broken down by setup
*/
private function reduceSelectors( $selectors, $details ) {
$keys = array_keys( $selectors );
$max = array_pop( $keys ) + 1;
for ( $i = 0; $i < $max; $i++ ) {
if ( ! isset( $selectors[$i] ) ) {
continue;
}
for ( $k = $i + 1; $k < $max; $k++ ) {
if ( ! isset( $selectors[$k] ) ) {
continue;
}
if ( $selectors[$i] == $selectors[$k] ) {
// Prevent noticies
if ( ! isset( $details[$i] ) ) {
$details[$i] = '';
}
if ( ! isset( $details[$k] ) ) {
$details[$k] = '';
}
// We kill the last semicolon before organization, so account for that.
if ( $details[$i] != '' && $details[$k] != '' && ! preg_match( $this->rlastsemi, $details[$i] ) ) {
$details[$i] .= ';' . $details[$k];
}
else {
$details[$i] .= $details[$k];
}
// Remove the second part
unset( $selectors[$k], $details[$k] );
}
}
}
return array($selectors, $details);
}
/**
* Combines multiply defined rule sets by merging the selectors
* in comma seperated format
*
* @param (array) selectors: Array of selectors broken down by setup
* @param (array) details: Array of rule sets broken down by setup
*/
private function reduceDetails( $selectors, $details ) {
$keys = array_keys( $selectors );
$max = array_pop( $keys ) + 1;
for ( $i = 0; $i < $max; $i++ ) {
if ( ! isset( $selectors[$i] ) ) {
continue;
}
$arr = preg_split( $this->rsemicolon, isset( $details[$i] ) ? $details[$i] : '' );
for ( $k = $i + 1; $k < $max; $k++ ) {
if ( ! isset( $selectors[$k] ) ) {
continue;
}
$match = preg_split( $this->rsemicolon, isset( $details[$k] ) ? $details[$k] : '' );
$x = array_diff( $arr, $match );
$y = array_diff( $match, $arr );
if ( count( $x ) < 1 && count( $y ) < 1 ) {
$selectors[$i] .= ',' . $selectors[$k];
unset( $details[$k], $selectors[$k] );
}
}
}
return array($selectors, $details);
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Organize Class - " . $method );
}
}
}

View file

@ -0,0 +1,242 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Selectors {
/**
* Selector patterns
*
* @class Control: Compression Controller
* @param (string) token: Copy of the injection token
* @param (regex) ridattr: ID Attribute matcher (combined with token)
* @param (regex) rclassattr: class Attribute matcher (combined with token)
* @param (array) options: Reference to options
* @param (regex) rmark: Stop points during selector parsing
* @param (regex) ridclassend: End of a id/class string
* @param (regex) rescapedspace: for replacement in class attributes
* @param (regex) rquote: Checks for the next quote character
* @param (regex) rcomma: looks for an unescaped comma character
* @param (regex) rspace: looks for an unescaped space character
* @param (regex) rid: looks for an unescaped hash character
* @param (regex) rpseudo: Add space after first-letter|line pseudo selector
* --- when it occurs before comma or rule set
*/
private $Control;
private $token = '';
private $ridattr = "";
private $rclassattr = "";
private $options = array();
private $rmark = "/(?<!\\\)(#|\.|=)/";
private $ridclassend = "/(?<!\\\)[:#>~\[\+\*\. ]/";
private $rquote = "/(?<!\\\)(\"|')?\]/";
private $rescapedspace = "/\\\ /";
private $rcomma = "/(?<!\\\),/";
private $rspace = "/(?<!\\\)\s/";
private $rid = "/(?<!\\\)#/";
private $rpseudo = "/:first-(letter|line)(,|$)/i";
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->token = CSSCompression::TOKEN;
$this->ridattr = "/\[id=$this->token(.*?)$this->token\]/";
$this->rclassattr = "/\[class=$this->token(.*?)$this->token\]/";
$this->options = &$control->Option->options;
}
/**
* Selector specific optimizations
*
* @param (array) selectors: Array of selectors
*/
public function selectors( &$selectors = array() ) {
foreach ( $selectors as &$selector ) {
// Auto ignore sections
if ( strpos( $selector, $this->token ) === 0 ) {
continue;
}
// Smart casing and token injection
$selector = $this->parse( $selector );
// Converting attr to shorthanded selectors
if ( $this->options['attr2selector'] ) {
// Use id hash instead of id attr
$selector = $this->idAttribute( $selector );
// Use class notation instead of class attr
$selector = $this->classAttribute( $selector );
}
// Remove everything before final id in a selector
if ( $this->options['strict-id'] ) {
$selector = $this->strictid( $selector );
}
// Get rid of possible repeated selectors
$selector = $this->repeats( $selector );
// Add space after pseudo selectors (so ie6 doesn't complain)
if ( $this->options['pseudo-space'] ) {
$selector = $this->pseudoSpace( $selector );
}
}
return $selectors;
}
/**
* Converts selectors like BODY => body, DIV => div
* and injects tokens wrappers for attribute values
*
* @param (string) selector: CSS Selector
*/
private function parse( $selector ) {
$clean = '';
$substr = '';
$pos = 0;
while ( preg_match( $this->rmark, $selector, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$substr = substr( $selector, $pos, $match[0][1] + 1 - $pos );
$clean .= $this->options['lowercase-selectors'] ? strtolower( $substr ) : $substr;
$pos = $match[0][1] + strlen( $match[1][0] );
// Class or id match
if ( $match[1][0] == '#' || $match[1][0] == '.' ) {
if ( preg_match( $this->ridclassend, $selector, $m, PREG_OFFSET_CAPTURE, $pos ) ) {
$clean .= substr( $selector, $pos, $m[0][1] - $pos );
$pos = $m[0][1];
}
else {
$clean .= substr( $selector, $pos );
$pos = strlen( $selector );
break;
}
}
// Start of a string
else if ( preg_match( $this->rquote, $selector, $m, PREG_OFFSET_CAPTURE, $pos ) ) {
if ( $selector[$pos] == "\"" || $selector[$pos] == "'" ) {
$pos++;
}
$clean .= $this->token . substr( $selector, $pos, $m[0][1] - $pos ) . $this->token . ']';
$pos = $m[0][1] + strlen( $m[0][0] );
}
// No break points left
else {
$clean .= substr( $selector, $pos );
$pos = strlen( $selector );
break;
}
}
return $clean . ( $this->options['lowercase-selectors'] ? strtolower( substr( $selector, $pos ) ) : substr( $selector, $pos ) );
}
/**
* Convert [id=blah] attribute selectors into id form selector (#blah)
*
* @param (string) selector: CSS Selector
*/
private function idAttribute( $selector ) {
$pos = 0;
while ( preg_match( $this->ridattr, $selector, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
// Don't convert if space found (not valid hash selector)
if ( strpos( $match[1][0], ' ' ) !== false ) {
$pos = $match[0][1] + strlen( $match[0][0] );
continue;
}
$selector = substr_replace( $selector, '#' . $match[1][0], $match[0][1], strlen( $match[0][0] ) );
$pos = $match[0][1] + strlen( $match[1][0] ) + 1;
}
return $selector;
}
/**
* Convert [class=blah] attribute selectors into class form selector (.blah)
*
* @param (string) selector: CSS Selector
*/
private function classAttribute( $selector ) {
$pos = 0;
while ( preg_match( $this->rclassattr, $selector, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
// Don't convert if prescense of dot separator found
if ( strpos( $match[1][0], '.' ) !== false ) {
$pos = $match[0][1] + strlen( $match[0][0] );
continue;
}
$replace = '.' . preg_replace( $this->rescapedspace, ".", $match[1][0] );
$selector = substr_replace( $selector, $replace, $match[0][1], strlen( $match[0][0] ) );
$pos = $match[0][1] + strlen( $match[1][0] ) + 1;
}
return $selector;
}
/**
* Promotes nested id's to the front of the selector
*
* @param (string) selector: CSS Selector
*/
private function strictid( $selector ) {
$parts = preg_split( $this->rcomma, $selector );
foreach ( $parts as &$s ) {
if ( preg_match( $this->rid, $s ) ) {
$p = preg_split( $this->rid, $s );
$s = '#' . array_pop( $p );
}
}
return implode( ',', $parts );
}
/**
* Removes repeated selectors that have been comma separated
*
* @param (string) selector: CSS Selector
*/
private function repeats( $selector ) {
$parts = preg_split( $this->rcomma, $selector );
$parts = array_flip( $parts );
$parts = array_flip( $parts );
return implode( ',', $parts );
}
/**
* Adds space after pseudo selector for ie6 like a:first-child{ => a:first-child {
*
* @param (string) selector: CSS Selector
*/
private function pseudoSpace( $selector ) {
return preg_replace( $this->rpseudo, ":first-$1 $2", $selector );
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
if ( $method == 'selectors' ) {
return $this->selectors( $args[0] );
}
else {
return call_user_func_array( array($this, $method), $args );
}
}
else {
throw new CSSCompression_Exception( "Unknown method in Selectors Class - " . $method );
}
}
}

View file

@ -0,0 +1,289 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Setup {
/**
* Trim Patterns
*
* @class Control: Compression Controller
* @class Individuals: Individuals Instance
* @instance instance: CSSCompression Instance
* @param (string) token: Copy of the injection token
* @param (array) options: Reference to options
* @param (array) stats: Reference to stats
* @param (regex) rsemicolon: Checks for semicolon without an escape '\' character before it
* @param (regex) rcolon: Checks for colon without an escape '\' character before it
* @param (regex) rbang: Checks for '!' without an escape '\' character before it
* @param (regex) rspacebank: Checks for an unescaped space before a bang character
* @param (regex) rliner: Matching known 1-line intros
* @param (regex) rnested: Matching known subsection handlers
* @param (regex) rurl: url wrapper matching
* @param (regex) rsinglequote: Checks for unescaped escaped single quote (mouthfull)
* @param (array) rsetup: Expanding stylesheet for semi-tokenizing
*/
private $Control;
private $Individuals;
private $instance;
private $token = '';
private $options = array();
private $stats = array();
private $rsemicolon = "/(?<!\\\);/";
private $rcolon = "/(?<!\\\):/";
private $rbang = "/(?<!\\\)\!/";
private $rspacebang = "/(?<!\\\)\s\!/";
private $rliner = "/^@(import|charset|namespace)/i";
private $rmedia = "/^@media/i";
private $rurl = "/url\((.*?)\)/";
private $rsinglequote = "/(?<!\\\)\\\'/";
private $rsetup = array(
'patterns' => array(
"/(?<!\\\){/",
"/(?<!\\\)}/",
"/(?<!\\\)@/",
"/(@(charset|import)[^;]*(?<!\\\);)/",
),
'replacements' => array(
"\n{\n",
"\n}\n",
"\n@",
"$1\n",
),
);
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->Individuals = $control->Individuals;
$this->token = CSSCompression::TOKEN;
$this->options = &$control->Option->options;
$this->stats = &$control->stats;
}
/**
* Setup selector and details arrays for compression methods
*
* @param (string) css: Trimed stylesheet
*/
public function setup( $css ) {
// Seperate the element from the elements details
$css = explode( "\n", preg_replace( $this->rsetup['patterns'], $this->rsetup['replacements'], $css ) );
$newline = $this->options['readability'] > CSSCompression::READ_NONE ? "\n" : '';
$setup = array(
'selectors' => array(),
'details' => array(),
'unknown' => array(),
'introliner' => '',
'namespace' => '',
'import' => '',
'charset' => '',
);
while ( count( $css ) ) {
$row = trim( array_shift( $css ) );
if ( $row == '' ) {
continue;
}
// Single block At-Rule set
else if ( $row[0] == '@' && $css[0] == '{' && trim( $css[1] ) != '' && $css[2] == '}' ) {
// Stash selector
array_push( $setup['selectors'], $row );
// Stash details (after the opening brace)
array_push( $setup['details'], $this->details( trim( $css[1] ) ) );
// drop the details from the stack
$css = array_slice( $css, 3 );
}
// Single line At-Rules (import/charset/namespace)
else if ( preg_match( $this->rliner, $row, $match ) ) {
$setup[$match[1]] .= $this->liner( $row ) . $newline;
}
// Nested At-Rule declaration blocks
else if ( $row[0] == '@' && $css[0] == '{' ) {
// Stash atrule as selector
array_push( $setup['selectors'], $this->token . $row );
// Stash details (after the opening brace)
array_push( $setup['details'], $this->nested( $css, preg_match( $this->rmedia, $row ) ) . $newline );
}
// Unknown single line At-Rules
else if ( $row[0] == '@' && substr( $row, -1 ) == ';' ) {
$setup['introliner'] .= $row . $newline;
}
// Declaration Block
else if ( count( $css ) >= 3 && $css[0] == '{' && $css[2] == '}' ) {
// Stash selector
array_push( $setup['selectors'], $row );
// Stash details (after the opening brace)
array_push( $setup['details'], $this->details( trim( $css[1] ) ) );
// drop the details from the stack
$css = array_slice( $css, 3 );
}
// Last catch, store unknown artifacts as selectors with a token
// and give it an empty rule set
else {
// Still add to unknown stack, for notification
array_push( $setup['unknown'], $row );
// Stash unknown artifacts as selectors with a token
array_push( $setup['selectors'], $this->token . $row );
// Give it an empty rule set
array_push( $setup['details'], '' );
}
}
return $setup;
}
/**
* Run nested elements through a separate instance of compression
*
* @param (array) css: Reference to the original css array
* @param (bool) organize: Whether or not to organize the subsection (only true for media sections)
*/
private function nested( &$css = array(), $organize = false ) {
$options = $this->options;
$left = 0;
$right = 0;
$row = '';
$independent = '';
$content = '';
$spacing = '';
$newline = $this->options['readability'] > CSSCompression::READ_NONE ? "\n" : '';
// Find the end of the nested section
while ( count( $css ) && ( $left < 1 || $left > $right ) ) {
$row = trim( array_shift( $css ) );
if ( $row == '' ) {
continue;
}
else if ( $row == '{' ) {
$left++;
}
else if ( $row == '}' ) {
$right++;
}
else if ( count( $css ) && substr( $row, 0, 1 ) != '@' && substr( $css[0], 0, 1 ) == '@' && substr( $row, -1 ) == ';' ) {
$independent .= $row;
continue;
}
$content .= $row;
}
// Ensure copy of instance exists
if ( ! $this->instance ) {
$this->instance = new CSSCompression();
}
// Fresh start
$this->instance->reset();
// Compress the nested section independently after removing the wrapping braces
// Also make sure to only organize media sections
if ( $options['organize'] == true && $organize == false ) {
$options['organize'] = false;
}
// Independent sections should be prepended to the next compressed section
$content = ( $independent == '' ? '' : $independent . $newline )
. $this->instance->compress( substr( $content, 1, -1 ), $options );
// Formatting for anything higher then 0 readability
if ( $newline == "\n" ) {
$content = "\n\t" . str_replace( "\n", "\n\t", $content ) . "\n";
$spacing = $this->options['readability'] > CSSCompression::READ_MIN ? ' ' : '';
}
// Stash the compressed nested script
return "$spacing{" . $content . "}$newline";
}
/**
* Converts import/namespace urls into strings
*
* @param (string) row: At-rule
*/
private function liner( $row ) {
$pos = 0;
while ( preg_match( $this->rurl, $row, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
$quote = preg_match( $this->rsinglequote, $match[1][0] ) ? '"' : "'";
$replace = $quote . $match[1][0] . $quote;
$row = substr_replace( $row, $replace, $match[0][1], strlen( $match[0][0] ) );
$pos = $match[0][1] + strlen( $replace );
}
return $row;
}
/**
* Run individual compression techniques on each property of a selector
*
* @param (string) row: Selector properties
*/
private function details( $row ) {
$row = preg_split( $this->rsemicolon, $row );
$parts = array();
$details = '';
foreach ( $row as $line ) {
// Set loopers
$parts = preg_split( $this->rcolon, $line, 2 );
$prop = '';
$value = '';
// Property
if ( isset( $parts[0] ) && ( $parts[0] = trim( $parts[0] ) ) != '' ) {
$prop = $parts[0];
}
// Value
if ( isset( $parts[1] ) && ( $parts[1] = trim( $parts[1] ) ) != '' ) {
$value = preg_replace( $this->rbang, ' !', $parts[1] );
}
// Fail safe, remove unspecified property/values
if ( $prop == '' || $value == '' ) {
continue;
}
// Run the tag/element through each compression
list( $prop, $value ) = $this->Individuals->individuals( $prop, $value );
// Add counter to before stats
$this->stats['before']['props']++;
// Store the compressed element
$details .= "$prop:" . preg_replace( $this->rspacebang, '!', $value ) . ";";
}
return $details;
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Setup Class - " . $method );
}
}
}

View file

@ -0,0 +1,210 @@
<?php
/**
* CSS Compressor [VERSION]
* [DATE]
* Corey Hart @ http://www.codenothing.com
*/
class CSSCompression_Trim {
/**
* Trim Patterns
*
* @class Control: Compression Controller
* @param (array) options: Reference to options
* @param (regex) rcmark: Marking point when traversing through sheet for comments
* @param (regex) rendcomment: Finds the ending comment point
* @param (regex) rendquote: Finds the ending quote point
* @param (regex) rendsinglequote: Finds the ending single quote point
* @param (array) rescape: Array of patterns of groupings that should be escaped
* @param (array) trimmings: Stylesheet trimming patterns/replacements
* @param (array) escaped: Array of characters that need to be escaped
*/
private $Control;
private $options = array();
private $rcmark = "/((?<!\\\)\/\*|(?<!\\\)\"|(?<!\\\)')/";
private $rendcomment = "/\*\//";
private $rendquote = "/(?<!\\\)\"/";
private $rendsinglequote = "/(?<!\\\)'/";
private $rescape = array(
"/(url\()([^'\"].*?)(\))/is",
"/((?<!\\\)\")(.*?)((?<!\\\)\")/s",
"/((?<!\\\)')(.*?)((?<!\\\)')/s",
);
private $trimmings = array(
'patterns' => array(
"/(?<!\\\)(\s+)?(?<!\\\)([!,{};>\~\+\/])(?<!\\\)(\s+)?/s", // Remove un-needed spaces around special characters
"/url\((?<!\\\)\"(.*?)(?<!\\\)\"\)/is", // Remove quotes from urls
"/url\((?<!\\\)'(.*?)(?<!\\\)'\)/is", // Remove single quotes from urls
"/url\((.*?)\)/is", // Lowercase url wrapper
"/(?<!\\\);{2,}/", // Remove unecessary semi-colons
"/(?<!\\\)\s+/s", // Compress all spaces into single space
),
'replacements' => array(
'$2',
'url($1)',
'url($1)',
'url($1)',
';',
' ',
),
);
private $escaped = array(
'search' => array(
":",
";",
"}",
"{",
"@",
"!",
",",
">",
"+",
"~",
"/",
"*",
".",
"=",
"#",
"\r",
"\n",
"\t",
" ",
),
'replace' => array(
"\\:",
"\\;",
"\\}",
"\\{",
"\\@",
"\\!",
"\\,",
"\\>",
"\\+",
"\\~",
"\\/",
"\\*",
"\\.",
"\\=",
"\\#",
"\\r",
"\\n",
"\\t",
"\\ ",
),
);
/**
* Stash a reference to the controller on each instantiation
*
* @param (class) control: CSSCompression Controller
*/
public function __construct( CSSCompression_Control $control ) {
$this->Control = $control;
$this->options = &$control->Option->options;
}
/**
* Central trim handler
*
* @param (string) css: Stylesheet to trim
*/
public function trim( $css ) {
$css = $this->comments( $css );
$css = $this->escape( $css );
$css = $this->strip( $css );
return $css;
}
/**
* Does a quick run through the script to remove all comments from the sheet,
*
* @param (string) css: Stylesheet to trim
*/
private function comments( $css ) {
$pos = 0;
while ( preg_match( $this->rcmark, $css, $match, PREG_OFFSET_CAPTURE, $pos ) ) {
switch ( $match[1][0] ) {
// Start of comment block
case "/*":
if ( preg_match( $this->rendcomment, $css, $m, PREG_OFFSET_CAPTURE, $match[1][1] + 1 ) ) {
$end = $m[0][1] - $match[1][1] + strlen( $m[0][0] );
$css = substr_replace( $css, '', $match[1][1], $end );
$pos = $match[0][1];
}
else {
$css = substr( $css, 0, $match[1][1] );
break 2;
}
break;
// Start of string
case "\"":
if ( preg_match( $this->rendquote, $css, $m, PREG_OFFSET_CAPTURE, $match[1][1] + 1 ) ) {
$pos = $m[0][1] + strlen( $m[0][0] );
}
else {
break 2;
}
break;
// Start of string
case "'":
if ( preg_match( $this->rendsinglequote, $css, $m, PREG_OFFSET_CAPTURE, $match[1][1] + 1 ) ) {
$pos = $m[0][1] + strlen( $m[0][0] );
}
else {
break 2;
}
break;
// Should have never gotten here
default:
break 2;
}
}
return $css;
}
/**
* Escape out possible splitter characters within urls
*
* @param (string) css: Full stylesheet
*/
private function escape( $css ) {
foreach ( $this->rescape as $regex ) {
$start = 0;
while ( preg_match( $regex, $css, $match, PREG_OFFSET_CAPTURE, $start ) ) {
$value = $match[1][0]
. str_replace( $this->escaped['search'], $this->escaped['replace'], $match[2][0] )
. $match[3][0];
$css = substr_replace( $css, $value, $match[0][1], strlen( $match[0][0] ) );
$start = $match[0][1] + strlen( $value ) + 1;
}
}
return $css;
}
/**
* Runs initial formatting to setup for compression
*
* @param (string) css: CSS Contents
*/
private function strip( $css ) {
// Run replacements
return trim( preg_replace( $this->trimmings['patterns'], $this->trimmings['replacements'], $css ) );
}
/**
* Access to private methods for testing
*
* @param (string) method: Method to be called
* @param (array) args: Array of paramters to be passed in
*/
public function access( $method, $args ) {
if ( method_exists( $this, $method ) ) {
return call_user_func_array( array($this, $method), $args );
}
else {
throw new CSSCompression_Exception( "Unknown method in Trim Class - " . $method );
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
<?php
/**
* CSSTidy - CSS Parser and Optimiser
*
* CSS ctype functions
* Defines some functions that can be not defined.
*
* This file is part of CSSTidy.
*
* CSSTidy is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* CSSTidy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CSSTidy; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @package csstidy
* @author Nikolay Matsievsky (speed at webo dot name) 2009-2010
* @version 1.0
*/
/* ctype_space Check for whitespace character(s) */
if (!function_exists('ctype_space')) {
function ctype_space($text) {
return;
preg_match("/[^\s\r\n\t\f]/", $text);
}
}
/* ctype_alpha Check for alphabetic character(s) */
if (!function_exists('ctype_alpha')) {
function ctype_alpha($text) {
return preg_match("/[a-zA-Z]/", $text);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,416 @@
<?php
/**
* CSSTidy - CSS Parser and Optimiser
*
* CSS Printing class
* This class prints CSS data generated by csstidy.
*
* Copyright 2005, 2006, 2007 Florian Schmitz
*
* This file is part of CSSTidy.
*
* CSSTidy is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* CSSTidy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @license http://opensource.org/licenses/lgpl-license.php GNU Lesser General Public License
* @package csstidy
* @author Florian Schmitz (floele at gmail dot com) 2005-2007
* @author Brett Zamir (brettz9 at yahoo dot com) 2007
* @author Cedric Morin (cedric at yterium dot com) 2010
*/
/**
* CSS Printing class
*
* This class prints CSS data generated by csstidy.
*
* @package csstidy
* @author Florian Schmitz (floele at gmail dot com) 2005-2006
* @version 1.0.1
*/
class csstidy_print {
/**
* Saves the input CSS string
* @var string
* @access private
*/
var $input_css = '';
/**
* Saves the formatted CSS string
* @var string
* @access public
*/
var $output_css = '';
/**
* Saves the formatted CSS string (plain text)
* @var string
* @access public
*/
var $output_css_plain = '';
/**
* Constructor
* @param array $css contains the class csstidy
* @access private
* @version 1.0
*/
function csstidy_print(&$css) {
$this->parser = & $css;
$this->css = & $css->css;
$this->template = & $css->template;
$this->tokens = & $css->tokens;
$this->charset = & $css->charset;
$this->import = & $css->import;
$this->namespace = & $css->namespace;
}
/**
* Resets output_css and output_css_plain (new css code)
* @access private
* @version 1.0
*/
function _reset() {
$this->output_css = '';
$this->output_css_plain = '';
}
/**
* Returns the CSS code as plain text
* @param string $default_media default @media to add to selectors without any @media
* @return string
* @access public
* @version 1.0
*/
function plain($default_media = '') {
$this->_print(true, $default_media);
return $this->output_css_plain;
}
/**
* Returns the formatted CSS code
* @param string $default_media default @media to add to selectors without any @media
* @return string
* @access public
* @version 1.0
*/
function formatted($default_media = '') {
$this->_print(false, $default_media);
return $this->output_css;
}
/**
* Returns the formatted CSS code to make a complete webpage
* @param string $doctype shorthand for the document type
* @param bool $externalcss indicates whether styles to be attached internally or as an external stylesheet
* @param string $title title to be added in the head of the document
* @param string $lang two-letter language code to be added to the output
* @return string
* @access public
* @version 1.4
*/
function formatted_page($doctype = 'xhtml1.1', $externalcss = true, $title = '', $lang = 'en') {
switch ($doctype) {
case 'xhtml1.0strict':
$doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
break;
case 'xhtml1.1':
default:
$doctype_output = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
break;
}
$output = $cssparsed = '';
$this->output_css_plain = & $output;
$output .= $doctype_output . "\n" . '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="' . $lang . '"';
$output .= ( $doctype === 'xhtml1.1') ? '>' : ' lang="' . $lang . '">';
$output .= "\n<head>\n <title>$title</title>";
if ($externalcss) {
$output .= "\n <style type=\"text/css\">\n";
$cssparsed = file_get_contents('cssparsed.css');
$output .= $cssparsed; // Adds an invisible BOM or something, but not in css_optimised.php
$output .= "\n</style>";
}
else {
$output .= "\n" . ' <link rel="stylesheet" type="text/css" href="cssparsed.css" />';
// }
}
$output .= "\n</head>\n<body><code id=\"copytext\">";
$output .= $this->formatted();
$output .= '</code>' . "\n" . '</body></html>';
return $this->output_css_plain;
}
/**
* Returns the formatted CSS Code and saves it into $this->output_css and $this->output_css_plain
* @param bool $plain plain text or not
* @param string $default_media default @media to add to selectors without any @media
* @access private
* @version 2.0
*/
function _print($plain = false, $default_media = '') {
if ($this->output_css && $this->output_css_plain) {
return;
}
$output = '';
if (!$this->parser->get_cfg('preserve_css')) {
$this->_convert_raw_css($default_media);
}
$template = & $this->template;
if ($plain) {
$template = array_map('strip_tags', $template);
}
if ($this->parser->get_cfg('timestamp')) {
array_unshift($this->tokens, array(COMMENT, ' CSSTidy ' . $this->parser->version . ': ' . date('r') . ' '));
}
if (!empty($this->charset)) {
$output .= $template[0] . '@charset ' . $template[5] . $this->charset . $template[6];
}
if (!empty($this->import)) {
for ($i = 0, $size = count($this->import); $i < $size; $i++) {
if (substr($this->import[$i], 0, 4) === 'url(' && substr($this->import[$i], -1, 1) === ')') {
$this->import[$i] = '\'' . substr($this->import[$i], 4, -1) . '\'';
$this->parser->log('Optimised @import : Removed "url("', 'Information');
}
$output .= $template[0] . '@import ' . $template[5] . $this->import[$i] . $template[6];
}
}
if (!empty($this->namespace)) {
if (substr($this->namespace, 0, 4) === 'url(' && substr($this->namespace, -1, 1) === ')') {
$this->namespace = '\'' . substr($this->namespace, 4, -1) . '\'';
$this->parser->log('Optimised @namespace : Removed "url("', 'Information');
}
$output .= $template[0] . '@namespace ' . $template[5] . $this->namespace . $template[6];
}
$output .= $template[13];
$in_at_out = '';
$out = & $output;
foreach ($this->tokens as $key => $token) {
switch ($token[0]) {
case AT_START:
$out .= $template[0] . $this->_htmlsp($token[1], $plain) . $template[1];
$out = & $in_at_out;
break;
case SEL_START:
if ($this->parser->get_cfg('lowercase_s')) {
$token[1] = strtolower($token[1]);
}
$out .= ( $token[1]{0} !== '@') ? $template[2] . $this->_htmlsp($token[1], $plain) : $template[0] . $this->_htmlsp($token[1], $plain);
$out .= $template[3];
break;
case PROPERTY:
if ($this->parser->get_cfg('case_properties') === 2) {
$token[1] = strtoupper($token[1]);
}
elseif ($this->parser->get_cfg('case_properties') === 1) {
$token[1] = strtolower($token[1]);
}
$out .= $template[4] . $this->_htmlsp($token[1], $plain) . ':' . $template[5];
break;
case VALUE:
$out .= $this->_htmlsp($token[1], $plain);
if ($this->_seeknocomment($key, 1) == SEL_END && $this->parser->get_cfg('remove_last_;')) {
$out .= str_replace(';', '', $template[6]);
}
else {
$out .= $template[6];
}
break;
case SEL_END:
$out .= $template[7];
if ($this->_seeknocomment($key, 1) != AT_END) {
$out .= $template[8];
}
break;
case AT_END:
$out = & $output;
$out .= $template[10] . str_replace("\n", "\n" . $template[10], $in_at_out);
$in_at_out = '';
$out .= $template[9];
break;
case COMMENT:
$out .= $template[11] . '/*' . $this->_htmlsp($token[1], $plain) . '*/' . $template[12];
break;
}
}
$output = trim($output);
if (!$plain) {
$this->output_css = $output;
$this->_print(true);
}
else {
// If using spaces in the template, don't want these to appear in the plain output
$this->output_css_plain = str_replace('&#160;', '', $output);
}
}
/**
* Gets the next token type which is $move away from $key, excluding comments
* @param integer $key current position
* @param integer $move move this far
* @return mixed a token type
* @access private
* @version 1.0
*/
function _seeknocomment($key, $move) {
$go = ($move > 0) ? 1 : -1;
for ($i = $key + 1; abs($key - $i) - 1 < abs($move); $i += $go) {
if (!isset($this->tokens[$i])) {
return;
}
if ($this->tokens[$i][0] == COMMENT) {
$move += 1;
continue;
}
return $this->tokens[$i][0];
}
}
/**
* Converts $this->css array to a raw array ($this->tokens)
* @param string $default_media default @media to add to selectors without any @media
* @access private
* @version 1.0
*/
function _convert_raw_css($default_media = '') {
$this->tokens = array();
foreach ($this->css as $medium => $val) {
if ($this->parser->get_cfg('sort_selectors')) {
ksort($val);
}
if (intval($medium) < DEFAULT_AT) {
$this->parser->_add_token(AT_START, $medium, true);
}
elseif ($default_media) {
$this->parser->_add_token(AT_START, $default_media, true);
}
foreach ($val as $selector => $vali) {
if ($this->parser->get_cfg('sort_properties')) {
ksort($vali);
}
$this->parser->_add_token(SEL_START, $selector, true);
foreach ($vali as $property => $valj) {
$this->parser->_add_token(PROPERTY, $property, true);
$this->parser->_add_token(VALUE, $valj, true);
}
$this->parser->_add_token(SEL_END, $selector, true);
}
if (intval($medium) < DEFAULT_AT) {
$this->parser->_add_token(AT_END, $medium, true);
}
elseif ($default_media) {
$this->parser->_add_token(AT_END, $default_media, true);
}
}
}
/**
* Same as htmlspecialchars, only that chars are not replaced if $plain !== true. This makes print_code() cleaner.
* @param string $string
* @param bool $plain
* @return string
* @see csstidy_print::_print()
* @access private
* @version 1.0
*/
function _htmlsp($string, $plain) {
if (!$plain) {
return htmlspecialchars($string, ENT_QUOTES, 'utf-8');
}
return $string;
}
/**
* Get compression ratio
* @access public
* @return float
* @version 1.2
*/
function get_ratio() {
if (!$this->output_css_plain) {
$this->formatted();
}
return round((strlen($this->input_css) - strlen($this->output_css_plain)) / strlen($this->input_css), 3) * 100;
}
/**
* Get difference between the old and new code in bytes and prints the code if necessary.
* @access public
* @return string
* @version 1.1
*/
function get_diff() {
if (!$this->output_css_plain) {
$this->formatted();
}
$diff = strlen($this->output_css_plain) - strlen($this->input_css);
if ($diff > 0) {
return '+' . $diff;
}
elseif ($diff == 0) {
return '+-' . $diff;
}
return $diff;
}
/**
* Get the size of either input or output CSS in KB
* @param string $loc default is "output"
* @access public
* @return integer
* @version 1.0
*/
function size($loc = 'output') {
if ($loc === 'output' && !$this->output_css) {
$this->formatted();
}
if ($loc === 'input') {
return (strlen($this->input_css) / 1000);
}
else {
return (strlen($this->output_css_plain) / 1000);
}
}
}

View file

@ -0,0 +1,528 @@
<?php
/**
* Various CSS Data for CSSTidy
*
* This file is part of CSSTidy.
*
* CSSTidy is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* CSSTidy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CSSTidy; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @package csstidy
* @author Florian Schmitz (floele at gmail dot com) 2005
* @author Nikolay Matsievsky (speed at webo dot name) 2010
*/
define('AT_START', 1);
define('AT_END', 2);
define('SEL_START', 3);
define('SEL_END', 4);
define('PROPERTY', 5);
define('VALUE', 6);
define('COMMENT', 7);
define('DEFAULT_AT', 41);
/**
* All whitespace allowed in CSS
*
* @global array $GLOBALS['csstidy']['whitespace']
* @version 1.0
*/
$GLOBALS['csstidy']['whitespace'] = array(' ', "\n", "\t", "\r", "\x0B");
/**
* All CSS tokens used by csstidy
*
* @global string $GLOBALS['csstidy']['tokens']
* @version 1.0
*/
$GLOBALS['csstidy']['tokens'] = '/@}{;:=\'"(,\\!$%&)*+.<>?[]^`|~';
/**
* All CSS units (CSS 3 units included)
*
* @see compress_numbers()
* @global array $GLOBALS['csstidy']['units']
* @version 1.0
*/
$GLOBALS['csstidy']['units'] = array('in', 'cm', 'mm', 'pt', 'pc', 'px', 'rem', 'em', '%', 'ex', 'gd', 'vw', 'vh', 'vm', 'deg', 'grad', 'rad', 'ms', 's', 'khz', 'hz');
/**
* Available at-rules
*
* @global array $GLOBALS['csstidy']['at_rules']
* @version 1.0
*/
$GLOBALS['csstidy']['at_rules'] = array(
'page' => 'is',
'font-face' => 'is',
'charset' => 'iv',
'import' => 'iv',
'namespace' => 'iv',
'media' => 'at',
);
/**
* Properties that need a value with unit
*
* @todo CSS3 properties
* @see compress_numbers();
* @global array $GLOBALS['csstidy']['unit_values']
* @version 1.2
*/
$GLOBALS['csstidy']['unit_values'] = array(
'background',
'background-position',
'border',
'border-top',
'border-right',
'border-bottom',
'border-left',
'border-width',
'border-top-width',
'border-right-width',
'border-left-width',
'border-bottom-width',
'bottom',
'border-spacing',
'font-size',
'height',
'left',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'max-height',
'max-width',
'min-height',
'min-width',
'outline',
'outline-width',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'right',
'top',
'text-indent',
'letter-spacing',
'word-spacing',
'width',
);
/**
* Properties that allow <color> as value
*
* @todo CSS3 properties
* @see compress_numbers();
* @global array $GLOBALS['csstidy']['color_values']
* @version 1.0
*/
$GLOBALS['csstidy']['color_values'] = array();
$GLOBALS['csstidy']['color_values'][] = 'background-color';
$GLOBALS['csstidy']['color_values'][] = 'border-color';
$GLOBALS['csstidy']['color_values'][] = 'border-top-color';
$GLOBALS['csstidy']['color_values'][] = 'border-right-color';
$GLOBALS['csstidy']['color_values'][] = 'border-bottom-color';
$GLOBALS['csstidy']['color_values'][] = 'border-left-color';
$GLOBALS['csstidy']['color_values'][] = 'color';
$GLOBALS['csstidy']['color_values'][] = 'outline-color';
/**
* Default values for the background properties
*
* @todo Possibly property names will change during CSS3 development
* @global array $GLOBALS['csstidy']['background_prop_default']
* @see dissolve_short_bg()
* @see merge_bg()
* @version 1.0
*/
$GLOBALS['csstidy']['background_prop_default'] = array();
$GLOBALS['csstidy']['background_prop_default']['background-image'] = 'none';
$GLOBALS['csstidy']['background_prop_default']['background-size'] = 'auto';
$GLOBALS['csstidy']['background_prop_default']['background-repeat'] = 'repeat';
$GLOBALS['csstidy']['background_prop_default']['background-position'] = '0 0';
$GLOBALS['csstidy']['background_prop_default']['background-attachment'] = 'scroll';
$GLOBALS['csstidy']['background_prop_default']['background-clip'] = 'border';
$GLOBALS['csstidy']['background_prop_default']['background-origin'] = 'padding';
$GLOBALS['csstidy']['background_prop_default']['background-color'] = 'transparent';
/**
* Default values for the font properties
*
* @global array $GLOBALS['csstidy']['font_prop_default']
* @see merge_fonts()
* @version 1.3
*/
$GLOBALS['csstidy']['font_prop_default'] = array();
$GLOBALS['csstidy']['font_prop_default']['font-style'] = 'normal';
$GLOBALS['csstidy']['font_prop_default']['font-variant'] = 'normal';
$GLOBALS['csstidy']['font_prop_default']['font-weight'] = 'normal';
$GLOBALS['csstidy']['font_prop_default']['font-size'] = '';
$GLOBALS['csstidy']['font_prop_default']['line-height'] = '';
$GLOBALS['csstidy']['font_prop_default']['font-family'] = '';
/**
* A list of non-W3C color names which get replaced by their hex-codes
*
* @global array $GLOBALS['csstidy']['replace_colors']
* @see cut_color()
* @version 1.0
*/
$GLOBALS['csstidy']['replace_colors'] = array();
$GLOBALS['csstidy']['replace_colors']['aliceblue'] = '#f0f8ff';
$GLOBALS['csstidy']['replace_colors']['antiquewhite'] = '#faebd7';
$GLOBALS['csstidy']['replace_colors']['aquamarine'] = '#7fffd4';
$GLOBALS['csstidy']['replace_colors']['azure'] = '#f0ffff';
$GLOBALS['csstidy']['replace_colors']['beige'] = '#f5f5dc';
$GLOBALS['csstidy']['replace_colors']['bisque'] = '#ffe4c4';
$GLOBALS['csstidy']['replace_colors']['blanchedalmond'] = '#ffebcd';
$GLOBALS['csstidy']['replace_colors']['blueviolet'] = '#8a2be2';
$GLOBALS['csstidy']['replace_colors']['brown'] = '#a52a2a';
$GLOBALS['csstidy']['replace_colors']['burlywood'] = '#deb887';
$GLOBALS['csstidy']['replace_colors']['cadetblue'] = '#5f9ea0';
$GLOBALS['csstidy']['replace_colors']['chartreuse'] = '#7fff00';
$GLOBALS['csstidy']['replace_colors']['chocolate'] = '#d2691e';
$GLOBALS['csstidy']['replace_colors']['coral'] = '#ff7f50';
$GLOBALS['csstidy']['replace_colors']['cornflowerblue'] = '#6495ed';
$GLOBALS['csstidy']['replace_colors']['cornsilk'] = '#fff8dc';
$GLOBALS['csstidy']['replace_colors']['crimson'] = '#dc143c';
$GLOBALS['csstidy']['replace_colors']['cyan'] = '#00ffff';
$GLOBALS['csstidy']['replace_colors']['darkblue'] = '#00008b';
$GLOBALS['csstidy']['replace_colors']['darkcyan'] = '#008b8b';
$GLOBALS['csstidy']['replace_colors']['darkgoldenrod'] = '#b8860b';
$GLOBALS['csstidy']['replace_colors']['darkgray'] = '#a9a9a9';
$GLOBALS['csstidy']['replace_colors']['darkgreen'] = '#006400';
$GLOBALS['csstidy']['replace_colors']['darkkhaki'] = '#bdb76b';
$GLOBALS['csstidy']['replace_colors']['darkmagenta'] = '#8b008b';
$GLOBALS['csstidy']['replace_colors']['darkolivegreen'] = '#556b2f';
$GLOBALS['csstidy']['replace_colors']['darkorange'] = '#ff8c00';
$GLOBALS['csstidy']['replace_colors']['darkorchid'] = '#9932cc';
$GLOBALS['csstidy']['replace_colors']['darkred'] = '#8b0000';
$GLOBALS['csstidy']['replace_colors']['darksalmon'] = '#e9967a';
$GLOBALS['csstidy']['replace_colors']['darkseagreen'] = '#8fbc8f';
$GLOBALS['csstidy']['replace_colors']['darkslateblue'] = '#483d8b';
$GLOBALS['csstidy']['replace_colors']['darkslategray'] = '#2f4f4f';
$GLOBALS['csstidy']['replace_colors']['darkturquoise'] = '#00ced1';
$GLOBALS['csstidy']['replace_colors']['darkviolet'] = '#9400d3';
$GLOBALS['csstidy']['replace_colors']['deeppink'] = '#ff1493';
$GLOBALS['csstidy']['replace_colors']['deepskyblue'] = '#00bfff';
$GLOBALS['csstidy']['replace_colors']['dimgray'] = '#696969';
$GLOBALS['csstidy']['replace_colors']['dodgerblue'] = '#1e90ff';
$GLOBALS['csstidy']['replace_colors']['feldspar'] = '#d19275';
$GLOBALS['csstidy']['replace_colors']['firebrick'] = '#b22222';
$GLOBALS['csstidy']['replace_colors']['floralwhite'] = '#fffaf0';
$GLOBALS['csstidy']['replace_colors']['forestgreen'] = '#228b22';
$GLOBALS['csstidy']['replace_colors']['gainsboro'] = '#dcdcdc';
$GLOBALS['csstidy']['replace_colors']['ghostwhite'] = '#f8f8ff';
$GLOBALS['csstidy']['replace_colors']['gold'] = '#ffd700';
$GLOBALS['csstidy']['replace_colors']['goldenrod'] = '#daa520';
$GLOBALS['csstidy']['replace_colors']['greenyellow'] = '#adff2f';
$GLOBALS['csstidy']['replace_colors']['honeydew'] = '#f0fff0';
$GLOBALS['csstidy']['replace_colors']['hotpink'] = '#ff69b4';
$GLOBALS['csstidy']['replace_colors']['indianred'] = '#cd5c5c';
$GLOBALS['csstidy']['replace_colors']['indigo'] = '#4b0082';
$GLOBALS['csstidy']['replace_colors']['ivory'] = '#fffff0';
$GLOBALS['csstidy']['replace_colors']['khaki'] = '#f0e68c';
$GLOBALS['csstidy']['replace_colors']['lavender'] = '#e6e6fa';
$GLOBALS['csstidy']['replace_colors']['lavenderblush'] = '#fff0f5';
$GLOBALS['csstidy']['replace_colors']['lawngreen'] = '#7cfc00';
$GLOBALS['csstidy']['replace_colors']['lemonchiffon'] = '#fffacd';
$GLOBALS['csstidy']['replace_colors']['lightblue'] = '#add8e6';
$GLOBALS['csstidy']['replace_colors']['lightcoral'] = '#f08080';
$GLOBALS['csstidy']['replace_colors']['lightcyan'] = '#e0ffff';
$GLOBALS['csstidy']['replace_colors']['lightgoldenrodyellow'] = '#fafad2';
$GLOBALS['csstidy']['replace_colors']['lightgrey'] = '#d3d3d3';
$GLOBALS['csstidy']['replace_colors']['lightgreen'] = '#90ee90';
$GLOBALS['csstidy']['replace_colors']['lightpink'] = '#ffb6c1';
$GLOBALS['csstidy']['replace_colors']['lightsalmon'] = '#ffa07a';
$GLOBALS['csstidy']['replace_colors']['lightseagreen'] = '#20b2aa';
$GLOBALS['csstidy']['replace_colors']['lightskyblue'] = '#87cefa';
$GLOBALS['csstidy']['replace_colors']['lightslateblue'] = '#8470ff';
$GLOBALS['csstidy']['replace_colors']['lightslategray'] = '#778899';
$GLOBALS['csstidy']['replace_colors']['lightsteelblue'] = '#b0c4de';
$GLOBALS['csstidy']['replace_colors']['lightyellow'] = '#ffffe0';
$GLOBALS['csstidy']['replace_colors']['limegreen'] = '#32cd32';
$GLOBALS['csstidy']['replace_colors']['linen'] = '#faf0e6';
$GLOBALS['csstidy']['replace_colors']['magenta'] = '#ff00ff';
$GLOBALS['csstidy']['replace_colors']['mediumaquamarine'] = '#66cdaa';
$GLOBALS['csstidy']['replace_colors']['mediumblue'] = '#0000cd';
$GLOBALS['csstidy']['replace_colors']['mediumorchid'] = '#ba55d3';
$GLOBALS['csstidy']['replace_colors']['mediumpurple'] = '#9370d8';
$GLOBALS['csstidy']['replace_colors']['mediumseagreen'] = '#3cb371';
$GLOBALS['csstidy']['replace_colors']['mediumslateblue'] = '#7b68ee';
$GLOBALS['csstidy']['replace_colors']['mediumspringgreen'] = '#00fa9a';
$GLOBALS['csstidy']['replace_colors']['mediumturquoise'] = '#48d1cc';
$GLOBALS['csstidy']['replace_colors']['mediumvioletred'] = '#c71585';
$GLOBALS['csstidy']['replace_colors']['midnightblue'] = '#191970';
$GLOBALS['csstidy']['replace_colors']['mintcream'] = '#f5fffa';
$GLOBALS['csstidy']['replace_colors']['mistyrose'] = '#ffe4e1';
$GLOBALS['csstidy']['replace_colors']['moccasin'] = '#ffe4b5';
$GLOBALS['csstidy']['replace_colors']['navajowhite'] = '#ffdead';
$GLOBALS['csstidy']['replace_colors']['oldlace'] = '#fdf5e6';
$GLOBALS['csstidy']['replace_colors']['olivedrab'] = '#6b8e23';
$GLOBALS['csstidy']['replace_colors']['orangered'] = '#ff4500';
$GLOBALS['csstidy']['replace_colors']['orchid'] = '#da70d6';
$GLOBALS['csstidy']['replace_colors']['palegoldenrod'] = '#eee8aa';
$GLOBALS['csstidy']['replace_colors']['palegreen'] = '#98fb98';
$GLOBALS['csstidy']['replace_colors']['paleturquoise'] = '#afeeee';
$GLOBALS['csstidy']['replace_colors']['palevioletred'] = '#d87093';
$GLOBALS['csstidy']['replace_colors']['papayawhip'] = '#ffefd5';
$GLOBALS['csstidy']['replace_colors']['peachpuff'] = '#ffdab9';
$GLOBALS['csstidy']['replace_colors']['peru'] = '#cd853f';
$GLOBALS['csstidy']['replace_colors']['pink'] = '#ffc0cb';
$GLOBALS['csstidy']['replace_colors']['plum'] = '#dda0dd';
$GLOBALS['csstidy']['replace_colors']['powderblue'] = '#b0e0e6';
$GLOBALS['csstidy']['replace_colors']['rosybrown'] = '#bc8f8f';
$GLOBALS['csstidy']['replace_colors']['royalblue'] = '#4169e1';
$GLOBALS['csstidy']['replace_colors']['saddlebrown'] = '#8b4513';
$GLOBALS['csstidy']['replace_colors']['salmon'] = '#fa8072';
$GLOBALS['csstidy']['replace_colors']['sandybrown'] = '#f4a460';
$GLOBALS['csstidy']['replace_colors']['seagreen'] = '#2e8b57';
$GLOBALS['csstidy']['replace_colors']['seashell'] = '#fff5ee';
$GLOBALS['csstidy']['replace_colors']['sienna'] = '#a0522d';
$GLOBALS['csstidy']['replace_colors']['skyblue'] = '#87ceeb';
$GLOBALS['csstidy']['replace_colors']['slateblue'] = '#6a5acd';
$GLOBALS['csstidy']['replace_colors']['slategray'] = '#708090';
$GLOBALS['csstidy']['replace_colors']['snow'] = '#fffafa';
$GLOBALS['csstidy']['replace_colors']['springgreen'] = '#00ff7f';
$GLOBALS['csstidy']['replace_colors']['steelblue'] = '#4682b4';
$GLOBALS['csstidy']['replace_colors']['tan'] = '#d2b48c';
$GLOBALS['csstidy']['replace_colors']['thistle'] = '#d8bfd8';
$GLOBALS['csstidy']['replace_colors']['tomato'] = '#ff6347';
$GLOBALS['csstidy']['replace_colors']['turquoise'] = '#40e0d0';
$GLOBALS['csstidy']['replace_colors']['violet'] = '#ee82ee';
$GLOBALS['csstidy']['replace_colors']['violetred'] = '#d02090';
$GLOBALS['csstidy']['replace_colors']['wheat'] = '#f5deb3';
$GLOBALS['csstidy']['replace_colors']['whitesmoke'] = '#f5f5f5';
$GLOBALS['csstidy']['replace_colors']['yellowgreen'] = '#9acd32';
/**
* A list of all shorthand properties that are devided into four properties and/or have four subvalues
*
* @global array $GLOBALS['csstidy']['shorthands']
* @todo Are there new ones in CSS3?
* @see dissolve_4value_shorthands()
* @see merge_4value_shorthands()
* @version 1.0
*/
$GLOBALS['csstidy']['shorthands'] = array();
$GLOBALS['csstidy']['shorthands']['border-color'] = array('border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color');
$GLOBALS['csstidy']['shorthands']['border-style'] = array('border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style');
$GLOBALS['csstidy']['shorthands']['border-width'] = array('border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width');
$GLOBALS['csstidy']['shorthands']['margin'] = array('margin-top', 'margin-right', 'margin-bottom', 'margin-left');
$GLOBALS['csstidy']['shorthands']['padding'] = array('padding-top', 'padding-right', 'padding-bottom', 'padding-left');
$GLOBALS['csstidy']['shorthands']['-moz-border-radius'] = 0;
/**
* All CSS Properties. Needed for csstidy::property_is_next()
*
* @global array $GLOBALS['csstidy']['all_properties']
* @todo Add CSS3 properties
* @version 1.0
* @see csstidy::property_is_next()
*/
$GLOBALS['csstidy']['all_properties'] = array();
$GLOBALS['csstidy']['all_properties']['background'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['background-color'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['background-image'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['background-position'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-top'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-right'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-left'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-color'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-top-color'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-bottom-color'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-left-color'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-right-color'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-style'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-top-style'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-right-style'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-left-style'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-bottom-style'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-width'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-collapse'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['border-spacing'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['bottom'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['caption-side'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['content'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['clear'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['clip'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['color'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['counter-reset'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['counter-increment'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['cursor'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['empty-cells'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['display'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['direction'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['float'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['font'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['font-family'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['font-style'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['font-stretch'] = 'CSS2.0';
$GLOBALS['csstidy']['all_properties']['font-size-adjust'] = 'CSS2.0';
$GLOBALS['csstidy']['all_properties']['font-size'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['height'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['left'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['line-height'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['list-style'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['margin'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['marks'] = 'CSS1.0,CSS2.0';
$GLOBALS['csstidy']['all_properties']['marker-offset'] = 'CSS2.0';
$GLOBALS['csstidy']['all_properties']['max-height'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['max-width'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['min-height'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['min-width'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['overflow'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['orphans'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['outline'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['outline-width'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['outline-style'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['outline-color'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['padding'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['page-break-before'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['page-break-after'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['page-break-inside'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['page'] = 'CSS2.0';
$GLOBALS['csstidy']['all_properties']['position'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['quotes'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['right'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['size'] = 'CSS1.0,CSS2.0';
$GLOBALS['csstidy']['all_properties']['speak-header'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['table-layout'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['top'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['text-align'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['text-shadow'] = 'CSS2.0';
$GLOBALS['csstidy']['all_properties']['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['white-space'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['unicode-bidi'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['visibility'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['width'] = 'CSS1.0,CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['widows'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['z-index'] = 'CSS1.0,CSS2.0,CSS2.1';
/* Speech */
$GLOBALS['csstidy']['all_properties']['volume'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['speak'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['pause'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['pause-before'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['pause-after'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['cue'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['cue-before'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['cue-after'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['play-during'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['azimuth'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['elevation'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['speech-rate'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['voice-family'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['pitch'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['pitch-range'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['stress'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['richness'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['speak-punctuation'] = 'CSS2.0,CSS2.1';
$GLOBALS['csstidy']['all_properties']['speak-numeral'] = 'CSS2.0,CSS2.1';
/**
* An array containing all predefined templates.
*
* @global array $GLOBALS['csstidy']['predefined_templates']
* @version 1.0
* @see csstidy::load_template()
*/
$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="at">'; //string before @rule
$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span> <span class="format">{</span>' . "\n"; //bracket after @-rule
$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="selector">'; //string before selector
$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span> <span class="format">{</span>' . "\n"; //bracket after selector
$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="property">'; //string before property
$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span><span class="value">'; //string after property+before value
$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span><span class="format">;</span>' . "\n"; //string after value
$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="format">}</span>'; //closing bracket - selector
$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n\n"; //space between blocks {...}
$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n" . '<span class="format">}</span>' . "\n\n"; //closing bracket @-rule
$GLOBALS['csstidy']['predefined_templates']['default'][] = ''; //indent in @-rule
$GLOBALS['csstidy']['predefined_templates']['default'][] = '<span class="comment">'; // before comment
$GLOBALS['csstidy']['predefined_templates']['default'][] = '</span>' . "\n"; // after comment
$GLOBALS['csstidy']['predefined_templates']['default'][] = "\n"; // after last line @-rule
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="at">';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span> <span class="format">{</span>' . "\n";
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="selector">';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="format">{</span>';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="property">';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="value">';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span><span class="format">;</span>';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="format">}</span>';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n";
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n" . '<span class="format">}' . "\n" . '</span>';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '';
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '<span class="comment">'; // before comment
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = '</span>'; // after comment
$GLOBALS['csstidy']['predefined_templates']['high_compression'][] = "\n";
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="at">';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">{</span>';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="selector">';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">{</span>';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="property">';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="value">';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span><span class="format">;</span>';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="format">}</span>';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="format">}</span>';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '';
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '<span class="comment">'; // before comment
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '</span>'; // after comment
$GLOBALS['csstidy']['predefined_templates']['highest_compression'][] = '';
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="at">';
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span> <span class="format">{</span>' . "\n";
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="selector">';
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span>' . "\n" . '<span class="format">{</span>' . "\n";
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' <span class="property">';
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span><span class="value">';
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span><span class="format">;</span>' . "\n";
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="format">}</span>';
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n\n";
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n" . '<span class="format">}</span>' . "\n\n";
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = ' ';
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '<span class="comment">'; // before comment
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = '</span>' . "\n"; // after comment
$GLOBALS['csstidy']['predefined_templates']['low_compression'][] = "\n";

View file

@ -0,0 +1,728 @@
<?php
/*!
* cssmin.php rev 91c5ea5
* Author: Tubal Martin - http://blog.margenn.com/
* Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
*
* This is a PHP port of the Javascript port of the CSS minification tool
* distributed with YUICompressor, itself a port of the cssmin utility by
* Isaac Schlueter - http://foohack.com/
* Permission is hereby granted to use the PHP version under the same
* conditions as the YUICompressor.
*/
/*!
* YUI Compressor
* http://developer.yahoo.com/yui/compressor/
* Author: Julien Lecomte - http://www.julienlecomte.net/
* Copyright (c) 2011 Yahoo! Inc. All rights reserved.
* The copyrights embodied in the content of this file are licensed
* by Yahoo! Inc. under the BSD (revised) open source license.
*/
class CSSmin
{
const NL = '___YUICSSMIN_PRESERVED_NL___';
const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_';
const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_';
const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___';
private $comments;
private $preserved_tokens;
private $memory_limit;
private $max_execution_time;
private $pcre_backtrack_limit;
private $pcre_recursion_limit;
private $raise_php_limits;
/**
* @param bool|int $raise_php_limits
* If true, PHP settings will be raised if needed
*/
public function __construct($raise_php_limits = TRUE)
{
// Set suggested PHP limits
$this->memory_limit = 128 * 1048576; // 128MB in bytes
$this->max_execution_time = 60; // 1 min
$this->pcre_backtrack_limit = 1000 * 1000;
$this->pcre_recursion_limit = 500 * 1000;
$this->raise_php_limits = (bool) $raise_php_limits;
}
/**
* Minify a string of CSS
* @param string $css
* @param int|bool $linebreak_pos
* @return string
*/
public function run($css = '', $linebreak_pos = FALSE)
{
if (empty($css)) {
return '';
}
if ($this->raise_php_limits) {
$this->do_raise_php_limits();
}
$this->comments = array();
$this->preserved_tokens = array();
$start_index = 0;
$length = strlen($css);
$css = $this->extract_data_urls($css);
// collect all comment blocks...
while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) {
$end_index = $this->index_of($css, '*/', $start_index + 2);
if ($end_index < 0) {
$end_index = $length;
}
$comment_found = $this->str_slice($css, $start_index + 2, $end_index);
$this->comments[] = $comment_found;
$comment_preserve_string = self::COMMENT . (count($this->comments) - 1) . '___';
$css = $this->str_slice($css, 0, $start_index + 2) . $comment_preserve_string . $this->str_slice($css, $end_index);
// Set correct start_index: Fixes issue #2528130
$start_index = $end_index + 2 + strlen($comment_preserve_string) - strlen($comment_found);
}
// preserve strings so their content doesn't get accidentally minified
$css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", array($this, 'replace_string'), $css);
// Let's divide css code in chunks of 25.000 chars aprox.
// Reason: PHP's PCRE functions like preg_replace have a "backtrack limit"
// of 100.000 chars by default (php < 5.3.7) so if we're dealing with really
// long strings and a (sub)pattern matches a number of chars greater than
// the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently
// returning NULL and $css would be empty.
$charset = '';
$charset_regexp = '/@charset [^;]+;/i';
$css_chunks = array();
$css_chunk_length = 25000; // aprox size, not exact
$start_index = 0;
$i = $css_chunk_length; // save initial iterations
$l = strlen($css);
// if the number of characters is 25000 or less, do not chunk
if ($l <= $css_chunk_length) {
$css_chunks[] = $css;
} else {
// chunk css code securely
while ($i < $l) {
$i += 50; // save iterations. 500 checks for a closing curly brace }
if ($l - $start_index <= $css_chunk_length || $i >= $l) {
$css_chunks[] = $this->str_slice($css, $start_index);
break;
}
if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) {
// If there are two ending curly braces }} separated or not by spaces,
// join them in the same chunk (i.e. @media blocks)
$next_chunk = substr($css, $i);
if (preg_match('/^\s*\}/', $next_chunk)) {
$i = $i + $this->index_of($next_chunk, '}') + 1;
}
$css_chunks[] = $this->str_slice($css, $start_index, $i);
$start_index = $i;
}
}
}
// Minify each chunk
for ($i = 0, $n = count($css_chunks); $i < $n; $i++) {
$css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos);
// Keep the first @charset at-rule found
if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) {
$charset = $matches[0];
}
// Delete all @charset at-rules
$css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]);
}
// Update the first chunk and push the charset to the top of the file.
$css_chunks[0] = $charset . $css_chunks[0];
return implode('', $css_chunks);
}
/**
* Sets the memory limit for this script
* @param int|string $limit
*/
public function set_memory_limit($limit)
{
$this->memory_limit = $this->normalize_int($limit);
}
/**
* Sets the maximum execution time for this script
* @param int|string $seconds
*/
public function set_max_execution_time($seconds)
{
$this->max_execution_time = (int) $seconds;
}
/**
* Sets the PCRE backtrack limit for this script
* @param int $limit
*/
public function set_pcre_backtrack_limit($limit)
{
$this->pcre_backtrack_limit = (int) $limit;
}
/**
* Sets the PCRE recursion limit for this script
* @param int $limit
*/
public function set_pcre_recursion_limit($limit)
{
$this->pcre_recursion_limit = (int) $limit;
}
/**
* Try to configure PHP to use at least the suggested minimum settings
*/
private function do_raise_php_limits()
{
$php_limits = array(
'memory_limit' => $this->memory_limit,
'max_execution_time' => $this->max_execution_time,
'pcre.backtrack_limit' => $this->pcre_backtrack_limit,
'pcre.recursion_limit' => $this->pcre_recursion_limit
);
// If current settings are higher respect them.
foreach ($php_limits as $name => $suggested) {
$current = $this->normalize_int(ini_get($name));
// memory_limit exception: allow -1 for "no memory limit".
if ($current > -1 && ($suggested == -1 || $current < $suggested)) {
ini_set($name, $suggested);
}
}
}
/**
* Does bulk of the minification
* @param string $css
* @param int|bool $linebreak_pos
* @return string
*/
private function minify($css, $linebreak_pos)
{
// strings are safe, now wrestle the comments
for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
$token = $this->comments[$i];
$placeholder = '/' . self::COMMENT . $i . '___/';
// ! in the first position of the comment means preserve
// so push to the preserved tokens keeping the !
if (substr($token, 0, 1) === '!') {
$this->preserved_tokens[] = $token;
$token_tring = self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
$css = preg_replace($placeholder, $token_tring, $css, 1);
// Preserve new lines for /*! important comments
$css = preg_replace('/\s*[\n\r\f]+\s*(\/\*'. $token_tring .')/S', self::NL.'$1', $css);
$css = preg_replace('/('. $token_tring .'\*\/)\s*[\n\r\f]+\s*/S', '$1'.self::NL, $css);
continue;
}
// \ in the last position looks like hack for Mac/IE5
// shorten that to /*\*/ and the next one to /**/
if (substr($token, (strlen($token) - 1), 1) === '\\') {
$this->preserved_tokens[] = '\\';
$css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
$i = $i + 1; // attn: advancing the loop
$this->preserved_tokens[] = '';
$css = preg_replace('/' . self::COMMENT . $i . '___/', self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
continue;
}
// keep empty comments after child selectors (IE7 hack)
// e.g. html >/**/ body
if (strlen($token) === 0) {
$start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1));
if ($start_index > 2) {
if (substr($css, $start_index - 3, 1) === '>') {
$this->preserved_tokens[] = '';
$css = preg_replace($placeholder, self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
}
}
}
// in all other cases kill the comment
$css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1);
}
// Normalize all whitespace strings to single spaces. Easier to work with that way.
$css = preg_replace('/\s+/', ' ', $css);
// Shorten & preserve calculations calc(...) since spaces are important
$css = preg_replace_callback('/calc(\((?:[^\(\)]+|(?1))*\))/i', array($this, 'replace_calc'), $css);
// Replace positive sign from numbers preceded by : or a white-space before the leading space is removed
// +1.2em to 1.2em, +.8px to .8px, +2% to 2%
$css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css);
// Remove leading zeros from integer and float numbers preceded by : or a white-space
// 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
$css = preg_replace('/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S', '$1$2$3', $css);
// Remove trailing zeros from float numbers preceded by : or a white-space
// -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
$css = preg_replace('/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css);
// Remove trailing .0 -> -9.0 to -9
$css = preg_replace('/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S', '$1$2$3', $css);
// Replace 0 length numbers with 0
$css = preg_replace('/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S', '${1}0$2', $css);
// Remove the spaces before the things that should not have spaces before them.
// But, be careful not to turn "p :link {...}" into "p:link{...}"
// Swap out any pseudo-class colons with the token, and then swap back.
$css = preg_replace_callback('/(?:^|\})(?:(?:[^\{\:])+\:)+(?:[^\{]*\{)/', array($this, 'replace_colon'), $css);
$css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css);
$css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css);
// retain space for special IE6 cases
$css = preg_replace('/\:first\-(line|letter)(\{|,)/i', ':first-$1 $2', $css);
// no space after the end of a preserved comment
$css = preg_replace('/\*\/ /', '*/', $css);
// Put the space back in some cases, to support stuff like
// @media screen and (-webkit-min-device-pixel-ratio:0){
$css = preg_replace('/\band\(/i', 'and (', $css);
// Remove the spaces after the things that should not have spaces after them.
$css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css);
// remove unnecessary semicolons
$css = preg_replace('/;+\}/', '}', $css);
// Fix for issue: #2528146
// Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack)
// to avoid issues on Symbian S60 3.x browsers.
$css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css);
// Replace 0 length units 0(px,em,%) with 0.
$css = preg_replace('/((?<!\\\\)\:|\s)\-?0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in|px|pt|pc|%)/iS', '${1}0', $css);
// Replace 0 0; or 0 0 0; or 0 0 0 0; with 0.
$css = preg_replace('/\:0(?: 0){1,3}(;|\})/', ':0$1', $css);
// Fix for issue: #2528142
// Replace text-shadow:0; with text-shadow:0 0 0;
$css = preg_replace('/(text-shadow\:0)(;|\})/ie', "strtolower('$1 0 0$2')", $css);
// Replace background-position:0; with background-position:0 0;
// same for transform-origin
$css = preg_replace('/(background\-position|(?:webkit|moz|o|ms|)\-?transform\-origin)\:0(;|\})/ieS', "strtolower('$1:0 0$2')", $css);
// Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
// Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
// This makes it more likely that it'll get further compressed in the next step.
$css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'rgb_to_hex'), $css);
$css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'hsl_to_hex'), $css);
// Shorten colors from #AABBCC to #ABC or short color name.
$css = $this->compress_hex_colors($css);
// border: none to border:0, outline: none to outline:0
$css = preg_replace('/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\})/ieS', "strtolower('$1:0$2')", $css);
// shorter opacity IE filter
$css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css);
// Remove empty rules.
$css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css);
// Some source control tools don't like it when files containing lines longer
// than, say 8000 characters, are checked in. The linebreak option is used in
// that case to split long lines after a specific column.
if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) {
$linebreak_pos = (int) $linebreak_pos;
$start_index = $i = 0;
while ($i < strlen($css)) {
$i++;
if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) {
$css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i);
$start_index = $i;
}
}
}
// Replace multiple semi-colons in a row by a single one
// See SF bug #1980989
$css = preg_replace('/;;+/', ';', $css);
// Restore new lines for /*! important comments
$css = preg_replace('/'. self::NL .'/', "\n", $css);
// restore preserved comments and strings
for ($i = 0, $max = count($this->preserved_tokens); $i < $max; $i++) {
$css = preg_replace('/' . self::TOKEN . $i . '___/', $this->preserved_tokens[$i], $css, 1);
}
// Trim the final string (for any leading or trailing white spaces)
return trim($css);
}
/**
* Utility method to replace all data urls with tokens before we start
* compressing, to avoid performance issues running some of the subsequent
* regexes against large strings chunks.
*
* @param string $css
* @return string
*/
private function extract_data_urls($css)
{
// Leave data urls alone to increase parse performance.
$max_index = strlen($css) - 1;
$append_index = $index = $last_index = $offset = 0;
$sb = array();
$pattern = '/url\(\s*(["\']?)data\:/i';
// Since we need to account for non-base64 data urls, we need to handle
// ' and ) being part of the data string. Hence switching to indexOf,
// to determine whether or not we have matching string terminators and
// handling sb appends directly, instead of using matcher.append* methods.
while (preg_match($pattern, $css, $m, 0, $offset)) {
$index = $this->index_of($css, $m[0], $offset);
$last_index = $index + strlen($m[0]);
$start_index = $index + 4; // "url(".length()
$end_index = $last_index - 1;
$terminator = $m[1]; // ', " or empty (not quoted)
$found_terminator = FALSE;
if (strlen($terminator) === 0) {
$terminator = ')';
}
while ($found_terminator === FALSE && $end_index+1 <= $max_index) {
$end_index = $this->index_of($css, $terminator, $end_index + 1);
// endIndex == 0 doesn't really apply here
if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') {
$found_terminator = TRUE;
if (')' != $terminator) {
$end_index = $this->index_of($css, ')', $end_index);
}
}
}
// Enough searching, start moving stuff over to the buffer
$sb[] = $this->substring($css, $append_index, $index);
if ($found_terminator) {
$token = $this->substring($css, $start_index, $end_index);
$token = preg_replace('/\s+/', '', $token);
$this->preserved_tokens[] = $token;
$preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)';
$sb[] = $preserver;
$append_index = $end_index + 1;
} else {
// No end terminator found, re-add the whole match. Should we throw/warn here?
$sb[] = $this->substring($css, $index, $last_index);
$append_index = $last_index;
}
$offset = $last_index;
}
$sb[] = $this->substring($css, $append_index);
return implode('', $sb);
}
/**
* Utility method to compress hex color values of the form #AABBCC to #ABC or short color name.
*
* DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
* e.g. #AddressForm { ... }
*
* DOES NOT compress IE filters, which have hex color values (which would break things).
* e.g. filter: chroma(color="#FFFFFF");
*
* DOES NOT compress invalid hex values.
* e.g. background-color: #aabbccdd
*
* @param string $css
* @return string
*/
private function compress_hex_colors($css)
{
// Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
$pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS';
$_index = $index = $last_index = $offset = 0;
$sb = array();
// See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors
$short_safe = array(
'#808080' => 'gray',
'#008000' => 'green',
'#800000' => 'maroon',
'#000080' => 'navy',
'#808000' => 'olive',
'#800080' => 'purple',
'#c0c0c0' => 'silver',
'#008080' => 'teal',
'#f00' => 'red'
);
while (preg_match($pattern, $css, $m, 0, $offset)) {
$index = $this->index_of($css, $m[0], $offset);
$last_index = $index + strlen($m[0]);
$is_filter = (bool) $m[1];
$sb[] = $this->substring($css, $_index, $index);
if ($is_filter) {
// Restore, maintain case, otherwise filter will break
$sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7];
} else {
if (strtolower($m[2]) == strtolower($m[3]) &&
strtolower($m[4]) == strtolower($m[5]) &&
strtolower($m[6]) == strtolower($m[7])) {
// Compress.
$hex = '#' . strtolower($m[3] . $m[5] . $m[7]);
} else {
// Non compressible color, restore but lower case.
$hex = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]);
}
// replace Hex colors to short safe color names
$sb[] = array_key_exists($hex, $short_safe) ? $short_safe[$hex] : $hex;
}
$_index = $offset = $last_index - strlen($m[8]);
}
$sb[] = $this->substring($css, $_index);
return implode('', $sb);
}
/* CALLBACKS
* ---------------------------------------------------------------------------------------------
*/
private function replace_string($matches)
{
$match = $matches[0];
$quote = substr($match, 0, 1);
// Must use addcslashes in PHP to avoid parsing of backslashes
$match = addcslashes($this->str_slice($match, 1, -1), '\\');
// maybe the string contains a comment-like substring?
// one, maybe more? put'em back then
if (($pos = $this->index_of($match, self::COMMENT)) >= 0) {
for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
$match = preg_replace('/' . self::COMMENT . $i . '___/', $this->comments[$i], $match, 1);
}
}
// minify alpha opacity in filter strings
$match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match);
$this->preserved_tokens[] = $match;
return $quote . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . $quote;
}
private function replace_colon($matches)
{
return preg_replace('/\:/', self::CLASSCOLON, $matches[0]);
}
private function replace_calc($matches)
{
$this->preserved_tokens[] = preg_replace('/\s?([\*\/\(\),])\s?/', '$1', $matches[0]);
return self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
}
private function rgb_to_hex($matches)
{
// Support for percentage values rgb(100%, 0%, 45%);
if ($this->index_of($matches[1], '%') >= 0){
$rgbcolors = explode(',', str_replace('%', '', $matches[1]));
for ($i = 0; $i < count($rgbcolors); $i++) {
$rgbcolors[$i] = $this->round_number(floatval($rgbcolors[$i]) * 2.55);
}
} else {
$rgbcolors = explode(',', $matches[1]);
}
// Values outside the sRGB color space should be clipped (0-255)
for ($i = 0; $i < count($rgbcolors); $i++) {
$rgbcolors[$i] = $this->clamp_number(intval($rgbcolors[$i], 10), 0, 255);
$rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]);
}
// Fix for issue #2528093
if (!preg_match('/[\s\,\);\}]/', $matches[2])){
$matches[2] = ' ' . $matches[2];
}
return '#' . implode('', $rgbcolors) . $matches[2];
}
private function hsl_to_hex($matches)
{
$values = explode(',', str_replace('%', '', $matches[1]));
$h = floatval($values[0]);
$s = floatval($values[1]);
$l = floatval($values[2]);
// Wrap and clamp, then fraction!
$h = ((($h % 360) + 360) % 360) / 360;
$s = $this->clamp_number($s, 0, 100) / 100;
$l = $this->clamp_number($l, 0, 100) / 100;
if ($s == 0) {
$r = $g = $b = $this->round_number(255 * $l);
} else {
$v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
$v1 = (2 * $l) - $v2;
$r = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h + (1/3)));
$g = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h));
$b = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h - (1/3)));
}
return $this->rgb_to_hex(array('', $r.','.$g.','.$b, $matches[2]));
}
/* HELPERS
* ---------------------------------------------------------------------------------------------
*/
private function hue_to_rgb($v1, $v2, $vh)
{
$vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
if ($vh * 6 < 1) return $v1 + ($v2 - $v1) * 6 * $vh;
if ($vh * 2 < 1) return $v2;
if ($vh * 3 < 2) return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6;
return $v1;
}
private function round_number($n)
{
return intval(floor(floatval($n) + 0.5), 10);
}
private function clamp_number($n, $min, $max)
{
return min(max($n, $min), $max);
}
/**
* PHP port of Javascript's "indexOf" function for strings only
* Author: Tubal Martin http://blog.margenn.com
*
* @param string $haystack
* @param string $needle
* @param int $offset index (optional)
* @return int
*/
private function index_of($haystack, $needle, $offset = 0)
{
$index = strpos($haystack, $needle, $offset);
return ($index !== FALSE) ? $index : -1;
}
/**
* PHP port of Javascript's "substring" function
* Author: Tubal Martin http://blog.margenn.com
* Tests: http://margenn.com/tubal/substring/
*
* @param string $str
* @param int $from index
* @param int|bool $to index (optional)
* @return string
*/
private function substring($str, $from = 0, $to = FALSE)
{
if ($to !== FALSE) {
if ($from == $to || ($from <= 0 && $to < 0)) {
return '';
}
if ($from > $to) {
$from_copy = $from;
$from = $to;
$to = $from_copy;
}
}
if ($from < 0) {
$from = 0;
}
$substring = ($to === FALSE) ? substr($str, $from) : substr($str, $from, $to - $from);
return ($substring === FALSE) ? '' : $substring;
}
/**
* PHP port of Javascript's "slice" function for strings only
* Author: Tubal Martin http://blog.margenn.com
* Tests: http://margenn.com/tubal/str_slice/
*
* @param string $str
* @param int $start index
* @param int|bool $end index (optional)
* @return string
*/
private function str_slice($str, $start = 0, $end = FALSE)
{
if ($end !== FALSE && ($start < 0 || $end <= 0)) {
$max = strlen($str);
if ($start < 0) {
if (($start = $max + $start) < 0) {
return '';
}
}
if ($end < 0) {
if (($end = $max + $end) < 0) {
return '';
}
}
if ($end <= $start) {
return '';
}
}
$slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start);
return ($slice === FALSE) ? '' : $slice;
}
/**
* Convert strings like "64M" or "30" to int values
* @param mixed $size
* @return int
*/
private function normalize_int($size)
{
if (is_string($size)) {
switch (substr($size, -1)) {
case 'M': case 'm': return $size * 1048576;
case 'K': case 'k': return $size * 1024;
case 'G': case 'g': return $size * 1073741824;
}
}
return (int) $size;
}
}