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 and url(''). Boolean, default is false.

import

Follow @import rules. Boolean, default is true.

io_exceptions

Throw an exception if a link can not be found. Boolean, default is true.

Constants

RE_AT_IMPORT_RULE

Initial parsing

STRIP_CSS_COMMENTS_RX
STRIP_HTML_COMMENTS_RX
USER_AGENT

Attributes

folded_declaration_cache[R]
loaded_uris[R]

Array of CSS files that have been loaded.

Public Class Methods

new(options = {}) click to toggle source
# File lib/css_parser/parser.rb, line 34
def initialize(options = {})
  @options = {:absolute_paths => false,
              :import => true,
              :io_exceptions => true}.merge(options)

  # array of RuleSets
  @rules = []


  @loaded_uris = []

  # unprocessed blocks of CSS
  @blocks = []
  reset!
end

Public Instance Methods

[](selector, media_types = :all)
Alias for: find_by_selector
add_block!(block, options = {}) click to toggle source

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 110
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)

  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.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_rule!(selectors, declarations, media_types = :all) click to toggle source

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 155
def add_rule!(selectors, declarations, media_types = :all)
  rule_set = RuleSet.new(selectors, declarations)
  add_rule_set!(rule_set, media_types)
end
add_rule_set!(ruleset, media_types = :all) click to toggle source

Add a CssParser RuleSet object.

media_types can be a symbol or an array of symbols.

# File lib/css_parser/parser.rb, line 163
def add_rule_set!(ruleset, media_types = :all)
  raise ArgumentError unless ruleset.kind_of?(CssParser::RuleSet)

  media_types = [media_types].flatten.collect { |mt| CssParser.sanitize_media_query(mt)}

  @rules << {:media_types => media_types, :rules => ruleset}
end
each_rule_set(media_types = :all) { |rule_set, media_types| ... } click to toggle source

Iterate through RuleSet objects.

media_types can be a symbol or an array of symbols.

# File lib/css_parser/parser.rb, line 174
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
each_selector(media_types = :all, options = {}) { |selectors, declarations, specificity, media_types| ... } click to toggle source

Iterate through CSS selectors.

media_types can be a symbol or an array of symbols. See CssParser::RuleSet#each_selector for options.

# File lib/css_parser/parser.rb, line 189
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, media_types
    end
  end
end
find_by_selector(selector, media_types = :all) click to toggle source

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 66
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
Also aliased as: []
find_rule_sets(selectors, media_types = :all) click to toggle source

Finds the rule sets that match the given selectors

# File lib/css_parser/parser.rb, line 76
def find_rule_sets(selectors, media_types = :all)
  rule_sets = []

  selectors.each do |selector|
    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_file!(file_name, base_dir = nil, media_types = :all) click to toggle source

Load a local CSS file.

# File lib/css_parser/parser.rb, line 383
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)

  src = IO.read(file_name)
  base_dir = File.dirname(file_name)

  add_block!(src, {:media_types => media_types, :base_dir => base_dir})
end
load_string!(src, base_dir = nil, media_types = :all) click to toggle source

Load a local CSS string.

# File lib/css_parser/parser.rb, line 395
def load_string!(src, base_dir = nil, media_types = :all)
  add_block!(src, {:media_types => media_types, :base_dir => base_dir})
end
load_uri!(uri, options = {}, deprecated = nil) click to toggle source

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 356
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)
  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?

  src, charset = read_remote_file(uri)
  if src
    add_block!(src, opts)
  end
end
rules_by_media_query() click to toggle source

A hash of { :media_query => rule_sets }

# File lib/css_parser/parser.rb, line 227
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)
        rules_by_media[mt] = []
      end
      rules_by_media[mt] << block[:rules]
    end
  end

  rules_by_media
end
to_s(media_types = :all) click to toggle source

Output all CSS rules as a single stylesheet.

# File lib/css_parser/parser.rb, line 198
def to_s(media_types = :all)
  out = ''
  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

Protected Instance Methods

circular_reference_check(path) click to toggle source

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 406
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
  else
    @loaded_uris << path
    return true
  end
end