New module 'Advanced CSS/JS Aggregation'
This commit is contained in:
parent
4237e850bc
commit
17a2a7a42d
66 changed files with 21456 additions and 0 deletions
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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" );
|
|
@ -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.
|
|
@ -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
|
|
@ -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.
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue