Blame | Letzte Änderung | Log anzeigen | RSS feed
<?/* Taken from minify by Ryan Grove and Steve Clay and distributedunder the following license:Copyright (c) 2008 Ryan Grove <ryan@wonko.com>Copyright (c) 2008 Steve Clay <steve@mrclay.org>All rights reserved.Redistribution and use in source and binary forms, with or withoutmodification, are permitted provided that the following conditions are met:* Redistributions of source code must retain the above copyright notice,this list of conditions and the following disclaimer.* Redistributions in binary form must reproduce the above copyright notice,this list of conditions and the following disclaimer in the documentationand/or other materials provided with the distribution.* Neither the name of this project nor the names of its contributors may beused to endorse or promote products derived from this software withoutspecific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ANDANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIEDWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE AREDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FORANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ONANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE*/class Minify_CSS {public static function minify($css, $options = array()){if (isset($options['preserveComments'])&& !$options['preserveComments']) {$css = Minify_CSS_Compressor::process($css, $options);} else {$css = Minify_CommentPreserver::process($css,array('Minify_CSS_Compressor', 'process'),array($options));}if (! isset($options['currentDir']) && ! isset($options['prependRelativePath'])) {return $css;}if (isset($options['currentDir'])) {return Minify_CSS_UriRewriter::rewrite($css,$options['currentDir'],isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT'],isset($options['symlinks']) ? $options['symlinks'] : array());} else {return Minify_CSS_UriRewriter::prepend($css,$options['prependRelativePath']);}}}class Minify_CSS_UriRewriter {/*** Defines which class to call as part of callbacks, change this* if you extend Minify_CSS_UriRewriter* @var string*/protected static $className = 'Minify_CSS_UriRewriter';/*** rewrite() and rewriteRelative() append debugging information here* @var string*/public static $debugText = '';/*** Rewrite file relative URIs as root relative in CSS files** @param string $css** @param string $currentDir The directory of the current CSS file.** @param string $docRoot The document root of the web site in which* the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).** @param array $symlinks (default = array()) If the CSS file is stored in* a symlink-ed directory, provide an array of link paths to* target paths, where the link paths are within the document root. Because* paths need to be normalized for this to work, use "//" to substitute* the doc root in the link paths (the array keys). E.g.:* <code>* array('//symlink' => '/real/target/path') // unix* array('//static' => 'D:\\staticStorage') // Windows* </code>** @return string*/public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array()){self::$_docRoot = self::_realpath($docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']);self::$_currentDir = self::_realpath($currentDir);self::$_symlinks = array();// normalize symlinksforeach ($symlinks as $link => $target) {$link = ($link === '//')? self::$_docRoot: str_replace('//', self::$_docRoot . '/', $link);$link = strtr($link, '/', DIRECTORY_SEPARATOR);self::$_symlinks[$link] = self::_realpath($target);}self::$debugText .= "docRoot : " . self::$_docRoot . "\n". "currentDir : " . self::$_currentDir . "\n";if (self::$_symlinks) {self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";}self::$debugText .= "\n";$css = self::_trimUrls($css);// rewrite$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/',array(self::$className, '_processUriCB'), $css);$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/',array(self::$className, '_processUriCB'), $css);return $css;}/*** Prepend a path to relative URIs in CSS files** @param string $css** @param string $path The path to prepend.** @return string*/public static function prepend($css, $path){self::$_prependPath = $path;$css = self::_trimUrls($css);// append$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/',array(self::$className, '_processUriCB'), $css);$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/',array(self::$className, '_processUriCB'), $css);self::$_prependPath = null;return $css;}/*** @var string directory of this stylesheet*/private static $_currentDir = '';/*** @var string DOC_ROOT*/private static $_docRoot = '';/*** @var array directory replacements to map symlink targets back to their* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'*/private static $_symlinks = array();/*** @var string path to prepend*/private static $_prependPath = null;private static function _trimUrls($css){return preg_replace('/url\\( # url(\\s*([^\\)]+?) # 1 = URI (assuming does not contain ")")\\s*\\) # )/x', 'url($1)', $css);}private static function _processUriCB($m){// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'$isImport = ($m[0][0] === '@');// determine URI and the quote character (if any)if ($isImport) {$quoteChar = $m[1];$uri = $m[2];} else {// $m[1] is either quoted or not$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')? $m[1][0]: '';$uri = ($quoteChar === '')? $m[1]: substr($m[1], 1, strlen($m[1]) - 2);}// analyze URIif ('/' !== $uri[0] // root-relative&& false === strpos($uri, '//') // protocol (non-data)&& 0 !== strpos($uri, 'data:') // data protocol) {// URI is file-relative: rewrite depending on options$uri = (self::$_prependPath !== null)? (self::$_prependPath . $uri): self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);}return $isImport? "@import {$quoteChar}{$uri}{$quoteChar}": "url({$quoteChar}{$uri}{$quoteChar})";}/*** Rewrite a file relative URI as root relative** <code>* Minify_CSS_UriRewriter::rewriteRelative(* '../img/hello.gif'* , '/home/user/www/css' // path of CSS file* , '/home/user/www' // doc root* );* // returns '/img/hello.gif'** // example where static files are stored in a symlinked directory* Minify_CSS_UriRewriter::rewriteRelative(* 'hello.gif'* , '/var/staticFiles/theme'* , '/home/user/www'* , array('/home/user/www/static' => '/var/staticFiles')* );* // returns '/static/theme/hello.gif'* </code>** @param string $uri file relative URI** @param string $realCurrentDir realpath of the current file's directory.** @param string $realDocRoot realpath of the site document root.** @param array $symlinks (default = array()) If the file is stored in* a symlink-ed directory, provide an array of link paths to* real target paths, where the link paths "appear" to be within the document* root. E.g.:* <code>* array('/home/foo/www/not/real/path' => '/real/target/path') // unix* array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows* </code>** @return string*/public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array()){// prepend path with current dir separator (OS-independent)$path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR). DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);self::$debugText .= "file-relative URI : {$uri}\n". "path prepended : {$path}\n";// "unresolve" a symlink back to doc rootforeach ($symlinks as $link => $target) {if (0 === strpos($path, $target)) {// replace $target with $link$path = $link . substr($path, strlen($target));self::$debugText .= "symlink unresolved : {$path}\n";break;}}// strip doc root$path = substr($path, strlen($realDocRoot));self::$debugText .= "docroot stripped : {$path}\n";// fix to root-relative URI$uri = strtr($path, '/\\', '//');// remove /./ and /../ where possible$uri = str_replace('/./', '/', $uri);// inspired by patch from Oleg Cherniydo {$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);} while ($changed);self::$debugText .= "traversals removed : {$uri}\n\n";return $uri;}/*** Get realpath with any trailing slash removed. If realpath() fails,* just remove the trailing slash.** @param string $path** @return mixed path with no trailing slash*/protected static function _realpath($path){$realPath = realpath($path);if ($realPath !== false) {$path = $realPath;}return rtrim($path, '/\\');}}class Minify_CommentPreserver {/*** String to be prepended to each preserved comment** @var string*/public static $prepend = "\n";/*** String to be appended to each preserved comment** @var string*/public static $append = "\n";/*** Process a string outside of C-style comments that begin with "/*!"** On each non-empty string outside these comments, the given processor* function will be called. The first "!" will be removed from the* preserved comments, and the comments will be surrounded by* Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.** @param string $content* @param callback $processor function* @param array $args array of extra arguments to pass to the processor* function (default = array())* @return string*/public static function process($content, $processor, $args = array()){$ret = '';while (true) {list($beforeComment, $comment, $afterComment) = self::_nextComment($content);if ('' !== $beforeComment) {$callArgs = $args;array_unshift($callArgs, $beforeComment);$ret .= call_user_func_array($processor, $callArgs);}if (false === $comment) {break;}$ret .= $comment;$content = $afterComment;}return $ret;}/*** Extract comments that YUI Compressor preserves.** @param string $in input** @return array 3 elements are returned. If a YUI comment is found, the* 2nd element is the comment and the 1st and 2nd are the surrounding* strings. If no comment is found, the entire string is returned as the* 1st element and the other two are false.*/private static function _nextComment($in){if (false === ($start = strpos($in, '/*!'))|| false === ($end = strpos($in, '*/', $start + 3))) {return array($in, false, false);}$ret = array(substr($in, 0, $start),self::$prepend . '/*' . substr($in, $start + 3, $end - $start - 1) . self::$append);$endChars = (strlen($in) - $end - 2);$ret[] = (0 === $endChars)? '': substr($in, -$endChars);return $ret;}}class Minify_CSS_Compressor {/*** Minify a CSS string** @param string $css** @param array $options (currently ignored)** @return string*/public static function process($css, $options = array()){$obj = new Minify_CSS_Compressor($options);return $obj->_process($css);}/*** @var array options*/protected $_options = null;/*** @var bool Are we "in" a hack?** I.e. are some browsers targetted until the next comment?*/protected $_inHack = false;/*** Constructor** @param array $options (currently ignored)** @return null*/private function __construct($options) {$this->_options = $options;}/*** 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 combinatorsreturn 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.htmlreturn '/*" "*/';}if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {// component of http://tantek.com/CSS/Examples/midpass.htmlreturn '/*";}}/* */';}if ($this->_inHack) {// inversion: feeding only to one browserif (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 spacereturn $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){$m[1] = preg_replace('/\\s*("[^"]+" # 1 = family in double qutoes|\'[^\']+\' # or 1 = family in single quotes|[\\w\\-]+ # or 1 = unquoted family)\\s*/x', '$1', $m[1]);return 'font-family:' . $m[1] . $m[2];}}