lib/css_parser/parser.rb in css_parser-1.4.10 vs lib/css_parser/parser.rb in css_parser-1.5.0.pre
- old
+ new
@@ -34,11 +34,12 @@
class << self; attr_reader :folded_declaration_cache; end
def initialize(options = {})
@options = {:absolute_paths => false,
:import => true,
- :io_exceptions => true}.merge(options)
+ :io_exceptions => true,
+ :capture_offsets => false}.merge(options)
# array of RuleSets
@rules = []
@redirect_count = nil
@@ -115,11 +116,11 @@
def add_block!(block, options = {})
options = {:base_uri => nil, :base_dir => nil, :charset => nil, :media_types => :all, :only_media_types => :all}.merge(options)
options[:media_types] = [options[:media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
options[:only_media_types] = [options[:only_media_types]].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
- block = cleanup_block(block)
+ block = cleanup_block(block, options)
if options[:base_uri] and @options[:absolute_paths]
block = CssParser.convert_uris(block, options[:base_uri])
end
@@ -137,21 +138,26 @@
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
+ import_options = { :media_types => media_types }
+ import_options[:capture_offsets] = true if options[:capture_offsets]
+
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)
+ import_options[:base_uri] = options[:base_uri]
+ load_uri!(import_uri, import_options)
elsif options[:base_dir]
- load_file!(import_path, options[:base_dir], media_types)
+ import_options[:base_dir] = options[:base_dir]
+ load_file!(import_path, import_options)
end
end
end
# Remove @import declarations
- block.gsub!(RE_AT_IMPORT_RULE, '')
+ block = ignore_pattern(block, RE_AT_IMPORT_RULE, options)
parse_block_into_rule_sets!(block, options)
end
# Add a CSS rule by setting the +selectors+, +declarations+ and +media_types+.
@@ -160,10 +166,20 @@
def add_rule!(selectors, declarations, media_types = :all)
rule_set = RuleSet.new(selectors, declarations)
add_rule_set!(rule_set, media_types)
end
+ # Add a CSS rule by setting the +selectors+, +declarations+, +filename+, +offset+ and +media_types+.
+ #
+ # +filename+ can be a string or uri pointing to the file or url location.
+ # +offset+ should be Range object representing the start and end byte locations where the rule was found in the file.
+ # +media_types+ can be a symbol or an array of symbols.
+ def add_rule_with_offsets!(selectors, declarations, filename, offset, media_types = :all)
+ rule_set = OffsetAwareRuleSet.new(filename, offset, selectors, declarations)
+ add_rule_set!(rule_set, media_types)
+ end
+
# Add a CssParser RuleSet object.
#
# +media_types+ can be a symbol or an array of symbols.
def add_rule_set!(ruleset, media_types = :all)
raise ArgumentError unless ruleset.kind_of?(CssParser::RuleSet)
@@ -287,13 +303,20 @@
current_selectors = ''
current_media_query = ''
current_declarations = ''
- block.scan(/(([\\]{2,})|([\\]?[{}\s"])|(.[^\s"{}\\]*))/).each do |matches|
+ # once we are in a rule, we will use this to store where we started if we are capturing offsets
+ rule_start = nil
+ offset = nil
+
+ block.scan(/(([\\]{2,})|([\\]?[{}\s"])|(.[^\s"{}\\]*))/) do |matches|
token = matches[0]
+ # save the regex offset so that we know where in the file we are
+ offset = Regexp.last_match.offset(0) if options[:capture_offsets]
+
if token =~ /\A"/ # found un-escaped double quote
in_string = !in_string
end
if in_declarations > 0
@@ -314,15 +337,22 @@
current_declarations.gsub!(/\}[\s]*$/, '')
in_declarations -= 1
unless current_declarations.strip.empty?
- add_rule!(current_selectors, current_declarations, current_media_queries)
+ if options[:capture_offsets]
+ add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
+ else
+ add_rule!(current_selectors, current_declarations, current_media_queries)
+ end
end
current_selectors = ''
current_declarations = ''
+
+ # restart our search for selectors and declarations
+ rule_start = nil if options[:capture_offsets]
end
elsif token =~ /@media/i
# found '@media', reset current media_types
in_at_media_rule = true
current_media_queries = []
@@ -354,23 +384,30 @@
current_media_queries = [:all]
in_media_block = false
end
else
if token =~ /\{/ and not in_string
- current_selectors.gsub!(/^[\s]*/, '')
- current_selectors.gsub!(/[\s]*$/, '')
+ current_selectors.strip!
in_declarations += 1
else
+ # if we are in a selector, add the token to the current selectors
current_selectors += token
+
+ # mark this as the beginning of the selector unless we have already marked it
+ rule_start = offset.first if options[:capture_offsets] && rule_start.nil? && token =~ /^[^\s]+$/
end
end
end
end
# check for unclosed braces
if in_declarations > 0
- add_rule!(current_selectors, current_declarations, current_media_queries)
+ if options[:capture_offsets]
+ add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
+ else
+ add_rule!(current_selectors, current_declarations, current_media_queries)
+ end
end
end
# Load a remote CSS file.
#
@@ -379,11 +416,10 @@
# See add_block! for options.
#
# Deprecated: originally accepted three params: `uri`, `base_uri` and `media_types`
def load_uri!(uri, options = {}, deprecated = nil)
uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme
- #base_uri = nil, media_types = :all, options = {}
opts = {:base_uri => nil, :media_types => :all}
if options.is_a? Hash
opts.merge!(options)
@@ -397,31 +433,54 @@
uri.scheme = 'file'
end
opts[:base_uri] = uri if opts[:base_uri].nil?
+ # pass on the uri if we are capturing file offsets
+ opts[:filename] = uri.to_s if opts[:capture_offsets]
+
src, = read_remote_file(uri) # skip charset
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)
+ def load_file!(file_name, options = {}, deprecated = nil)
+ opts = {:base_dir => nil, :media_types => :all}
+
+ if options.is_a? Hash
+ opts.merge!(options)
+ else
+ opts[:base_dir] = options if options.is_a? String
+ opts[:media_types] = deprecated if deprecated
+ end
+
+ file_name = File.expand_path(file_name, opts[:base_dir])
return unless File.readable?(file_name)
return unless circular_reference_check(file_name)
src = IO.read(file_name)
- base_dir = File.dirname(file_name)
- add_block!(src, {:media_types => media_types, :base_dir => base_dir})
+ opts[:filename] = file_name if opts[:capture_offsets]
+ opts[:base_dir] = File.dirname(file_name)
+
+ add_block!(src, opts)
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})
+ def load_string!(src, options = {}, deprecated = nil)
+ opts = {:base_dir => nil, :media_types => :all}
+
+ if options.is_a? Hash
+ opts.merge!(options)
+ else
+ opts[:base_dir] = options if options.is_a? String
+ opts[:media_types] = deprecated if deprecated
+ end
+
+ add_block!(src, opts)
end
protected
@@ -438,24 +497,35 @@
@loaded_uris << path
return true
end
end
+ # Remove a pattern from a given string
+ #
+ # Returns a string.
+ def ignore_pattern(css, regex, options)
+ # if we are capturing file offsets, replace the characters with spaces to retail the original positions
+ return css.gsub(regex) { |m| ' ' * m.length } if options[:capture_offsets]
+
+ # otherwise just strip it out
+ css.gsub(regex, '')
+ end
+
# Strip comments and clean up blank lines from a block of CSS.
#
# Returns a string.
- def cleanup_block(block) # :nodoc:
+ def cleanup_block(block, options = {}) # :nodoc:
# Strip CSS comments
- utf8_block = block.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
- utf8_block.gsub!(STRIP_CSS_COMMENTS_RX, '')
+ utf8_block = block.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: ' ')
+ utf8_block = ignore_pattern(utf8_block, STRIP_CSS_COMMENTS_RX, options)
# Strip HTML comments - they shouldn't really be in here but
# some people are just crazy...
- utf8_block.gsub!(STRIP_HTML_COMMENTS_RX, '')
+ utf8_block = ignore_pattern(utf8_block, STRIP_HTML_COMMENTS_RX, options)
# Strip lines containing just whitespace
- utf8_block.gsub!(/^\s+$/, "")
+ utf8_block.gsub!(/^\s+$/, "") unless options[:capture_offsets]
utf8_block
end
# Download a file into a string.
@@ -481,16 +551,20 @@
return nil, nil
end
src = '', charset = nil
- uri = Addressable::URI.parse(uri.to_s)
begin
+ uri = Addressable::URI.parse(uri.to_s)
+
if uri.scheme == 'file'
# local file
- fh = open(uri.path, 'rb')
+ path = uri.path
+ path.gsub!(/^\//, '') if Gem.win_platform?
+ fh = open(path, 'rb')
src = fh.read
+ charset = fh.respond_to?(:charset) ? fh.charset : 'utf-8'
fh.close
else
# remote file
if uri.scheme == 'https'
uri.port = 443 unless uri.port
@@ -501,10 +575,10 @@
http = Net::HTTP.new(uri.host, uri.port)
end
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'
+ charset = res.respond_to?(:charset) ? res.encoding : 'utf-8'
if res.code.to_i >= 400
@redirect_count = nil
raise RemoteFileError.new(uri.to_s) if @options[:io_exceptions]
return '', nil