diff --git a/min/README.txt b/MIN.txt similarity index 92% rename from min/README.txt rename to MIN.txt index 7e6fdb1..dbeff0f 100644 --- a/min/README.txt +++ b/MIN.txt @@ -1,11 +1,11 @@ -The files in this directory represent the default Minify setup designed to ease +The files in the /min/ directory represent the default Minify setup designed to ease integration with your site. This app will combine and minify your Javascript or CSS files and serve them with HTTP compression and cache headers. RECOMMENDED -It's recommended to edit config.php to set $min_cachePath to a writeable +It's recommended to edit /min/config.php to set $min_cachePath to a writeable (by PHP) directory on your system. This will improve performance. @@ -23,7 +23,7 @@ Let's say you want to serve this file: Here's the "Minify URL" for this file: http://example.com/min/?f=wp-content/themes/default/default.css -In other words, the "f" argument is set to the file path from root without the +In other words, the "f" argument is set to the file path from root without the initial "/". As CSS files may contain relative URIs, Minify will automatically "fix" these by rewriting them as root relative. @@ -53,14 +53,14 @@ E.g., the following URLs will serve the exact same content: MINIFY URLS IN HTML -In (X)HTML files, don't forget to replace any "&" characters with "&". +In HTML files, don't forget to replace any "&" characters with "&". SPECIFYING ALLOWED DIRECTORIES By default, Minify will serve any *.css/*.js files within the DOCUMENT_ROOT. If -you'd prefer to limit Minify's access to certain directories, set the -$min_serveOptions['minApp']['allowDirs'] array in config.php. E.g. to limit +you'd prefer to limit Minify's access to certain directories, set the +$min_serveOptions['minApp']['allowDirs'] array in config.php. E.g. to limit to the /js and /themes/default directories, use: $min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default'); @@ -69,7 +69,7 @@ $min_serveOptions['minApp']['allowDirs'] = array('//js', '//themes/default'); GROUPS: NICER URLS For nicer URLs, edit groupsConfig.php to pre-specify groups of files -to be combined under preset keys. E.g., here's an example configuration in +to be combined under preset keys. E.g., here's an example configuration in groupsConfig.php: return array( @@ -79,7 +79,7 @@ return array( This pre-selects the following files to be combined under the key "js": http://example.com/js/Class.js http://example.com/js/email.js - + You can now serve these files with this simple URL: http://example.com/min/?g=js @@ -88,7 +88,7 @@ GROUPS: SPECIFYING FILES OUTSIDE THE DOC_ROOT In the groupsConfig.php array, the "//" in the file paths is a shortcut for the DOCUMENT_ROOT, but you can also specify paths from the root of the filesystem -or relative to the DOC_ROOT: +or relative to the DOC_ROOT: return array( 'js' => array( @@ -110,8 +110,8 @@ Separate group keys with commas: FAR-FUTURE EXPIRES HEADERS Minify can send far-future (one year) Expires headers. To enable this you must -add a number to the querystring (e.g. /min/?g=js&1234 or /min/f=file.js&1234) -and alter it whenever a source file is changed. If you have a build process you +add a number to the querystring (e.g. /min/?g=js&1234 or /min/f=file.js&1234) +and alter it whenever a source file is changed. If you have a build process you can use a build/source control revision number. You can alternately use the utility function Minify_getUri() to get a "versioned" diff --git a/README.txt b/README.txt index 5d2e232..2fc627b 100644 --- a/README.txt +++ b/README.txt @@ -1,4 +1,4 @@ -WELCOME TO MINIFY 2.1! +WELCOME TO MINIFY! Minify is an HTTP content server. It compresses sources of content (usually files), combines the result and serves it with appropriate @@ -12,33 +12,40 @@ WORDPRESS USER? These WP plugins integrate Minify into WordPress's style and script hooks to get you set up faster. - http://wordpress.org/extend/plugins/wp-minify/ + http://wordpress.org/extend/plugins/bwp-minify/ http://wordpress.org/extend/plugins/w3-total-cache/ +INSTALLATION + +Place the /min/ directory as a child of your DOCUMENT_ROOT +directory: i.e. you will have: /home/example/www/min + + +CONFIGURATION & USAGE + +See the MIN.txt file and http://code.google.com/p/minify/wiki/UserGuide + +Minify also comes with a URI Builder application that can help you write URLs +for use with Minify or configure groups of files. + +To enable this, edit min/config.php, set $min_enableBuilder = true; and visit + http://example.org/min/builder/ + +When you're finished with this, please set $min_enableBuilder = false; + + UPGRADING See UPGRADING.txt for instructions. -INSTALLATION AND USAGE: - -1. Place the /min/ directory as a child of your DOCUMENT_ROOT -directory: i.e. you will have: /home/user/www/min - -2. Open http://yourdomain/min/ in a web browser. This will forward -you to the Minify URI Builder application, which will help you -quickly start using Minify to serve content on your site. - -See the User Guide: http://code.google.com/p/minify/wiki/UserGuide - - UNIT TESTING: 1. Place the /min_unit_tests/ directory as a child of your DOCUMENT_ROOT -directory: i.e. you will have: /home/user/www/public_html/min_unit_tests +directory: i.e. you will have: /home/example/www/min_unit_tests -2. To run unit tests, access: http://yourdomain/min_unit_tests/test_all.php +2. To run unit tests, access: http://example.org/min_unit_tests/test_all.php (If you wish, the other test_*.php files can be run to test individual components with more verbose output.) diff --git a/UPGRADING.txt b/UPGRADING.txt index 5025faf..647b9cc 100644 --- a/UPGRADING.txt +++ b/UPGRADING.txt @@ -4,7 +4,7 @@ UPGRADING FROM 2.1.* 1. Rename the following files: - /min/config.php --> /min/old_config.php + /min/config.php --> /min/old_config.php /min/groupsConfig.php --> /min/old_groupsConfig.php 2. Overwrite all files in /min (and /min_unit_tests) with those from this zip. @@ -15,14 +15,7 @@ UPGRADING FROM 2.1.* 5. Merge your settings in old_config.php into config.php. - * If you've set $_SERVER['DOCUMENT_ROOT'], instead set the new option - $min_documentRoot. This is advantageous on IIS systems because Minify - will no longer overwrite the path you specified. - - * $min_errorLogger adds the ability to enable FirePHP logging. - -6. (optional) Delete /min/old_config.php and the Minify files from your cache - directory (specified in $min_cachePath). +6. (optional) Delete /min/old_config.php. INSTALLING FRESH diff --git a/min/config.php b/min/config.php index 46397e7..bb83cfb 100644 --- a/min/config.php +++ b/min/config.php @@ -7,6 +7,12 @@ */ +/** + * Allow use of the Minify URI Builder app. Only set this to true while you need it. + **/ +$min_enableBuilder = true; + + /** * Set to true to log messages to FirePHP (Firefox Firebug addon). * Set to false for no error logging (Minify may be slightly faster). @@ -33,12 +39,6 @@ $min_errorLogger = false; $min_allowDebugFlag = false; -/** - * Allow use of the Minify URI Builder app. Only set this to true while you need it. - **/ -$min_enableBuilder = false; - - /** * For best performance, specify your temp directory here. Otherwise Minify * will have to load extra code to guess. Some examples below: diff --git a/min/lib/HTTP/ConditionalGet.php b/min/lib/HTTP/ConditionalGet.php index 158f73a..93b7e75 100644 --- a/min/lib/HTTP/ConditionalGet.php +++ b/min/lib/HTTP/ConditionalGet.php @@ -105,8 +105,6 @@ class HTTP_ConditionalGet { * seconds, and also set the Expires header to the equivalent GMT date. * After the max-age period has passed, the browser will again send a * conditional GET to revalidate its cache. - * - * @return null */ public function __construct($spec) { @@ -234,8 +232,6 @@ class HTTP_ConditionalGet { * "private" will be sent, allowing only browser caching. * * @param array $options (default empty) additional options for constructor - * - * @return null */ public static function check($lastModifiedTime = null, $isPublic = false, $options = array()) { @@ -271,13 +267,21 @@ class HTTP_ConditionalGet { protected $_lmTime = null; protected $_etag = null; protected $_stripEtag = false; - + + /** + * @param string $hash + * + * @param string $scope + */ protected function _setEtag($hash, $scope) { $this->_etag = '"' . substr($scope, 0, 3) . $hash . '"'; $this->_headers['ETag'] = $this->_etag; } + /** + * @param int $time + */ protected function _setLastModified($time) { $this->_lmTime = (int)$time; @@ -286,6 +290,8 @@ class HTTP_ConditionalGet { /** * Determine validity of client cache and queue 304 header if valid + * + * @return bool */ protected function _isCacheValid() { @@ -302,6 +308,9 @@ class HTTP_ConditionalGet { return $isValid; } + /** + * @return bool + */ protected function resourceMatchedEtag() { if (!isset($_SERVER['HTTP_IF_NONE_MATCH'])) { @@ -323,7 +332,12 @@ class HTTP_ConditionalGet { } return false; } - + + /** + * @param string $etag + * + * @return string + */ protected function normalizeEtag($etag) { $etag = trim($etag); return $this->_stripEtag @@ -331,6 +345,9 @@ class HTTP_ConditionalGet { : $etag; } + /** + * @return bool + */ protected function resourceNotModified() { if (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { diff --git a/min/lib/HTTP/Encoder.php b/min/lib/HTTP/Encoder.php index 534d648..8f34779 100644 --- a/min/lib/HTTP/Encoder.php +++ b/min/lib/HTTP/Encoder.php @@ -85,8 +85,6 @@ class HTTP_Encoder { * method. If not set, the best method will be chosen by getAcceptedEncoding() * The available methods are 'gzip', 'deflate', 'compress', and '' (no * encoding) - * - * @return null */ public function __construct($spec) { @@ -114,7 +112,7 @@ class HTTP_Encoder { * * Call after encode() for encoded content. * - * return string + * @return string */ public function getContent() { @@ -148,8 +146,6 @@ class HTTP_Encoder { * not handled purposefully. * * @see getHeaders() - * - * @return null */ public function sendHeaders() { @@ -166,8 +162,6 @@ class HTTP_Encoder { * You must call this before headers are sent and it probably cannot be * used in conjunction with zlib output buffering / mod_gzip. Errors are * not handled purposefully. - * - * @return null */ public function sendAll() { diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index 226431e..b6879f3 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -74,6 +74,7 @@ class JSMin { * Minify Javascript. * * @param string $js Javascript to be minified + * * @return string */ public static function minify($js) @@ -92,6 +93,8 @@ class JSMin { /** * Perform minification, return result + * + * @return string */ public function min() { @@ -151,6 +154,9 @@ class JSMin { * ACTION_KEEP_A = Output A. Copy B to A. Get the next B. * ACTION_DELETE_A = Copy B to A. Get the next B. * ACTION_DELETE_A_B = Get the next B. + * + * @param int $command + * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException */ protected function action($command) { @@ -226,6 +232,9 @@ class JSMin { } } + /** + * @return bool + */ protected function isRegexpLiteral() { if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing @@ -253,6 +262,8 @@ class JSMin { /** * Get next char. Convert ctrl char to space. + * + * @return string */ protected function get() { @@ -277,6 +288,8 @@ class JSMin { /** * Get next char. If is ctrl character, translate to a space or newline. + * + * @return string */ protected function peek() { @@ -286,12 +299,19 @@ class JSMin { /** * Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII? + * + * @param string $c + * + * @return bool */ protected function isAlphaNum($c) { return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126); } + /** + * @return string + */ protected function singleLineComment() { $comment = ''; @@ -308,6 +328,10 @@ class JSMin { } } + /** + * @return string + * @throws JSMin_UnterminatedCommentException + */ protected function multipleLineComment() { $this->get(); @@ -339,6 +363,8 @@ class JSMin { /** * Get the next character, skipping over comments. * Some comments may be preserved. + * + * @return string */ protected function next() { diff --git a/min/lib/Minify.php b/min/lib/Minify.php index f04b8f1..9634f22 100644 --- a/min/lib/Minify.php +++ b/min/lib/Minify.php @@ -156,8 +156,8 @@ class Minify { * * Any controller options are documented in that controller's setupSources() method. * - * @param mixed instance of subclass of Minify_Controller_Base or string name of - * controller. E.g. 'Files' + * @param mixed $controller instance of subclass of Minify_Controller_Base or string + * name of controller. E.g. 'Files' * * @param array $options controller/serve options * @@ -179,6 +179,7 @@ class Minify { . str_replace('_', '/', $controller) . ".php"; } $controller = new $class(); + /* @var Minify_Controller_Base $controller */ } // set up controller sources and mix remaining options with @@ -398,31 +399,41 @@ class Minify { if ($docRoot) { $_SERVER['DOCUMENT_ROOT'] = $docRoot; } elseif (isset($_SERVER['SERVER_SOFTWARE']) - && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/') - ) { - $_SERVER['DOCUMENT_ROOT'] = rtrim(substr( + && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')) { + $_SERVER['DOCUMENT_ROOT'] = substr( $_SERVER['SCRIPT_FILENAME'] ,0 - ,strlen($_SERVER['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME']) - ), '\\'); + ,strlen($_SERVER['SCRIPT_FILENAME']) - strlen($_SERVER['SCRIPT_NAME'])); + $_SERVER['DOCUMENT_ROOT'] = rtrim($_SERVER['DOCUMENT_ROOT'], '\\'); } } /** - * @var mixed Minify_Cache_* object or null (i.e. no server cache is used) + * Any Minify_Cache_* object or null (i.e. no server cache is used) + * + * @var Minify_Cache_File */ private static $_cache = null; /** - * @var Minify_Controller active controller for current request + * Active controller for current request + * + * @var Minify_Controller_Base */ protected static $_controller = null; /** - * @var array options for current request + * Options for current request + * + * @var array */ protected static $_options = null; - + + /** + * @param string $header + * + * @param string $url + */ protected static function _errorExit($header, $url) { $url = htmlspecialchars($url, ENT_QUOTES); @@ -441,8 +452,6 @@ class Minify { * Set up sources to use Minify_Lines * * @param array $sources Minify_Source instances - * - * @return null */ protected static function _setupDebug($sources) { @@ -572,13 +581,17 @@ class Minify { ,self::$_options['minifierOptions'] ,self::$_options['postprocessor'] ,self::$_options['bubbleCssImports'] + ,self::VERSION ))); return "{$prefix}_{$name}_{$md5}"; } /** - * Bubble CSS @imports to the top or prepend a warning if an - * @import is detected not at the top. + * Bubble CSS @imports to the top or prepend a warning if an import is detected not at the top. + * + * @param string $css + * + * @return string */ protected static function _handleCssImports($css) { diff --git a/min/lib/Minify/CSS/Compressor.php b/min/lib/Minify/CSS/Compressor.php index f6fb034..c6cdd8b 100644 --- a/min/lib/Minify/CSS/Compressor.php +++ b/min/lib/Minify/CSS/Compressor.php @@ -36,14 +36,14 @@ class Minify_CSS_Compressor { } /** - * @var array options + * @var array */ protected $_options = null; /** - * @var bool Are we "in" a hack? - * - * I.e. are some browsers targetted until the next comment? + * Are we "in" a hack? I.e. are some browsers targetted until the next comment? + * + * @var bool */ protected $_inHack = false; @@ -52,8 +52,6 @@ class Minify_CSS_Compressor { * Constructor * * @param array $options (currently ignored) - * - * @return null */ private function __construct($options) { $this->_options = $options; diff --git a/min/lib/Minify/CSS/UriRewriter.php b/min/lib/Minify/CSS/UriRewriter.php index 1404371..8845f15 100644 --- a/min/lib/Minify/CSS/UriRewriter.php +++ b/min/lib/Minify/CSS/UriRewriter.php @@ -14,6 +14,7 @@ class Minify_CSS_UriRewriter { /** * rewrite() and rewriteRelative() append debugging information here + * * @var string */ public static $debugText = ''; @@ -164,9 +165,7 @@ class Minify_CSS_UriRewriter { self::$debugText .= "docroot stripped : {$path}\n"; // fix to root-relative URI - $uri = strtr($path, '/\\', '//'); - $uri = self::removeDots($uri); self::$debugText .= "traversals removed : {$uri}\n\n"; @@ -176,7 +175,9 @@ class Minify_CSS_UriRewriter { /** * Remove instances of "./" and "../" where possible from a root-relative URI + * * @param string $uri + * * @return string */ public static function removeDots($uri) @@ -192,6 +193,7 @@ 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'; @@ -214,26 +216,39 @@ class Minify_CSS_UriRewriter { } /** - * @var string directory of this stylesheet + * Directory of this stylesheet + * + * @var string */ private static $_currentDir = ''; /** - * @var string DOC_ROOT + * DOC_ROOT + * + * @var string */ private static $_docRoot = ''; /** - * @var array directory replacements to map symlink targets back to their + * directory replacements to map symlink targets back to their * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath' + * + * @var array */ private static $_symlinks = array(); /** - * @var string path to prepend + * Path to prepend + * + * @var string */ private static $_prependPath = null; + /** + * @param string $css + * + * @return string + */ private static function _trimUrls($css) { return preg_replace('/ @@ -245,6 +260,11 @@ class Minify_CSS_UriRewriter { /x', 'url($1)', $css); } + /** + * @param array $m + * + * @return string + */ private static function _processUriCB($m) { // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/' diff --git a/min/lib/Minify/Controller/Base.php b/min/lib/Minify/Controller/Base.php index 724dd8d..240b544 100644 --- a/min/lib/Minify/Controller/Base.php +++ b/min/lib/Minify/Controller/Base.php @@ -27,7 +27,7 @@ abstract class Minify_Controller_Base { * * @param array $options controller and Minify options * - * return array $options Minify::serve options + * @return array $options Minify::serve options */ abstract public function setupSources($options); @@ -138,7 +138,13 @@ abstract class Minify_Controller_Base { return in_array(strrev($revExt), array('js', 'css', 'html', 'txt')); } - + /** + * @param string $file + * @param array $allowDirs + * @param string $uri + * @return bool + * @throws Exception + */ public static function checkAllowDirs($file, $allowDirs, $uri) { foreach ((array)$allowDirs as $allowDir) { @@ -151,6 +157,10 @@ abstract class Minify_Controller_Base { . " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';"); } + /** + * @param string $file + * @throws Exception + */ public static function checkNotHidden($file) { $b = basename($file); @@ -160,19 +170,22 @@ abstract class Minify_Controller_Base { } /** - * @var array instances of Minify_Source, which provide content and - * any individual minification needs. + * instances of Minify_Source, which provide content and any individual minification needs. + * + * @var array * * @see Minify_Source */ public $sources = array(); /** - * The setupSources() method may choose to set this, making it easier to + * Short name to place inside cache id + * + * The setupSources() method may choose to set this, making it easier to * recognize a particular set of sources/settings in the cache folder. It * will be filtered and truncated to make the final cache id <= 250 bytes. * - * @var string short name to place inside cache id + * @var string */ public $selectionId = ''; @@ -225,7 +238,9 @@ abstract class Minify_Controller_Base { /** * Send message to the Minify logger + * * @param string $msg + * * @return null */ public function log($msg) { diff --git a/min/lib/Minify/Controller/Groups.php b/min/lib/Minify/Controller/Groups.php index 1ac5770..2d4e43b 100644 --- a/min/lib/Minify/Controller/Groups.php +++ b/min/lib/Minify/Controller/Groups.php @@ -34,12 +34,11 @@ class Minify_Controller_Groups extends Minify_Controller_Base { * Set up groups of files as sources * * @param array $options controller and Minify options - * @return array Minify options - * - * Controller options: - * + * * 'groups': (required) array mapping PATH_INFO strings to arrays - * of complete file paths. @see Minify_Controller_Groups + * of complete file paths. @see Minify_Controller_Groups + * + * @return array Minify options */ public function setupSources($options) { // strip controller options diff --git a/min/lib/Minify/Controller/MinApp.php b/min/lib/Minify/Controller/MinApp.php index 2fb10f7..d47c60d 100644 --- a/min/lib/Minify/Controller/MinApp.php +++ b/min/lib/Minify/Controller/MinApp.php @@ -18,8 +18,8 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { * Set up groups of files as sources * * @param array $options controller and Minify options + * * @return array Minify options - * */ public function setupSources($options) { // filter controller options @@ -185,6 +185,13 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { return $options; } + /** + * @param string $file + * + * @param array $cOptions + * + * @return Minify_Source + */ protected function _getFileSource($file, $cOptions) { $spec['filepath'] = $file; @@ -197,8 +204,12 @@ class Minify_Controller_MinApp extends Minify_Controller_Base { protected $_type = null; - /* + /** * Make sure that only source files of a single type are registered + * + * @param string $sourceOrExt + * + * @throws Exception */ public function checkType($sourceOrExt) { diff --git a/min_unit_tests/_test_files/importProcessor/css/output.css b/min_unit_tests/_test_files/importProcessor/css/output.css index 2acb048..ca7d872 100644 --- a/min_unit_tests/_test_files/importProcessor/css/output.css +++ b/min_unit_tests/_test_files/importProcessor/css/output.css @@ -1,52 +1,52 @@ -@media screen { -@charset "utf-8"; - -/* some CSS to try to exercise things in general */ - -@import url(/more.css); - - body, td, th { - font-family: Verdana , "Bitstream Vera Sans" , Arial Narrow, sans-serif ; - - font-size : 12px; -} - -.nav { - margin-left: 20%; -} -#main-nav { - background-color: red; - border: 1px solid #00ff77; -} - -div#content -h1 + p { - padding-top: 0; - margin-top: 0; -} - -@media all and (min-width: 640px) { - #media-queries-1 { background-color: #0f0; } -} - -@media screen and (max-width: 2000px) { - #media-queries-2 { background-color: #0f0; } -} -@import url(http://example.com/hello.css); -adjacent foo { background: red url(/red.gif); } -adjacent bar { background: url('../green.gif') } -} - -@media tv,projection { -/* @import url('1/bad.css') bad; */ -adjacent2 foo { background: red url(/red.gif); } -adjacent2 bar { background: url('green.gif') } -@import '../input.css'; -tv foo { background: red url(/red.gif); } -tv bar { background: url('green.gif') } -} - -input.test bar { background: url('../lib/img/green.gif') } - -input foo { background: red url(/red.gif); } +@media screen { +@charset "utf-8"; + +/* some CSS to try to exercise things in general */ + +@import url(/more.css); + + body, td, th { + font-family: Verdana , "Bitstream Vera Sans" , Arial Narrow, sans-serif ; + + font-size : 12px; +} + +.nav { + margin-left: 20%; +} +#main-nav { + background-color: red; + border: 1px solid #00ff77; +} + +div#content +h1 + p { + padding-top: 0; + margin-top: 0; +} + +@media all and (min-width: 640px) { + #media-queries-1 { background-color: #0f0; } +} + +@media screen and (max-width: 2000px) { + #media-queries-2 { background-color: #0f0; } +} +@import url(http://example.com/hello.css); +adjacent foo { background: red url(/red.gif); } +adjacent bar { background: url('../green.gif') } +} + +@media tv,projection { +/* @import url('1/bad.css') bad; */ +adjacent2 foo { background: red url(/red.gif); } +adjacent2 bar { background: url('green.gif') } +@import '../input.css'; +tv foo { background: red url(/red.gif); } +tv bar { background: url('green.gif') } +} + +input.test bar { background: url('../lib/img/green.gif') } + +input foo { background: red url(/red.gif); } input bar { background: url('../green.gif') } \ No newline at end of file