lib/css_parser/parser.rb in css_parser-1.0.1 vs lib/css_parser/parser.rb in css_parser-1.1.1

- old
+ new

@@ -13,17 +13,17 @@ # When calling Parser#new there are some configuaration options: # [<tt>absolute_paths</tt>] Convert relative paths to absolute paths (<tt>href</tt>, <tt>src</tt> and <tt>url('')</tt>. Boolean, default is <tt>false</tt>. # [<tt>import</tt>] Follow <tt>@import</tt> rules. Boolean, default is <tt>true</tt>. # [<tt>io_exceptions</tt>] Throw an exception if a link can not be found. Boolean, default is <tt>true</tt>. class Parser - USER_AGENT = "Ruby CSS Parser/#{RUBY_VERSION} (http://code.dunae.ca/css_parser/)" + USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (http://github.com/alexdunae/css_parser)" STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m # Initial parsing - RE_AT_IMPORT_RULE = /\@import[\s]+(url\()?["']+(.[^'"]*)["']\)?([\w\s\,]*);?/i + RE_AT_IMPORT_RULE = /\@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s\)]*)["']?\)?([\w\s\,^\])]*)\)?[;\n]?/ #-- # RE_AT_IMPORT_RULE = Regexp.new('@import[\s]*(' + RE_STRING.to_s + ')([\w\s\,]*)[;]?', Regexp::IGNORECASE) -- should handle url() even though it is not allowed #++ @@ -80,35 +80,59 @@ alias_method :[], :find_by_selector # Add a raw block of CSS. # + # In order to follow +@import+ rules you must supply either a + # +:base_dir+ or +:base_uri+ option. + # # ==== Example # css = <<-EOT # body { font-size: 10pt } # p { margin: 0px; } # @media screen, print { # body { line-height: 1.2 } # } # EOT # # parser = CssParser::Parser.new - # parser.load_css!(css) + # parser.add_block!(css) #-- # TODO: add media_type #++ def add_block!(block, options = {}) - options = {:base_uri => nil, :charset => nil, :media_types => :all}.merge(options) + options = {:base_uri => nil, :base_dir => nil, :charset => nil, :media_types => :all}.merge(options) 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(/\s|\,/).each do |t| + media_types << t.to_sym unless t.empty? + end + end + + import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip + + if options[:base_uri] + import_uri = URI.parse(options[:base_uri].to_s).merge(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 + + # 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+. # # +media_types+ can be a symbol or an array of symbols. @@ -243,37 +267,29 @@ end # Load a remote CSS file. def load_uri!(uri, base_uri = nil, media_types = :all) base_uri = uri if base_uri.nil? - src, charset = read_remote_file(uri) - # Load @imported CSS - src.scan(RE_AT_IMPORT_RULE).each do |import_rule| - import_path = import_rule[1].to_s.gsub(/['"]*/, '').strip - import_uri = URI.parse(base_uri.to_s).merge(import_path) - #puts import_uri.to_s - - media_types = [] - if media_string = import_rule[import_rule.length-1] - media_string.split(/\s|\,/).each do |t| - media_types << t.to_sym unless t.empty? - end - end - - # Recurse - load_uri!(import_uri, nil, media_types) + src, charset = read_remote_file(uri) + unless src.empty? + add_block!(src, {:media_types => media_types, :base_uri => base_uri}) 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) - # Remove @import declarations - src.gsub!(RE_AT_IMPORT_RULE, '') + src = IO.read(file_name) + base_dir = File.dirname(file_name) - # Relative paths need to be converted here - src = CssParser.convert_uris(src, base_uri) if base_uri and @options[:absolute_paths] - - add_block!(src, {:media_types => media_types}) + add_block!(src, {:media_types => media_types, :base_dir => base_dir}) end + + protected # Strip comments and clean up blank lines from a block of CSS. # # Returns a string. @@ -296,11 +312,15 @@ # 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: - raise CircularReferenceError, "can't load #{uri.to_s} more than once" if @loaded_uris.include?(uri.to_s) + if @loaded_uris.include?(uri.to_s) + raise CircularReferenceError, "can't load #{uri.to_s} more than once" if @options[:io_exceptions] + return '', nil + end + @loaded_uris << uri.to_s begin #fh = open(uri, 'rb') fh = open(uri, 'rb', 'User-Agent' => USER_AGENT, 'Accept-Encoding' => 'gzip') @@ -316,11 +336,11 @@ ic = Iconv.new('UTF-8//IGNORE', fh.charset) src = ic.iconv(remote_src) fh.close return src, fh.charset - rescue + rescue Exception => e raise RemoteFileError if @options[:io_exceptions] return '', nil end end @@ -340,6 +360,6 @@ @css_source = '' @css_rules = [] @css_warnings = [] end end -end \ No newline at end of file +end