class CssParser::Parser
Parser
class¶ ↑
All CSS is converted to UTF-8.
When calling Parser#new there are some configuaration options:
absolute_paths
-
Convert relative paths to absolute paths (
href
,src
andurl('')
. Boolean, default isfalse
. import
-
Follow
@import
rules. Boolean, default istrue
. io_exceptions
-
Throw an exception if a link can not be found. Boolean, default is
true
.
Constants
- MAX_REDIRECTS
- RE_AT_IMPORT_RULE
Initial parsing
- STRIP_CSS_COMMENTS_RX
- STRIP_HTML_COMMENTS_RX
- USER_AGENT
Attributes
Array of CSS files that have been loaded.
Public Class Methods
# File lib/css_parser/parser.rb, line 38 def initialize(options = {}) @options = {absolute_paths: false, import: true, io_exceptions: true, rule_set_exceptions: true, capture_offsets: false}.merge(options) # array of RuleSets @rules = [] @redirect_count = nil @loaded_uris = [] # unprocessed blocks of CSS @blocks = [] reset! end
Public Instance Methods
Add a raw block of CSS.
In order to follow +@import+ rules you must supply either a :base_dir
or :base_uri
option.
Use the :media_types
option to set the media type(s) for this block. Takes an array of symbols.
Use the :only_media_types
option to selectively follow +@import+ rules. Takes an array of symbols.
Example¶ ↑
css = <<-EOT body { font-size: 10pt } p { margin: 0px; } @media screen, print { body { line-height: 1.2 } } EOT parser = CssParser::Parser.new parser.add_block!(css)
# File lib/css_parser/parser.rb, line 118 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, options) if options[:base_uri] and @options[:absolute_paths] block = CssParser.convert_uris(block, options[:base_uri]) end # 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| 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.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[: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] load_uri!(import_uri, import_options) elsif options[:base_dir] import_options[:base_dir] = options[:base_dir] load_file!(import_path, import_options) end end end # Remove @import declarations 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
.
media_types
can be a symbol or an array of symbols.
# File lib/css_parser/parser.rb, line 168 def add_rule!(selectors, declarations, media_types = :all) rule_set = RuleSet.new(selectors, declarations) add_rule_set!(rule_set, media_types) rescue ArgumentError => e raise e if @options[:rule_set_exceptions] end
Add a CssParser
RuleSet
object.
media_types
can be a symbol or an array of symbols.
# File lib/css_parser/parser.rb, line 188 def add_rule_set!(ruleset, media_types = :all) raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet) 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} 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.
# File lib/css_parser/parser.rb, line 180 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
Iterate through RuleSet
objects.
media_types
can be a symbol or an array of symbols.
# File lib/css_parser/parser.rb, line 213 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], 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
.
# File lib/css_parser/parser.rb, line 249 def each_selector(all_media_types = :all, options = {}) # :yields: selectors, declarations, specificity, media_types 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
Get declarations by selector.
media_types
are optional, and can be a symbol or an array of symbols. The default value is :all
.
Examples¶ ↑
find_by_selector('#content') => 'font-size: 13px; line-height: 1.2;' find_by_selector('#content', [:screen, :handheld]) => 'font-size: 13px; line-height: 1.2;' find_by_selector('#content', :print) => 'font-size: 11pt; line-height: 1.2;'
Returns an array of declarations.
# File lib/css_parser/parser.rb, line 73 def find_by_selector(selector, media_types = :all) out = [] each_selector(media_types) do |sel, dec, _spec| out << dec if sel.strip == selector.strip end out end
Finds the rule sets that match the given selectors
# File lib/css_parser/parser.rb, line 83 def find_rule_sets(selectors, media_types = :all) rule_sets = [] selectors.each do |selector| 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 rule_sets end
Load a local CSS file.
# File lib/css_parser/parser.rb, line 464 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) 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.
# File lib/css_parser/parser.rb, line 487 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
Load a remote CSS file.
You can also pass in file://test.css
See add_block! for options.
Deprecated: originally accepted three params: ‘uri`, `base_uri` and `media_types`
# File lib/css_parser/parser.rb, line 436 def load_uri!(uri, options = {}, deprecated = nil) uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme 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 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 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 add_block!(src, opts) if src end
Remove a CssParser
RuleSet
object.
media_types
can be a symbol or an array of symbols.
# File lib/css_parser/parser.rb, line 200 def remove_rule_set!(ruleset, media_types = :all) raise ArgumentError unless ruleset.is_a?(CssParser::RuleSet) 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
A hash of { :media_query => rule_sets }
# File lib/css_parser/parser.rb, line 291 def rules_by_media_query rules_by_media = {} @rules.each do |block| block[:media_types].each do |mt| unless rules_by_media.key?(mt) rules_by_media[mt] = [] end rules_by_media[mt] << block[:rules] end end rules_by_media end
Output all CSS rules as a Hash
# File lib/css_parser/parser.rb, line 225 def to_h(which_media = :all) out = {} styles_by_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| ms = {} media_styles.each do |media_style| ms = css_node_to_h(ms, media_style[0], media_style[1]) end out[media_type.to_s] = ms end out end
Output all CSS rules as a single stylesheet.
# File lib/css_parser/parser.rb, line 260 def to_s(which_media = :all) out = [] styles_by_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} {" if media_block media_styles.each do |media_style| if media_block out.push(" #{media_style[0]} {\n #{media_style[1]}\n }") else out.push("#{media_style[0]} {\n#{media_style[1]}\n}") end end out << '}' if media_block end out << '' out.join("\n") end
Protected Instance Methods
Check that a path hasn’t been loaded already
Raises a CircularReferenceError
exception if io_exceptions are on, otherwise returns true/false.
# File lib/css_parser/parser.rb, line 506 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] false else @loaded_uris << path true end end
Remove a pattern from a given string
Returns a string.
# File lib/css_parser/parser.rb, line 521 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
Private Instance Methods
recurse through nested nodes and return them as Hashes nested in passed hash
# File lib/css_parser/parser.rb, line 656 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] =~ /:/ nodes[parts[0]] = css_node_to_h(hash, parts[0], parts[1]) else nodes[parts[0].to_s.strip] = parts[1].to_s.strip end end hash[key.strip] = nodes hash end