Advertisement
englishextra

lib_packscrips.inc

Sep 28th, 2011
223
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 119.57 KB | None | 0 0
  1. <?php
  2. /* 9 April 2008. version 1.1
  3.  *
  4.  * This is the php version of the Dean Edwards JavaScript's Packer,
  5.  * Based on :
  6.  *
  7.  * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards
  8.  * a multi-pattern parser.
  9.  * KNOWN BUG: erroneous behavior when using escapeChar with a replacement
  10.  * value that is a function
  11.  *
  12.  * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards
  13.  *
  14.  * License: http://creativecommons.org/licenses/LGPL/2.1/
  15.  *
  16.  * Ported to PHP by Nicolas Martin.
  17.  *
  18.  * ----------------------------------------------------------------------
  19.  * changelog:
  20.  * 1.1 : correct a bug, '\0' packed then unpacked becomes '\'.
  21.  * ----------------------------------------------------------------------
  22.  *
  23.  * examples of usage :
  24.  * $myPacker = new JavaScriptPacker($script, 62, true, false);
  25.  * $packed = $myPacker->pack();
  26.  *
  27.  * or
  28.  *
  29.  * $myPacker = new JavaScriptPacker($script, 'Normal', true, false);
  30.  * $packed = $myPacker->pack();
  31.  *
  32.  * or (default values)
  33.  *
  34.  * $myPacker = new JavaScriptPacker($script);
  35.  * $packed = $myPacker->pack();
  36.  *
  37.  *
  38.  * params of the constructor :
  39.  * $script:      the JavaScript to pack, string.
  40.  * $encoding:    level of encoding, int or string :
  41.  *              0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'.
  42.  *              default: 62.
  43.  * $fastDecode:  include the fast decoder in the packed result, boolean.
  44.  *              default : true.
  45.  * $specialChars: if you are flagged your private and local variables
  46.  *              in the script, boolean.
  47.  *              default: false.
  48.  *
  49.  * The pack() method return the compressed JavasScript, as a string.
  50.  *
  51.  * see http://dean.edwards.name/packer/usage/ for more information.
  52.  *
  53.  * Notes :
  54.  * # need PHP 5 . Tested with PHP 5.1.2, 5.1.3, 5.1.4, 5.2.3
  55.  *
  56.  * # The packed result may be different than with the Dean Edwards
  57.  *   version, but with the same length. The reason is that the PHP
  58.  *   function usort to sort array don't necessarily preserve the
  59.  *   original order of two equal member. The Javascript sort function
  60.  *   in fact preserve this order (but that's not require by the
  61.  *   ECMAScript standard). So the encoded keywords order can be
  62.  *   different in the two results.
  63.  *
  64.  * # Be careful with the 'High ASCII' Level encoding if you use
  65.  *   UTF-8 in your files...
  66.  */
  67.  
  68. class JavaScriptPacker {
  69.     // constants
  70.     const IGNORE = '$1';
  71.  
  72.     // validate parameters
  73.     private $_script = '';
  74.     private $_encoding = 62;
  75.     private $_fastDecode = true;
  76.     private $_specialChars = false;
  77.    
  78.     private $LITERAL_ENCODING = array(
  79.         'None' => 0,
  80.         'Numeric' => 10,
  81.         'Normal' => 62,
  82.         'High ASCII' => 95
  83.     );
  84.    
  85.     public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false)
  86.     {
  87.         $this->_script = $_script . "\n";
  88.         if (array_key_exists($_encoding, $this->LITERAL_ENCODING))
  89.             $_encoding = $this->LITERAL_ENCODING[$_encoding];
  90.         $this->_encoding = min((int)$_encoding, 95);
  91.         $this->_fastDecode = $_fastDecode; 
  92.         $this->_specialChars = $_specialChars;
  93.     }
  94.    
  95.     public function pack() {
  96.         $this->_addParser('_basicCompression');
  97.         if ($this->_specialChars)
  98.             $this->_addParser('_encodeSpecialChars');
  99.         if ($this->_encoding)
  100.             $this->_addParser('_encodeKeywords');
  101.        
  102.         // go!
  103.         return $this->_pack($this->_script);
  104.     }
  105.    
  106.     // apply all parsing routines
  107.     private function _pack($script) {
  108.         for ($i = 0; isset($this->_parsers[$i]); $i++) {
  109.             $script = call_user_func(array(&$this,$this->_parsers[$i]), $script);
  110.         }
  111.         return $script;
  112.     }
  113.    
  114.     // keep a list of parsing functions, they'll be executed all at once
  115.     private $_parsers = array();
  116.     private function _addParser($parser) {
  117.         $this->_parsers[] = $parser;
  118.     }
  119.    
  120.     // zero encoding - just removal of white space and comments
  121.     private function _basicCompression($script) {
  122.         $parser = new ParseMaster();
  123.         // make safe
  124.         $parser->escapeChar = '\\';
  125.         // protect strings
  126.         $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE);
  127.         $parser->add('/"[^"\\n\\r]*"/', self::IGNORE);
  128.         // remove comments
  129.         $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ');
  130.         $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
  131.         // protect regular expressions
  132.         $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
  133.         $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE);
  134.         // remove: ;;; doSomething();
  135.         if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
  136.         // remove redundant semi-colons
  137.         $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops
  138.         $parser->add('/;+\\s*([};])/', '$2');
  139.         // apply the above
  140.         $script = $parser->exec($script);
  141.  
  142.         // remove white-space
  143.         $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
  144.         $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
  145.         $parser->add('/\\s+/', '');
  146.         // done
  147.         return $parser->exec($script);
  148.     }
  149.    
  150.     private function _encodeSpecialChars($script) {
  151.         $parser = new ParseMaster();
  152.         // replace: $name -> n, $$name -> na
  153.         $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
  154.                      array('fn' => '_replace_name')
  155.         );
  156.         // replace: _name -> _0, double-underscore (__name) is ignored
  157.         $regexp = '/\\b_[A-Za-z\\d]\\w*/';
  158.         // build the word list
  159.         $keywords = $this->_analyze($script, $regexp, '_encodePrivate');
  160.         // quick ref
  161.         $encoded = $keywords['encoded'];
  162.        
  163.         $parser->add($regexp,
  164.             array(
  165.                 'fn' => '_replace_encoded',
  166.                 'data' => $encoded
  167.             )
  168.         );
  169.         return $parser->exec($script);
  170.     }
  171.    
  172.     private function _encodeKeywords($script) {
  173.         // escape high-ascii values already in the script (i.e. in strings)
  174.         if ($this->_encoding > 62)
  175.             $script = $this->_escape95($script);
  176.         // create the parser
  177.         $parser = new ParseMaster();
  178.         $encode = $this->_getEncoder($this->_encoding);
  179.         // for high-ascii, don't encode single character low-ascii
  180.         $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
  181.         // build the word list
  182.         $keywords = $this->_analyze($script, $regexp, $encode);
  183.         $encoded = $keywords['encoded'];
  184.        
  185.         // encode
  186.         $parser->add($regexp,
  187.             array(
  188.                 'fn' => '_replace_encoded',
  189.                 'data' => $encoded
  190.             )
  191.         );
  192.         if (empty($script)) return $script;
  193.         else {
  194.             //$res = $parser->exec($script);
  195.             //$res = $this->_bootStrap($res, $keywords);
  196.             //return $res;
  197.             return $this->_bootStrap($parser->exec($script), $keywords);
  198.         }
  199.     }
  200.    
  201.     private function _analyze($script, $regexp, $encode) {
  202.         // analyse
  203.         // retreive all words in the script
  204.         $all = array();
  205.         preg_match_all($regexp, $script, $all);
  206.         $_sorted = array(); // list of words sorted by frequency
  207.         $_encoded = array(); // dictionary of word->encoding
  208.         $_protected = array(); // instances of "protected" words
  209.         $all = $all[0]; // simulate the javascript comportement of global match
  210.         if (!empty($all)) {
  211.             $unsorted = array(); // same list, not sorted
  212.             $protected = array(); // "protected" words (dictionary of word->"word")
  213.             $value = array(); // dictionary of charCode->encoding (eg. 256->ff)
  214.             $this->_count = array(); // word->count
  215.             $i = count($all); $j = 0; //$word = null;
  216.             // count the occurrences - used for sorting later
  217.             do {
  218.                 --$i;
  219.                 $word = '$' . $all[$i];
  220.                 if (!isset($this->_count[$word])) {
  221.                     $this->_count[$word] = 0;
  222.                     $unsorted[$j] = $word;
  223.                     // make a dictionary of all of the protected words in this script
  224.                     //  these are words that might be mistaken for encoding
  225.                     //if (is_string($encode) && method_exists($this, $encode))
  226.                     $values[$j] = call_user_func(array(&$this, $encode), $j);
  227.                     $protected['$' . $values[$j]] = $j++;
  228.                 }
  229.                 // increment the word counter
  230.                 $this->_count[$word]++;
  231.             } while ($i > 0);
  232.             // prepare to sort the word list, first we must protect
  233.             //  words that are also used as codes. we assign them a code
  234.             //  equivalent to the word itself.
  235.             // e.g. if "do" falls within our encoding range
  236.             //      then we store keywords["do"] = "do";
  237.             // this avoids problems when decoding
  238.             $i = count($unsorted);
  239.             do {
  240.                 $word = $unsorted[--$i];
  241.                 if (isset($protected[$word]) /*!= null*/) {
  242.                     $_sorted[$protected[$word]] = substr($word, 1);
  243.                     $_protected[$protected[$word]] = true;
  244.                     $this->_count[$word] = 0;
  245.                 }
  246.             } while ($i);
  247.            
  248.             // sort the words by frequency
  249.             // Note: the javascript and php version of sort can be different :
  250.             // in php manual, usort :
  251.             // " If two members compare as equal,
  252.             // their order in the sorted array is undefined."
  253.             // so the final packed script is different of the Dean's javascript version
  254.             // but equivalent.
  255.             // the ECMAscript standard does not guarantee this behaviour,
  256.             // and thus not all browsers (e.g. Mozilla versions dating back to at
  257.             // least 2003) respect this.
  258.             usort($unsorted, array(&$this, '_sortWords'));
  259.             $j = 0;
  260.             // because there are "protected" words in the list
  261.             //  we must add the sorted words around them
  262.             do {
  263.                 if (!isset($_sorted[$i]))
  264.                     $_sorted[$i] = substr($unsorted[$j++], 1);
  265.                 $_encoded[$_sorted[$i]] = $values[$i];
  266.             } while (++$i < count($unsorted));
  267.         }
  268.         return array(
  269.             'sorted'    => $_sorted,
  270.             'encoded' => $_encoded,
  271.             'protected' => $_protected);
  272.     }
  273.    
  274.     private $_count = array();
  275.     private function _sortWords($match1, $match2) {
  276.         return $this->_count[$match2] - $this->_count[$match1];
  277.     }
  278.    
  279.     // build the boot function used for loading and decoding
  280.     private function _bootStrap($packed, $keywords) {
  281.         $ENCODE = $this->_safeRegExp('$encode\\($count\\)');
  282.  
  283.         // $packed: the packed script
  284.         $packed = "'" . $this->_escape($packed) . "'";
  285.  
  286.         // $ascii: base for encoding
  287.         $ascii = min(count($keywords['sorted']), $this->_encoding);
  288.         if ($ascii == 0) $ascii = 1;
  289.  
  290.         // $count: number of words contained in the script
  291.         $count = count($keywords['sorted']);
  292.  
  293.         // $keywords: list of words contained in the script
  294.         foreach ($keywords['protected'] as $i=>$value) {
  295.             $keywords['sorted'][$i] = '';
  296.         }
  297.         // convert from a string to an array
  298.         ksort($keywords['sorted']);
  299.         $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')";
  300.  
  301.         $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
  302.         $encode = $this->_getJSFunction($encode);
  303.         $encode = preg_replace('/_encoding/','$ascii', $encode);
  304.         $encode = preg_replace('/arguments\\.callee/','$encode', $encode);
  305.         $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : '');
  306.  
  307.         // $decode: code snippet to speed up decoding
  308.         if ($this->_fastDecode) {
  309.             // create the decoder
  310.             $decode = $this->_getJSFunction('_decodeBody');
  311.             if ($this->_encoding > 62)
  312.                 $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
  313.             // perform the encoding inline for lower ascii values
  314.             elseif ($ascii < 36)
  315.                 $decode = preg_replace($ENCODE, $inline, $decode);
  316.             // special case: when $count==0 there are no keywords. I want to keep
  317.             //  the basic shape of the unpacking funcion so i'll frig the code...
  318.             if ($count == 0)
  319.                 $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
  320.         }
  321.  
  322.         // boot function
  323.         $unpack = $this->_getJSFunction('_unpack');
  324.         if ($this->_fastDecode) {
  325.             // insert the decoder
  326.             $this->buffer = $decode;
  327.             $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1);
  328.         }
  329.         $unpack = preg_replace('/"/', "'", $unpack);
  330.         if ($this->_encoding > 62) { // high-ascii
  331.             // get rid of the word-boundaries for regexp matches
  332.             $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
  333.         }
  334.         if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
  335.             // insert the encode function
  336.             $this->buffer = $encode;
  337.             $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1);
  338.         } else {
  339.             // perform the encoding inline
  340.             $unpack = preg_replace($ENCODE, $inline, $unpack);
  341.         }
  342.         // pack the boot function too
  343.         $unpackPacker = new JavaScriptPacker($unpack, 0, false, true);
  344.         $unpack = $unpackPacker->pack();
  345.        
  346.         // arguments
  347.         $params = array($packed, $ascii, $count, $keywords);
  348.         if ($this->_fastDecode) {
  349.             $params[] = 0;
  350.             $params[] = '{}';
  351.         }
  352.         $params = implode(',', $params);
  353.        
  354.         // the whole thing
  355.         return 'eval(' . $unpack . '(' . $params . "))\n";
  356.     }
  357.    
  358.     private $buffer;
  359.     private function _insertFastDecode($match) {
  360.         return '{' . $this->buffer . ';';
  361.     }
  362.     private function _insertFastEncode($match) {
  363.         return '{$encode=' . $this->buffer . ';';
  364.     }
  365.    
  366.     // mmm.. ..which one do i need ??
  367.     private function _getEncoder($ascii) {
  368.         return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
  369.                  '_encode95' : '_encode62' : '_encode36' : '_encode10';
  370.     }
  371.    
  372.     // zero encoding
  373.     // characters: 0123456789
  374.     private function _encode10($charCode) {
  375.         return $charCode;
  376.     }
  377.    
  378.     // inherent base36 support
  379.     // characters: 0123456789abcdefghijklmnopqrstuvwxyz
  380.     private function _encode36($charCode) {
  381.         return base_convert($charCode, 10, 36);
  382.     }
  383.    
  384.     // hitch a ride on base36 and add the upper case alpha characters
  385.     // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
  386.     private function _encode62($charCode) {
  387.         $res = '';
  388.         if ($charCode >= $this->_encoding) {
  389.             $res = $this->_encode62((int)($charCode / $this->_encoding));
  390.         }
  391.         $charCode = $charCode % $this->_encoding;
  392.        
  393.         if ($charCode > 35)
  394.             return $res . chr($charCode + 29);
  395.         else
  396.             return $res . base_convert($charCode, 10, 36);
  397.     }
  398.    
  399.     // use high-ascii values
  400.     // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
  401.     private function _encode95($charCode) {
  402.         $res = '';
  403.         if ($charCode >= $this->_encoding)
  404.             $res = $this->_encode95($charCode / $this->_encoding);
  405.        
  406.         return $res . chr(($charCode % $this->_encoding) + 161);
  407.     }
  408.    
  409.     private function _safeRegExp($string) {
  410.         return '/'.preg_replace('/\$/', '\\\$', $string).'/';
  411.     }
  412.    
  413.     private function _encodePrivate($charCode) {
  414.         return "_" . $charCode;
  415.     }
  416.    
  417.     // protect characters used by the parser
  418.     private function _escape($script) {
  419.         return preg_replace('/([\\\\\'])/', '\\\$1', $script);
  420.     }
  421.    
  422.     // protect high-ascii characters already in the script
  423.     private function _escape95($script) {
  424.         return preg_replace_callback(
  425.             '/[\\xa1-\\xff]/',
  426.             array(&$this, '_escape95Bis'),
  427.             $script
  428.         );
  429.     }
  430.     private function _escape95Bis($match) {
  431.         return '\x'.((string)dechex(ord($match)));
  432.     }
  433.    
  434.    
  435.     private function _getJSFunction($aName) {
  436.         if (defined('self::JSFUNCTION'.$aName))
  437.             return constant('self::JSFUNCTION'.$aName);
  438.         else
  439.             return '';
  440.     }
  441.    
  442.     // JavaScript Functions used.
  443.     // Note : In Dean's version, these functions are converted
  444.     // with 'String(aFunctionName);'.
  445.     // This internal conversion complete the original code, ex :
  446.     // 'while (aBool) anAction();' is converted to
  447.     // 'while (aBool) { anAction(); }'.
  448.     // The JavaScript functions below are corrected.
  449.    
  450.     // unpacking function - this is the boot strap function
  451.     //  data extracted from this packing routine is passed to
  452.     //  this function when decoded in the target
  453.     // NOTE ! : without the ';' final.
  454.     const JSFUNCTION_unpack =
  455.  
  456. 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
  457.     while ($count--) {
  458.         if ($keywords[$count]) {
  459.             $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
  460.         }
  461.     }
  462.     return $packed;
  463. }';
  464. /*
  465. 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
  466.     while ($count--)
  467.         if ($keywords[$count])
  468.             $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
  469.     return $packed;
  470. }';
  471. */
  472.    
  473.     // code-snippet inserted into the unpacker to speed up decoding
  474.     const JSFUNCTION_decodeBody =
  475. //_decode = function() {
  476. // does the browser support String.replace where the
  477. //  replacement value is a function?
  478.  
  479. '   if (!\'\'.replace(/^/, String)) {
  480.         // decode all the values we need
  481.         while ($count--) {
  482.             $decode[$encode($count)] = $keywords[$count] || $encode($count);
  483.         }
  484.         // global replacement function
  485.         $keywords = [function ($encoded) {return $decode[$encoded]}];
  486.         // generic match
  487.         $encode = function () {return \'\\\\w+\'};
  488.         // reset the loop counter - we are now doing a global replace
  489.         $count = 1;
  490.     }
  491. ';
  492. //};
  493. /*
  494. '   if (!\'\'.replace(/^/, String)) {
  495.         // decode all the values we need
  496.         while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
  497.         // global replacement function
  498.         $keywords = [function ($encoded) {return $decode[$encoded]}];
  499.         // generic match
  500.         $encode = function () {return\'\\\\w+\'};
  501.         // reset the loop counter - we are now doing a global replace
  502.         $count = 1;
  503.     }';
  504. */
  505.    
  506.      // zero encoding
  507.      // characters: 0123456789
  508.      const JSFUNCTION_encode10 =
  509. 'function($charCode) {
  510.     return $charCode;
  511. }';//;';
  512.    
  513.      // inherent base36 support
  514.      // characters: 0123456789abcdefghijklmnopqrstuvwxyz
  515.      const JSFUNCTION_encode36 =
  516. 'function($charCode) {
  517.     return $charCode.toString(36);
  518. }';//;';
  519.    
  520.     // hitch a ride on base36 and add the upper case alpha characters
  521.     // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
  522.     const JSFUNCTION_encode62 =
  523. 'function($charCode) {
  524.     return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
  525.     (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
  526. }';
  527.    
  528.     // use high-ascii values
  529.     // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
  530.     const JSFUNCTION_encode95 =
  531. 'function($charCode) {
  532.     return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
  533.         String.fromCharCode($charCode % _encoding + 161);
  534. }';
  535.    
  536. }
  537.  
  538. class ParseMaster {
  539.     public $ignoreCase = false;
  540.     public $escapeChar = '';
  541.    
  542.     // constants
  543.     const EXPRESSION = 0;
  544.     const REPLACEMENT = 1;
  545.     const LENGTH = 2;
  546.    
  547.     // used to determine nesting levels
  548.     private $GROUPS = '/\\(/';//g
  549.     private $SUB_REPLACE = '/\\$\\d/';
  550.     private $INDEXED = '/^\\$\\d+$/';
  551.     private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
  552.     private $ESCAPE = '/\\\./';//g
  553.     private $QUOTE = '/\'/';
  554.     private $DELETED = '/\\x01[^\\x01]*\\x01/';//g
  555.    
  556.     public function add($expression, $replacement = '') {
  557.         // count the number of sub-expressions
  558.         //  - add one because each pattern is itself a sub-expression
  559.         $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out);
  560.        
  561.         // treat only strings $replacement
  562.         if (is_string($replacement)) {
  563.             // does the pattern deal with sub-expressions?
  564.             if (preg_match($this->SUB_REPLACE, $replacement)) {
  565.                 // a simple lookup? (e.g. "$2")
  566.                 if (preg_match($this->INDEXED, $replacement)) {
  567.                     // store the index (used for fast retrieval of matched strings)
  568.                     $replacement = (int)(substr($replacement, 1)) - 1;
  569.                 } else { // a complicated lookup (e.g. "Hello $2 $1")
  570.                     // build a function to do the lookup
  571.                     $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
  572.                              ? '"' : "'";
  573.                     $replacement = array(
  574.                         'fn' => '_backReferences',
  575.                         'data' => array(
  576.                             'replacement' => $replacement,
  577.                             'length' => $length,
  578.                             'quote' => $quote
  579.                         )
  580.                     );
  581.                 }
  582.             }
  583.         }
  584.         // pass the modified arguments
  585.         if (!empty($expression)) $this->_add($expression, $replacement, $length);
  586.         else $this->_add('/^$/', $replacement, $length);
  587.     }
  588.    
  589.     public function exec($string) {
  590.         // execute the global replacement
  591.         $this->_escaped = array();
  592.        
  593.         // simulate the _patterns.toSTring of Dean
  594.         $regexp = '/';
  595.         foreach ($this->_patterns as $reg) {
  596.             $regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|';
  597.         }
  598.         $regexp = substr($regexp, 0, -1) . '/';
  599.         $regexp .= ($this->ignoreCase) ? 'i' : '';
  600.        
  601.         $string = $this->_escape($string, $this->escapeChar);
  602.         $string = preg_replace_callback(
  603.             $regexp,
  604.             array(
  605.                 &$this,
  606.                 '_replacement'
  607.             ),
  608.             $string
  609.         );
  610.         $string = $this->_unescape($string, $this->escapeChar);
  611.        
  612.         return preg_replace($this->DELETED, '', $string);
  613.     }
  614.        
  615.     public function reset() {
  616.         // clear the patterns collection so that this object may be re-used
  617.         $this->_patterns = array();
  618.     }
  619.  
  620.     // private
  621.     private $_escaped = array();    // escaped characters
  622.     private $_patterns = array(); // patterns stored by index
  623.    
  624.     // create and add a new pattern to the patterns collection
  625.     private function _add() {
  626.         $arguments = func_get_args();
  627.         $this->_patterns[] = $arguments;
  628.     }
  629.    
  630.     // this is the global replace function (it's quite complicated)
  631.     private function _replacement($arguments) {
  632.         if (empty($arguments)) return '';
  633.        
  634.         $i = 1; $j = 0;
  635.         // loop through the patterns
  636.         while (isset($this->_patterns[$j])) {
  637.             $pattern = $this->_patterns[$j++];
  638.             // do we have a result?
  639.             if (isset($arguments[$i]) && ($arguments[$i] != '')) {
  640.                 $replacement = $pattern[self::REPLACEMENT];
  641.                
  642.                 if (is_array($replacement) && isset($replacement['fn'])) {
  643.                    
  644.                     if (isset($replacement['data'])) $this->buffer = $replacement['data'];
  645.                     return call_user_func(array(&$this, $replacement['fn']), $arguments, $i);
  646.                    
  647.                 } elseif (is_int($replacement)) {
  648.                     return $arguments[$replacement + $i];
  649.                
  650.                 }
  651.                 $delete = ($this->escapeChar == '' ||
  652.                              strpos($arguments[$i], $this->escapeChar) === false)
  653.                         ? '' : "\x01" . $arguments[$i] . "\x01";
  654.                 return $delete . $replacement;
  655.            
  656.             // skip over references to sub-expressions
  657.             } else {
  658.                 $i += $pattern[self::LENGTH];
  659.             }
  660.         }
  661.     }
  662.    
  663.     private function _backReferences($match, $offset) {
  664.         $replacement = $this->buffer['replacement'];
  665.         $quote = $this->buffer['quote'];
  666.         $i = $this->buffer['length'];
  667.         while ($i) {
  668.             $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
  669.         }
  670.         return $replacement;
  671.     }
  672.    
  673.     private function _replace_name($match, $offset){
  674.         $length = strlen($match[$offset + 2]);
  675.         $start = $length - max($length - strlen($match[$offset + 3]), 0);
  676.         return substr($match[$offset + 1], $start, $length) . $match[$offset + 4];
  677.     }
  678.    
  679.     private function _replace_encoded($match, $offset) {
  680.         return $this->buffer[$match[$offset]];
  681.     }
  682.    
  683.    
  684.     // php : we cannot pass additional data to preg_replace_callback,
  685.     // and we cannot use &$this in create_function, so let's go to lower level
  686.     private $buffer;
  687.    
  688.     // encode escaped characters
  689.     private function _escape($string, $escapeChar) {
  690.         if ($escapeChar) {
  691.             $this->buffer = $escapeChar;
  692.             return preg_replace_callback(
  693.                 '/\\' . $escapeChar . '(.)' .'/',
  694.                 array(&$this, '_escapeBis'),
  695.                 $string
  696.             );
  697.            
  698.         } else {
  699.             return $string;
  700.         }
  701.     }
  702.     private function _escapeBis($match) {
  703.         $this->_escaped[] = $match[1];
  704.         return $this->buffer;
  705.     }
  706.    
  707.     // decode escaped characters
  708.     private function _unescape($string, $escapeChar) {
  709.         if ($escapeChar) {
  710.             $regexp = '/'.'\\'.$escapeChar.'/';
  711.             $this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0);
  712.             return preg_replace_callback
  713.             (
  714.                 $regexp,
  715.                 array(&$this, '_unescapeBis'),
  716.                 $string
  717.             );
  718.            
  719.         } else {
  720.             return $string;
  721.         }
  722.     }
  723.     private function _unescapeBis() {
  724.         if (isset($this->_escaped[$this->buffer['i']])
  725.             && $this->_escaped[$this->buffer['i']] != '')
  726.         {
  727.              $temp = $this->_escaped[$this->buffer['i']];
  728.         } else {
  729.             $temp = '';
  730.         }
  731.         $this->buffer['i']++;
  732.         return $this->buffer['escapeChar'] . $temp;
  733.     }
  734.    
  735.     private function _internalEscape($string) {
  736.         return preg_replace($this->ESCAPE, '', $string);
  737.     }
  738. }
  739.  
  740. /**
  741.  * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
  742.  *
  743.  * This is pretty much a direct port of jsmin.c to PHP with just a few
  744.  * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
  745.  * outputs to stdout, this library accepts a string as input and returns another
  746.  * string as output.
  747.  *
  748.  * PHP 5 or higher is required.
  749.  *
  750.  * Permission is hereby granted to use this version of the library under the
  751.  * same terms as jsmin.c, which has the following license:
  752.  *
  753.  * --
  754.  * Copyright (c) 2002 Douglas Crockford (www.crockford.com)
  755.  *
  756.  * Permission is hereby granted, free of charge, to any person obtaining a copy of
  757.  * this software and associated documentation files (the "Software"), to deal in
  758.  * the Software without restriction, including without limitation the rights to
  759.  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  760.  * of the Software, and to permit persons to whom the Software is furnished to do
  761.  * so, subject to the following conditions:
  762.  *
  763.  * The above copyright notice and this permission notice shall be included in all
  764.  * copies or substantial portions of the Software.
  765.  *
  766.  * The Software shall be used for Good, not Evil.
  767.  *
  768.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  769.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  770.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  771.  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  772.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  773.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  774.  * SOFTWARE.
  775.  * --
  776.  *
  777.  * @package JSMin
  778.  * @author Ryan Grove <ryan@wonko.com>
  779.  * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
  780.  * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
  781.  * @license http://opensource.org/licenses/mit-license.php MIT License
  782.  * @version 1.1.1 (2008-03-02)
  783.  * @link https://github.com/rgrove/jsmin-php/
  784.  */
  785.  
  786. class JSMin {
  787.     const ORD_LF            = 10;
  788.     const ORD_SPACE      = 32;
  789.     const ACTION_KEEP_A  = 1;
  790.     const ACTION_DELETE_A    = 2;
  791.     const ACTION_DELETE_A_B = 3;
  792.  
  793.     protected $a             = '';
  794.     protected $b             = '';
  795.     protected $input         = '';
  796.     protected $inputIndex   = 0;
  797.     protected $inputLength = 0;
  798.     protected $lookAhead     = null;
  799.     protected $output       = '';
  800.  
  801.     // -- Public Static Methods --------------------------------------------------
  802.  
  803.     /**
  804.      * Minify Javascript
  805.      *
  806.      * @uses __construct()
  807.      * @uses min()
  808.      * @param string $js Javascript to be minified
  809.      * @return string
  810.      */
  811.     public static function minify($js) {
  812.     $jsmin = new JSMin($js);
  813.     return $jsmin->min();
  814.     }
  815.  
  816.     // -- Public Instance Methods ------------------------------------------------
  817.  
  818.     /**
  819.      * Constructor
  820.      *
  821.      * @param string $input Javascript to be minified
  822.      */
  823.     public function __construct($input) {
  824.     $this->input         = str_replace("\r\n", "\n", $input);
  825.     $this->inputLength = strlen($this->input);
  826.     }
  827.  
  828.     // -- Protected Instance Methods ---------------------------------------------
  829.  
  830.     /**
  831.      * Action -- do something! What to do is determined by the $command argument.
  832.      *
  833.      * action treats a string as a single character. Wow!
  834.      * action recognizes a regular expression if it is preceded by ( or , or =.
  835.      *
  836.      * @uses next()
  837.      * @uses get()
  838.      * @throws JSMinException If parser errors are found:
  839.      *       - Unterminated string literal
  840.      *       - Unterminated regular expression set in regex literal
  841.      *       - Unterminated regular expression literal
  842.      * @param int $command One of class constants:
  843.      *      ACTION_KEEP_A       Output A. Copy B to A. Get the next B.
  844.      *      ACTION_DELETE_A Copy B to A. Get the next B. (Delete A).
  845.      *      ACTION_DELETE_A_B   Get the next B. (Delete B).
  846.     */
  847.     protected function action($command) {
  848.     switch($command) {
  849.         case self::ACTION_KEEP_A:
  850.         $this->output .= $this->a;
  851.  
  852.         case self::ACTION_DELETE_A:
  853.         $this->a = $this->b;
  854.  
  855.         if ($this->a === "'" || $this->a === '"') {
  856.             for (;;) {
  857.             $this->output .= $this->a;
  858.             $this->a         = $this->get();
  859.  
  860.             if ($this->a === $this->b) {
  861.                 break;
  862.             }
  863.  
  864.             if (ord($this->a) <= self::ORD_LF) {
  865.                 throw new JSMinException('Unterminated string literal.');
  866.             }
  867.  
  868.             if ($this->a === '\\') {
  869.                 $this->output .= $this->a;
  870.                 $this->a         = $this->get();
  871.             }
  872.             }
  873.         }
  874.  
  875.         case self::ACTION_DELETE_A_B:
  876.         $this->b = $this->next();
  877.  
  878.         if ($this->b === '/' && (
  879.             $this->a === '(' || $this->a === ',' || $this->a === '=' ||
  880.             $this->a === ':' || $this->a === '[' || $this->a === '!' ||
  881.             $this->a === '&' || $this->a === '|' || $this->a === '?' ||
  882.             $this->a === '{' || $this->a === '}' || $this->a === ';' ||
  883.             $this->a === "\n" )) {
  884.  
  885.             $this->output .= $this->a . $this->b;
  886.  
  887.             for (;;) {
  888.             $this->a = $this->get();
  889.  
  890.             if ($this->a === '[') {
  891.                 /*
  892.                 inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460:
  893.                     return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
  894.                 */
  895.                 for (;;) {
  896.                 $this->output .= $this->a;
  897.                 $this->a = $this->get();
  898.  
  899.                 if ($this->a === ']') {
  900.                     break;
  901.                 } elseif ($this->a === '\\') {
  902.                     $this->output .= $this->a;
  903.                     $this->a         = $this->get();
  904.                 } elseif (ord($this->a) <= self::ORD_LF) {
  905.                     throw new JSMinException('Unterminated regular expression set in regex literal.');
  906.                 }
  907.                 }
  908.             } elseif ($this->a === '/') {
  909.                 break;
  910.             } elseif ($this->a === '\\') {
  911.                 $this->output .= $this->a;
  912.                 $this->a         = $this->get();
  913.             } elseif (ord($this->a) <= self::ORD_LF) {
  914.                 throw new JSMinException('Unterminated regular expression literal.');
  915.             }
  916.  
  917.             $this->output .= $this->a;
  918.             }
  919.  
  920.             $this->b = $this->next();
  921.         }
  922.     }
  923.     }
  924.  
  925.     /**
  926.      * Get next char. Convert ctrl char to space.
  927.      *
  928.      * @return string|null
  929.      */
  930.     protected function get() {
  931.     $c = $this->lookAhead;
  932.     $this->lookAhead = null;
  933.  
  934.     if ($c === null) {
  935.         if ($this->inputIndex < $this->inputLength) {
  936.         $c = substr($this->input, $this->inputIndex, 1);
  937.         $this->inputIndex += 1;
  938.         } else {
  939.         $c = null;
  940.         }
  941.     }
  942.  
  943.     if ($c === "\r") {
  944.         return "\n";
  945.     }
  946.  
  947.     if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
  948.         return $c;
  949.     }
  950.  
  951.     return ' ';
  952.     }
  953.  
  954.     /**
  955.      * Is $c a letter, digit, underscore, dollar sign, or non-ASCII character.
  956.      *
  957.      * @return bool
  958.      */
  959.     protected function isAlphaNum($c) {
  960.     return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
  961.     }
  962.  
  963.     /**
  964.      * Perform minification, return result
  965.      *
  966.      * @uses action()
  967.      * @uses isAlphaNum()
  968.      * @return string
  969.      */
  970.     protected function min() {
  971.     $this->a = "\n";
  972.     $this->action(self::ACTION_DELETE_A_B);
  973.  
  974.     while ($this->a !== null) {
  975.         switch ($this->a) {
  976.         case ' ':
  977.             if ($this->isAlphaNum($this->b)) {
  978.             $this->action(self::ACTION_KEEP_A);
  979.             } else {
  980.             $this->action(self::ACTION_DELETE_A);
  981.             }
  982.             break;
  983.  
  984.         case "\n":
  985.             switch ($this->b) {
  986.             case '{':
  987.             case '[':
  988.             case '(':
  989.             case '+':
  990.             case '-':
  991.                 $this->action(self::ACTION_KEEP_A);
  992.                 break;
  993.  
  994.             case ' ':
  995.                 $this->action(self::ACTION_DELETE_A_B);
  996.                 break;
  997.  
  998.             default:
  999.                 if ($this->isAlphaNum($this->b)) {
  1000.                 $this->action(self::ACTION_KEEP_A);
  1001.                 }
  1002.                 else {
  1003.                 $this->action(self::ACTION_DELETE_A);
  1004.                 }
  1005.             }
  1006.             break;
  1007.  
  1008.         default:
  1009.             switch ($this->b) {
  1010.             case ' ':
  1011.                 if ($this->isAlphaNum($this->a)) {
  1012.                 $this->action(self::ACTION_KEEP_A);
  1013.                 break;
  1014.                 }
  1015.  
  1016.                 $this->action(self::ACTION_DELETE_A_B);
  1017.                 break;
  1018.  
  1019.             case "\n":
  1020.                 switch ($this->a) {
  1021.                 case '}':
  1022.                 case ']':
  1023.                 case ')':
  1024.                 case '+':
  1025.                 case '-':
  1026.                 case '"':
  1027.                 case "'":
  1028.                     $this->action(self::ACTION_KEEP_A);
  1029.                     break;
  1030.  
  1031.                 default:
  1032.                     if ($this->isAlphaNum($this->a)) {
  1033.                     $this->action(self::ACTION_KEEP_A);
  1034.                     }
  1035.                     else {
  1036.                     $this->action(self::ACTION_DELETE_A_B);
  1037.                     }
  1038.                 }
  1039.                 break;
  1040.  
  1041.             default:
  1042.                 $this->action(self::ACTION_KEEP_A);
  1043.                 break;
  1044.             }
  1045.         }
  1046.     }
  1047.  
  1048.     return $this->output;
  1049.     }
  1050.  
  1051.     /**
  1052.      * Get the next character, skipping over comments. peek() is used to see
  1053.      *  if a '/' is followed by a '/' or '*'.
  1054.      *
  1055.      * @uses get()
  1056.      * @uses peek()
  1057.      * @throws JSMinException On unterminated comment.
  1058.      * @return string
  1059.      */
  1060.     protected function next() {
  1061.     $c = $this->get();
  1062.  
  1063.     if ($c === '/') {
  1064.         switch($this->peek()) {
  1065.         case '/':
  1066.             for (;;) {
  1067.             $c = $this->get();
  1068.  
  1069.             if (ord($c) <= self::ORD_LF) {
  1070.                 return $c;
  1071.             }
  1072.             }
  1073.  
  1074.         case '*':
  1075.             $this->get();
  1076.  
  1077.             for (;;) {
  1078.             switch($this->get()) {
  1079.                 case '*':
  1080.                 if ($this->peek() === '/') {
  1081.                     $this->get();
  1082.                     return ' ';
  1083.                 }
  1084.                 break;
  1085.  
  1086.                 case null:
  1087.                 throw new JSMinException('Unterminated comment.');
  1088.             }
  1089.             }
  1090.  
  1091.         default:
  1092.             return $c;
  1093.         }
  1094.     }
  1095.  
  1096.     return $c;
  1097.     }
  1098.  
  1099.     /**
  1100.      * Get next char. If is ctrl character, translate to a space or newline.
  1101.      *
  1102.      * @uses get()
  1103.      * @return string|null
  1104.      */
  1105.     protected function peek() {
  1106.     $this->lookAhead = $this->get();
  1107.     return $this->lookAhead;
  1108.     }
  1109. }
  1110.  
  1111. // -- Exceptions ---------------------------------------------------------------
  1112. class JSMinException extends Exception {}
  1113.  
  1114. /**
  1115.  * Compress CSS
  1116.  *
  1117.  * This is a heavy regex-based removal of whitespace, unnecessary
  1118.  * comments and tokens, and some CSS value minimization, where practical.
  1119.  * Many steps have been taken to avoid breaking comment-based hacks,
  1120.  * including the ie5/mac filter (and its inversion), but expect tricky
  1121.  * hacks involving comment tokens in 'content' value strings to break
  1122.  * minimization badly. A test suite is available.
  1123.  *
  1124.  * @package Minify
  1125.  * @author Stephen Clay <steve@mrclay.org>
  1126.  * @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
  1127.  */
  1128. class Minify_CSS_Compressor {
  1129.  
  1130.     /**
  1131.      * Minify a CSS string
  1132.      *
  1133.      * @param string $css
  1134.      *
  1135.      * @param array $options (currently ignored)
  1136.      *
  1137.      * @return string
  1138.      */
  1139.     public static function process($css, $options = array())
  1140.     {
  1141.         $obj = new Minify_CSS_Compressor($options);
  1142.         return $obj->_process($css);
  1143.     }
  1144.  
  1145.     /**
  1146.      * @var array options
  1147.      */
  1148.     protected $_options = null;
  1149.  
  1150.     /**
  1151.      * @var bool Are we "in" a hack?
  1152.      *
  1153.      * I.e. are some browsers targetted until the next comment?
  1154.      */
  1155.     protected $_inHack = false;
  1156.  
  1157.     /**
  1158.      * Constructor
  1159.      *
  1160.      * @param array $options (currently ignored)
  1161.      *
  1162.      * @return null
  1163.      */
  1164.     private function __construct($options) {
  1165.         $this->_options = $options;
  1166.     }
  1167.  
  1168.     /**
  1169.      * Minify a CSS string
  1170.      *
  1171.      * @param string $css
  1172.      *
  1173.      * @return string
  1174.      */
  1175.     protected function _process($css)
  1176.     {
  1177.         $css = str_replace("\r\n", "\n", $css);
  1178.  
  1179.         // preserve empty comment after '>'
  1180.         // http://www.webdevout.net/css-hacks#in_css-selectors
  1181.         $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
  1182.  
  1183.         // preserve empty comment between property and value
  1184.         // http://css-discuss.incutio.com/?page=BoxModelHack
  1185.         $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
  1186.         $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
  1187.  
  1188.         // apply callback to all valid comments (and strip out surrounding ws
  1189.         $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
  1190.             ,array($this, '_commentCB'), $css);
  1191.  
  1192.         // remove ws around { } and last semicolon in declaration block
  1193.         $css = preg_replace('/\\s*{\\s*/', '{', $css);
  1194.         $css = preg_replace('/;?\\s*}\\s*/', '}', $css);
  1195.  
  1196.         // remove ws surrounding semicolons
  1197.         $css = preg_replace('/\\s*;\\s*/', ';', $css);
  1198.  
  1199.         // remove ws around urls
  1200.         $css = preg_replace('/
  1201.                 url\\(      # url(
  1202.                 \\s*
  1203.                 ([^\\)]+?)  # 1 = the URL (really just a bunch of non right parenthesis)
  1204.                 \\s*
  1205.                 \\)      # )
  1206.             /x', 'url($1)', $css);
  1207.  
  1208.         // remove ws between rules and colons
  1209.         $css = preg_replace('/
  1210.                 \\s*
  1211.                 ([{;])              # 1 = beginning of block or rule separator
  1212.                 \\s*
  1213.                 ([\\*_]?[\\w\\-]+)  # 2 = property (and maybe IE filter)
  1214.                 \\s*
  1215.                 :
  1216.                 \\s*
  1217.                 (\\b|[#\'"-])       # 3 = first character of a value
  1218.             /x', '$1$2:$3', $css);
  1219.  
  1220.         // remove ws in selectors
  1221.         $css = preg_replace_callback('/
  1222.                 (?:             # non-capture
  1223.                     \\s*
  1224.                     [^~>+,\\s]+ # selector part
  1225.                     \\s*
  1226.                     [,>+~]       # combinators
  1227.                 )+
  1228.                 \\s*
  1229.                 [^~>+,\\s]+     # selector part
  1230.                 {               # open declaration block
  1231.             /x'
  1232.             ,array($this, '_selectorsCB'), $css);
  1233.  
  1234.         // minimize hex colors
  1235.         $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
  1236.             , '$1#$2$3$4$5', $css);
  1237.  
  1238.         // remove spaces between font families
  1239.         $css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
  1240.             ,array($this, '_fontFamilyCB'), $css);
  1241.  
  1242.         $css = preg_replace('/@import\\s+url/', '@import url', $css);
  1243.  
  1244.         // replace any ws involving newlines with a single newline
  1245.         $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
  1246.  
  1247.         // separate common descendent selectors w/ newlines (to limit line lengths)
  1248.         $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
  1249.  
  1250.         // Use newline after 1st numeric value (to limit line lengths).
  1251.         $css = preg_replace('/
  1252.             ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
  1253.             \\s+
  1254.             /x'
  1255.             ,"$1\n", $css);
  1256.  
  1257.         // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
  1258.         $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
  1259.  
  1260.         return trim($css);
  1261.     }
  1262.  
  1263.     /**
  1264.      * Replace what looks like a set of selectors
  1265.      *
  1266.      * @param array $m regex matches
  1267.      *
  1268.      * @return string
  1269.      */
  1270.     protected function _selectorsCB($m)
  1271.     {
  1272.         // remove ws around the combinators
  1273.         return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
  1274.     }
  1275.  
  1276.     /**
  1277.      * Process a comment and return a replacement
  1278.      *
  1279.      * @param array $m regex matches
  1280.      *
  1281.      * @return string
  1282.      */
  1283.     protected function _commentCB($m)
  1284.     {
  1285.         $hasSurroundingWs = (trim($m[0]) !== $m[1]);
  1286.         $m = $m[1];
  1287.         // $m is the comment content w/o the surrounding tokens,
  1288.         // but the return value will replace the entire comment.
  1289.         if ($m === 'keep') {
  1290.             return '/**/';
  1291.         }
  1292.         if ($m === '" "') {
  1293.             // component of http://tantek.com/CSS/Examples/midpass.html
  1294.             return '/*" "*/';
  1295.         }
  1296.         if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
  1297.             // component of http://tantek.com/CSS/Examples/midpass.html
  1298.             return '/*";}}/* */';
  1299.         }
  1300.         if ($this->_inHack) {
  1301.             // inversion: feeding only to one browser
  1302.             if (preg_match('@
  1303.                     ^/               # comment started like /*/
  1304.                     \\s*
  1305.                     (\\S[\\s\\S]+?) # has at least some non-ws content
  1306.                     \\s*
  1307.                     /\\*             # ends like /*/ or /**/
  1308.                 @x', $m, $n)) {
  1309.                 // end hack mode after this comment, but preserve the hack and comment content
  1310.                 $this->_inHack = false;
  1311.                 return "/*/{$n[1]}/**/";
  1312.             }
  1313.         }
  1314.         if (substr($m, -1) === '\\') { // comment ends like \*/
  1315.             // begin hack mode and preserve hack
  1316.             $this->_inHack = true;
  1317.             return '/*\\*/';
  1318.         }
  1319.         if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
  1320.             // begin hack mode and preserve hack
  1321.             $this->_inHack = true;
  1322.             return '/*/*/';
  1323.         }
  1324.         if ($this->_inHack) {
  1325.             // a regular comment ends hack mode but should be preserved
  1326.             $this->_inHack = false;
  1327.             return '/**/';
  1328.         }
  1329.         // Issue 107: if there's any surrounding whitespace, it may be important, so
  1330.         // replace the comment with a single space
  1331.         return $hasSurroundingWs // remove all other comments
  1332.             ? ' '
  1333.             : '';
  1334.     }
  1335.  
  1336.     /**
  1337.      * Process a font-family listing and return a replacement
  1338.      *
  1339.      * @param array $m regex matches
  1340.      *
  1341.      * @return string
  1342.      */
  1343.     protected function _fontFamilyCB($m)
  1344.     {
  1345.         $m[1] = preg_replace('/
  1346.                 \\s*
  1347.                 (
  1348.                     "[^"]+"     # 1 = family in double qutoes
  1349.                     |\'[^\']+\' # or 1 = family in single quotes
  1350.                     |[\\w\\-]+   # or 1 = unquoted family
  1351.                 )
  1352.                 \\s*
  1353.             /x', '$1', $m[1]);
  1354.         return 'font-family:' . $m[1] . $m[2];
  1355.     }
  1356. }
  1357.  
  1358. /**
  1359.  * CssMin - A (simple) css minifier with benefits
  1360.  *
  1361.  * --
  1362.  * Copyright (c) 2011 Joe Scylla <joe.scylla@gmail.com>
  1363.  *
  1364.  * Permission is hereby granted, free of charge, to any person obtaining a copy
  1365.  * of this software and associated documentation files (the "Software"), to deal
  1366.  * in the Software without restriction, including without limitation the rights
  1367.  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  1368.  * copies of the Software, and to permit persons to whom the Software is
  1369.  * furnished to do so, subject to the following conditions:
  1370.  *
  1371.  * The above copyright notice and this permission notice shall be included in
  1372.  * all copies or substantial portions of the Software.
  1373.  *
  1374.  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  1375.  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  1376.  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  1377.  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  1378.  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  1379.  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  1380.  * THE SOFTWARE.
  1381.  * --
  1382.  *
  1383.  * @package     CssMin
  1384.  * @link        http://code.google.com/p/cssmin/
  1385.  * @author      Joe Scylla <joe.scylla@gmail.com>
  1386.  * @copyright   2008 - 2011 Joe Scylla <joe.scylla@gmail.com>
  1387.  * @license     http://opensource.org/licenses/mit-license.php MIT License
  1388.  * @version     3.0.1
  1389.  */
  1390.  abstract class aCssToken { abstract public function __toString(); } abstract class aCssRulesetStartToken extends aCssToken { } abstract class aCssRulesetEndToken extends aCssToken { public function __toString() { return "}"; } } abstract class aCssParserPlugin { protected $configuration = array(); protected $parser = null; protected $buffer = ""; public function __construct(CssParser $parser, array $configuration = null) { $this->configuration = $configuration; $this->parser = $parser; } abstract public function getTriggerChars(); abstract public function getTriggerStates(); abstract public function parse($index, $char, $previousChar, $state); } abstract class aCssMinifierPlugin { protected $configuration = array(); protected $minifier = null; public function __construct(CssMinifier $minifier, array $configuration = array()) { $this->configuration = $configuration; $this->minifier = $minifier; } abstract public function apply(aCssToken &$token); abstract public function getTriggerTokens(); } abstract class aCssMinifierFilter { protected $configuration = array(); protected $minifier = null; public function __construct(CssMinifier $minifier, array $configuration = array()) { $this->configuration = $configuration; $this->minifier = $minifier; } abstract public function apply(array &$tokens); } abstract class aCssFormatter { protected $indent = "       "; protected $padding = 0; protected $tokens = array(); public function __construct(array $tokens, $indent = null, $padding = null) { $this->tokens = $tokens; $this->indent = !is_null($indent) ? $indent : $this->indent; $this->padding = !is_null($padding) ? $padding : $this->padding; } abstract public function __toString(); } abstract class aCssDeclarationToken extends aCssToken { public $IsImportant = false; public $IsLast = false; public $Property = ""; public $Value = ""; public function __construct($property, $value, $isImportant = false, $isLast = false) { $this->Property = $property; $this->Value = $value; $this->IsImportant = $isImportant; $this->IsLast = $isLast; } public function __toString() { return $this->Property . ":" . $this->Value . ($this->IsImportant ? " !important" : "") . ($this->IsLast ? "" : ";"); } } abstract class aCssAtBlockStartToken extends aCssToken { } abstract class aCssAtBlockEndToken extends aCssToken { public function __toString() { return "}"; } } class CssWhitesmithsFormatter extends aCssFormatter { public function __toString() { $r = array(); $level = 0; for ($i = 0, $l = count($this->tokens); $i < $l; $i++) { $token = $this->tokens[$i]; $class = get_class($token); $indent = str_repeat($this->indent, $level); if ($class === "CssCommentToken") { $lines = array_map("trim", explode("\n", $token->Comment)); for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++) { $r[] = $indent . (substr($lines[$ii], 0, 1) == "*" ? " " : "") . $lines[$ii]; } } elseif ($class === "CssAtCharsetToken") { $r[] = $indent . "@charset " . $token->Charset . ";"; } elseif ($class === "CssAtFontFaceStartToken") { $r[] = $indent . "@font-face"; $r[] = $this->indent . $indent . "{"; $level++; } elseif ($class === "CssAtImportToken") { $r[] = $indent . "@import " . $token->Import . " " . implode(", ", $token->MediaTypes) . ";"; } elseif ($class === "CssAtKeyframesStartToken") { $r[] = $indent . "@keyframes \"" . $token->Name . "\""; $r[] = $this->indent . $indent . "{"; $level++; } elseif ($class === "CssAtMediaStartToken") { $r[] = $indent . "@media " . implode(", ", $token->MediaTypes); $r[] = $this->indent . $indent . "{"; $level++; } elseif ($class === "CssAtPageStartToken") { $r[] = $indent . "@page"; $r[] = $this->indent . $indent . "{"; $level++; } elseif ($class === "CssAtVariablesStartToken") { $r[] = $indent . "@variables " . implode(", ", $token->MediaTypes); $r[] = $this->indent . $indent . "{"; $level++; } elseif ($class === "CssRulesetStartToken" || $class === "CssAtKeyframesRulesetStartToken") { $r[] = $indent . implode(", ", $token->Selectors); $r[] = $this->indent . $indent . "{"; $level++; } elseif ($class == "CssAtFontFaceDeclarationToken" || $class === "CssAtKeyframesRulesetDeclarationToken" || $class === "CssAtPageDeclarationToken" || $class == "CssAtVariablesDeclarationToken" || $class === "CssRulesetDeclarationToken" ) { $declaration = $indent . $token->Property . ": "; if ($this->padding) { $declaration = str_pad($declaration, $this->padding, " ", STR_PAD_RIGHT); } $r[] = $declaration . $token->Value . ($token->IsImportant ? " !important" : "") . ";"; } elseif ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtKeyframesEndToken" || $class === "CssAtKeyframesRulesetEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken" || $class === "CssRulesetEndToken" ) { $r[] = $indent . "}"; $level--; } } return implode("\n", $r); } } class CssVariablesMinifierPlugin extends aCssMinifierPlugin { private $reMatch = "/var\((.+)\)/iSU"; private $variables = null; public function getVariables() { return $this->variables; } public function apply(aCssToken &$token) { if (stripos($token->Value, "var") !== false && preg_match_all($this->reMatch, $token->Value, $m)) { $mediaTypes = $token->MediaTypes; if (!in_array("all", $mediaTypes)) { $mediaTypes[] = "all"; } for ($i = 0, $l = count($m[0]); $i < $l; $i++) { $variable = trim($m[1][$i]); foreach ($mediaTypes as $mediaType) { if (isset($this->variables[$mediaType], $this->variables[$mediaType][$variable])) { $token->Value = str_replace($m[0][$i], $this->variables[$mediaType][$variable], $token->Value); continue 2; } } CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": No value found for variable <code>" . $variable . "</code> in media types <code>" . implode(", ", $mediaTypes) . "</code>", (string) $token)); $token = new CssNullToken(); return true; } } return false; } public function getTriggerTokens() { return array ( "CssAtFontFaceDeclarationToken", "CssAtPageDeclarationToken", "CssRulesetDeclarationToken" ); } public function setVariables(array $variables) { $this->variables = $variables; } } class CssVariablesMinifierFilter extends aCssMinifierFilter { public function apply(array &$tokens) { $variables = array(); $defaultMediaTypes = array("all"); $mediaTypes = array(); $remove = array(); for($i = 0, $l = count($tokens); $i < $l; $i++) { if (get_class($tokens[$i]) === "CssAtVariablesStartToken") { $remove[] = $i; $mediaTypes = (count($tokens[$i]->MediaTypes) == 0 ? $defaultMediaTypes : $tokens[$i]->MediaTypes); foreach ($mediaTypes as $mediaType) { if (!isset($variables[$mediaType])) { $variables[$mediaType] = array(); } } for($i = $i; $i < $l; $i++) { if (get_class($tokens[$i]) === "CssAtVariablesDeclarationToken") { foreach ($mediaTypes as $mediaType) { $variables[$mediaType][$tokens[$i]->Property] = $tokens[$i]->Value; } $remove[] = $i; } elseif (get_class($tokens[$i]) === "CssAtVariablesEndToken") { $remove[] = $i; break; } } } } foreach($variables as $mediaType => $null) { foreach($variables[$mediaType] as $variable => $value) { if (stripos($value, "var") !== false && preg_match_all("/var\((.+)\)/iSU", $value, $m)) { for ($i = 0, $l = count($m[0]); $i < $l; $i++) { $variables[$mediaType][$variable] = str_replace($m[0][$i], (isset($variables[$mediaType][$m[1][$i]]) ? $variables[$mediaType][$m[1][$i]] : ""), $variables[$mediaType][$variable]); } } } } foreach ($remove as $i) { $tokens[$i] = null; } if (!($plugin = $this->minifier->getPlugin("CssVariablesMinifierPlugin"))) { CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>CssVariablesMinifierPlugin</code> was not found but is required for <code>" . __CLASS__ . "</code>")); } else { $plugin->setVariables($variables); } return count($remove); } } class CssUrlParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array("(", ")"); } public function getTriggerStates() { return false; } public function parse($index, $char, $previousChar, $state) { if ($char === "(" && strtolower(substr($this->parser->getSource(), $index - 3, 4)) === "url(" && $state !== "T_URL") { $this->parser->pushState("T_URL"); $this->parser->setExclusive(__CLASS__); } elseif ($char === "\n" && $previousChar === "\\" && $state === "T_URL") { $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2)); } elseif ($char === "\n" && $previousChar !== "\\" && $state === "T_URL") { $line = $this->parser->getBuffer(); $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -1) . ")"); $this->parser->popState(); $this->parser->unsetExclusive(); CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated string literal", $line . "_")); } elseif ($char === ")" && $state === "T_URL") { $this->parser->popState(); $this->parser->unsetExclusive(); } else { return false; } return true; } } class CssStringParserPlugin extends aCssParserPlugin { private $delimiterChar = null; public function getTriggerChars() { return array("\"", "'", "\n"); } public function getTriggerStates() { return false; } public function parse($index, $char, $previousChar, $state) { if (($char === "\"" || $char === "'") && $state !== "T_STRING") { $this->delimiterChar = $char; $this->parser->pushState("T_STRING"); $this->parser->setExclusive(__CLASS__); } elseif ($char === "\n" && $previousChar === "\\" && $state === "T_STRING") { $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -2)); } elseif ($char === "\n" && $previousChar !== "\\" && $state === "T_STRING") { $line = $this->parser->getBuffer(); $this->parser->popState(); $this->parser->unsetExclusive(); $this->parser->setBuffer(substr($this->parser->getBuffer(), 0, -1) . $this->delimiterChar); CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated string literal", $line . "_")); $this->delimiterChar = null; } elseif ($char === $this->delimiterChar && $state === "T_STRING") { if ($previousChar == "\\") { $source = $this->parser->getSource(); $c = 1; $i = $index - 2; while (substr($source, $i, 1) === "\\") { $c++; $i--; } if ($c % 2) { return false; } } $this->parser->popState(); $this->parser->unsetExclusive(); $this->delimiterChar = null; } else { return false; } return true; } } class CssSortRulesetPropertiesMinifierFilter extends aCssMinifierFilter { public function apply(array &$tokens) { $r = 0; for ($i = 0, $l = count($tokens); $i < $l; $i++) { if (get_class($tokens[$i]) !== "CssRulesetStartToken") { continue; } $endIndex = false; for ($ii = $i + 1; $ii < $l; $ii++) { if (get_class($tokens[$ii]) !== "CssRulesetEndToken") { continue; } $endIndex = $ii; break; } if (!$endIndex) { break; } $startIndex = $i; $i = $endIndex; if ($endIndex - $startIndex <= 2) { continue; } for ($ii = $startIndex + 1; $ii < $endIndex; $ii++) { if (get_class($tokens[$ii]) !== "CssRulesetDeclarationToken") { continue(2); } } $declarations = array_slice($tokens, $startIndex + 1, $endIndex - $startIndex - 1); $sortRequired = $lastPropertyName = false; foreach ($declarations as $declaration) { if ($lastPropertyName) { if (strcmp($lastPropertyName, $declaration->Property) > 0) { $sortRequired = true; break; } } $lastPropertyName = $declaration->Property; } if (!$sortRequired) { continue; } usort($declarations, array(__CLASS__, "userDefinedSort1")); for ($ii = 0, $ll = count($declarations) - 1; $ii <= $ll; $ii++) { if ($ii == $ll) { $declarations[$ii]->IsLast = true; } else { $declarations[$ii]->IsLast = false; } } array_splice($tokens, $startIndex + 1, $endIndex - $startIndex - 1, $declarations); $r += $endIndex - $startIndex - 1; } return $r; } public static function userDefinedSort1($a, $b) { return strcmp($a->Property, $b->Property); } } class CssRulesetStartToken extends aCssRulesetStartToken { public $Selectors = array(); public function __construct(array $selectors = array()) { $this->Selectors = $selectors; } public function __toString() { return implode(",", $this->Selectors) . "{"; } } class CssRulesetParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array(",", "{", "}", ":", ";"); } public function getTriggerStates() { return array("T_DOCUMENT", "T_AT_MEDIA", "T_RULESET::SELECTORS", "T_RULESET", "T_RULESET_DECLARATION"); } private $selectors = array(); public function parse($index, $char, $previousChar, $state) { if ($char === "," && ($state === "T_DOCUMENT" || $state === "T_AT_MEDIA" || $state === "T_RULESET::SELECTORS")) { if ($state !== "T_RULESET::SELECTORS") { $this->parser->pushState("T_RULESET::SELECTORS"); } $this->selectors[] = $this->parser->getAndClearBuffer(",{"); } elseif ($char === "{" && ($state === "T_DOCUMENT" || $state === "T_AT_MEDIA" || $state === "T_RULESET::SELECTORS")) { if ($this->parser->getBuffer() !== "") { $this->selectors[] = $this->parser->getAndClearBuffer(",{"); if ($state == "T_RULESET::SELECTORS") { $this->parser->popState(); } $this->parser->pushState("T_RULESET"); $this->parser->appendToken(new CssRulesetStartToken($this->selectors)); $this->selectors = array(); } } elseif ($char === ":" && $state === "T_RULESET") { $this->parser->pushState("T_RULESET_DECLARATION"); $this->buffer = $this->parser->getAndClearBuffer(":;", true); } elseif ($char === ":" && $state === "T_RULESET_DECLARATION") { if ($this->buffer === "filter") { return false; } CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); } elseif (($char === ";" || $char === "}") && $state === "T_RULESET_DECLARATION") { $value = $this->parser->getAndClearBuffer(";}"); if (strtolower(substr($value, -10, 10)) === "!important") { $value = trim(substr($value, 0, -10)); $isImportant = true; } else { $isImportant = false; } $this->parser->popState(); $this->parser->appendToken(new CssRulesetDeclarationToken($this->buffer, $value, $this->parser->getMediaTypes(), $isImportant)); if ($char === "}") { $this->parser->appendToken(new CssRulesetEndToken()); $this->parser->popState(); } $this->buffer = ""; } elseif ($char === "}" && $state === "T_RULESET") { $this->parser->popState(); $this->parser->clearBuffer(); $this->parser->appendToken(new CssRulesetEndToken()); $this->buffer = ""; $this->selectors = array(); } else { return false; } return true; } } class CssRulesetEndToken extends aCssRulesetEndToken { } class CssRulesetDeclarationToken extends aCssDeclarationToken { public $MediaTypes = array("all"); public function __construct($property, $value, $mediaTypes = null, $isImportant = false, $isLast = false) { parent::__construct($property, $value, $isImportant, $isLast); $this->MediaTypes = $mediaTypes ? $mediaTypes : array("all"); } } class CssRemoveLastDelarationSemiColonMinifierFilter extends aCssMinifierFilter { public function apply(array &$tokens) { for ($i = 0, $l = count($tokens); $i < $l; $i++) { $current = get_class($tokens[$i]); $next = isset($tokens[$i+1]) ? get_class($tokens[$i+1]) : false; if (($current === "CssRulesetDeclarationToken" && $next === "CssRulesetEndToken") || ($current === "CssAtFontFaceDeclarationToken" && $next === "CssAtFontFaceEndToken") || ($current === "CssAtPageDeclarationToken" && $next === "CssAtPageEndToken")) { $tokens[$i]->IsLast = true; } } return 0; } } class CssRemoveEmptyRulesetsMinifierFilter extends aCssMinifierFilter { public function apply(array &$tokens) { $r = 0; for ($i = 0, $l = count($tokens); $i < $l; $i++) { $current = get_class($tokens[$i]); $next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false; if (($current === "CssRulesetStartToken" && $next === "CssRulesetEndToken") || ($current === "CssAtKeyframesRulesetStartToken" && $next === "CssAtKeyframesRulesetEndToken" && !array_intersect(array("from", "0%", "to", "100%"), array_map("strtolower", $tokens[$i]->Selectors))) ) { $tokens[$i] = null; $tokens[$i + 1] = null; $i++; $r = $r + 2; } } return $r; } } class CssRemoveEmptyAtBlocksMinifierFilter extends aCssMinifierFilter { public function apply(array &$tokens) { $r = 0; for ($i = 0, $l = count($tokens); $i < $l; $i++) { $current = get_class($tokens[$i]); $next = isset($tokens[$i + 1]) ? get_class($tokens[$i + 1]) : false; if (($current === "CssAtFontFaceStartToken" && $next === "CssAtFontFaceEndToken") || ($current === "CssAtKeyframesStartToken" && $next === "CssAtKeyframesEndToken") || ($current === "CssAtPageStartToken" && $next === "CssAtPageEndToken") || ($current === "CssAtMediaStartToken" && $next === "CssAtMediaEndToken")) { $tokens[$i] = null; $tokens[$i + 1] = null; $i++; $r = $r + 2; } } return $r; } } class CssRemoveCommentsMinifierFilter extends aCssMinifierFilter { public function apply(array &$tokens) { $r = 0; for ($i = 0, $l = count($tokens); $i < $l; $i++) { if (get_class($tokens[$i]) === "CssCommentToken") { $tokens[$i] = null; $r++; } } return $r; } } class CssParser { private $buffer = ""; private $plugins = array(); private $source = ""; private $state = "T_DOCUMENT"; private $stateExclusive = false; private $stateMediaTypes = false; private $states = array("T_DOCUMENT"); private $tokens = array(); public function __construct($source = null, array $plugins = null) { $plugins = array_merge(array ( "Comment" => true, "String" => true, "Url" => true, "Expression" => true, "Ruleset" => true, "AtCharset" => true, "AtFontFace" => true, "AtImport" => true, "AtKeyframes" => true, "AtMedia" => true, "AtPage" => true, "AtVariables" => true ), is_array($plugins) ? $plugins : array()); foreach ($plugins as $name => $config) { if ($config !== false) { $class = "Css" . $name . "ParserPlugin"; $config = is_array($config) ? $config : array(); if (class_exists($class)) { $this->plugins[] = new $class($this, $config); } else { CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found")); } } } if (!is_null($source)) { $this->parse($source); } } public function appendToken(aCssToken $token) { $this->tokens[] = $token; } public function clearBuffer() { $this->buffer = ""; } public function getAndClearBuffer($trim = "", $tolower = false) { $r = $this->getBuffer($trim, $tolower); $this->buffer = ""; return $r; } public function getBuffer($trim = "", $tolower = false) { $r = $this->buffer; if ($trim) { $r = trim($r, " \t\n\r\0\x0B" . $trim); } if ($tolower) { $r = strtolower($r); } return $r; } public function getMediaTypes() { return $this->stateMediaTypes; } public function getSource() { return $this->source; } public function getState() { return $this->state; } public function getPlugin($class) { static $index = null; if (is_null($index)) { $index = array(); for ($i = 0, $l = count($this->plugins); $i < $l; $i++) { $index[get_class($this->plugins[$i])] = $i; } } return isset($index[$class]) ? $this->plugins[$index[$class]] : false; } public function getTokens() { return $this->tokens; } public function isState($state) { return ($this->state == $state); } public function parse($source) { $this->source = ""; $this->tokens = array(); $globalTriggerChars = ""; $plugins = $this->plugins; $pluginCount = count($plugins); $pluginIndex = array(); $pluginTriggerStates = array(); $pluginTriggerChars = array(); for ($i = 0, $l = count($plugins); $i < $l; $i++) { $tPluginClassName = get_class($plugins[$i]); $pluginTriggerChars[$i] = implode("", $plugins[$i]->getTriggerChars()); $tPluginTriggerStates = $plugins[$i]->getTriggerStates(); $pluginTriggerStates[$i] = $tPluginTriggerStates === false ? false : "|" . implode("|", $tPluginTriggerStates) . "|"; $pluginIndex[$tPluginClassName] = $i; for ($ii = 0, $ll = strlen($pluginTriggerChars[$i]); $ii < $ll; $ii++) { $c = substr($pluginTriggerChars[$i], $ii, 1); if (strpos($globalTriggerChars, $c) === false) { $globalTriggerChars .= $c; } } } $source = str_replace("\r\n", "\n", $source); $source = str_replace("\r", "\n", $source); $this->source = $source; $buffer = &$this->buffer; $exclusive = &$this->stateExclusive; $state = &$this->state; $c = $p = null; for ($i = 0, $l = strlen($source); $i < $l; $i++) { $c = $source[$i]; if ($exclusive === false) { if ($c === "\n" || $c === "\t") { $c = " "; } if ($c === " " && $p === " ") { continue; } } $buffer .= $c; if (strpos($globalTriggerChars, $c) !== false) { if ($exclusive) { $tPluginIndex = $pluginIndex[$exclusive]; if (strpos($pluginTriggerChars[$tPluginIndex], $c) !== false && ($pluginTriggerStates[$tPluginIndex] === false || strpos($pluginTriggerStates[$tPluginIndex], $state) !== false)) { $r = $plugins[$tPluginIndex]->parse($i, $c, $p, $state); if ($r === true) { continue; } elseif ($r !== false && $r != $i) { $i = $r; continue; } } } else { $triggerState = "|" . $state . "|"; for ($ii = 0, $ll = $pluginCount; $ii < $ll; $ii++) { if (strpos($pluginTriggerChars[$ii], $c) !== false && ($pluginTriggerStates[$ii] === false || strpos($pluginTriggerStates[$ii], $triggerState) !== false)) { $r = $plugins[$ii]->parse($i, $c, $p, $state); if ($r === true) { break; } elseif ($r !== false && $r != $i) { $i = $r; break; } } } } } $p = $c; } return $this->tokens; } public function popState() { $r = array_pop($this->states); $this->state = $this->states[count($this->states) - 1]; return $r; } public function pushState($state) { $r = array_push($this->states, $state); $this->state = $this->states[count($this->states) - 1]; return $r; } public function setBuffer($buffer) { $this->buffer = $buffer; } public function setExclusive($exclusive) { $this->stateExclusive = $exclusive; } public function setMediaTypes(array $mediaTypes) { $this->stateMediaTypes = $mediaTypes; } public function setState($state) { $r = array_pop($this->states); array_push($this->states, $state); $this->state = $this->states[count($this->states) - 1]; return $r; } public function unsetExclusive() { $this->stateExclusive = false; } public function unsetMediaTypes() { $this->stateMediaTypes = false; } } class CssOtbsFormatter extends aCssFormatter { public function __toString() { $r = array(); $level = 0; for ($i = 0, $l = count($this->tokens); $i < $l; $i++) { $token = $this->tokens[$i]; $class = get_class($token); $indent = str_repeat($this->indent, $level); if ($class === "CssCommentToken") { $lines = array_map("trim", explode("\n", $token->Comment)); for ($ii = 0, $ll = count($lines); $ii < $ll; $ii++) { $r[] = $indent . (substr($lines[$ii], 0, 1) == "*" ? " " : "") . $lines[$ii]; } } elseif ($class === "CssAtCharsetToken") { $r[] = $indent . "@charset " . $token->Charset . ";"; } elseif ($class === "CssAtFontFaceStartToken") { $r[] = $indent . "@font-face {"; $level++; } elseif ($class === "CssAtImportToken") { $r[] = $indent . "@import " . $token->Import . " " . implode(", ", $token->MediaTypes) . ";"; } elseif ($class === "CssAtKeyframesStartToken") { $r[] = $indent . "@keyframes \"" . $token->Name . "\" {"; $level++; } elseif ($class === "CssAtMediaStartToken") { $r[] = $indent . "@media " . implode(", ", $token->MediaTypes) . " {"; $level++; } elseif ($class === "CssAtPageStartToken") { $r[] = $indent . "@page {"; $level++; } elseif ($class === "CssAtVariablesStartToken") { $r[] = $indent . "@variables " . implode(", ", $token->MediaTypes) . " {"; $level++; } elseif ($class === "CssRulesetStartToken" || $class === "CssAtKeyframesRulesetStartToken") { $r[] = $indent . implode(", ", $token->Selectors) . " {"; $level++; } elseif ($class == "CssAtFontFaceDeclarationToken" || $class === "CssAtKeyframesRulesetDeclarationToken" || $class === "CssAtPageDeclarationToken" || $class == "CssAtVariablesDeclarationToken" || $class === "CssRulesetDeclarationToken" ) { $declaration = $indent . $token->Property . ": "; if ($this->padding) { $declaration = str_pad($declaration, $this->padding, " ", STR_PAD_RIGHT); } $r[] = $declaration . $token->Value . ($token->IsImportant ? " !important" : "") . ";"; } elseif ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtKeyframesEndToken" || $class === "CssAtKeyframesRulesetEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken" || $class === "CssRulesetEndToken" ) { $level--; $r[] = str_repeat($indent, $level) . "}"; } } return implode("\n", $r); } } class CssNullToken extends aCssToken { public function __toString() { return ""; } } class CssMinifier { private $filters = array(); private $plugins = array(); private $minified = ""; public function __construct($source = null, array $filters = null, array $plugins = null) { $filters = array_merge(array ( "ImportImports" => false, "RemoveComments" => true, "RemoveEmptyRulesets" => true, "RemoveEmptyAtBlocks" => true, "ConvertLevel3Properties" => false, "ConvertLevel3AtKeyframes" => false, "Variables" => true, "RemoveLastDelarationSemiColon" => true ), is_array($filters) ? $filters : array()); $plugins = array_merge(array ( "Variables" => true, "ConvertFontWeight" => false, "ConvertHslColors" => false, "ConvertRgbColors" => false, "ConvertNamedColors" => false, "CompressColorValues" => false, "CompressUnitValues" => false, "CompressExpressionValues" => false ), is_array($plugins) ? $plugins : array()); foreach ($filters as $name => $config) { if ($config !== false) { $class = "Css" . $name . "MinifierFilter"; $config = is_array($config) ? $config : array(); if (class_exists($class)) { $this->filters[] = new $class($this, $config); } else { CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The filter <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found")); } } } foreach ($plugins as $name => $config) { if ($config !== false) { $class = "Css" . $name . "MinifierPlugin"; $config = is_array($config) ? $config : array(); if (class_exists($class)) { $this->plugins[] = new $class($this, $config); } else { CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": The plugin <code>" . $name . "</code> with the class name <code>" . $class . "</code> was not found")); } } } if (!is_null($source)) { $this->minify($source); } } public function getMinified() { return $this->minified; } public function getPlugin($class) { static $index = null; if (is_null($index)) { $index = array(); for ($i = 0, $l = count($this->plugins); $i < $l; $i++) { $index[get_class($this->plugins[$i])] = $i; } } return isset($index[$class]) ? $this->plugins[$index[$class]] : false; } public function minify($source) { $r = ""; $parser = new CssParser($source); $tokens = $parser->getTokens(); $filters = $this->filters; $filterCount = count($this->filters); $plugins = $this->plugins; $pluginCount = count($plugins); $pluginIndex = array(); $pluginTriggerTokens = array(); $globalTriggerTokens = array(); for ($i = 0, $l = count($plugins); $i < $l; $i++) { $tPluginClassName = get_class($plugins[$i]); $pluginTriggerTokens[$i] = $plugins[$i]->getTriggerTokens(); foreach ($pluginTriggerTokens[$i] as $v) { if (!in_array($v, $globalTriggerTokens)) { $globalTriggerTokens[] = $v; } } $pluginTriggerTokens[$i] = "|" . implode("|", $pluginTriggerTokens[$i]) . "|"; $pluginIndex[$tPluginClassName] = $i; } $globalTriggerTokens = "|" . implode("|", $globalTriggerTokens) . "|"; for($i = 0; $i < $filterCount; $i++) { if ($filters[$i]->apply($tokens) > 0) { $tokens = array_values(array_filter($tokens)); } } $tokenCount = count($tokens); for($i = 0; $i < $tokenCount; $i++) { $triggerToken = "|" . get_class($tokens[$i]) . "|"; if (strpos($globalTriggerTokens, $triggerToken) !== false) { for($ii = 0; $ii < $pluginCount; $ii++) { if (strpos($pluginTriggerTokens[$ii], $triggerToken) !== false || $pluginTriggerTokens[$ii] === false) { if ($plugins[$ii]->apply($tokens[$i]) === true) { continue 2; } } } } } for($i = 0; $i < $tokenCount; $i++) { $r .= (string) $tokens[$i]; } $this->minified = $r; return $r; } } class CssMin { private static $classIndex = array(); private static $errors = array(); private static $isVerbose = false; public static function autoload($class) { if (isset(self::$classIndex[$class])) { require(self::$classIndex[$class]); } } public static function getErrors() { return self::$errors; } public static function hasErrors() { return count(self::$errors) > 0; } public static function initialise() { $paths = array(dirname(__FILE__)); while (list($i, $path) = each($paths)) { $subDirectorys = glob($path . "*", GLOB_MARK | GLOB_ONLYDIR | GLOB_NOSORT); if (is_array($subDirectorys)) { foreach ($subDirectorys as $subDirectory) { $paths[] = $subDirectory; } } $files = glob($path . "*.php", 0); if (is_array($files)) { foreach ($files as $file) { $class = substr(basename($file), 0, -4); self::$classIndex[$class] = $file; } } } krsort(self::$classIndex); if (function_exists("spl_autoload_register") && !is_callable("__autoload")) { spl_autoload_register(array(__CLASS__, "autoload")); } else { foreach (self::$classIndex as $class => $file) { if (!class_exists($class)) { require_once($file); } } } } public static function minify($source, array $filters = null, array $plugins = null) { self::$errors = array(); $minifier = new CssMinifier($source, $filters, $plugins); return $minifier->getMinified(); } public static function parse($source, array $plugins = null) { self::$errors = array(); $parser = new CssParser($source, $plugins); return $parser->getTokens(); } public static function setVerbose($to) { self::$isVerbose = (boolean) $to; return self::$isVerbose; } public static function triggerError(CssError $error) { self::$errors[] = $error; if (self::$isVerbose) { trigger_error((string) $error, E_USER_WARNING); } } } CssMin::initialise(); class CssImportImportsMinifierFilter extends aCssMinifierFilter { private $imported = array(); public function apply(array &$tokens) { if (!isset($this->configuration["BasePath"]) || !is_dir($this->configuration["BasePath"])) { CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Base path <code>" . ($this->configuration["BasePath"] ? $this->configuration["BasePath"] : "null"). "</code> is not a directory")); return 0; } for ($i = 0, $l = count($tokens); $i < $l; $i++) { if (get_class($tokens[$i]) === "CssAtImportToken") { $import = $this->configuration["BasePath"] . "/" . $tokens[$i]->Import; if (!is_file($import)) { CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Import file <code>" . $import. "</code> was not found.", (string) $tokens[$i])); } elseif (in_array($import, $this->imported)) { CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Import file <code>" . $import. "</code> was already imported.", (string) $tokens[$i])); $tokens[$i] = null; } else { $this->imported[] = $import; $parser = new CssParser(file_get_contents($import)); $import = $parser->getTokens(); if (count($tokens[$i]->MediaTypes) > 0 && !(count($tokens[$i]->MediaTypes) == 1 && $tokens[$i]->MediaTypes[0] == "all")) { $blocks = array(); for($ii = 0, $ll = count($import); $ii < $ll; $ii++) { if (get_class($import[$ii]) === "CssAtImportToken") { if (count($import[$ii]->MediaTypes) == 0 || (count($import[$ii]->MediaTypes) == 1 && $import[$ii]->MediaTypes[0] == "all")) { $import[$ii]->MediaTypes = $tokens[$i]->MediaTypes; } elseif (count($import[$ii]->MediaTypes > 0)) { foreach ($import[$ii]->MediaTypes as $index => $mediaType) { if (!in_array($mediaType, $tokens[$i]->MediaTypes)) { unset($import[$ii]->MediaTypes[$index]); } } $import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes); if (count($import[$ii]->MediaTypes) == 0) { $import[$ii] = null; } } } } for($ii = 0, $ll = count($import); $ii < $ll; $ii++) { if (get_class($import[$ii]) === "CssAtMediaStartToken") { foreach ($import[$ii]->MediaTypes as $index => $mediaType) { if (!in_array($mediaType, $tokens[$i]->MediaTypes)) { unset($import[$ii]->MediaTypes[$index]); } $import[$ii]->MediaTypes = array_values($import[$ii]->MediaTypes); } } } for($ii = 0, $ll = count($import); $ii < $ll; $ii++) { if (get_class($import[$ii]) === "CssAtMediaStartToken") { if (count($import[$ii]->MediaTypes) === 0) { for ($iii = $ii; $iii < $ll; $iii++) { if (get_class($import[$iii]) === "CssAtMediaEndToken") { break; } } if (get_class($import[$iii]) === "CssAtMediaEndToken") { array_splice($import, $ii, $iii - $ii + 1, array()); $ll = count($import); } } } } for($ii = 0, $ll = count($import); $ii < $ll; $ii++) { if (get_class($import[$ii]) === "CssAtMediaStartToken" && count(array_diff($tokens[$i]->MediaTypes, $import[$ii]->MediaTypes)) === 0) { for ($iii = $ii; $iii < $ll; $iii++) { if (get_class($import[$iii]) == "CssAtMediaEndToken") { break; } } if (get_class($import[$iii]) == "CssAtMediaEndToken") { unset($import[$ii]); unset($import[$iii]); $import = array_values($import); $ll = count($import); } } } for($ii = 0, $ll = count($import); $ii < $ll; $ii++) { $class = get_class($import[$ii]); if ($class === "CssAtImportToken" || $class === "CssAtCharsetToken") { $blocks = array_merge($blocks, array_splice($import, $ii, 1, array())); $ll = count($import); } } for($ii = 0, $ll = count($import); $ii < $ll; $ii++) { $class = get_class($import[$ii]); if ($class === "CssAtFontFaceStartToken" || $class === "CssAtMediaStartToken" || $class === "CssAtPageStartToken" || $class === "CssAtVariablesStartToken") { for ($iii = $ii; $iii < $ll; $iii++) { $class = get_class($import[$iii]); if ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken") { break; } } $class = get_class($import[$iii]); if (isset($import[$iii]) && ($class === "CssAtFontFaceEndToken" || $class === "CssAtMediaEndToken" || $class === "CssAtPageEndToken" || $class === "CssAtVariablesEndToken")) { $blocks = array_merge($blocks, array_splice($import, $ii, $iii - $ii + 1, array())); $ll = count($import); } } } $import = array_merge($blocks, array(new CssAtMediaStartToken($tokens[$i]->MediaTypes)), $import, array(new CssAtMediaEndToken())); } array_splice($tokens, $i, 1, $import); $i--; $l = count($tokens); } } } } } class CssExpressionParserPlugin extends aCssParserPlugin { private $leftBraces = 0; private $rightBraces = 0; public function getTriggerChars() { return array("(", ")", ";", "}"); } public function getTriggerStates() { return false; } public function parse($index, $char, $previousChar, $state) { if ($char === "(" && strtolower(substr($this->parser->getSource(), $index - 10, 11)) === "expression(" && $state !== "T_EXPRESSION") { $this->parser->pushState("T_EXPRESSION"); $this->leftBraces++; } elseif ($char === "(" && $state === "T_EXPRESSION") { $this->leftBraces++; } elseif ($char === ")" && $state === "T_EXPRESSION") { $this->rightBraces++; } elseif (($char === ";" || $char === "}") && $state === "T_EXPRESSION" && $this->leftBraces === $this->rightBraces) { $this->leftBraces = $this->rightBraces = 0; $this->parser->popState(); return $index - 1; } else { return false; } return true; } } class CssError { public $File = ""; public $Line = 0; public $Message = ""; public $Source = ""; public function __construct($file, $line, $message, $source = "") { $this->File = $file; $this->Line = $line; $this->Message = $message; $this->Source = $source; } public function __toString() { return $this->Message . ($this->Source ? ":<br /><code>" . $this->Source . "</code>": "") . "<br />in file " . $this->File . " at line " . $this->Line; } } class CssConvertRgbColorsMinifierPlugin extends aCssMinifierPlugin { private $reMatch = "/rgb\s*\(\s*([0-9%]+)\s*,\s*([0-9%]+)\s*,\s*([0-9%]+)\s*\)/iS"; public function apply(aCssToken &$token) { if (stripos($token->Value, "rgb") !== false && preg_match($this->reMatch, $token->Value, $m)) { for ($i = 1, $l = count($m); $i < $l; $i++) { if (strpos("%", $m[$i]) !== false) { $m[$i] = substr($m[$i], 0, -1); $m[$i] = (int) (256 * ($m[$i] / 100)); } $m[$i] = str_pad(dechex($m[$i]), 2, "0", STR_PAD_LEFT); } $token->Value = str_replace($m[0], "#" . $m[1] . $m[2] . $m[3], $token->Value); } return false; } public function getTriggerTokens() { return array ( "CssAtFontFaceDeclarationToken", "CssAtPageDeclarationToken", "CssRulesetDeclarationToken" ); } } class CssConvertNamedColorsMinifierPlugin extends aCssMinifierPlugin { private $reMatch = null; private $reReplace = "\"\${1}\" . \$this->transformation[strtolower(\"\${2}\")] . \"\${3}\""; private $transformation = array ( "aliceblue" => "#f0f8ff", "antiquewhite" => "#faebd7", "aqua" => "#0ff", "aquamarine" => "#7fffd4", "azure" => "#f0ffff", "beige" => "#f5f5dc", "black" => "#000", "blue" => "#00f", "blueviolet" => "#8a2be2", "brown" => "#a52a2a", "burlywood" => "#deb887", "cadetblue" => "#5f9ea0", "chartreuse" => "#7fff00", "chocolate" => "#d2691e", "coral" => "#ff7f50", "cornflowerblue" => "#6495ed", "cornsilk" => "#fff8dc", "crimson" => "#dc143c", "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" => "#f0f", "gainsboro" => "#dcdcdc", "ghostwhite" => "#f8f8ff", "gold" => "#ffd700", "goldenrod" => "#daa520", "gray" => "#808080", "green" => "#008000", "greenyellow" => "#adff2f", "honeydew" => "#f0fff0", "hotpink" => "#ff69b4", "indianred" => "#cd5c5c", "indigo" => "#4b0082", "ivory" => "#fffff0", "khaki" => "#f0e68c", "lavender" => "#e6e6fa", "lavenderblush" => "#fff0f5", "lawngreen" => "#7cfc00", "lemonchiffon" => "#fffacd", "lightblue" => "#add8e6", "lightcoral" => "#f08080", "lightcyan" => "#e0ffff", "lightgoldenrodyellow" => "#fafad2", "lightgreen" => "#90ee90", "lightgrey" => "#d3d3d3", "lightpink" => "#ffb6c1", "lightsalmon" => "#ffa07a", "lightseagreen" => "#20b2aa", "lightskyblue" => "#87cefa", "lightslategray" => "#789", "lightsteelblue" => "#b0c4de", "lightyellow" => "#ffffe0", "lime" => "#0f0", "limegreen" => "#32cd32", "linen" => "#faf0e6", "maroon" => "#800000", "mediumaquamarine" => "#66cdaa", "mediumblue" => "#0000cd", "mediumorchid" => "#ba55d3", "mediumpurple" => "#9370db", "mediumseagreen" => "#3cb371", "mediumslateblue" => "#7b68ee", "mediumspringgreen" => "#00fa9a", "mediumturquoise" => "#48d1cc", "mediumvioletred" => "#c71585", "midnightblue" => "#191970", "mintcream" => "#f5fffa", "mistyrose" => "#ffe4e1", "moccasin" => "#ffe4b5", "navajowhite" => "#ffdead", "navy" => "#000080", "oldlace" => "#fdf5e6", "olive" => "#808000", "olivedrab" => "#6b8e23", "orange" => "#ffa500", "orangered" => "#ff4500", "orchid" => "#da70d6", "palegoldenrod" => "#eee8aa", "palegreen" => "#98fb98", "paleturquoise" => "#afeeee", "palevioletred" => "#db7093", "papayawhip" => "#ffefd5", "peachpuff" => "#ffdab9", "peru" => "#cd853f", "pink" => "#ffc0cb", "plum" => "#dda0dd", "powderblue" => "#b0e0e6", "purple" => "#800080", "red" => "#f00", "rosybrown" => "#bc8f8f", "royalblue" => "#4169e1", "saddlebrown" => "#8b4513", "salmon" => "#fa8072", "sandybrown" => "#f4a460", "seagreen" => "#2e8b57", "seashell" => "#fff5ee", "sienna" => "#a0522d", "silver" => "#c0c0c0", "skyblue" => "#87ceeb", "slateblue" => "#6a5acd", "slategray" => "#708090", "snow" => "#fffafa", "springgreen" => "#00ff7f", "steelblue" => "#4682b4", "tan" => "#d2b48c", "teal" => "#008080", "thistle" => "#d8bfd8", "tomato" => "#ff6347", "turquoise" => "#40e0d0", "violet" => "#ee82ee", "wheat" => "#f5deb3", "white" => "#fff", "whitesmoke" => "#f5f5f5", "yellow" => "#ff0", "yellowgreen" => "#9acd32" ); public function __construct(CssMinifier $minifier, array $configuration = array()) { $this->reMatch = "/(^|\s)+(" . implode("|", array_keys($this->transformation)) . ")(\s|$)+/eiS"; parent::__construct($minifier, $configuration); } public function apply(aCssToken &$token) { $lcValue = strtolower($token->Value); if (isset($this->transformation[$lcValue])) { $token->Value = $this->transformation[$lcValue]; } elseif (preg_match($this->reMatch, $token->Value)) { $token->Value = preg_replace($this->reMatch, $this->reReplace, $token->Value); } return false; } public function getTriggerTokens() { return array ( "CssAtFontFaceDeclarationToken", "CssAtPageDeclarationToken", "CssRulesetDeclarationToken" ); } } class CssConvertLevel3PropertiesMinifierFilter extends aCssMinifierFilter { private $transformations = array ( "animation" => array(null, "-webkit-animation", null, null), "animation-delay" => array(null, "-webkit-animation-delay", null, null), "animation-direction" => array(null, "-webkit-animation-direction", null, null), "animation-duration" => array(null, "-webkit-animation-duration", null, null), "animation-fill-mode" => array(null, "-webkit-animation-fill-mode", null, null), "animation-iteration-count" => array(null, "-webkit-animation-iteration-count", null, null), "animation-name" => array(null, "-webkit-animation-name", null, null), "animation-play-state" => array(null, "-webkit-animation-play-state", null, null), "animation-timing-function" => array(null, "-webkit-animation-timing-function", null, null), "appearance" => array("-moz-appearance", "-webkit-appearance", null, null), "backface-visibility" => array(null, "-webkit-backface-visibility", null, null), "background-clip" => array(null, "-webkit-background-clip", null, null), "background-composite" => array(null, "-webkit-background-composite", null, null), "background-inline-policy" => array("-moz-background-inline-policy", null, null, null), "background-origin" => array(null, "-webkit-background-origin", null, null), "background-position-x" => array(null, null, null, "-ms-background-position-x"), "background-position-y" => array(null, null, null, "-ms-background-position-y"), "background-size" => array(null, "-webkit-background-size", null, null), "behavior" => array(null, null, null, "-ms-behavior"), "binding" => array("-moz-binding", null, null, null), "border-after" => array(null, "-webkit-border-after", null, null), "border-after-color" => array(null, "-webkit-border-after-color", null, null), "border-after-style" => array(null, "-webkit-border-after-style", null, null), "border-after-width" => array(null, "-webkit-border-after-width", null, null), "border-before" => array(null, "-webkit-border-before", null, null), "border-before-color" => array(null, "-webkit-border-before-color", null, null), "border-before-style" => array(null, "-webkit-border-before-style", null, null), "border-before-width" => array(null, "-webkit-border-before-width", null, null), "border-border-bottom-colors" => array("-moz-border-bottom-colors", null, null, null), "border-bottom-left-radius" => array("-moz-border-radius-bottomleft", "-webkit-border-bottom-left-radius", null, null), "border-bottom-right-radius" => array("-moz-border-radius-bottomright", "-webkit-border-bottom-right-radius", null, null), "border-end" => array("-moz-border-end", "-webkit-border-end", null, null), "border-end-color" => array("-moz-border-end-color", "-webkit-border-end-color", null, null), "border-end-style" => array("-moz-border-end-style", "-webkit-border-end-style", null, null), "border-end-width" => array("-moz-border-end-width", "-webkit-border-end-width", null, null), "border-fit" => array(null, "-webkit-border-fit", null, null), "border-horizontal-spacing" => array(null, "-webkit-border-horizontal-spacing", null, null), "border-image" => array("-moz-border-image", "-webkit-border-image", null, null), "border-left-colors" => array("-moz-border-left-colors", null, null, null), "border-radius" => array("-moz-border-radius", "-webkit-border-radius", null, null), "border-border-right-colors" => array("-moz-border-right-colors", null, null, null), "border-start" => array("-moz-border-start", "-webkit-border-start", null, null), "border-start-color" => array("-moz-border-start-color", "-webkit-border-start-color", null, null), "border-start-style" => array("-moz-border-start-style", "-webkit-border-start-style", null, null), "border-start-width" => array("-moz-border-start-width", "-webkit-border-start-width", null, null), "border-top-colors" => array("-moz-border-top-colors", null, null, null), "border-top-left-radius" => array("-moz-border-radius-topleft", "-webkit-border-top-left-radius", null, null), "border-top-right-radius" => array("-moz-border-radius-topright", "-webkit-border-top-right-radius", null, null), "border-vertical-spacing" => array(null, "-webkit-border-vertical-spacing", null, null), "box-align" => array("-moz-box-align", "-webkit-box-align", null, null), "box-direction" => array("-moz-box-direction", "-webkit-box-direction", null, null), "box-flex" => array("-moz-box-flex", "-webkit-box-flex", null, null), "box-flex-group" => array(null, "-webkit-box-flex-group", null, null), "box-flex-lines" => array(null, "-webkit-box-flex-lines", null, null), "box-ordinal-group" => array("-moz-box-ordinal-group", "-webkit-box-ordinal-group", null, null), "box-orient" => array("-moz-box-orient", "-webkit-box-orient", null, null), "box-pack" => array("-moz-box-pack", "-webkit-box-pack", null, null), "box-reflect" => array(null, "-webkit-box-reflect", null, null), "box-shadow" => array("-moz-box-shadow", "-webkit-box-shadow", null, null), "box-sizing" => array("-moz-box-sizing", null, null, null), "color-correction" => array(null, "-webkit-color-correction", null, null), "column-break-after" => array(null, "-webkit-column-break-after", null, null), "column-break-before" => array(null, "-webkit-column-break-before", null, null), "column-break-inside" => array(null, "-webkit-column-break-inside", null, null), "column-count" => array("-moz-column-count", "-webkit-column-count", null, null), "column-gap" => array("-moz-column-gap", "-webkit-column-gap", null, null), "column-rule" => array("-moz-column-rule", "-webkit-column-rule", null, null), "column-rule-color" => array("-moz-column-rule-color", "-webkit-column-rule-color", null, null), "column-rule-style" => array("-moz-column-rule-style", "-webkit-column-rule-style", null, null), "column-rule-width" => array("-moz-column-rule-width", "-webkit-column-rule-width", null, null), "column-span" => array(null, "-webkit-column-span", null, null), "column-width" => array("-moz-column-width", "-webkit-column-width", null, null), "columns" => array(null, "-webkit-columns", null, null), "filter" => array(__CLASS__, "filter"), "float-edge" => array("-moz-float-edge", null, null, null), "font-feature-settings" => array("-moz-font-feature-settings", null, null, null), "font-language-override" => array("-moz-font-language-override", null, null, null), "font-size-delta" => array(null, "-webkit-font-size-delta", null, null), "font-smoothing" => array(null, "-webkit-font-smoothing", null, null), "force-broken-image-icon" => array("-moz-force-broken-image-icon", null, null, null), "highlight" => array(null, "-webkit-highlight", null, null), "hyphenate-character" => array(null, "-webkit-hyphenate-character", null, null), "hyphenate-locale" => array(null, "-webkit-hyphenate-locale", null, null), "hyphens" => array(null, "-webkit-hyphens", null, null), "force-broken-image-icon" => array("-moz-image-region", null, null, null), "ime-mode" => array(null, null, null, "-ms-ime-mode"), "interpolation-mode" => array(null, null, null, "-ms-interpolation-mode"), "layout-flow" => array(null, null, null, "-ms-layout-flow"), "layout-grid" => array(null, null, null, "-ms-layout-grid"), "layout-grid-char" => array(null, null, null, "-ms-layout-grid-char"), "layout-grid-line" => array(null, null, null, "-ms-layout-grid-line"), "layout-grid-mode" => array(null, null, null, "-ms-layout-grid-mode"), "layout-grid-type" => array(null, null, null, "-ms-layout-grid-type"), "line-break" => array(null, "-webkit-line-break", null, "-ms-line-break"), "line-clamp" => array(null, "-webkit-line-clamp", null, null), "line-grid-mode" => array(null, null, null, "-ms-line-grid-mode"), "logical-height" => array(null, "-webkit-logical-height", null, null), "logical-width" => array(null, "-webkit-logical-width", null, null), "margin-after" => array(null, "-webkit-margin-after", null, null), "margin-after-collapse" => array(null, "-webkit-margin-after-collapse", null, null), "margin-before" => array(null, "-webkit-margin-before", null, null), "margin-before-collapse" => array(null, "-webkit-margin-before-collapse", null, null), "margin-bottom-collapse" => array(null, "-webkit-margin-bottom-collapse", null, null), "margin-collapse" => array(null, "-webkit-margin-collapse", null, null), "margin-end" => array("-moz-margin-end", "-webkit-margin-end", null, null), "margin-start" => array("-moz-margin-start", "-webkit-margin-start", null, null), "margin-top-collapse" => array(null, "-webkit-margin-top-collapse", null, null), "marquee " => array(null, "-webkit-marquee", null, null), "marquee-direction" => array(null, "-webkit-marquee-direction", null, null), "marquee-increment" => array(null, "-webkit-marquee-increment", null, null), "marquee-repetition" => array(null, "-webkit-marquee-repetition", null, null), "marquee-speed" => array(null, "-webkit-marquee-speed", null, null), "marquee-style" => array(null, "-webkit-marquee-style", null, null), "mask" => array(null, "-webkit-mask", null, null), "mask-attachment" => array(null, "-webkit-mask-attachment", null, null), "mask-box-image" => array(null, "-webkit-mask-box-image", null, null), "mask-clip" => array(null, "-webkit-mask-clip", null, null), "mask-composite" => array(null, "-webkit-mask-composite", null, null), "mask-image" => array(null, "-webkit-mask-image", null, null), "mask-origin" => array(null, "-webkit-mask-origin", null, null), "mask-position" => array(null, "-webkit-mask-position", null, null), "mask-position-x" => array(null, "-webkit-mask-position-x", null, null), "mask-position-y" => array(null, "-webkit-mask-position-y", null, null), "mask-repeat" => array(null, "-webkit-mask-repeat", null, null), "mask-repeat-x" => array(null, "-webkit-mask-repeat-x", null, null), "mask-repeat-y" => array(null, "-webkit-mask-repeat-y", null, null), "mask-size" => array(null, "-webkit-mask-size", null, null), "match-nearest-mail-blockquote-color" => array(null, "-webkit-match-nearest-mail-blockquote-color", null, null), "max-logical-height" => array(null, "-webkit-max-logical-height", null, null), "max-logical-width" => array(null, "-webkit-max-logical-width", null, null), "min-logical-height" => array(null, "-webkit-min-logical-height", null, null), "min-logical-width" => array(null, "-webkit-min-logical-width", null, null), "object-fit" => array(null, null, "-o-object-fit", null), "object-position" => array(null, null, "-o-object-position", null), "opacity" => array(__CLASS__, "opacity"), "outline-radius" => array("-moz-outline-radius", null, null, null), "outline-bottom-left-radius" => array("-moz-outline-radius-bottomleft", null, null, null), "outline-bottom-right-radius" => array("-moz-outline-radius-bottomright", null, null, null), "outline-top-left-radius" => array("-moz-outline-radius-topleft", null, null, null), "outline-top-right-radius" => array("-moz-outline-radius-topright", null, null, null), "padding-after" => array(null, "-webkit-padding-after", null, null), "padding-before" => array(null, "-webkit-padding-before", null, null), "padding-end" => array("-moz-padding-end", "-webkit-padding-end", null, null), "padding-start" => array("-moz-padding-start", "-webkit-padding-start", null, null), "perspective" => array(null, "-webkit-perspective", null, null), "perspective-origin" => array(null, "-webkit-perspective-origin", null, null), "perspective-origin-x" => array(null, "-webkit-perspective-origin-x", null, null), "perspective-origin-y" => array(null, "-webkit-perspective-origin-y", null, null), "rtl-ordering" => array(null, "-webkit-rtl-ordering", null, null), "scrollbar-3dlight-color" => array(null, null, null, "-ms-scrollbar-3dlight-color"), "scrollbar-arrow-color" => array(null, null, null, "-ms-scrollbar-arrow-color"), "scrollbar-base-color" => array(null, null, null, "-ms-scrollbar-base-color"), "scrollbar-darkshadow-color" => array(null, null, null, "-ms-scrollbar-darkshadow-color"), "scrollbar-face-color" => array(null, null, null, "-ms-scrollbar-face-color"), "scrollbar-highlight-color" => array(null, null, null, "-ms-scrollbar-highlight-color"), "scrollbar-shadow-color" => array(null, null, null, "-ms-scrollbar-shadow-color"), "scrollbar-track-color" => array(null, null, null, "-ms-scrollbar-track-color"), "stack-sizing" => array("-moz-stack-sizing", null, null, null), "svg-shadow" => array(null, "-webkit-svg-shadow", null, null), "tab-size" => array("-moz-tab-size", null, "-o-tab-size", null), "table-baseline" => array(null, null, "-o-table-baseline", null), "text-align-last" => array(null, null, null, "-ms-text-align-last"), "text-autospace" => array(null, null, null, "-ms-text-autospace"), "text-combine" => array(null, "-webkit-text-combine", null, null), "text-decorations-in-effect" => array(null, "-webkit-text-decorations-in-effect", null, null), "text-emphasis" => array(null, "-webkit-text-emphasis", null, null), "text-emphasis-color" => array(null, "-webkit-text-emphasis-color", null, null), "text-emphasis-position" => array(null, "-webkit-text-emphasis-position", null, null), "text-emphasis-style" => array(null, "-webkit-text-emphasis-style", null, null), "text-fill-color" => array(null, "-webkit-text-fill-color", null, null), "text-justify" => array(null, null, null, "-ms-text-justify"), "text-kashida-space" => array(null, null, null, "-ms-text-kashida-space"), "text-overflow" => array(null, null, "-o-text-overflow", "-ms-text-overflow"), "text-security" => array(null, "-webkit-text-security", null, null), "text-size-adjust" => array(null, "-webkit-text-size-adjust", null, "-ms-text-size-adjust"), "text-stroke" => array(null, "-webkit-text-stroke", null, null), "text-stroke-color" => array(null, "-webkit-text-stroke-color", null, null), "text-stroke-width" => array(null, "-webkit-text-stroke-width", null, null), "text-underline-position" => array(null, null, null, "-ms-text-underline-position"), "transform" => array("-moz-transform", "-webkit-transform", "-o-transform", null), "transform-origin" => array("-moz-transform-origin", "-webkit-transform-origin", "-o-transform-origin", null), "transform-origin-x" => array(null, "-webkit-transform-origin-x", null, null), "transform-origin-y" => array(null, "-webkit-transform-origin-y", null, null), "transform-origin-z" => array(null, "-webkit-transform-origin-z", null, null), "transform-style" => array(null, "-webkit-transform-style", null, null), "transition" => array("-moz-transition", "-webkit-transition", "-o-transition", null), "transition-delay" => array("-moz-transition-delay", "-webkit-transition-delay", "-o-transition-delay", null), "transition-duration" => array("-moz-transition-duration", "-webkit-transition-duration", "-o-transition-duration", null), "transition-property" => array("-moz-transition-property", "-webkit-transition-property", "-o-transition-property", null), "transition-timing-function" => array("-moz-transition-timing-function", "-webkit-transition-timing-function", "-o-transition-timing-function", null), "user-drag" => array(null, "-webkit-user-drag", null, null), "user-focus" => array("-moz-user-focus", null, null, null), "user-input" => array("-moz-user-input", null, null, null), "user-modify" => array("-moz-user-modify", "-webkit-user-modify", null, null), "user-select" => array("-moz-user-select", "-webkit-user-select", null, null), "white-space" => array(__CLASS__, "whiteSpace"), "window-shadow" => array("-moz-window-shadow", null, null, null), "word-break" => array(null, null, null, "-ms-word-break"), "word-wrap" => array(null, null, null, "-ms-word-wrap"), "writing-mode" => array(null, "-webkit-writing-mode", null, "-ms-writing-mode"), "zoom" => array(null, null, null, "-ms-zoom") ); public function apply(array &$tokens) { $r = 0; $transformations = &$this->transformations; for ($i = 0, $l = count($tokens); $i < $l; $i++) { if (get_class($tokens[$i]) === "CssRulesetDeclarationToken") { $tProperty = $tokens[$i]->Property; if (isset($transformations[$tProperty])) { $result = array(); if (is_callable($transformations[$tProperty])) { $result = call_user_func_array($transformations[$tProperty], array($tokens[$i])); if (!is_array($result) && is_object($result)) { $result = array($result); } } else { $tValue = $tokens[$i]->Value; $tMediaTypes = $tokens[$i]->MediaTypes; foreach ($transformations[$tProperty] as $property) { if ($property !== null) { $result[] = new CssRulesetDeclarationToken($property, $tValue, $tMediaTypes); } } } if (count($result) > 0) { array_splice($tokens, $i + 1, 0, $result); $i += count($result); $l += count($result); } } } } return $r; } private static function filter($token) { $r = array ( new CssRulesetDeclarationToken("-ms-filter", "\"" . $token->Value . "\"", $token->MediaTypes), ); return $r; } private static function opacity($token) { $ieValue = (int) ((float) $token->Value * 100); $r = array ( new CssRulesetDeclarationToken("-ms-filter", "\"alpha(opacity=" . $ieValue . ")\"", $token->MediaTypes), new CssRulesetDeclarationToken("filter", "alpha(opacity=" . $ieValue . ")", $token->MediaTypes), new CssRulesetDeclarationToken("zoom", "1", $token->MediaTypes) ); return $r; } private static function whiteSpace($token) { if (strtolower($token->Value) === "pre-wrap") { $r = array ( new CssRulesetDeclarationToken("white-space", "-moz-pre-wrap", $token->MediaTypes), new CssRulesetDeclarationToken("white-space", "-webkit-pre-wrap", $token->MediaTypes), new CssRulesetDeclarationToken("white-space", "-pre-wrap", $token->MediaTypes), new CssRulesetDeclarationToken("white-space", "-o-pre-wrap", $token->MediaTypes), new CssRulesetDeclarationToken("word-wrap", "break-word", $token->MediaTypes) ); return $r; } else { return array(); } } } class CssConvertLevel3AtKeyframesMinifierFilter extends aCssMinifierFilter { public function apply(array &$tokens) { $r = 0; $transformations = array("-moz-keyframes", "-webkit-keyframes"); for ($i = 0, $l = count($tokens); $i < $l; $i++) { if (get_class($tokens[$i]) === "CssAtKeyframesStartToken") { for ($ii = $i; $ii < $l; $ii++) { if (get_class($tokens[$ii]) === "CssAtKeyframesEndToken") { break; } } if (get_class($tokens[$ii]) === "CssAtKeyframesEndToken") { $add = array(); $source = array(); for ($iii = $i; $iii <= $ii; $iii++) { $source[] = clone($tokens[$iii]); } foreach ($transformations as $transformation) { $t = array(); foreach ($source as $token) { $t[] = clone($token); } $t[0]->AtRuleName = $transformation; $add = array_merge($add, $t); } if (isset($this->configuration["RemoveSource"]) && $this->configuration["RemoveSource"] === true) { array_splice($tokens, $i, $ii - $i + 1, $add); } else { array_splice($tokens, $ii + 1, 0, $add); } $l = count($tokens); $i = $ii + count($add); $r += count($add); } } } return $r; } } class CssConvertHslColorsMinifierPlugin extends aCssMinifierPlugin { private $reMatch = "/^hsl\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*%\s*,\s*([0-9]+)\s*%\s*\)/iS"; public function apply(aCssToken &$token) { if (stripos($token->Value, "hsl") !== false && preg_match($this->reMatch, $token->Value, $m)) { $token->Value = str_replace($m[0], $this->hsl2hex($m[1], $m[2], $m[3]), $token->Value); } return false; } public function getTriggerTokens() { return array ( "CssAtFontFaceDeclarationToken", "CssAtPageDeclarationToken", "CssRulesetDeclarationToken" ); } private function hsl2hex($hue, $saturation, $lightness) { $hue = $hue / 360; $saturation = $saturation / 100; $lightness = $lightness / 100; if ($saturation == 0) { $red = $lightness * 255; $green = $lightness * 255; $blue = $lightness * 255; } else { if ($lightness < 0.5 ) { $v2 = $lightness * (1 + $saturation); } else { $v2 = ($lightness + $saturation) - ($saturation * $lightness); } $v1 = 2 * $lightness - $v2; $red = 255 * self::hue2rgb($v1, $v2, $hue + (1 / 3)); $green = 255 * self::hue2rgb($v1, $v2, $hue); $blue = 255 * self::hue2rgb($v1, $v2, $hue - (1 / 3)); } return "#" . str_pad(dechex(round($red)), 2, "0", STR_PAD_LEFT) . str_pad(dechex(round($green)), 2, "0", STR_PAD_LEFT) . str_pad(dechex(round($blue)), 2, "0", STR_PAD_LEFT); } private function hue2rgb($v1, $v2, $hue) { if ($hue < 0) { $hue += 1; } if ($hue > 1) { $hue -= 1; } if ((6 * $hue) < 1) { return ($v1 + ($v2 - $v1) * 6 * $hue); } if ((2 * $hue) < 1) { return ($v2); } if ((3 * $hue) < 2) { return ($v1 + ($v2 - $v1) * (( 2 / 3) - $hue) * 6); } return $v1; } } class CssConvertFontWeightMinifierPlugin extends aCssMinifierPlugin { private $include = array ( "font", "font-weight" ); private $reMatch = null; private $reReplace = "\"\${1}\" . \$this->transformation[\"\${2}\"] . \"\${3}\""; private $transformation = array ( "normal" => "400", "bold" => "700" ); public function __construct(CssMinifier $minifier) { $this->reMatch = "/(^|\s)+(" . implode("|", array_keys($this->transformation)). ")(\s|$)+/eiS"; parent::__construct($minifier); } public function apply(aCssToken &$token) { if (in_array($token->Property, $this->include) && preg_match($this->reMatch, $token->Value, $m)) { $token->Value = preg_replace($this->reMatch, $this->reReplace, $token->Value); } return false; } public function getTriggerTokens() { return array ( "CssAtFontFaceDeclarationToken", "CssAtPageDeclarationToken", "CssRulesetDeclarationToken" ); } } class CssCompressUnitValuesMinifierPlugin extends aCssMinifierPlugin { private $re = array ( "/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)/iS" => "\${1}.\${2}\${4}", "/(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)/iS" => "\${1}0", "/(^0\s0\s0\s0)|(^0\s0\s0$)|(^0\s0$)/iS" => "0" ); private $reMatch = "/(^| |-)0\.([0-9]+?)(0+)?(%|em|ex|px|in|cm|mm|pt|pc)|(^| )-?(\.?)0(%|em|ex|px|in|cm|mm|pt|pc)|(^0\s0\s0\s0$)|(^0\s0\s0$)|(^0\s0$)/iS"; public function apply(aCssToken &$token) { if (preg_match($this->reMatch, $token->Value)) { foreach ($this->re as $reMatch => $reReplace) { $token->Value = preg_replace($reMatch, $reReplace, $token->Value); } } return false; } public function getTriggerTokens() { return array ( "CssAtFontFaceDeclarationToken", "CssAtPageDeclarationToken", "CssRulesetDeclarationToken" ); } } class CssCompressExpressionValuesMinifierPlugin extends aCssMinifierPlugin { public function apply(aCssToken &$token) { if (class_exists("JSMin") && stripos($token->Value, "expression(") !== false) { $value = $token->Value; $value = substr($token->Value, stripos($token->Value, "expression(") + 10); $value = trim(JSMin::minify($value)); $token->Value = "expression(" . $value . ")"; } return false; } public function getTriggerTokens() { return array ( "CssAtFontFaceDeclarationToken", "CssAtPageDeclarationToken", "CssRulesetDeclarationToken" ); } } class CssCompressColorValuesMinifierPlugin extends aCssMinifierPlugin { private $reMatch = "/\#([0-9a-f]{6})/iS"; public function apply(aCssToken &$token) { if (strpos($token->Value, "#") !== false && preg_match($this->reMatch, $token->Value, $m)) { $value = strtolower($m[1]); if ($value[0] == $value[1] && $value[2] == $value[3] && $value[4] == $value[5]) { $token->Value = str_replace($m[0], "#" . $value[0] . $value[2] . $value[4], $token->Value); } } return false; } public function getTriggerTokens() { return array ( "CssAtFontFaceDeclarationToken", "CssAtPageDeclarationToken", "CssRulesetDeclarationToken" ); } } class CssCommentToken extends aCssToken { public $Comment = ""; public function __construct($comment) { $this->Comment = $comment; } public function __toString() { return $this->Comment; } } class CssCommentParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array("*", "/"); } public function getTriggerStates() { return false; } private $restoreBuffer = ""; public function parse($index, $char, $previousChar, $state) { if ($char === "*" && $previousChar === "/" && $state !== "T_COMMENT") { $this->parser->pushState("T_COMMENT"); $this->parser->setExclusive(__CLASS__); $this->restoreBuffer = substr($this->parser->getAndClearBuffer(), 0, -2); } elseif ($char === "/" && $previousChar === "*" && $state === "T_COMMENT") { $this->parser->popState(); $this->parser->unsetExclusive(); $this->parser->appendToken(new CssCommentToken("/*" . $this->parser->getAndClearBuffer())); $this->parser->setBuffer($this->restoreBuffer); } else { return false; } return true; } } class CssAtVariablesStartToken extends aCssAtBlockStartToken { public $MediaTypes = array(); public function __construct($mediaTypes = null) { $this->MediaTypes = $mediaTypes ? $mediaTypes : array("all"); } public function __toString() { return ""; } } class CssAtVariablesParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array("@", "{", "}", ":", ";"); } public function getTriggerStates() { return array("T_DOCUMENT", "T_AT_VARIABLES::PREPARE", "T_AT_VARIABLES", "T_AT_VARIABLES_DECLARATION"); } public function parse($index, $char, $previousChar, $state) { if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@variables") { $this->parser->pushState("T_AT_VARIABLES::PREPARE"); $this->parser->clearBuffer(); return $index + 10; } elseif ($char === "{" && $state === "T_AT_VARIABLES::PREPARE") { $this->parser->setState("T_AT_VARIABLES"); $mediaTypes = array_filter(array_map("trim", explode(",", $this->parser->getAndClearBuffer("{")))); $this->parser->appendToken(new CssAtVariablesStartToken($mediaTypes)); } if ($char === ":" && $state === "T_AT_VARIABLES") { $this->buffer = $this->parser->getAndClearBuffer(":"); $this->parser->pushState("T_AT_VARIABLES_DECLARATION"); } elseif ($char === ":" && $state === "T_AT_VARIABLES_DECLARATION") { if ($this->buffer === "filter") { return false; } CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @variables declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); } elseif (($char === ";" || $char === "}") && $state === "T_AT_VARIABLES_DECLARATION") { $value = $this->parser->getAndClearBuffer(";}"); if (strtolower(substr($value, -10, 10)) === "!important") { $value = trim(substr($value, 0, -10)); $isImportant = true; } else { $isImportant = false; } $this->parser->popState(); $this->parser->appendToken(new CssAtVariablesDeclarationToken($this->buffer, $value, $isImportant)); $this->buffer = ""; } elseif ($char === "}" && $state === "T_AT_VARIABLES") { $this->parser->popState(); $this->parser->clearBuffer(); $this->parser->appendToken(new CssAtVariablesEndToken()); } else { return false; } return true; } } class CssAtVariablesEndToken extends aCssAtBlockEndToken { public function __toString() { return ""; } } class CssAtVariablesDeclarationToken extends aCssDeclarationToken { public function __toString() { return ""; } } class CssAtPageStartToken extends aCssAtBlockStartToken { public $Selector = ""; public function __construct($selector = "") { $this->Selector = $selector; } public function __toString() { return "@page" . ($this->Selector ? " " . $this->Selector : "") . "{"; } } class CssAtPageParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array("@", "{", "}", ":", ";"); } public function getTriggerStates() { return array("T_DOCUMENT", "T_AT_PAGE::SELECTOR", "T_AT_PAGE", "T_AT_PAGE_DECLARATION"); } public function parse($index, $char, $previousChar, $state) { if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 5)) === "@page") { $this->parser->pushState("T_AT_PAGE::SELECTOR"); $this->parser->clearBuffer(); return $index + 5; } elseif ($char === "{" && $state === "T_AT_PAGE::SELECTOR") { $selector = $this->parser->getAndClearBuffer("{"); $this->parser->setState("T_AT_PAGE"); $this->parser->clearBuffer(); $this->parser->appendToken(new CssAtPageStartToken($selector)); } elseif ($char === ":" && $state === "T_AT_PAGE") { $this->parser->pushState("T_AT_PAGE_DECLARATION"); $this->buffer = $this->parser->getAndClearBuffer(":", true); } elseif ($char === ":" && $state === "T_AT_PAGE_DECLARATION") { if ($this->buffer === "filter") { return false; } CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @page declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); } elseif (($char === ";" || $char === "}") && $state == "T_AT_PAGE_DECLARATION") { $value = $this->parser->getAndClearBuffer(";}"); if (strtolower(substr($value, -10, 10)) == "!important") { $value = trim(substr($value, 0, -10)); $isImportant = true; } else { $isImportant = false; } $this->parser->popState(); $this->parser->appendToken(new CssAtPageDeclarationToken($this->buffer, $value, $isImportant)); if ($char === "}") { $this->parser->popState(); $this->parser->appendToken(new CssAtPageEndToken()); } $this->buffer = ""; } elseif ($char === "}" && $state === "T_AT_PAGE") { $this->parser->popState(); $this->parser->clearBuffer(); $this->parser->appendToken(new CssAtPageEndToken()); } else { return false; } return true; } } class CssAtPageEndToken extends aCssAtBlockEndToken { } class CssAtPageDeclarationToken extends aCssDeclarationToken { } class CssAtMediaStartToken extends aCssAtBlockStartToken { public function __construct(array $mediaTypes = array()) { $this->MediaTypes = $mediaTypes; } public function __toString() { return "@media " . implode(",", $this->MediaTypes) . "{"; } } class CssAtMediaParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array("@", "{", "}"); } public function getTriggerStates() { return array("T_DOCUMENT", "T_AT_MEDIA::PREPARE", "T_AT_MEDIA"); } public function parse($index, $char, $previousChar, $state) { if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 6)) === "@media") { $this->parser->pushState("T_AT_MEDIA::PREPARE"); $this->parser->clearBuffer(); return $index + 6; } elseif ($char === "{" && $state === "T_AT_MEDIA::PREPARE") { $mediaTypes = array_filter(array_map("trim", explode(",", $this->parser->getAndClearBuffer("{")))); $this->parser->setMediaTypes($mediaTypes); $this->parser->setState("T_AT_MEDIA"); $this->parser->appendToken(new CssAtMediaStartToken($mediaTypes)); } elseif ($char === "}" && $state === "T_AT_MEDIA") { $this->parser->appendToken(new CssAtMediaEndToken()); $this->parser->clearBuffer(); $this->parser->unsetMediaTypes(); $this->parser->popState(); } else { return false; } return true; } } class CssAtMediaEndToken extends aCssAtBlockEndToken { } class CssAtKeyframesStartToken extends aCssAtBlockStartToken { public $AtRuleName = "keyframes"; public $Name = ""; public function __construct($name, $atRuleName = null) { $this->Name = $name; if (!is_null($atRuleName)) { $this->AtRuleName = $atRuleName; } } public function __toString() { return "@" . $this->AtRuleName . " \"" . $this->Name . "\"{"; } } class CssAtKeyframesRulesetStartToken extends aCssRulesetStartToken { public $Selectors = array(); public function __construct(array $selectors = array()) { $this->Selectors = $selectors; } public function __toString() { return implode(",", $this->Selectors) . "{"; } } class CssAtKeyframesRulesetEndToken extends aCssRulesetEndToken { } class CssAtKeyframesRulesetDeclarationToken extends aCssDeclarationToken { } class CssAtKeyframesParserPlugin extends aCssParserPlugin { private $atRuleName = ""; private $selectors = array(); public function getTriggerChars() { return array("@", "{", "}", ":", ",", ";"); } public function getTriggerStates() { return array("T_DOCUMENT", "T_AT_KEYFRAMES::NAME", "T_AT_KEYFRAMES", "T_AT_KEYFRAMES_RULESETS", "T_AT_KEYFRAMES_RULESET", "T_AT_KEYFRAMES_RULESET_DECLARATION"); } public function parse($index, $char, $previousChar, $state) { if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@keyframes") { $this->atRuleName = "keyframes"; $this->parser->pushState("T_AT_KEYFRAMES::NAME"); $this->parser->clearBuffer(); return $index + 10; } elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 15)) === "@-moz-keyframes") { $this->atRuleName = "-moz-keyframes"; $this->parser->pushState("T_AT_KEYFRAMES::NAME"); $this->parser->clearBuffer(); return $index + 15; } elseif ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 18)) === "@-webkit-keyframes") { $this->atRuleName = "-webkit-keyframes"; $this->parser->pushState("T_AT_KEYFRAMES::NAME"); $this->parser->clearBuffer(); return $index + 18; } elseif ($char === "{" && $state === "T_AT_KEYFRAMES::NAME") { $name = $this->parser->getAndClearBuffer("{\"'"); $this->parser->setState("T_AT_KEYFRAMES_RULESETS"); $this->parser->clearBuffer(); $this->parser->appendToken(new CssAtKeyframesStartToken($name, $this->atRuleName)); } if ($char === "," && $state === "T_AT_KEYFRAMES_RULESETS") { $this->selectors[] = $this->parser->getAndClearBuffer(",{"); } elseif ($char === "{" && $state === "T_AT_KEYFRAMES_RULESETS") { if ($this->parser->getBuffer() !== "") { $this->selectors[] = $this->parser->getAndClearBuffer(",{"); $this->parser->pushState("T_AT_KEYFRAMES_RULESET"); $this->parser->appendToken(new CssAtKeyframesRulesetStartToken($this->selectors)); $this->selectors = array(); } } elseif ($char === ":" && $state === "T_AT_KEYFRAMES_RULESET") { $this->parser->pushState("T_AT_KEYFRAMES_RULESET_DECLARATION"); $this->buffer = $this->parser->getAndClearBuffer(":;", true); } elseif ($char === ":" && $state === "T_AT_KEYFRAMES_RULESET_DECLARATION") { if ($this->buffer === "filter") { return false; } CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @keyframes ruleset declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); } elseif (($char === ";" || $char === "}") && $state === "T_AT_KEYFRAMES_RULESET_DECLARATION") { $value = $this->parser->getAndClearBuffer(";}"); if (strtolower(substr($value, -10, 10)) === "!important") { $value = trim(substr($value, 0, -10)); $isImportant = true; } else { $isImportant = false; } $this->parser->popState(); $this->parser->appendToken(new CssAtKeyframesRulesetDeclarationToken($this->buffer, $value, $isImportant)); if ($char === "}") { $this->parser->appendToken(new CssAtKeyframesRulesetEndToken()); $this->parser->popState(); } $this->buffer = ""; } elseif ($char === "}" && $state === "T_AT_KEYFRAMES_RULESET") { $this->parser->clearBuffer(); $this->parser->popState(); $this->parser->appendToken(new CssAtKeyframesRulesetEndToken()); } elseif ($char === "}" && $state === "T_AT_KEYFRAMES_RULESETS") { $this->parser->clearBuffer(); $this->parser->popState(); $this->parser->appendToken(new CssAtKeyframesEndToken()); } else { return false; } return true; } } class CssAtKeyframesEndToken extends aCssAtBlockEndToken { } class CssAtImportToken extends aCssToken { public $Import = ""; public $MediaTypes = array(); public function __construct($import, $mediaTypes) { $this->Import = $import; $this->MediaTypes = $mediaTypes ? $mediaTypes : array(); } public function __toString() { return "@import \"" . $this->Import . "\"" . (count($this->MediaTypes) > 0 ? " " . implode(",", $this->MediaTypes) : ""). ";"; } } class CssAtImportParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array("@", ";", ",", "\n"); } public function getTriggerStates() { return array("T_DOCUMENT", "T_AT_IMPORT"); } public function parse($index, $char, $previousChar, $state) { if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 7)) === "@import") { $this->parser->pushState("T_AT_IMPORT"); $this->parser->clearBuffer(); return $index + 7; } elseif (($char === ";" || $char === "\n") && $state === "T_AT_IMPORT") { $this->buffer = $this->parser->getAndClearBuffer(";"); $pos = false; foreach (array(")", "\"", "'") as $needle) { if (($pos = strrpos($this->buffer, $needle)) !== false) { break; } } $import = substr($this->buffer, 0, $pos + 1); if (stripos($import, "url(") === 0) { $import = substr($import, 4, -1); } $import = trim($import, " \t\n\r\0\x0B'\""); $mediaTypes = array_filter(array_map("trim", explode(",", trim(substr($this->buffer, $pos + 1), " \t\n\r\0\x0B{")))); if ($pos) { $this->parser->appendToken(new CssAtImportToken($import, $mediaTypes)); } else { CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Invalid @import at-rule syntax", $this->parser->buffer)); } $this->parser->popState(); } else { return false; } return true; } } class CssAtFontFaceStartToken extends aCssAtBlockStartToken { public function __toString() { return "@font-face{"; } } class CssAtFontFaceParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array("@", "{", "}", ":", ";"); } public function getTriggerStates() { return array("T_DOCUMENT", "T_AT_FONT_FACE::PREPARE", "T_AT_FONT_FACE", "T_AT_FONT_FACE_DECLARATION"); } public function parse($index, $char, $previousChar, $state) { if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 10)) === "@font-face") { $this->parser->pushState("T_AT_FONT_FACE::PREPARE"); $this->parser->clearBuffer(); return $index + 10; } elseif ($char === "{" && $state === "T_AT_FONT_FACE::PREPARE") { $this->parser->setState("T_AT_FONT_FACE"); $this->parser->clearBuffer(); $this->parser->appendToken(new CssAtFontFaceStartToken()); } elseif ($char === ":" && $state === "T_AT_FONT_FACE") { $this->parser->pushState("T_AT_FONT_FACE_DECLARATION"); $this->buffer = $this->parser->getAndClearBuffer(":", true); } elseif ($char === ":" && $state === "T_AT_FONT_FACE_DECLARATION") { if ($this->buffer === "filter") { return false; } CssMin::triggerError(new CssError(__FILE__, __LINE__, __METHOD__ . ": Unterminated @font-face declaration", $this->buffer . ":" . $this->parser->getBuffer() . "_")); } elseif (($char === ";" || $char === "}") && $state === "T_AT_FONT_FACE_DECLARATION") { $value = $this->parser->getAndClearBuffer(";}"); if (strtolower(substr($value, -10, 10)) === "!important") { $value = trim(substr($value, 0, -10)); $isImportant = true; } else { $isImportant = false; } $this->parser->popState(); $this->parser->appendToken(new CssAtFontFaceDeclarationToken($this->buffer, $value, $isImportant)); $this->buffer = ""; if ($char === "}") { $this->parser->appendToken(new CssAtFontFaceEndToken()); $this->parser->popState(); } } elseif ($char === "}" && $state === "T_AT_FONT_FACE") { $this->parser->appendToken(new CssAtFontFaceEndToken()); $this->parser->clearBuffer(); $this->parser->popState(); } else { return false; } return true; } } class CssAtFontFaceEndToken extends aCssAtBlockEndToken { } class CssAtFontFaceDeclarationToken extends aCssDeclarationToken { } class CssAtCharsetToken extends aCssToken { public $Charset = ""; public function __construct($charset) { $this->Charset = $charset; } public function __toString() { return "@charset " . $this->Charset . ";"; } } class CssAtCharsetParserPlugin extends aCssParserPlugin { public function getTriggerChars() { return array("@", ";", "\n"); } public function getTriggerStates() { return array("T_DOCUMENT", "T_AT_CHARSET"); } public function parse($index, $char, $previousChar, $state) { if ($char === "@" && $state === "T_DOCUMENT" && strtolower(substr($this->parser->getSource(), $index, 8)) === "@charset") { $this->parser->pushState("T_AT_CHARSET"); $this->parser->clearBuffer(); return $index + 8; } elseif (($char === ";" || $char === "\n") && $state === "T_AT_CHARSET") { $charset = $this->parser->getAndClearBuffer(";"); $this->parser->popState(); $this->parser->appendToken(new CssAtCharsetToken($charset)); } else { return false; } return true; } }
  1391.  
  1392.  // Simple minification WITHOUT filter or plugin configuration
  1393. //$result = CssMin::minify(file_get_contents("path/to/source.css"));
  1394.  
  1395. // Minification WITH filter or plugin configuration
  1396. //$filters = array(...);
  1397. //$plugins = array(...);
  1398. // Minify via CssMin adapter function
  1399. //$result = CssMin::minify(file_get_contents("path/to/source.css"), $filters, $plugins);
  1400. // Minify via CssMinifier class
  1401. //$minifier = new CssMinifier(file_get_contents("path/to/source.css"), $filters, $plugins);
  1402. //$result = $minifier->getMinified();
  1403.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement