class YUI::CSS
Methods for working with CSS
.
Attributes
input_size[R]
output_size[R]
Public Class Methods
compress(css, line_length=0)
click to toggle source
Compress CSS
using the Ruby port of the YUI
Compressor (by <a href=“Richard”>github.com/rhulse“>Richard Hulse</a>).
@param [String] css The CSS
to be compressed.
@param [Integer] line_length (optional) Number of columns to wrap to.
Default is no wrapping.
@return [String] Compressed CSS
.
# File lib/yui/css_compressor.rb, line 62 def compress(css, line_length=0) @preservedTokens = [] @comments = [] @output_size = 0 @input_size = css.length css = process_comments_and_strings(css) # Normalize all whitespace strings to single spaces. Easier to work with that way. css.gsub!(/\s+/, ' ') # Remove the spaces before the things that should not have spaces before them. # But, be careful not to turn "p :link {...}" into "p:link{...}" # Swap out any pseudo-class colons with the token, and then swap back. css.gsub!(/(?:^|\})[^\{:]+\s+:+[^\{]*\{/) do |match| match.gsub(':', '___PSEUDOCLASSCOLON___') end css.gsub!(/\s+([!\{\};:>+\(\)\],])/, '\1') css.gsub!(/([!\{\}:;>+\(\[,])\s+/, '\1') css.gsub!('___PSEUDOCLASSCOLON___', ':') # special case for IE css.gsub!(/:first-(line|letter)(\{|,)/, ':first-\1 \2') # no space after the end of a preserved comment css.gsub!(/\*\/ /, '*/') # If there is a @charset, then only allow one, and push to the top of the file. css.gsub!(/^(.*)(@charset "[^"]*";)/i, '\2\1') css.gsub!(/^(\s*@charset [^;]+;\s*)+/i, '\1') # Put the space back in some cases, to support stuff like # @media screen and (-webkit-min-device-pixel-ratio:0){ css.gsub!(/\band\(/i, "and (") # remove unnecessary semicolons css.gsub!(/;+\}/, '}') # Replace 0(%, em, ex, px, in, cm, mm, pt, pc) with just 0. css.gsub!(/([\s:])([+-]?0)(?:%|em|ex|px|in|cm|mm|pt|pc)/i, '\1\2') # Replace 0 0 0 0; with 0. css.gsub!(/:(?:0 )+0(;|\})/, ':0\1') # Restore background-position:0 0; if required css.gsub!(/background-position:0(;|\})/i, 'background-position:0 0\1') # Replace 0.6 with .6, but only when preceded by : or a space. css.gsub!(/(:|\s)0+\.(\d+)/, '\1.\2') # Shorten colors from rgb(51,102,153) to #336699 # This makes it more likely that it'll get further compressed in the next step. css.gsub!(/rgb\s*\(\s*([0-9,\s]+)\s*\)/) do |match| '#' << $1.scan(/\d+/).map{|n| n.to_i.to_s(16).rjust(2, '0') }.join end # Shorten colors from #AABBCC to #ABC. Note that we want to make sure # the color is not preceded by either ", " or =. Indeed, the property # filter: chroma(color="#FFFFFF"); # would become # filter: chroma(color="#FFF"); # which makes the filter break in IE. css.gsub!(/([^"'=\s])(\s?)\s*#([0-9a-f])\3([0-9a-f])\4([0-9a-f])\5/i, '\1\2#\3\4\5') # shorter opacity IE filter css.gsub!(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i, "alpha(opacity=") # Remove empty rules. css.gsub!(/[^\};\{\/]+\{\}/, '') if (line_length > 0) # Some source control tools don't like it when files containing lines longer # than, say 8000 characters, are checked in. The linebreak option is used in # that case to split long lines after a specific column. startIndex = 0 index = 0 length = css.length while (index < length) index += 1 if (css[index - 1,1] === '}' && index - startIndex > line_length) css = css.slice(0, index) + "\n" + css.slice(index, length) startIndex = index end end end # Replace multiple semi-colons in a row by a single one # See SF bug #1980989 css.gsub!(/[;]+/, ';') #restore preserved comments and strings css = restore_preserved_comments_and_strings(css) # top and tail whitespace css.strip! @output_size = css.length css end
Private Class Methods
process_comments_and_strings(css_text)
click to toggle source
Process comments (preserve special comments, nuke the rest) and strings (preserve them).
# File lib/yui/css_compressor.rb, line 166 def process_comments_and_strings(css_text) css = css_text.clone startIndex = 0 endIndex = 0 i = 0 max = 0 token = '' totallen = css.length placeholder = '' # collect all comment blocks while (startIndex = css.index(/\/\*/, startIndex)) endIndex = css.index(/\*\//, startIndex + 2) unless endIndex endIndex = totallen end token = css.slice(startIndex+2..endIndex-1) @comments.push(token) css = css.slice(0..startIndex+1).to_s + "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (@comments.length - 1).to_s + "___" + css.slice(endIndex, totallen).to_s startIndex += 2 end # preserve strings so their content doesn't get accidentally minified css.gsub!(/("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/) do |match| quote = match[0,1] string = match.slice(1..-2) # maybe the string contains a comment-like substring? # one, maybe more? put'em back then if string =~ /___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_/ @comments.each_index do |index| string.gsub!(/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_#{index.to_s}___/, @comments[index]) end end # minify alpha opacity in filter strings string.gsub!(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i, "alpha(opacity=") @preservedTokens.push(string) quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___" + quote end # used to jump one index in loop ie5_hack = false # strings are safe, now wrestle the comments @comments.each_index do |index| if ie5_hack ie5_hack = false next end token = @comments[index] placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + index.to_s + "___" # ! in the first position of the comment means preserve # so push to the preserved tokens keeping the ! if (token[0,1] === "!") @preservedTokens.push(token) css.gsub!( /#{placeholder}/i, "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___") next end # \ in the last position looks like hack for Mac/IE5 # shorten that to /*\*/ and the next one to /**/ if (token[-1,1] === "\\") @preservedTokens.push("\\") css.gsub!( /#{placeholder}/, "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___") # keep the next comment but remove its content @preservedTokens.push("") css.gsub!(/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_#{index+1}___/, "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___") ie5_hack = true next end # keep empty comments after child selectors (IE7 hack) # e.g. html >/**/ body if ((token.length === 0) && (startIndex = css.index( /#{placeholder}/))) if (startIndex > 2) if (css[startIndex - 3,1] === '>') @preservedTokens.push("") css.gsub!(/#{placeholder}/, "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___") end end end # in all other cases kill the comment css.gsub!( /\/\*#{placeholder}\*\//, "") end css end
restore_preserved_comments_and_strings(clean_css)
click to toggle source
Restore @preservedTokens back in to the css.
# File lib/yui/css_compressor.rb, line 260 def restore_preserved_comments_and_strings(clean_css) css = clean_css.clone css_length = css.length @preservedTokens.each_index do |index| # slice these back into place rather than regex, because # complex nested strings cause the replacement to fail placeholder = "___YUICSSMIN_PRESERVED_TOKEN_#{index}___" startIndex = css.index(placeholder, 0) next unless startIndex # skip if nil endIndex = startIndex + placeholder.length css = css.slice(0..startIndex-1).to_s + @preservedTokens[index] + css.slice(endIndex, css_length).to_s end css end