Skip to content

Commit

Permalink
better coverage, fixes, version bump
Browse files Browse the repository at this point in the history
  • Loading branch information
Macario committed Oct 29, 2009
2 parents 71dc377 + 7beaf37 commit 95186e3
Show file tree
Hide file tree
Showing 19 changed files with 352 additions and 333 deletions.
14 changes: 0 additions & 14 deletions Manifest.txt

This file was deleted.

22 changes: 13 additions & 9 deletions README.rdoc
@@ -1,11 +1,13 @@
= inline-style

* http://github.com/maca/inline-style
http://github.com/maca/inline-style

== DESCRIPTION:

Simple utility for "inlining" all CSS in the style attribute for the html tags. Useful for html emails that won't
correctly render stylesheets in some clients such as gmail.
Will take all css in a page (either from linked stylesheet or from style tag) and will embed it in the style attribute for
each refered element taking selector specificity and declarator order.

Useful for html email: some clients (gmail, et all) won't render non inline styles.

* Includes a Rack middleware for using with Rails, Sinatra, etc...
* It takes into account selector specificity.
Expand All @@ -14,7 +16,7 @@ correctly render stylesheets in some clients such as gmail.
require 'inline-style'

html = File.read("#{ dir }/index.html")
puts InlineStyle.process(html, "#{ dir }/styles")
puts InlineStyle.process(html, :stylesheets_paths => "#{ dir }/styles")

index.html contains:

Expand Down Expand Up @@ -63,7 +65,7 @@ index.html contains:
</li>
</ul>

Will output:
Will become:

<ul id="number" class="listing inlined" style='font-family: "Lucida Grande", Lucida, Verdana, sans-serif;margin: 4.0px 3.0px 2.0px 1.0px;padding: 0.0;background-color: yellow;'>
<li class="list-element odd" style='font-family: "Lucida Grande", Lucida, Verdana, sans-serif;margin: 4.0px 3.0px 2.0px 1.0px;padding: 0.0;background-color: black;'>
Expand All @@ -80,7 +82,7 @@ Will output:
</li>
</ul>

As rack middleware:
== RACK MIDDLEWARE:

# Process all routes:
use InlineStyle::Rack::Middleware
Expand All @@ -92,16 +94,18 @@ As rack middleware:
use InlineStyle::Rack::Middleware, :paths => [%r(/mails/.*), "/somepath"]

== ISSUES:

* Doesn't work with relative stylesheet links

* It supports pseudo classes according to W3C specification for style in style attribute: http://www.w3.org/TR/css-style-attr, although browsers
doesn't seems to.
* It strips any numeric character (Rails may add) at the end of the stylesheet file name, anyway stylesheets should end with .css extension

== REQUIREMENTS:

tenderlove's nokogiri and csspool

== INSTALL:

sudo gem install inline-style --source http://gemcutter.org
sudo gem install inline-style --source http://gemcutter.org

== LICENSE:

Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Expand Up @@ -8,7 +8,7 @@ $hoe = Hoe.new('inline-style', InlineStyle::VERSION) do |p|
p.rubyforge_name = p.name
p.extra_deps = [
['nokogiri','>= 1.3.3'],
['csspool', '>= 2.0.0']
['maca-fork-csspool', '>= 2.0.2']
]
p.extra_dev_deps = [
['newgem', ">= #{::Newgem::VERSION}"]
Expand Down
12 changes: 9 additions & 3 deletions example.rb
@@ -1,6 +1,12 @@
require 'rubygems'
require "#{ dir = File.dirname(__FILE__) }/lib/inline-style"
require 'benchmark'

html = File.read("#{ dir }/spec/fixtures/with-style-tag.html")
puts InlineStyle.process(html, "#{ dir }/spec/fixtures")
html = File.read("#{ fixtures = dir + '/spec/fixtures' }/boletin.html")



File.open("#{ fixtures }/inline.html", 'w') {|f| f.write InlineStyle.process(html, dir) }


# h = Nokogiri.HTML(html)
# p InlineStyle.extract_css(h)
46 changes: 0 additions & 46 deletions inline-style.gemspec

This file was deleted.

59 changes: 43 additions & 16 deletions lib/inline-style.rb
@@ -1,22 +1,36 @@
require 'nokogiri'
require 'open-uri'
require 'csspool'
require '/Users/sistemasinteractivos/Gems/Web/csspool/lib/csspool'

require "#{ File.dirname( __FILE__ ) }/inline-style/rack/middleware"

module InlineStyle
VERSION = '0.0.1'

def self.process html, document_root = ''
VERSION = '0.4'

# Options:
# +:stylesheets_path+
# Stylesheets root path, can also be a URL
#
# +pseudo+
# If set to true will inline style for pseudo classes according to the W3C specification:
# http://www.w3.org/TR/css-style-attr.
# Defaults to false and should probably be left like that because at least Safari and Firefox don't seem to
# comply with the specification for pseudo class style in the style attribute.
def self.process html, opts = {}
stylesheets_path = opts[:stylesheets_path] || ''
pseudo = opts[:pseudo] || false

nokogiri_doc_given = Nokogiri::HTML::Document === html

html = nokogiri_doc_given ? html : Nokogiri.HTML(html)
css = extract_css html, document_root
css = extract_css html, stylesheets_path
nodes = {}

css.rule_sets.each do |rule_set|
rule_set.selectors.each do |selector|
html.css("body #{ selector }").each do |node|
css_selector = selector.to_s
css_selector = "#{ 'body ' unless /^body/ === css_selector }#{ css_selector.gsub /:.*/, '' }"

html.css(css_selector).each do |node|
nodes[node] ||= []
nodes[node].push selector

Expand All @@ -26,29 +40,42 @@ def self.process html, document_root = ''
path << "##{ node['id'] }" if node['id']
path << ".#{ node['class'].scan(/\S+/).join('.') }" if node['class']

CSSPool.CSS("#{ path }{#{ node['style'] }}").rule_sets.each{ |rule| nodes[node].push *rule.selectors}
CSSPool.CSS("#{ path }{#{ node['style'] }}").rule_sets.each{ |rule| nodes[node].push *rule.selectors }
end
end
end

nodes.each_pair do |node, style|
style = style.sort_by{ |sel| "#{ sel.specificity }#{ style.index sel }" } #TO fix
style.map!{ |sel| sel.declarations.map{ |d| d.to_css.strip }.join } and style.uniq!
node['style'] = style.join
style = style.sort_by{ |sel| "#{ sel.specificity }%03d" % style.index(sel) }
sets = style.partition{ |sel| not /:\w+/ === sel.to_s }

sets.pop if not pseudo or sets.last.empty?

node['style'] = sets.collect do |selectors|
index = sets.index selectors

set = selectors.map do |selector|
declarations = selector.declarations.map{ |d| d.to_css.squeeze(' ') }.join
index == 0 ? declarations : "\n#{ selector.to_s.gsub /\w(?=:)/, '' } {#{ declarations }}"
end

index == 0 && sets.size > 1 ? "{#{ set }}" : set.join
end.join.strip
end

nokogiri_doc_given ? html : html.to_s
end

def self.extract_css html, document_root
# Returns CSSPool::Document
def self.extract_css html, stylesheets_path = ''
CSSPool.CSS html.css('style, link').collect { |e|
next unless e['media'].nil? or e['media'].match /\bscreen\b/
next unless e['media'].nil? or ['screen', 'all'].include? e['media']
next(e.remove and e.content) if e.name == 'style'
next unless e['rel'] == 'stylesheet'
e.remove
next open(e['href']).read if %r{^https?://} === e['href']
File.read File.join(document_root, e['href']) rescue ''

uri = %r{^https?://} === e['href'] ? e['href'] : File.join(stylesheets_path, e['href'].sub(/\?\d+$/,''))
open(uri).read rescue nil
}.join("\n")
end

end
23 changes: 15 additions & 8 deletions lib/inline-style/rack/middleware.rb
@@ -1,7 +1,7 @@
module InlineStyle
module Rack

class Middleware
#
# Options:
# +document_root+
# File system path for app's public directory where the stylesheets are to be found, defaults to
Expand All @@ -10,11 +10,19 @@ class Middleware
# +paths+
# Limit processing to the passed absolute paths
# Can be an array of strings or regular expressions, a single string or regular expression
# If not passed will process output for every path
# If not passed will process output for every path.
# Regexps and strings must comence with '/'
#
# +pseudo+
# If set to true will inline style for pseudo classes according to the W3C specification:
# http://www.w3.org/TR/css-style-attr.
# Defaults to false and should probably be left like that because at least Safari and Firefox don't seem to
# comply with the specification for pseudo class style in the style attribute.
#
def initialize app, opts = {}
@app = app
@document_root = opts[:document_root]
@paths = Regexp.new [*opts[:paths]].join('|')
@app = app
@opts = {:document_root => env['DOCUMENT_ROOT']}.merge(opts)
@paths = /^(?:#{ [*opts[:paths]].join('|') })/
end

def call env
Expand All @@ -24,12 +32,11 @@ def call env
status, headers, content = response
response = ::Rack::Response.new '', status, headers
body = content.respond_to?(:body) ? content.body : content
response.write InlineStyle.process(body, @document_root || env['DOCUMENT_ROOT'])

response.write InlineStyle.process(body, @opts)
response.finish
end
end

end
end

37 changes: 0 additions & 37 deletions rack-inline-styles.gemspec

This file was deleted.

30 changes: 15 additions & 15 deletions spec/as_middleware_spec.rb
Expand Up @@ -12,46 +12,46 @@ def get_response path, body, opts = {}
end

before do
@html = File.read("#{ FIXTURES }/with-style-tag.html")
@html = File.read("#{ FIXTURES }/boletin.html")
end

it "should inline css" do
get_response('/', @html, :stylesheets_path => FIXTURES).should have_inline_style_for('.list-element')
get_response('/', @html, :stylesheets_path => FIXTURES).should have_inline_style_for('#A')
end

describe 'Path inclusion' do

it "should inline style for string path" do
paths = "/some/path"
get_response('/some/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('.list-element')
get_response('/some/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('#A')
end

it "should not inline style for string path" do
paths = "/some/path"
get_response('/some/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('.list-element')
get_response('/some/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('#A')
end

it "should inline style for regexp path" do
paths = %r{some/.*}
get_response('/some/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('.list-element')
get_response('/some/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('.list-element')
paths = %r{/some/.*}
get_response('/some/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('#A')
get_response('/some/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('#A')
end

it "should not inline style for regexp path" do
paths = %r{some/.*}
get_response('/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('.list-element')
paths = %r{/some/.*}
get_response('/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('#A')
end

it "should inline style for array regexp path" do
paths = [%r{some/path}, %r{/some/other/path}]
get_response('/some/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('.list-element')
get_response('/some/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('.list-element')
paths = [%r{/some/path}, %r{/some/other/path}]
get_response('/some/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('#A')
get_response('/some/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should have_inline_style_for('#A')
end

it "should not inline style for array regexp path" do
paths = [%r{some/path}, %r{/some/other/path}]
get_response('/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('.list-element')
get_response('/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('.list-element')
paths = [%r{/some/path}, %r{/some/other/path}]
get_response('/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('#A')
get_response('/other/path', @html, :stylesheets_path => FIXTURES, :paths => paths).should_not have_inline_style_for('#A')
end
end
end

0 comments on commit 95186e3

Please sign in to comment.