Report abuse

#!/usr/bin/ruby

# Code to take a CSS file and an HTML file and render the styles inline.
# This is bad practice on the web, but can be very helpful when producing
# HTML email where external stylesheets can be problematic. Using this you
# can develop using external stylesheets and then easily move the styles
# inline.
#
# If the file is run from the command line it will take paths to a 
# stylesheet and an HTML file and output the combined code
#
# Author::    James Stewart  (mailto: james@jystewart.net)
# Copyright:: Copyright (c) 2007 James Stewart
# License::   Distributes under the same terms as Ruby

# This class takes a CSS file and provides a method to
# parse it into a hash. Usage is:
# 
# parser = SimpleCSSParser.new('/path/to/myfile.css')
# hash_of_rules = parser.to_hash
#
# For more advanced CSS handling check out the cssparser gem
# http://code.dunae.ca/css_parser/
class SimpleCSSParser

  # Receive and open the CSS file, storing its contents
  def initialize(path_to_file)
    @css = open(path_to_file).read
  end

  # Convert the CSS into a hash, where the keys are the selectors
  # and the values are the rules
  def to_hash
    @to_hash ||= separate_rules.inject({}) do |collection, rule|
      identifiers, rule = prepare_selectors_and_rule(rule)
      identifiers.each do |identifier|
        collection[identifier] ||= ''
        collection[identifier] += rule
      end
      collection
    end
  end

  private
    def separate_rules
      @css.split('}')
    end

    # Strip comments and extraneous white space from our CSS rules
    def clean_up_rule(css_rule)
      css_rule = css_rule.gsub(/\/\*.+?\*\//, '')
      css_rule.gsub(/\n|\s{2,}/, '')
    end

    # Break apart our selector(s) and rule. We return an array
    # of selectors to allow for situations where multiple selectors
    # are specified (comma separated) for a single rule
    def prepare_selectors_and_rule(rule)
      parts = rule.split('{')
      selectors = parts[0].split(',').map(&:strip)
      return selectors, clean_up_rule(parts[1])
    end
end

if __FILE__ == $0
  if ARGV.length == 2
    require 'rubygems'
    require 'hpricot'

    doc = Hpricot(open(ARGV[1]))
    parser = SimpleCSSParser.new(ARGV[0])

    parser.to_hash.each do |selector, rule|
      (doc/selector).set('style', rule)
    end

    puts doc
  else
    puts "Usage ./css-inliner.rb /path/to/stylesheet.css /path/to/webpage.html"
  end
end