lib/css_parser/parser.rb in css_parser-1.3.4 vs lib/css_parser/parser.rb in css_parser-1.3.5

- old
+ new

@@ -22,11 +22,11 @@ # Initial parsing RE_AT_IMPORT_RULE = /\@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s\)]*)["']?\)?([\w\s\,^\]\(\))]*)\)?[;\n]?/ # Array of CSS files that have been loaded. attr_reader :loaded_uris - + #-- # Class variable? see http://www.oreillynet.com/ruby/blog/2007/01/nubygems_dont_use_class_variab_1.html #++ @folded_declaration_cache = {} class << self; attr_reader :folded_declaration_cache; end @@ -36,14 +36,14 @@ :import => true, :io_exceptions => true}.merge(options) # array of RuleSets @rules = [] - - + + @loaded_uris = [] - + # unprocessed blocks of CSS @blocks = [] reset! end @@ -75,11 +75,11 @@ # Finds the rule sets that match the given selectors def find_rule_sets(selectors, media_types = :all) rule_sets = [] selectors.each do |selector| - each_rule_set(media_types) do |rule_set| + each_rule_set(media_types) do |rule_set, media_type| if !rule_sets.member?(rule_set) && rule_set.selectors.member?(selector) rule_sets << rule_set end end end @@ -115,37 +115,37 @@ block = cleanup_block(block) if options[:base_uri] and @options[:absolute_paths] block = CssParser.convert_uris(block, options[:base_uri]) end - + # Load @imported CSS block.scan(RE_AT_IMPORT_RULE).each do |import_rule| media_types = [] if media_string = import_rule[-1] media_string.split(/[,]/).each do |t| media_types << CssParser.sanitize_media_query(t) unless t.empty? end else media_types = [:all] end - + next unless options[:only_media_types].include?(:all) or media_types.length < 1 or (media_types & options[:only_media_types]).length > 0 import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip - + if options[:base_uri] import_uri = Addressable::URI.parse(options[:base_uri].to_s) + Addressable::URI.parse(import_path) load_uri!(import_uri, options[:base_uri], media_types) elsif options[:base_dir] load_file!(import_path, options[:base_dir], media_types) - end + end end # Remove @import declarations block.gsub!(RE_AT_IMPORT_RULE, '') - + parse_block_into_rule_sets!(block, options) end # Add a CSS rule by setting the +selectors+, +declarations+ and +media_types+. # @@ -167,42 +167,62 @@ end # Iterate through RuleSet objects. # # +media_types+ can be a symbol or an array of symbols. - def each_rule_set(media_types = :all) # :yields: rule_set + def each_rule_set(media_types = :all) # :yields: rule_set, media_types media_types = [:all] if media_types.nil? media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)} @rules.each do |block| if media_types.include?(:all) or block[:media_types].any? { |mt| media_types.include?(mt) } - yield block[:rules] + yield(block[:rules], block[:media_types]) end end end # Iterate through CSS selectors. # # +media_types+ can be a symbol or an array of symbols. # See RuleSet#each_selector for +options+. - def each_selector(media_types = :all, options = {}) # :yields: selectors, declarations, specificity - each_rule_set(media_types) do |rule_set| + def each_selector(media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types + each_rule_set(media_types) do |rule_set, media_types| rule_set.each_selector(options) do |selectors, declarations, specificity| - yield selectors, declarations, specificity + yield selectors, declarations, specificity, media_types end end end # Output all CSS rules as a single stylesheet. def to_s(media_types = :all) out = '' - each_selector(media_types) do |selectors, declarations, specificity| - out << "#{selectors} {\n#{declarations}\n}\n" + styles_by_media_types = {} + each_selector(media_types) do |selectors, declarations, specificity, media_types| + media_types.each do |media_type| + styles_by_media_types[media_type] ||= [] + styles_by_media_types[media_type] << [selectors, declarations] + end end + + styles_by_media_types.each_pair do |media_type, media_styles| + media_block = (media_type != :all) + out += "@media #{media_type} {\n" if media_block + + media_styles.each do |media_style| + if media_block + out += " #{media_style[0]} {\n #{media_style[1]}\n }\n" + else + out += "#{media_style[0]} {\n#{media_style[1]}\n}\n" + end + end + + out += "}\n" if media_block + end + out end - + # A hash of { :media_query => rule_sets } def rules_by_media_query rules_by_media = {} @rules.each do |block| block[:media_types].each do |mt| @@ -210,11 +230,11 @@ rules_by_media[mt] = [] end rules_by_media[mt] << block[:rules] end end - + rules_by_media end # Merge declarations with the same selector. def compact! # :nodoc: @@ -244,29 +264,29 @@ block.scan(/([\\]?[{}\s"]|(.[^\s"{}\\]*))/).each do |matches| token = matches[0] if token =~ /\A"/ # found un-escaped double quote in_string = !in_string - end + end if in_declarations > 0 # too deep, malformed declaration block if in_declarations > 1 in_declarations -= 1 if token =~ /\}/ next end - + if token =~ /\{/ in_declarations += 1 next end - + current_declarations += token if token =~ /\}/ and not in_string current_declarations.gsub!(/\}[\s]*$/, '') - + in_declarations -= 1 unless current_declarations.strip.empty? add_rule!(current_selectors, current_declarations, current_media_queries) end @@ -316,11 +336,11 @@ end end end end - # check for unclosed braces + # check for unclosed braces if in_declarations > 0 add_rule!(current_selectors, current_declarations, current_media_queries) end end @@ -341,11 +361,11 @@ opts.merge!(options) else opts[:base_uri] = options if options.is_a? String opts[:media_types] = deprecated if deprecated end - + if uri.scheme == 'file' or uri.scheme.nil? uri.path = File.expand_path(uri.path) uri.scheme = 'file' end @@ -354,11 +374,11 @@ src, charset = read_remote_file(uri) if src add_block!(src, opts) end end - + # Load a local CSS file. def load_file!(file_name, base_dir = nil, media_types = :all) file_name = File.expand_path(file_name, base_dir) return unless File.readable?(file_name) return unless circular_reference_check(file_name) @@ -366,22 +386,22 @@ src = IO.read(file_name) base_dir = File.dirname(file_name) add_block!(src, {:media_types => media_types, :base_dir => base_dir}) end - + # Load a local CSS string. def load_string!(src, base_dir = nil, media_types = :all) add_block!(src, {:media_types => media_types, :base_dir => base_dir}) end - - + + protected # Check that a path hasn't been loaded already # - # Raises a CircularReferenceError exception if io_exceptions are on, + # Raises a CircularReferenceError exception if io_exceptions are on, # otherwise returns true/false. def circular_reference_check(path) path = path.to_s if @loaded_uris.include?(path) raise CircularReferenceError, "can't load #{path} more than once" if @options[:io_exceptions] @@ -389,19 +409,19 @@ else @loaded_uris << path return true end end - + # Strip comments and clean up blank lines from a block of CSS. # # Returns a string. def cleanup_block(block) # :nodoc: # Strip CSS comments block.gsub!(STRIP_CSS_COMMENTS_RX, '') - # Strip HTML comments - they shouldn't really be in here but + # Strip HTML comments - they shouldn't really be in here but # some people are just crazy... block.gsub!(STRIP_HTML_COMMENTS_RX, '') # Strip lines containing just whitespace block.gsub!(/^\s+$/, "") @@ -414,16 +434,16 @@ # Returns the file's data and character set in an array. #-- # TODO: add option to fail silently or throw and exception on a 404 #++ def read_remote_file(uri) # :nodoc: - return nil, nil unless circular_reference_check(uri.to_s) + return nil, nil unless circular_reference_check(uri.to_s) src = '', charset = nil begin - uri = Addressable::URI.parse(uri.to_s) + uri = Addressable::URI.parse(uri.to_s) if uri.scheme == 'file' # local file fh = open(uri.path, 'rb') src = fh.read @@ -431,17 +451,17 @@ else # remote file if uri.scheme == 'https' uri.port = 443 unless uri.port http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true + http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE else http = Net::HTTP.new(uri.host, uri.port) end - res = http.get(uri.path, {'User-Agent' => USER_AGENT, 'Accept-Encoding' => 'gzip'}) + res = http.get(uri.request_uri, {'User-Agent' => USER_AGENT, 'Accept-Encoding' => 'gzip'}) src = res.body charset = fh.respond_to?(:charset) ? fh.charset : 'utf-8' if res.code.to_i >= 400 raise RemoteFileError if @options[:io_exceptions] @@ -469,10 +489,10 @@ rescue raise RemoteFileError if @options[:io_exceptions] return nil, nil end - return src, charset + return src, charset end private # Save a folded declaration block to the internal cache. def save_folded_declaration(block_hash, folded_declaration) # :nodoc: