| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- <?php
- class CssNode {
- static $shorthands = array(
- 'margin' => 'top right bottom left',
- 'padding' => 'top right bottom left',
- 'font' => 'weight [style] size [/line-height] family',
- 'border' => 'width style color',
- 'background' => '[color] image repeat [attachment] [position]',
- 'list-style' => '[type] [position] [image]',
- 'outline' => '[color] [style] [width]'
- );
- static $colors = array(
- '#f0f' => 'aqua', 'black' => '#000', 'fuchsia' => '#f0f', '#808080' => 'grey',
- '#008000' => 'green', '#800000' => 'maroon', '#000080' => 'navy', '#808000' => 'olive',
- '#800080' => 'purple', '#f00' => 'red', '#c0c0c0' => 'silver', '#008080' => 'teal',
- 'white' => '#fff', 'yellow' => '#ff0'
- );
- static $config;
- public $selector;
- public $css;
- public $parent_node;
- public $rules = array();
- public $children = array();
-
- function __construct($selector, $css='', $parent_node=null) {
- $this->selector = trim($selector);
- $this->css = trim($css);
- $this->parent_node = $parent_node;
- }
-
- static function extract_importance($values) {
- $important = '';
-
- foreach( $values as $rule => $value ) {
- if( preg_match('/^(."+?) !important$/', $value, $m) ) {
- $important = ' !important';
- $values[$rule] = $m[1];
- }
- }
-
- return array($values, $important);
- }
-
- function replace_shorthands() {
- $rules = array();
-
- // Put sub-selectors in arrays
- $pattern = '/^('.implode('|', array_keys(self::$shorthands)).')-([\w-]+)/';
-
- foreach( $this->rules as $rule => $value ) {
- if( preg_match($pattern, $rule, $m) ) {
- $base_rule = $m[1];
-
- if( isset($rules[$base_rule]) ) {
- if( !is_array($rules[$base_rule]) )
- $rules[$base_rule] = array('__main__' => $rules[$base_rule]);
- } else {
- $rules[$base_rule] = array();
- }
-
- $rules[$base_rule][$m[2]] = $value;
- } else {
- $rules[$rule] = $value;
- }
- }
-
- // Filter out base rules with one property value
- foreach( $rules as $rule => $values ) {
- if( is_array($values) && count($values) == 1 ) {
- $rules[$rule.'-'.key($values)] = reset($values);
- unset($rules[$rule]);
- }
- }
-
- foreach( $rules as $rule => $values ) {
- if( is_array($values) ) {
- list($values, $important) = self::extract_importance($values);
-
- if( $rule == 'font' && isset($rules['line-height']) ) {
- $values['line-height'] = $rules['line-height'];
- unset($rules['line-height']);
- }
-
- if( isset(self::$shorthands[$rule]) ) {
- $replace = true;
- $replacement = '';
- $parts = explode(' ', self::$shorthands[$rule]);
-
- foreach( array_keys($parts) as $i ) {
- $part = $parts[$i];
-
- if( $part == '[' ) {
- $parts[$i + 1] = '[ '.$parts[$i + 1];
- continue;
- }
-
- if( preg_match('%^\[(/)?([^]]+)\]$%', $part, $m) ) {
- $part = $m[2];
-
- if( isset($values[$part]) ) {
- $value = $values[$part];
-
- if( self::$config['compress_colors'] && strpos($part, 'color') !== false )
- $value = self::compress_color($value);
-
- $replacement .= (!strlen($m[1]) ? ' ' : $m[1]).$value;
- }
- } elseif( isset($values[$part]) ) {
- $value = $values[$part];
-
- if( self::$config['compress_colors'] && strpos($part, 'color') !== false )
- $value = self::compress_color($value);
-
- $i && $replacement .= ' ';
- $replacement .= $value;
- } else {
- $replace = false;
- break;
- }
- }
-
- $replace && $values['__main__'] = $replacement;
- }
-
- if( isset($values['__main__']) ) {
- $rules[$rule] = $values['__main__'];
- } else {
- foreach( $values as $sub_rule => $value )
- $rules[$rule.'-'.$sub_rule] = $value;
-
- unset($rules[$rule]);
- }
- }
- }
-
- return $rules;
- }
-
- function compress_color($color) {
- $color = preg_replace('/#(\w)\1(\w)\2(\w)\3/', '#\1\2\3', strtolower($color));
-
- if( isset(self::$colors[$color]) )
- $color = self::$colors[$color];
-
- return $color;
- }
-
- function compress($value, $rule) {
- // Compress colors
- if( self::$config['compress_colors'] && preg_match('/color$/', $rule) )
- $value = self::compress_color($value);
-
- // Compress measurements
- if( self::$config['compress_measurements']
- && ($rule == 'margin' || $rule == 'padding') ) {
- if( preg_match('/^0\w+$/', $value, $m) ) {
- // Replace zero with unit by just zero
- $value = 0;
- } elseif( preg_match('/^(\w+) (\w+) (\w+)(?: (\w+))?$/', $value, $m) ) {
- // Replace redundant margins and paddings
- $value = $m[1].' '.$m[2];
- $left_needed = isset($m[4]) && $m[4] != $m[2];
- $bottom_needed = $left_needed || $m[3] != $m[1];
-
- if( $bottom_needed ) {
- $value .= ' '.$m[3];
- $left_needed && $value .= ' '.$m[4];
- }
- }
- }
-
- return $rule.(self::$config['minify'] ? ':' : ': ').trim($value);
- }
-
- function parse_rules($rules) {
- foreach( preg_split('/\s*;\s*/', trim($rules)) as $rule ) {
- $split = preg_split('/\s*:\s*/', $rule, 2);
-
- if( count($split) == 2 && strlen($split[0]) && strlen($split[1]) )
- $this->rules[$split[0]] = $split[1];
- }
- }
-
- function parse() {
- $minify = self::$config['minify'];
- $current_node = $this;
-
- // Remove comments and redundant whitespaces
- $css = preg_replace(array('/\s+/', '%/\*.*?\*/%'), array(' ', ''), $this->css);
-
- foreach( array_map('trim', preg_split('/;|\}/', $css)) as $line ) {
- if( preg_match('/^ ?([^{]+) ?\{ ?(.*)$/', $line, $m) ) {
- // Start tag
- $self = preg_match('/^self(.*)/', $m[1], $selector_match);
- $child_selectors = $self ? $selector_match[1] : $m[1];
-
- if( strpos($child_selectors, ',') !== false ) {
- $selectors = array();
-
- foreach( preg_split('/ ?, ?/', trim($child_selectors)) as $child_selector )
- $selectors[] = $current_node->selector.' '.$child_selector;
-
- $selector = implode(',', $selectors);
- } else {
- $selector = $current_node->selector.($self ? '' : ' ').$child_selectors;
- }
-
- $current_node = $current_node->children[] = new CssNode($selector, $m[2], $current_node);
- $line = $m[2];
- }
-
- if( strlen($line) ) {
- // Normal rule
- $current_node->parse_rules($line);
- } else {
- // End tag
- $current_node = $current_node->parent_node;
- }
- }
-
- // Build new CSS string according to config
- $css = '';
-
- if( strlen($this->selector) ) {
- $rules = self::$config['replace_shorthands'] ? $this->replace_shorthands() : $this->rules;
- self::$config['sort_rules'] && ksort($rules);
- $rules = array_map('self::compress', $rules, array_keys($rules));
-
- if( $minify )
- $css .= $this->selector.'{'.implode(';', $rules).'}';
- else
- $css .= preg_replace('/, ?/', ",\n", $this->selector)." {\n\t".implode(";\n\t", $rules).";\n}";
- }
-
- // Parse children recursively
- foreach( $this->children as $child ) {
- $minify || $css .= "\n\n";
- $css .= $child->parse();
- }
-
- return trim($css);
- }
- }
- class CssParser {
- static $default_config = array(
- 'replace_shorthands' => true,
- 'sort_rules' => true,
- 'minify' => true,
- 'compress_measurements' => true,
- 'compress_colors' => true
- );
-
- static function minify($css, $config=array()) {
- CssNode::$config = array_merge(self::$default_config, $config);
- $node = new CssNode('', $css);
-
- return $node->parse();
- }
- }
- ?>
|