lib/css_parser/parser.rb in css_parser-1.7.1 vs lib/css_parser/parser.rb in css_parser-1.8.0
- old
+ new
@@ -1,6 +1,7 @@
# frozen_string_literal: true
+
module CssParser
# Exception class used for any errors encountered while downloading remote files.
class RemoteFileError < IOError; end
# Exception class used if a request is made to load a CSS file more than once.
@@ -13,17 +14,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/#{CssParser::VERSION} (https://github.com/premailer/css_parser)"
+ USER_AGENT = "Ruby CSS Parser/#{CssParser::VERSION} (https://github.com/premailer/css_parser)"
- STRIP_CSS_COMMENTS_RX = /\/\*.*?\*\//m
- STRIP_HTML_COMMENTS_RX = /\<\!\-\-|\-\-\>/m
+ STRIP_CSS_COMMENTS_RX = %r{/\*.*?\*/}m.freeze
+ STRIP_HTML_COMMENTS_RX = /<!--|-->/m.freeze
# Initial parsing
- RE_AT_IMPORT_RULE = /\@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s\)]*)["']?\)?([\w\s\,^\]\(\)]*)\)?[;\n]?/
+ RE_AT_IMPORT_RULE = /@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s)]*)["']?\)?([\w\s,^\]()]*)\)?[;\n]?/.freeze
MAX_REDIRECTS = 3
# Array of CSS files that have been loaded.
attr_reader :loaded_uris
@@ -33,14 +34,14 @@
#++
@folded_declaration_cache = {}
class << self; attr_reader :folded_declaration_cache; end
def initialize(options = {})
- @options = {:absolute_paths => false,
- :import => true,
- :io_exceptions => true,
- :capture_offsets => false}.merge(options)
+ @options = {absolute_paths: false,
+ import: true,
+ io_exceptions: true,
+ capture_offsets: false}.merge(options)
# array of RuleSets
@rules = []
@redirect_count = nil
@@ -68,25 +69,24 @@
# => 'font-size: 11pt; line-height: 1.2;'
#
# Returns an array of declarations.
def find_by_selector(selector, media_types = :all)
out = []
- each_selector(media_types) do |sel, dec, spec|
+ each_selector(media_types) do |sel, dec, _spec|
out << dec if sel.strip == selector.strip
end
out
end
- alias_method :[], :find_by_selector
+ alias [] find_by_selector
# Finds the rule sets that match the given selectors
def find_rule_sets(selectors, media_types = :all)
rule_sets = []
selectors.each do |selector|
- selector.gsub!(/\s+/, ' ')
- selector.strip!
- each_rule_set(media_types) do |rule_set, media_type|
+ selector = selector.gsub(/\s+/, ' ').strip
+ 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
@@ -113,13 +113,13 @@
# EOT
#
# parser = CssParser::Parser.new
# parser.add_block!(css)
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)}
+ 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, options)
if options[:base_uri] and @options[:absolute_paths]
block = CssParser.convert_uris(block, options[:base_uri])
@@ -127,23 +127,23 @@
# Load @imported CSS
if @options[:import]
block.scan(RE_AT_IMPORT_RULE).each do |import_rule|
media_types = []
- if media_string = import_rule[-1]
- media_string.split(/[,]/).each do |t|
+ 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
+ next unless options[:only_media_types].include?(:all) or media_types.empty? or !(media_types & options[:only_media_types]).empty?
import_path = import_rule[0].to_s.gsub(/['"]*/, '').strip
- import_options = { :media_types => media_types }
+ 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)
import_options[:base_uri] = options[:base_uri]
@@ -181,25 +181,25 @@
# 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)
+ raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
- media_types = [media_types] unless Array === media_types
- media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt)}
+ media_types = [media_types] unless media_types.is_a?(Array)
+ media_types = media_types.flat_map { |mt| CssParser.sanitize_media_query(mt) }
- @rules << {:media_types => media_types, :rules => ruleset}
+ @rules << {media_types: media_types, rules: ruleset}
end
# Remove a CssParser RuleSet object.
#
# +media_types+ can be a symbol or an array of symbols.
def remove_rule_set!(ruleset, media_types = :all)
- raise ArgumentError unless ruleset.kind_of?(CssParser::RuleSet)
+ raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet)
- media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
+ media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
@rules.reject! do |rule|
rule[:media_types] == media_types && rule[:rules].to_s == ruleset.to_s
end
end
@@ -207,11 +207,11 @@
# 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, media_types
media_types = [:all] if media_types.nil?
- media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
+ 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], block[:media_types])
end
@@ -220,11 +220,11 @@
# Output all CSS rules as a Hash
def to_h(which_media = :all)
out = {}
styles_by_media_types = {}
- each_selector(which_media) do |selectors, declarations, specificity, media_types|
+ each_selector(which_media) 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
@@ -242,54 +242,56 @@
# Iterate through CSS selectors.
#
# +media_types+ can be a symbol or an array of symbols.
# See RuleSet#each_selector for +options+.
def each_selector(all_media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types
- return to_enum(:each_selector) unless block_given?
+ return to_enum(__method__, all_media_types, options) unless block_given?
each_rule_set(all_media_types) do |rule_set, media_types|
rule_set.each_selector(options) do |selectors, declarations, specificity|
yield selectors, declarations, specificity, media_types
end
end
end
# Output all CSS rules as a single stylesheet.
def to_s(which_media = :all)
- out = String.new
+ out = []
styles_by_media_types = {}
- each_selector(which_media) do |selectors, declarations, specificity, media_types|
+
+ each_selector(which_media) 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
+ out << "@media #{media_type} {" if media_block
media_styles.each do |media_style|
if media_block
- out << " #{media_style[0]} {\n #{media_style[1]}\n }\n"
+ out.push(" #{media_style[0]} {\n #{media_style[1]}\n }")
else
- out << "#{media_style[0]} {\n#{media_style[1]}\n}\n"
+ out.push("#{media_style[0]} {\n#{media_style[1]}\n}")
end
end
- out << "}\n" if media_block
+ out << '}' if media_block
end
- out
+ out << ''
+ out.join("\n")
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|
- unless rules_by_media.has_key?(mt)
+ unless rules_by_media.key?(mt)
rules_by_media[mt] = []
end
rules_by_media[mt] << block[:rules]
end
end
@@ -297,19 +299,17 @@
rules_by_media
end
# Merge declarations with the same selector.
def compact! # :nodoc:
- compacted = []
-
- compacted
+ []
end
def parse_block_into_rule_sets!(block, options = {}) # :nodoc:
current_media_queries = [:all]
if options[:media_types]
- current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}
+ current_media_queries = options[:media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt) }
end
in_declarations = 0
block_depth = 0
@@ -324,11 +324,11 @@
# 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(/\s+|[\\]{2,}|[\\]?[{}\s"]|.[^\s"{}\\]*/) do |token|
+ block.scan(/\s+|\\{2,}|\\?[{}\s"]|.[^\s"{}\\]*/) do |token|
# 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.start_with?('"') # found un-escaped double quote
in_string = !in_string
@@ -347,11 +347,11 @@
end
current_declarations << token
if !in_string && token.include?('}')
- current_declarations.gsub!(/\}[\s]*$/, '')
+ current_declarations.gsub!(/\}\s*$/, '')
in_declarations -= 1
current_declarations.strip!
unless current_declarations.empty?
@@ -372,11 +372,11 @@
# found '@media', reset current media_types
in_at_media_rule = true
current_media_queries = []
elsif in_at_media_rule
if token.include?('{')
- block_depth = block_depth + 1
+ block_depth += 1
in_at_media_rule = false
in_media_block = true
current_media_queries << CssParser.sanitize_media_query(current_media_query)
current_media_query = String.new
elsif token.include?(',')
@@ -391,42 +391,38 @@
current_media_query << token << ' '
end
elsif in_charset or token =~ /@charset/i
# iterate until we are out of the charset declaration
in_charset = !token.include?(';')
- else
- if !in_string && token.include?('}')
- block_depth = block_depth - 1
+ elsif !in_string && token.include?('}')
+ block_depth -= 1
- # reset the current media query scope
- if in_media_block
- current_media_queries = [:all]
- in_media_block = false
- end
- else
- if !in_string && token.include?('{')
- 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
+ # reset the current media query scope
+ if in_media_block
+ current_media_queries = [:all]
+ in_media_block = false
end
+ elsif !in_string && token.include?('{')
+ 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
# check for unclosed braces
- if in_declarations > 0
- 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
+ return unless in_declarations > 0
+
+ unless options[:capture_offsets]
+ return add_rule!(current_selectors, current_declarations, current_media_queries)
end
+
+ add_rule_with_offsets!(current_selectors, current_declarations, options[:filename], (rule_start..offset.last), current_media_queries)
end
# Load a remote CSS file.
#
# You can also pass in file://test.css
@@ -435,11 +431,11 @@
#
# 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
- opts = {:base_uri => nil, :media_types => :all}
+ opts = {base_uri: nil, media_types: :all}
if options.is_a? Hash
opts.merge!(options)
else
opts[:base_uri] = options if options.is_a? String
@@ -455,18 +451,17 @@
# 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
+
+ add_block!(src, opts) if src
end
# Load a local CSS file.
def load_file!(file_name, options = {}, deprecated = nil)
- opts = {:base_dir => nil, :media_types => :all}
+ 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
@@ -485,11 +480,11 @@
add_block!(src, opts)
end
# Load a local CSS string.
def load_string!(src, options = {}, deprecated = nil)
- opts = {:base_dir => nil, :media_types => :all}
+ 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
@@ -497,25 +492,25 @@
end
add_block!(src, opts)
end
-
-
protected
+
# Check that a path hasn't been loaded already
#
# 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]
- return false
+
+ false
else
@loaded_uris << path
- return true
+ true
end
end
# Remove a pattern from a given string
#
@@ -539,11 +534,11 @@
# Strip HTML comments - they shouldn't really be in here but
# some people are just crazy...
utf8_block = ignore_pattern(utf8_block, STRIP_HTML_COMMENTS_RX, options)
# Strip lines containing just whitespace
- utf8_block.gsub!(/^\s+$/, "") unless options[:capture_offsets]
+ utf8_block.gsub!(/^\s+$/, '') unless options[:capture_offsets]
utf8_block
end
# Download a file into a string.
@@ -575,15 +570,12 @@
uri = Addressable::URI.parse(uri.to_s)
if uri.scheme == 'file'
# local file
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
+ path.gsub!(%r{^/}, '') if Gem.win_platform?
+ src = File.read(path, mode: 'rb')
else
# remote file
if uri.scheme == 'https'
uri.port = 443 unless uri.port
http = Net::HTTP.new(uri.host, uri.port)
@@ -597,25 +589,26 @@
src = res.body
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]
+ raise RemoteFileError, uri.to_s if @options[:io_exceptions]
+
return '', nil
elsif res.code.to_i >= 300 and res.code.to_i < 400
- if res['Location'] != nil
+ unless res['Location'].nil?
return read_remote_file Addressable::URI.parse(Addressable::URI.escape(res['Location']))
end
end
case res['content-encoding']
- when 'gzip'
- io = Zlib::GzipReader.new(StringIO.new(res.body))
- src = io.read
- when 'deflate'
- io = Zlib::Inflate.new
- src = io.inflate(res.body)
+ when 'gzip'
+ io = Zlib::GzipReader.new(StringIO.new(res.body))
+ src = io.read
+ when 'deflate'
+ io = Zlib::Inflate.new
+ src = io.inflate(res.body)
end
end
if charset
if String.method_defined?(:encode)
@@ -625,27 +618,29 @@
src = ic.iconv(src)
end
end
rescue
@redirect_count = nil
- raise RemoteFileError.new(uri.to_s)if @options[:io_exceptions]
+ raise RemoteFileError, uri.to_s if @options[:io_exceptions]
+
return nil, nil
end
@redirect_count = nil
- return src, charset
+ [src, charset]
end
private
+
# Save a folded declaration block to the internal cache.
def save_folded_declaration(block_hash, folded_declaration) # :nodoc:
@folded_declaration_cache[block_hash] = folded_declaration
end
# Retrieve a folded declaration block from the internal cache.
def get_folded_declaration(block_hash) # :nodoc:
- return @folded_declaration_cache[block_hash] ||= nil
+ @folded_declaration_cache[block_hash] ||= nil
end
def reset! # :nodoc:
@folded_declaration_cache = {}
@css_source = ''
@@ -655,17 +650,18 @@
# recurse through nested nodes and return them as Hashes nested in
# passed hash
def css_node_to_h(hash, key, val)
hash[key.strip] = '' and return hash if val.nil?
+
lines = val.split(';')
nodes = {}
lines.each do |line|
parts = line.split(':', 2)
- if (parts[1] =~ /:/)
+ if parts[1] =~ /:/
nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1])
else
- nodes[parts[0].to_s.strip] =parts[1].to_s.strip
+ nodes[parts[0].to_s.strip] = parts[1].to_s.strip
end
end
hash[key.strip] = nodes
hash
end