diff --git a/libraries/Gelato/Gelato.php b/libraries/Gelato/Gelato.php index 35eefb3..fc7d5d2 100644 --- a/libraries/Gelato/Gelato.php +++ b/libraries/Gelato/Gelato.php @@ -88,7 +88,9 @@ ClassLoader::mapClasses(array( 'Html' => __DIR__.'/Html/Html.php', 'Image' => __DIR__.'/Image/Image.php', 'Inflector' => __DIR__.'/Inflector/Inflector.php', - 'Minify' => __DIR__.'/Minify/Minify.php', + 'MinifyCSS' => __DIR__.'/Minify/MinifyCSS.php', + 'MinifyHTML' => __DIR__.'/Minify/MinifyHTML.php', + 'MinifyJS' => __DIR__.'/Minify/MinifyJS.php', 'Notification' => __DIR__.'/Notification/Notification.php', 'Number' => __DIR__.'/Number/Number.php', 'Registry' => __DIR__.'/Registry/Registry.php', diff --git a/libraries/Gelato/Minify/Minify.php b/libraries/Gelato/Minify/Minify.php deleted file mode 100644 index 62c4342..0000000 --- a/libraries/Gelato/Minify/Minify.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @copyright 2012-2013 Romanenko Sergey / Awilum - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -class Minify -{ - /** - * Protected constructor since this is a static class. - * - * @access protected - */ - protected function __construct() - { - // Nothing here - } - - /** - * Minify html - * - * - * echo Minify::html($buffer); - * - * - * @param string $buffer html - * @return string - */ - public static function html($buffer) - { - return preg_replace('/^\\s+|\\s+$/m', '', $buffer); - } - - /** - * Minify css - * - * - * echo Minify::css($buffer); - * - * - * @param string $buffer css - * @return string - */ - public static function css($buffer) - { - // Remove comments - $buffer = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $buffer); - - // Remove tabs, spaces, newlines, etc. - $buffer = str_replace(array("\r\n", "\r", "\n", "\t", ' ', ' ', ' '), '', $buffer); - - // Preserve empty comment after '>' http://www.webdevout.net/css-hacks#in_css-selectors - $buffer = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $buffer); - - // Preserve empty comment between property and value - // http://css-discuss.incutio.com/?page=BoxModelHack - $buffer = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $buffer); - $buffer = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $buffer); - - // Remove ws around { } and last semicolon in declaration block - $buffer = preg_replace('/\\s*{\\s*/', '{', $buffer); - $buffer = preg_replace('/;?\\s*}\\s*/', '}', $buffer); - - // Remove ws surrounding semicolons - $buffer = preg_replace('/\\s*;\\s*/', ';', $buffer); - - // Remove ws around urls - $buffer = preg_replace('/url\\(\\s*([^\\)]+?)\\s*\\)/x', 'url($1)', $buffer); - - // Remove ws between rules and colons - $buffer = preg_replace('/\\s*([{;])\\s*([\\*_]?[\\w\\-]+)\\s*:\\s*(\\b|[#\'"])/x', '$1$2:$3', $buffer); - - // Minimize hex colors - $buffer = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i', '$1#$2$3$4$5', $buffer); - - // Replace any ws involving newlines with a single newline - $buffer = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $buffer); - - return $buffer; - } - -} diff --git a/libraries/Gelato/Minify/MinifyCSS.php b/libraries/Gelato/Minify/MinifyCSS.php new file mode 100644 index 0000000..0f5ee4f --- /dev/null +++ b/libraries/Gelato/Minify/MinifyCSS.php @@ -0,0 +1,255 @@ + + * @author Stephen Clay + * @copyright 2012-2013 Romanenko Sergey / Awilum + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class MinifyCSS +{ + + /** + * @var array + */ + protected $_options = null; + + /** + * Are we "in" a hack? I.e. are some browsers targetted until the next comment? + * + * @var bool + */ + protected $_inHack = false; + + /** + * Constructor + * + * @param array $options (currently ignored) + */ + private function __construct($options) + { + $this->_options = $options; + } + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options (currently ignored) + * + * @return string + */ + public static function process($css, $options = array()) + { + $obj = new MinifyCSS($options); + + return $obj->_process($css); + } + + /** + * Minify a CSS string + * + * @param string $css + * + * @return string + */ + protected function _process($css) + { + $css = str_replace("\r\n", "\n", $css); + + // preserve empty comment after '>' + // http://www.webdevout.net/css-hacks#in_css-selectors + $css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css); + + // preserve empty comment between property and value + // http://css-discuss.incutio.com/?page=BoxModelHack + $css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css); + $css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css); + + // apply callback to all valid comments (and strip out surrounding ws + $css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@' + ,array($this, '_commentCB'), $css); + + // remove ws around { } and last semicolon in declaration block + $css = preg_replace('/\\s*{\\s*/', '{', $css); + $css = preg_replace('/;?\\s*}\\s*/', '}', $css); + + // remove ws surrounding semicolons + $css = preg_replace('/\\s*;\\s*/', ';', $css); + + // remove ws around urls + $css = preg_replace('/ + url\\( # url( + \\s* + ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis) + \\s* + \\) # ) + /x', 'url($1)', $css); + + // remove ws between rules and colons + $css = preg_replace('/ + \\s* + ([{;]) # 1 = beginning of block or rule separator + \\s* + ([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter) + \\s* + : + \\s* + (\\b|[#\'"-]) # 3 = first character of a value + /x', '$1$2:$3', $css); + + // remove ws in selectors + $css = preg_replace_callback('/ + (?: # non-capture + \\s* + [^~>+,\\s]+ # selector part + \\s* + [,>+~] # combinators + )+ + \\s* + [^~>+,\\s]+ # selector part + { # open declaration block + /x' + ,array($this, '_selectorsCB'), $css); + + // minimize hex colors + $css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i' + , '$1#$2$3$4$5', $css); + + // remove spaces between font families + $css = preg_replace_callback('/font-family:([^;}]+)([;}])/' + ,array($this, '_fontFamilyCB'), $css); + + $css = preg_replace('/@import\\s+url/', '@import url', $css); + + // replace any ws involving newlines with a single newline + $css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css); + + // separate common descendent selectors w/ newlines (to limit line lengths) + $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css); + + // Use newline after 1st numeric value (to limit line lengths). + $css = preg_replace('/ + ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value + \\s+ + /x' + ,"$1\n", $css); + + // prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/ + $css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css); + + return trim($css); + } + + /** + * Replace what looks like a set of selectors + * + * @param array $m regex matches + * + * @return string + */ + protected function _selectorsCB($m) + { + // remove ws around the combinators + return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]); + } + + /** + * Process a comment and return a replacement + * + * @param array $m regex matches + * + * @return string + */ + protected function _commentCB($m) + { + $hasSurroundingWs = (trim($m[0]) !== $m[1]); + $m = $m[1]; + // $m is the comment content w/o the surrounding tokens, + // but the return value will replace the entire comment. + if ($m === 'keep') { + return '/**/'; + } + if ($m === '" "') { + // component of http://tantek.com/CSS/Examples/midpass.html + return '/*" "*/'; + } + if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) { + // component of http://tantek.com/CSS/Examples/midpass.html + return '/*";}}/* */'; + } + if ($this->_inHack) { + // inversion: feeding only to one browser + if (preg_match('@ + ^/ # comment started like /*/ + \\s* + (\\S[\\s\\S]+?) # has at least some non-ws content + \\s* + /\\* # ends like /*/ or /**/ + @x', $m, $n)) { + // end hack mode after this comment, but preserve the hack and comment content + $this->_inHack = false; + + return "/*/{$n[1]}/**/"; + } + } + if (substr($m, -1) === '\\') { // comment ends like \*/ + // begin hack mode and preserve hack + $this->_inHack = true; + + return '/*\\*/'; + } + if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */ + // begin hack mode and preserve hack + $this->_inHack = true; + + return '/*/*/'; + } + if ($this->_inHack) { + // a regular comment ends hack mode but should be preserved + $this->_inHack = false; + + return '/**/'; + } + // Issue 107: if there's any surrounding whitespace, it may be important, so + // replace the comment with a single space + return $hasSurroundingWs // remove all other comments + ? ' ' + : ''; + } + + /** + * Process a font-family listing and return a replacement + * + * @param array $m regex matches + * + * @return string + */ + protected function _fontFamilyCB($m) + { + // Issue 210: must not eliminate WS between words in unquoted families + $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + $out = 'font-family:'; + while (null !== ($piece = array_shift($pieces))) { + if ($piece[0] !== '"' && $piece[0] !== "'") { + $piece = preg_replace('/\\s+/', ' ', $piece); + $piece = preg_replace('/\\s?,\\s?/', ',', $piece); + } + $out .= $piece; + } + + return $out . $m[2]; + } + +} diff --git a/libraries/Gelato/Minify/MinifyHTML.php b/libraries/Gelato/Minify/MinifyHTML.php new file mode 100644 index 0000000..ba0a6af --- /dev/null +++ b/libraries/Gelato/Minify/MinifyHTML.php @@ -0,0 +1,47 @@ + + * @copyright 2012-2013 Romanenko Sergey / Awilum + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class MinifyHTML +{ + /** + * Minify html + * + * + * echo MinifyHTML::process($html); + * + * + * @param string $buffer html + * @return string + */ + public static function process($html) + { + // Remove HTML comments (not containing IE conditional comments). + $html = preg_replace_callback('//', 'self::_comments', $html); + + // Trim each line. + $html = preg_replace('/^\\s+|\\s+$/m', '', $html); + + // Return HTML + return $html; + } + + protected static function _comments($m) + { + return (0 === strpos($m[1], '[') || false !== strpos($m[1], ' + * @copyright 2012-2013 Romanenko Sergey / Awilum + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class MinifyJS +{ + /** + * Minify js + * + * + * echo MinifyJS::process($js); + * + * + * @param string $buffer html + * @return string + */ + public static function process($js) + { + // newlines > linefeed + $js = str_replace(array("\r\n", "\r", "\n"), "\n", $js); + + // empty lines > collapse + $js = preg_replace('/^[ \t]*|[ \t]*$/m', '', $js); + $js = preg_replace('/\n+/m', "\n", $js); + $js = trim($js); + + // redundant whitespace > remove + $js = preg_replace('/(?<=[{}\[\]\(\)=><&\|;:,\?!\+-])[ \t]*|[ \t]*(?=[{}\[\]\(\)=><&\|;:,\?!\+-])/i', '', $js); + $js = preg_replace('/[ \t]+/', ' ', $js); + + // redundant semicolons (followed by another semicolon or closing curly bracket) > remove + $js = preg_replace('/;\s*(?=[;}])/s', '', $js); + + // Return JS + return $js; + } +}