inline-style 0.4.6 → 0.4.10

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- inline-style (0.4.6)
4
+ inline-style (0.4.9)
5
5
  css_parser
6
6
  maca-fork-csspool
7
7
  nokogiri
@@ -12,7 +12,7 @@ GEM
12
12
  activesupport (3.0.4)
13
13
  css_parser (1.1.5)
14
14
  diff-lcs (1.1.2)
15
- ffi (1.0.6)
15
+ ffi (1.0.7)
16
16
  rake (>= 0.8.7)
17
17
  i18n (0.5.0)
18
18
  maca-fork-csspool (2.0.2)
@@ -42,11 +42,8 @@ PLATFORMS
42
42
  ruby
43
43
 
44
44
  DEPENDENCIES
45
- css_parser
46
45
  inline-style!
47
- maca-fork-csspool
48
46
  mail
49
- nokogiri
50
47
  rack
51
48
  rspec
52
49
  rspec-core
@@ -2,7 +2,8 @@
2
2
 
3
3
  http://github.com/maca/inline-style
4
4
 
5
- == DESCRIPTION:
5
+
6
+ == Description
6
7
 
7
8
  Will take all css in a page (either from linked stylesheet or from style tag) and will embed it in the style attribute for
8
9
  each refered element taking selector specificity and declarator order.
@@ -14,7 +15,13 @@ Useful for html email: some clients (gmail, et all) won't render non inline styl
14
15
  inline processing for both mail as well as ActionMailer.
15
16
  * It takes into account selector specificity.
16
17
 
17
- == USAGE
18
+
19
+ == Usage
20
+
21
+
22
+ gem 'maca-fork-csspool' # this is optional if csspool is required inline-style will resort to it for css parsing, otherwise it will use the pure ruby css_parser
23
+ require 'csspool' # csspool should be faster because it uses librcroco written in C wich can also be an issue, use css_parser if you deploy to heroku
24
+
18
25
  require 'inline-style'
19
26
 
20
27
  html = File.read("#{ dir }/index.html")
@@ -83,8 +90,9 @@ Will become:
83
90
  <span style='font-family: "Lucida Grande", Lucida, Verdana, sans-serif;margin: 4.0px 3.0px 2.0px 1.0px;padding: 0.0;'>4</span>
84
91
  </li>
85
92
  </ul>
93
+
86
94
 
87
- == RACK MIDDLEWARE:
95
+ == Rack Middleware:
88
96
 
89
97
  # Process all routes:
90
98
  use InlineStyle::Rack::Middleware
@@ -95,7 +103,8 @@ Will become:
95
103
  # Restrict processing to some routes:
96
104
  use InlineStyle::Rack::Middleware, :paths => [%r(/mails/.*), "/somepath"]
97
105
 
98
- == MAIL INTERCEPTOR
106
+
107
+ == Mail Interceptor
99
108
 
100
109
  If using the mail library the following code will work:
101
110
 
@@ -106,21 +115,26 @@ If using ActionMailer (which wraps mail):
106
115
 
107
116
  ActionMailer::Base.register_interceptor \
108
117
  InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
118
+
109
119
 
110
- == ISSUES:
120
+ == Issues:
111
121
 
112
122
  * It supports pseudo classes according to W3C specification for style in style attribute: http://www.w3.org/TR/css-style-attr, although browsers
113
123
  doesn't seems to.
114
124
 
115
- == REQUIREMENTS:
116
125
 
117
- tenderlove's nokogiri and csspool
126
+ == Requirements:
127
+
128
+ nokogiri && (css_parser || maca-fork-csspool)
129
+
130
+
131
+
132
+ == Install:
118
133
 
119
- == INSTALL:
134
+ sudo gem install inline-style
120
135
 
121
- sudo gem install inline-style --source http://gemcutter.org
122
136
 
123
- == LICENSE:
137
+ == License:
124
138
 
125
139
  (The MIT License)
126
140
 
@@ -4,15 +4,14 @@ require "inline-style/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "inline-style"
7
- s.version = Inline::Style::VERSION
7
+ s.version = InlineStyle::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
9
  s.authors = ["Macario Ortega", "Eric Anderson"]
10
10
  s.email = ["macarui@gmail.com"]
11
11
  s.homepage = ""
12
12
  s.summary = %q{Inlines CSS for html email delivery}
13
13
  s.description = %q{Inlines CSS for html email delivery}
14
-
15
- # s.rubyforge_project = "inline-style"
14
+ s.post_install_message = %{Please read documentation for changes on the default css parser gem, specifically if you use csspool}
16
15
 
17
16
  s.files = `git ls-files`.split("\n")
18
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -1,37 +1,49 @@
1
1
  require 'nokogiri'
2
2
  require 'open-uri'
3
3
 
4
- require "#{ File.dirname( __FILE__ ) }/inline-style/css_parsers"
5
- require "#{ File.dirname( __FILE__ ) }/inline-style/rack/middleware"
6
- require "#{ File.dirname( __FILE__ ) }/inline-style/mail/interceptor"
4
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
7
5
 
8
- module InlineStyle
9
- # Options:
10
- # +:stylesheets_path+
11
- # Stylesheets root path, can also be a URL
6
+ require "inline-style/selector"
7
+ require "inline-style/rack/middleware" # This two may be should be required by user if she needs it
8
+ require "inline-style/mail/interceptor"
9
+
10
+ class InlineStyle
11
+ CSSParser =
12
+ if const_defined? :CSSPool
13
+ require 'inline-style/csspool_wrapper'
14
+ CSSPoolWrapper
15
+ else
16
+ require 'inline-style/css_parser_wrapper'
17
+ CssParserWrapper
18
+ end
19
+
20
+ # @param [String, Nokogiri::HTML::Document] html Html or Nokogiri html to be inlined
21
+ # @param [Hash] opts Processing options
12
22
  #
13
- # +pseudo+
23
+ # @option opts [String] :stylesheets_path (env['DOCUMENT_ROOT'])
24
+ # Stylesheets root path or app's public directory where the stylesheets are to be found
25
+ # @option opts [boolean] :pseudo (false)
14
26
  # If set to true will inline style for pseudo classes according to the W3C specification:
15
27
  # http://www.w3.org/TR/css-style-attr.
16
- # Defaults to false and should probably be left like that because at least Safari and Firefox don't seem to
17
- # comply with the specification for pseudo class style in the style attribute.
28
+ # Should probably be left as false because browsers don't seem to comply with the specification for pseudo class style in the style attribute.
18
29
  def self.process html, opts = {}
19
- stylesheets_path = opts[:stylesheets_path] || ''
20
- pseudo = opts[:pseudo] || false
21
-
22
- nokogiri_doc_given = Nokogiri::HTML::Document === html
23
- html = nokogiri_doc_given ? html : Nokogiri.HTML(html)
24
- css = extract_css html, stylesheets_path
30
+ new(html, opts).process
31
+ end
32
+
33
+ def initialize html, opts = {}
34
+ @html = html
35
+ @stylesheets_path = opts[:stylesheets_path] || ''
36
+ @pseudo = opts[:pseudo]
37
+ end
38
+
39
+ def process
25
40
  nodes = {}
26
41
 
27
42
  css.each_rule_set do |rule_set|
28
- rule_set.each_selector do |css_selector, declarations, specificity|
29
- orig_selector = css_selector.dup
30
- css_selector = "#{ 'body ' unless /^body/ === css_selector }#{ css_selector.gsub /:.*/, '' }"
31
-
32
- html.css(css_selector).each do |node|
43
+ rule_set.each_selector do |selector|
44
+ dom.css(selector.search).each do |node|
33
45
  nodes[node] ||= []
34
- nodes[node].push [css_selector, declarations, specificity, orig_selector]
46
+ nodes[node].push selector
35
47
 
36
48
  next unless node['style']
37
49
 
@@ -39,44 +51,50 @@ module InlineStyle
39
51
  path << "##{ node['id'] }" if node['id']
40
52
  path << ".#{ node['class'].scan(/\S+/).join('.') }" if node['class']
41
53
 
42
- InlineStyle::CssParsers.parser.new("#{ path }{#{ node['style'] }}").each_rule_set do |rule|
43
- rule.each_selector do |css_selector_inner, declarations_inner, specificity_inner|
44
- nodes[node].push [css_selector_inner, declarations_inner, specificity_inner, css_selector_inner]
54
+ CSSParser.new("#{path}{#{node['style']}}").each_rule_set do |rule|
55
+ rule.each_selector do |selector_inner|
56
+ nodes[node].push selector_inner
45
57
  end
46
58
  end
47
59
  end
48
60
  end
49
61
  end
50
62
 
51
- nodes.each_pair do |node, style|
52
- style = style.sort_by{ |(sel, dec, spe, orig)| "#{ spe }%03d" % style.index([sel, dec, spe, orig]) }
53
- sets = style.partition{ |(sel, dec, spe, orig)| not /:\w+/ === orig }
54
-
55
- sets.pop if not pseudo or sets.last.empty?
63
+ nodes.each_pair do |node, selectors|
64
+ selectors = selectors.sort_by{ |sel| "#{ sel.specificity }%03d" % selectors.index(sel) }
65
+ selectors = selectors.reject {|sel| !@pseudo && sel.pseudo? }
66
+ using_pseudo = selectors.any? &:pseudo?
56
67
 
57
- node['style'] = sets.collect do |selectors|
58
- index = sets.index selectors
59
-
60
- set = selectors.map do |(css_selector, declarations, specificity, orig_selector)|
61
- index == 0 ? declarations : "\n#{ orig_selector.gsub /\w(?=:)/, '' } {#{ declarations }}"
68
+ node['style'] = selectors.collect do |selector|
69
+ if using_pseudo && !selector.pseudo?
70
+ "{#{selector.inline_declarations}}"
71
+ else
72
+ selector.inline_declarations
62
73
  end
63
-
64
- index == 0 && sets.size > 1 ? "{#{ set }}" : set.collect(&:strip).join(' ')
65
- end.join.strip
74
+ end.join(' ').strip
66
75
  end
67
76
 
68
- nokogiri_doc_given ? html : html.to_s
77
+ html_already_parsed? ? @dom : @dom.to_s
78
+ end
79
+
80
+ private
81
+ def dom
82
+ @dom ||= html_already_parsed? ? @html : Nokogiri.HTML(@html)
69
83
  end
70
-
71
- # Returns CSSPool::Document
72
- def self.extract_css html, stylesheets_path = ''
73
- InlineStyle::CssParsers.parser.new html.css('style, link').collect { |e|
84
+
85
+ def html_already_parsed?
86
+ @html.respond_to? :css
87
+ end
88
+
89
+ # Returns parsed CSS
90
+ def css
91
+ @css ||= CSSParser.new dom.css('style, link').collect { |e|
74
92
  next unless e['media'].nil? || ['screen', 'all'].include?(e['media'])
75
93
  next(e.remove and e.content) if e.name == 'style'
76
94
  next unless e['rel'] == 'stylesheet'
77
95
  e.remove
78
96
 
79
- uri = %r{^https?://} === e['href'] ? e['href'] : File.join(stylesheets_path, e['href'].sub(/\?\d+$/,''))
97
+ uri = %r{^https?://} === e['href'] ? e['href'] : File.join(@stylesheets_path, e['href'].sub(/\?.+$/,''))
80
98
  open(uri).read rescue nil
81
99
  }.join("\n")
82
100
  end
@@ -0,0 +1,28 @@
1
+ require 'css_parser'
2
+
3
+ class InlineStyle
4
+ class CssParserWrapper
5
+ def initialize(css_code)
6
+ @parser = ::CssParser::Parser.new
7
+ @parser.add_block! css_code
8
+ end
9
+
10
+ def each_rule_set(&blk)
11
+ @parser.each_rule_set do |rule_set|
12
+ yield Ruleset.new(rule_set)
13
+ end
14
+ end
15
+
16
+ class Ruleset
17
+ def initialize(ruleset)
18
+ @ruleset = ruleset
19
+ end
20
+
21
+ def each_selector
22
+ @ruleset.each_selector do |sel, dec, spe|
23
+ yield InlineStyle::Selector.new(sel, dec, spe)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,8 +1,5 @@
1
- gem 'maca-fork-csspool'
2
- require 'csspool'
3
-
4
- module InlineStyle::CssParsers
5
- class Csspool
1
+ class InlineStyle
2
+ class CSSPoolWrapper
6
3
 
7
4
  def initialize(css_code)
8
5
  @parser = CSSPool.CSS css_code
@@ -22,13 +19,11 @@ module InlineStyle::CssParsers
22
19
 
23
20
  def each_selector(&blk)
24
21
  @ruleset.selectors.each do |selector|
25
- yield selector.to_s,
22
+ yield InlineStyle::Selector.new(selector.to_s,
26
23
  selector.declarations.map{ |d| d.to_s.squeeze(' ') }.join.strip,
27
- selector.specificity.inject(0) {|t, s| t+s}
24
+ selector.specificity.inject(0) {|t, s| t+s})
28
25
  end
29
26
  end
30
-
31
27
  end
32
-
33
28
  end
34
29
  end
@@ -9,29 +9,27 @@
9
9
  #
10
10
  # ActionMailer::Base.register_interceptor \
11
11
  # InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
12
- module InlineStyle
13
- module Mail
14
- class Interceptor
15
- # The mime types we should inline. Basically HTML and XHTML.
16
- # If you have something else you can just push it onto the list
17
- INLINE_MIME_TYPES = %w(text/html application/xhtml+xml)
12
+ module InlineStyle::Mail
13
+ class Interceptor
14
+ # The mime types we should inline. Basically HTML and XHTML.
15
+ # If you have something else you can just push it onto the list
16
+ INLINE_MIME_TYPES = %w(text/html application/xhtml+xml)
18
17
 
19
- # Save the options to later pass to InlineStyle.process
20
- def initialize(options={})
21
- @options = options
22
- end
18
+ # Save the options to later pass to InlineStyle.process
19
+ def initialize(options={})
20
+ @options = options
21
+ end
23
22
 
24
- # Mail callback where we actually inline the styles
25
- def delivering_email(part)
26
- if part.multipart?
27
- for part in part.parts
28
- delivering_email part
29
- end
30
- elsif INLINE_MIME_TYPES.any? {|m| part.content_type.starts_with? m}
31
- part.body = InlineStyle.process(part.body.to_s, @options)
23
+ # Mail callback where we actually inline the styles
24
+ def delivering_email(part)
25
+ if part.multipart?
26
+ for part in part.parts
27
+ delivering_email part
32
28
  end
29
+ elsif INLINE_MIME_TYPES.any? {|m| part.content_type.starts_with? m}
30
+ part.body = InlineStyle.process(part.body.to_s, @options)
33
31
  end
34
-
35
32
  end
33
+
36
34
  end
37
35
  end
@@ -1,42 +1,33 @@
1
- module InlineStyle
2
- module Rack
3
- class Middleware
4
- #
5
- # Options:
6
- # +stylesheets_path+
7
- # Stylesheets root path or app's public directory where the stylesheets are to be found, defaults to
8
- # env['DOCUMENT_ROOT']
9
- #
10
- # +paths+
11
- # Limit processing to the passed absolute paths
12
- # Can be an array of strings or regular expressions, a single string or regular expression
13
- # If not passed will process output for every path.
14
- # Regexps and strings must comence with '/'
15
- #
16
- # +pseudo+
17
- # If set to true will inline style for pseudo classes according to the W3C specification:
18
- # http://www.w3.org/TR/css-style-attr.
19
- # Defaults to false and should probably be left like that because at least Safari and Firefox don't seem to
20
- # comply with the specification for pseudo class style in the style attribute.
21
- #
22
- def initialize app, opts = {}
23
- @app = app
24
- @opts = opts
25
- @paths = /^(?:#{ [*opts[:paths]].join('|') })/
26
- end
1
+ module InlineStyle::Rack
2
+ class Middleware
3
+ # @param [Hash] opts Middlewar options
4
+ #
5
+ # @option opts [String] :stylesheets_path (env['DOCUMENT_ROOT'])
6
+ # Stylesheets root path or app's public directory where the stylesheets are to be found
7
+ # @option opts [Regexp, Array, String] :paths
8
+ # Limit processing to the passed absolute paths
9
+ # Can be an array of strings or regular expressions, a single string or regular expression
10
+ # If not passed will process output for every path.
11
+ # Regexps and strings must comence with '/'
12
+ # @option opts [Boolean] :pseudo (false)
13
+ # If set to true will inline style for pseudo classes according to the W3C specification:
14
+ # http://www.w3.org/TR/css-style-attr.
15
+ # Should probably be left as false because browsers don't seem to comply with the specification for pseudo class style in the style attribute.
16
+ def initialize app, opts = {}
17
+ @app = app
18
+ @opts = opts
19
+ @paths = /^(?:#{ [*opts[:paths]].join('|') })/
20
+ end
27
21
 
28
- def call env
29
- response = @app.call env
30
- return response unless @paths === env['PATH_INFO']
31
-
32
- status, headers, content = response
33
- response = ::Rack::Response.new '', status, headers
34
- body = content.respond_to?(:body) ? content.body : content
35
-
36
- response.write InlineStyle.process(body, {:stylesheets_path => env['DOCUMENT_ROOT']}.merge(@opts))
37
- response.finish
38
- end
22
+ def call env
23
+ response = @app.call env
24
+ return response unless @paths === env['PATH_INFO']
25
+
26
+ status, headers, body = response
27
+
28
+ body = InlineStyle.process(body.first, {:stylesheets_path => env['DOCUMENT_ROOT']}.merge(@opts))
29
+ [status, headers, [body]]
39
30
  end
40
31
  end
41
32
  end
42
-
33
+
@@ -0,0 +1,41 @@
1
+
2
+
3
+ # A simple abstraction of the data we get back from the parsers. CSSPool
4
+ # actually already does this for us but CSSParser does not so we need
5
+ # to create the abstraction ourselves.
6
+ class InlineStyle
7
+ class Selector < Struct.new :selector_text, :declarations, :specificity
8
+ # A slightly adjusted version of the selector_text that should be
9
+ # used for finding nodes. Will remove the pseudo selector and prepend
10
+ # 'body '.
11
+ def search
12
+ selector_text.dup.tap do |s|
13
+ state_based_pseudo_selectors.each {|p| s.gsub! /:#{p}$/, ''}
14
+ s.insert(0, 'body ') unless s =~ /^body/
15
+ end
16
+ end
17
+
18
+ # For the most part is just declarations unless a pseudo selector.
19
+ # Then it uses the inline pseudo declarations
20
+ def inline_declarations
21
+ if pseudo?
22
+ "\n#{ selector_text.gsub /\w(?=:)/, '' } {#{ declarations }}"
23
+ else
24
+ declarations
25
+ end
26
+ end
27
+
28
+ # Is this selector using a pseudo class?
29
+ def pseudo?
30
+ state_based_pseudo_selectors.any? {|p| selector_text.end_with? ":#{p}"}
31
+ end
32
+
33
+ # A list of state based pseudo selectors (like hover) that should
34
+ # be handled based on the pseudo option. Unlike position-based
35
+ # pseudo selectors (like :first-child) which once resolved to the
36
+ # correct node effectively get inlined like a normal selector.
37
+ def state_based_pseudo_selectors
38
+ %w(link visited active hover focus target enabled disabled checked)
39
+ end
40
+ end
41
+ end
@@ -1,5 +1,3 @@
1
- module Inline
2
- module Style
3
- VERSION = '0.4.6'
4
- end
1
+ class InlineStyle
2
+ VERSION = '0.4.10'
5
3
  end
@@ -1,72 +1,91 @@
1
- require "#{ File.dirname __FILE__ }/spec_helper"
1
+ require "spec_helper"
2
2
 
3
3
  describe InlineStyle do
4
- before do
5
- @processed = InlineStyle.process Nokogiri.HTML(File.read("#{ FIXTURES }/boletin.html")),
6
- :pseudo => false,
7
- :stylesheets_path => FIXTURES
8
- end
4
+ shared_examples_for 'inlines styles' do
5
+ before do
6
+ processed = InlineStyle.process File.read("#{FIXTURES}/boletin.html"), :pseudo => false, :stylesheets_path => FIXTURES
7
+ @processed = Nokogiri.HTML(processed)
8
+ end
9
9
 
10
- it "should extract from linked stylesheet" do
11
- @processed.css('#izq').first['style'].should =~ /margin: 30.0px;/
12
- end
13
-
14
- it "should extract styles from linked stylesheet with no media specified" do
15
- @processed.css('#izq').first['style'].should =~ /color: red;/
16
- end
17
-
18
- it "should extract styles from linked stylesheet with media 'all'" do
19
- @processed.css('#izq').first['style'].should =~ /padding: 10.0px;/
20
- end
21
-
22
- it "should ignore styles from linked stylesheet with media other than screen" do
23
- @processed.css('#izq').first['style'].should_not =~ /display: none;/
24
- end
10
+ it "should extract from linked stylesheet" do
11
+ @processed.css('#izq').first['style'].should match_style /margin: 30.0px;/
12
+ end
25
13
 
26
- it "should not process pseudo classes" do
27
- @processed.to_s.should_not =~ /:hover/
28
- @processed.to_s.should_not =~ /\{/
29
- end
30
-
31
- it "should should process pseudo classes" do
32
- processed = InlineStyle.process Nokogiri.HTML(File.read("#{ FIXTURES }/boletin.html")), :pseudo => true
33
- processed.css('a').first['style'].should =~ /:hover/
34
- processed.css('a').first['style'].should =~ /\{/
35
- end
14
+ it "should extract styles from linked stylesheet with no media specified" do
15
+ @processed.css('#izq').first['style'].should match_style /color: red;/
16
+ end
36
17
 
37
- it 'should apply to #A #B' do
38
- @processed.css('#logos #der').first['style'].should =~ /float: right;/
39
- end
40
-
41
- # it 'should order selectors by specificity'
42
- # it 'should order selectors by specificity and defininition order'
43
- # it 'should overwrite rule with less specificity'
44
- # it 'should overwrite rule previously defined'
45
- # it 'should not overwrite rules defined inline'
46
-
47
- describe 'Box model' do
48
- before do
49
- @processed = InlineStyle.process( Nokogiri.HTML(File.read("#{ FIXTURES }/box-model.html")) )
18
+ it "should extract styles from linked stylesheet with media 'all'" do
19
+ @processed.css('#izq').first['style'].should match_style /padding: 10.0px;/
50
20
  end
51
21
 
52
- it "should inline style for selector ul" do
53
- @processed.css('ul').first['style'].should == "background: yellow; margin: 12.0px 12.0px 12.0px 12.0px; padding: 3.0px 3.0px 3.0px 3.0px;"
22
+ it "should ignore styles from linked stylesheet with media other than screen" do
23
+ @processed.css('#izq').first['style'].should_not match_style /display: none;/
54
24
  end
55
25
 
56
- it "should inline style for selector li" do
57
- @processed.css('li').each do |li|
58
- li['style'].should =~ /^color: white; background: blue; margin: 12.0px 12.0px 12.0px 12.0px; padding: 12.0px 0.0px 12.0px 12.0px; list-style: none/
59
- end
26
+ it "should not process pseudo classes" do
27
+ @processed.css('a').first['style'].should_not match_style /^:hover \{background-color: #8ae0ea; color: #126b5d;\}$/
60
28
  end
61
29
 
62
- it "should inline style for selector li.withborder" do
63
- @processed.css('li.withborder').first['style'].
64
- should == "color: white; background: blue; margin: 12.0px 12.0px 12.0px 12.0px; padding: 12.0px 0.0px 12.0px 12.0px; list-style: none; border-style: dashed; border-width: medium; border-color: lime;"
30
+ it "should should process pseudo classes" do
31
+ processed = InlineStyle.process Nokogiri.HTML(File.read("#{FIXTURES}/boletin.html")), :pseudo => true
32
+ processed.css('a').first['style'].should match_style /^:hover \{background-color: #8ae0ea; color: #126b5d;\}$/
65
33
  end
66
- end
67
- end
68
34
 
35
+ it 'should process location-based pseudo classes' do
36
+ @processed.at_css('#izq')['style'].should match_style /padding: 1.0px;/
37
+ end
69
38
 
39
+ it 'should apply to #A #B' do
40
+ @processed.css('#logos #der').first['style'].should match_style /float: right;/
41
+ end
70
42
 
43
+ # it 'should overwrite rule with less specificity'
44
+ # it 'should overwrite rule previously defined'
45
+ # it 'should not overwrite rules defined inline'
71
46
 
47
+ describe 'Box model' do
48
+ before do
49
+ @processed = InlineStyle.process(Nokogiri.HTML(File.read("#{FIXTURES}/box-model.html")))
50
+ end
72
51
 
52
+ it "should inline style for selector ul" do
53
+ @processed.css('ul').first['style'].should match_style /^background: yellow; margin: 12.0px 12.0px 12.0px 12.0px; padding: 3.0px 3.0px 3.0px 3.0px;$/
54
+ end
55
+
56
+ it "should inline style for selector li" do
57
+ @processed.css('li').each do |li|
58
+ li['style'].should match_style /^color: white; background: blue; margin: 12.0px 12.0px 12.0px 12.0px; padding: 12.0px 0.0px 12.0px 12.0px; list-style: none/
59
+ end
60
+ end
61
+
62
+ it "should inline style for selector li.withborder" do
63
+ @processed.css('li.withborder').first['style'].
64
+ should match_style /^color: white; background: blue; margin: 12.0px 12.0px 12.0px 12.0px; padding: 12.0px 0.0px 12.0px 12.0px; list-style: none; border-style: dashed; border-width: medium; border-color: lime;$/
65
+ end
66
+ end
67
+ end
68
+
69
+ describe CssParser do
70
+ it { InlineStyle::CSSParser.should == InlineStyle::CssParserWrapper }
71
+ it_should_behave_like 'inlines styles'
72
+ end
73
+
74
+ describe CSSPool do
75
+ before do
76
+ class InlineStyle
77
+ remove_const :CSSParser
78
+ CSSParser = InlineStyle::CSSPoolWrapper
79
+ end
80
+ end
81
+
82
+ after do
83
+ class InlineStyle
84
+ remove_const :CSSParser
85
+ CSSParser = InlineStyle::CssParserWrapper
86
+ end
87
+ end
88
+ it { InlineStyle::CSSParser.should == InlineStyle::CSSPoolWrapper }
89
+ it_should_behave_like 'inlines styles'
90
+ end
91
+ end
@@ -1,8 +1,6 @@
1
- require "#{ File.dirname __FILE__ }/spec_helper"
2
- require "#{ DIR }/../lib/inline-style/css_parsers/css_parser"
3
- require "#{ DIR }/../lib/inline-style/css_parsers/csspool"
1
+ require "spec_helper"
4
2
 
5
- describe InlineStyle::CssParsers::CssParser do
3
+ describe InlineStyle::CssParserWrapper do
6
4
 
7
5
  it 'should wrap css_parser' do
8
6
  rs_count = 0
@@ -10,15 +8,15 @@ describe InlineStyle::CssParsers::CssParser do
10
8
  selectors = %w(p b i)
11
9
  decs = ['color: black;', 'color: black;', 'color: green; text-decoration: none;']
12
10
  spes = [1, 1, 1]
13
- p = InlineStyle::CssParsers::CssParser.new "p, b {color: black}\ni {color: green; text-decoration: none}"
11
+ p = InlineStyle::CssParserWrapper.new "p, b {color: black}\ni {color: green; text-decoration: none}"
14
12
 
15
13
  p.each_rule_set do |rs|
16
14
  rs_count += 1
17
- rs.each_selector do |sel, dec, spe|
15
+ rs.each_selector do |sel|
18
16
  sel_count += 1
19
- sel.should == selectors.shift
20
- dec.should == decs.shift
21
- spe.should == spes.shift
17
+ sel.selector_text.should == selectors.shift
18
+ sel.declarations.should == decs.shift
19
+ sel.specificity.should == spes.shift
22
20
  end
23
21
  end
24
22
 
@@ -28,7 +26,7 @@ describe InlineStyle::CssParsers::CssParser do
28
26
 
29
27
  end
30
28
 
31
- describe InlineStyle::CssParsers::Csspool do
29
+ describe InlineStyle::CSSPoolWrapper do
32
30
 
33
31
  it 'should wrap csspool' do
34
32
  rs_count = 0
@@ -36,15 +34,15 @@ describe InlineStyle::CssParsers::Csspool do
36
34
  selectors = %w(p b i)
37
35
  decs = ['color: black;', 'color: black;', 'color: green; text-decoration: none;']
38
36
  spes = [1, 1, 1]
39
- p = InlineStyle::CssParsers::Csspool.new "p, b {color: black}\ni {color: green; text-decoration: none}"
37
+ p = InlineStyle::CSSPoolWrapper.new "p, b {color: black}\ni {color: green; text-decoration: none}"
40
38
 
41
39
  p.each_rule_set do |rs|
42
40
  rs_count += 1
43
- rs.each_selector do |sel, dec, spe|
41
+ rs.each_selector do |sel|
44
42
  sel_count += 1
45
- sel.should == selectors.shift
46
- dec.should == decs.shift
47
- spe.should == spes.shift
43
+ sel.selector_text.should == selectors.shift
44
+ sel.declarations.should == decs.shift
45
+ sel.specificity.should == spes.shift
48
46
  end
49
47
  end
50
48
 
@@ -124,6 +124,10 @@ div#contacto a {
124
124
  background-image: url('/images/boletines/Noviembre/logos_fondo.jpg');
125
125
  }
126
126
 
127
+ #logos img:first-child {
128
+ padding: 1px;
129
+ }
130
+
127
131
  #izq {
128
132
  float: left;
129
133
  }
@@ -253,4 +257,4 @@ div#contacto a {
253
257
  </div></body>
254
258
 
255
259
 
256
- </html>
260
+ </html>
@@ -96,7 +96,7 @@
96
96
  contacto <br style="margin: 0.0; padding: 0.0;"><a href="mailto:mail@host.org" style="margin: 0.0; padding: 0.0; color: #185d6b; font-weight: bold; font-size: 1.2em;">mail@host.org</a>
97
97
  </div>
98
98
  <div id="logos" style="margin: 0.0; padding: 0.0; width: 100.0%; margin-top: 40.0px; background-image: url(/images/boletines/Noviembre/logos_fondo.jpg);">
99
- <img src="A" id="izq" alt="A" style="margin: 0.0; padding: 0.0; float: left; margin: 30.0px; padding: 10.0px; color: red;"><img src="B" id="der" alt="B" style="margin: 0.0; padding: 0.0; float: right;"><div class="clearer" style="margin: 0.0; padding: 0.0; clear: both;">
99
+ <img src="A" id="izq" alt="A" style="margin: 0.0; padding: 0.0; float: left; margin: 30.0px; padding: 10.0px; color: red; padding: 1.0px;"><img src="B" id="der" alt="B" style="margin: 0.0; padding: 0.0; float: right;"><div class="clearer" style="margin: 0.0; padding: 0.0; clear: both;">
100
100
  </div>
101
101
  </div>
102
102
 
@@ -1,16 +1,27 @@
1
- require "#{ File.dirname __FILE__ }/spec_helper"
1
+ require "spec_helper"
2
+ require "inline-style/mail/interceptor"
3
+ require "mail"
2
4
 
3
- require 'mail'
4
5
  Mail.defaults do
5
6
  delivery_method :test
6
7
  end
7
- Mail.register_interceptor \
8
- InlineStyle::Mail::Interceptor.new(:stylesheets_path => FIXTURES)
9
8
 
10
- describe InlineStyle::Mail::Interceptor do
9
+ Mail.register_interceptor InlineStyle::Mail::Interceptor.new(:stylesheets_path => FIXTURES)
11
10
 
11
+ describe InlineStyle::Mail::Interceptor do
12
12
  before do
13
13
  Mail::TestMailer.deliveries.clear
14
+ class InlineStyle
15
+ remove_const :CSSParser
16
+ CSSParser = InlineStyle::CSSPoolWrapper
17
+ end
18
+ end
19
+
20
+ after do
21
+ class InlineStyle
22
+ remove_const :CSSParser
23
+ CSSParser = InlineStyle::CssParserWrapper
24
+ end
14
25
  end
15
26
 
16
27
  it 'should inline html e-mail' do
@@ -59,5 +70,4 @@ describe InlineStyle::Mail::Interceptor do
59
70
  Mail::TestMailer.deliveries.first.parts[1].body.to_s.
60
71
  should == File.read("#{ FIXTURES }/inline.html")
61
72
  end
62
-
63
73
  end
@@ -1,12 +1,12 @@
1
- require "#{ File.dirname __FILE__ }/spec_helper"
1
+ require "spec_helper"
2
+ require "inline-style/rack/middleware"
2
3
 
3
4
  describe InlineStyle::Rack::Middleware do
4
-
5
5
  def get_response path, body, opts = {}
6
6
  content_type = opts.delete(:content_type) || 'text/html'
7
7
  app = Rack::Builder.new do
8
8
  use InlineStyle::Rack::Middleware, opts
9
- run lambda { |env| env['DOCUMENT_ROOT'] = FIXTURES; [200, {'Content-Type' => content_type}, body ] }
9
+ run lambda { |env| env['DOCUMENT_ROOT'] = FIXTURES; [200, {'Content-Type' => content_type}, [body]] }
10
10
  end
11
11
  Nokogiri.HTML Rack::MockRequest.new(app).get(path).body
12
12
  end
@@ -20,7 +20,7 @@ describe InlineStyle::Rack::Middleware do
20
20
  end
21
21
 
22
22
  it "should use external css" do
23
- get_response('/', Nokogiri.HTML(@html), :stylesheets_path => FIXTURES).css('#izq').first['style'].should =~ /margin: 30.0px/
23
+ get_response('/', @html, :stylesheets_path => FIXTURES).css('#izq').first['style'].should match_style /margin: 30.0px/
24
24
  end
25
25
 
26
26
  describe 'Path inclusion' do
@@ -1,12 +1,15 @@
1
1
  require 'rubygems'
2
2
  require 'rspec'
3
- require "#{ DIR = File.dirname(__FILE__) }/../lib/inline-style"
4
3
  require 'rack'
5
4
  require 'rack/mock'
6
5
 
7
- FIXTURES = "#{ DIR }/fixtures"
6
+ require "inline-style"
7
+ require "inline-style/csspool_wrapper"
8
+ require "csspool"
8
9
 
9
- module HaveInlineStyleMatcher
10
+ FIXTURES = "#{File.dirname __FILE__}/fixtures"
11
+
12
+ module InlineStyleMatchers
10
13
  class HaveInlineStyle
11
14
  def initialize selector
12
15
  @selector = selector
@@ -18,17 +21,40 @@ module HaveInlineStyleMatcher
18
21
  end
19
22
 
20
23
  def failure_message
21
- "expected elements with selector #{ @selector } style attribute not to be nil"
24
+ "expected elements with selector #{@selector} style attribute not to be nil"
25
+ end
26
+
27
+ def negative_failure_message
28
+ "expected elements with selector #{@selector} style attribute to be nil"
29
+ end
30
+ end
31
+
32
+ class MatchStyle
33
+ def initialize style
34
+ @style = style
35
+ end
36
+
37
+ def matches? actual
38
+ @actual = actual.gsub(/([^\.0-9]\d+)(px|;|%|\s)/, '\1.0\2')
39
+ @actual =~ @style
40
+ end
41
+
42
+ def failure_message
43
+ "expected #{@style} to match #{@actual}"
22
44
  end
23
45
 
24
46
  def negative_failure_message
25
- "expected elements with selector #{ @selector } style attribute to be nil"
47
+ "expected #{@style} not to match #{@actual}"
26
48
  end
27
49
  end
28
50
 
29
51
  def have_inline_style_for selector
30
52
  HaveInlineStyle.new selector
31
53
  end
54
+
55
+ def match_style style
56
+ MatchStyle.new style
57
+ end
32
58
  end
33
59
 
34
- RSpec.configure { |config| config.include HaveInlineStyleMatcher }
60
+ RSpec.configure { |config| config.include InlineStyleMatchers }
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inline-style
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 27
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
8
  - 4
8
- - 6
9
- version: 0.4.6
9
+ - 10
10
+ version: 0.4.10
10
11
  platform: ruby
11
12
  authors:
12
13
  - Macario Ortega
@@ -15,7 +16,7 @@ autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2011-03-07 00:00:00 -06:00
19
+ date: 2011-03-23 00:00:00 -06:00
19
20
  default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
@@ -26,6 +27,7 @@ dependencies:
26
27
  requirements:
27
28
  - - ">="
28
29
  - !ruby/object:Gem::Version
30
+ hash: 3
29
31
  segments:
30
32
  - 0
31
33
  version: "0"
@@ -39,6 +41,7 @@ dependencies:
39
41
  requirements:
40
42
  - - ">="
41
43
  - !ruby/object:Gem::Version
44
+ hash: 3
42
45
  segments:
43
46
  - 0
44
47
  version: "0"
@@ -52,6 +55,7 @@ dependencies:
52
55
  requirements:
53
56
  - - ">="
54
57
  - !ruby/object:Gem::Version
58
+ hash: 3
55
59
  segments:
56
60
  - 0
57
61
  version: "0"
@@ -65,6 +69,7 @@ dependencies:
65
69
  requirements:
66
70
  - - ">="
67
71
  - !ruby/object:Gem::Version
72
+ hash: 3
68
73
  segments:
69
74
  - 0
70
75
  version: "0"
@@ -78,6 +83,7 @@ dependencies:
78
83
  requirements:
79
84
  - - ">="
80
85
  - !ruby/object:Gem::Version
86
+ hash: 3
81
87
  segments:
82
88
  - 0
83
89
  version: "0"
@@ -91,6 +97,7 @@ dependencies:
91
97
  requirements:
92
98
  - - ">="
93
99
  - !ruby/object:Gem::Version
100
+ hash: 3
94
101
  segments:
95
102
  - 0
96
103
  version: "0"
@@ -104,6 +111,7 @@ dependencies:
104
111
  requirements:
105
112
  - - ">="
106
113
  - !ruby/object:Gem::Version
114
+ hash: 3
107
115
  segments:
108
116
  - 0
109
117
  version: "0"
@@ -120,24 +128,24 @@ extra_rdoc_files: []
120
128
 
121
129
  files:
122
130
  - .gitignore
131
+ - .rspec
123
132
  - Gemfile
124
133
  - Gemfile.lock
125
134
  - History.txt
126
135
  - Manifest.txt
127
- - README.txt
136
+ - README.rdoc
128
137
  - Rakefile
129
138
  - example.rb
130
139
  - inline-style.gemspec
131
140
  - lib/inline-style.rb
132
- - lib/inline-style/css_parsers.rb
133
- - lib/inline-style/css_parsers/css_parser.rb
134
- - lib/inline-style/css_parsers/csspool.rb
141
+ - lib/inline-style/css_parser_wrapper.rb
142
+ - lib/inline-style/csspool_wrapper.rb
135
143
  - lib/inline-style/mail/interceptor.rb
136
144
  - lib/inline-style/rack/middleware.rb
145
+ - lib/inline-style/selector.rb
137
146
  - lib/inline-style/version.rb
138
147
  - spec/css_inlining_spec.rb
139
148
  - spec/css_parsers_spec.rb
140
- - spec/factory_spec.rb
141
149
  - spec/fixtures/all.css
142
150
  - spec/fixtures/boletin.html
143
151
  - spec/fixtures/box-model.html
@@ -153,7 +161,7 @@ has_rdoc: true
153
161
  homepage: ""
154
162
  licenses: []
155
163
 
156
- post_install_message:
164
+ post_install_message: Please read documentation for changes on the default css parser gem, specifically if you use csspool
157
165
  rdoc_options: []
158
166
 
159
167
  require_paths:
@@ -163,6 +171,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
163
171
  requirements:
164
172
  - - ">="
165
173
  - !ruby/object:Gem::Version
174
+ hash: 3
166
175
  segments:
167
176
  - 0
168
177
  version: "0"
@@ -171,13 +180,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
180
  requirements:
172
181
  - - ">="
173
182
  - !ruby/object:Gem::Version
183
+ hash: 3
174
184
  segments:
175
185
  - 0
176
186
  version: "0"
177
187
  requirements: []
178
188
 
179
189
  rubyforge_project:
180
- rubygems_version: 1.3.7
190
+ rubygems_version: 1.6.2
181
191
  signing_key:
182
192
  specification_version: 3
183
193
  summary: Inlines CSS for html email delivery
@@ -1,21 +0,0 @@
1
- module InlineStyle
2
-
3
- # A factory for returning a configured CSS parser. Defaults to
4
- # :css_parser if not specified. Will also use ENV['CSS_PARSER'].
5
- module CssParsers
6
-
7
- # Allows you to specify the CSS parser to use.
8
- def self.parser=(parser)
9
- @parser = nil
10
- @parser_name = parser
11
- end
12
-
13
- def self.parser
14
- return @parser if @parser
15
- @parser_name = ENV['CSS_PARSER'] || :css_parser unless @parser_name
16
- require "inline-style/css_parsers/#{@parser_name}"
17
- @parser = const_get(@parser_name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase })
18
- end
19
-
20
- end
21
- end
@@ -1,56 +0,0 @@
1
- require 'css_parser'
2
-
3
- module InlineStyle::CssParsers
4
- class CssParser
5
-
6
- def initialize(css_code)
7
- @parser = ::CssParser::Parser.new
8
- @parser.add_block! css_code
9
- end
10
-
11
- def each_rule_set(&blk)
12
- @parser.each_rule_set do |rule_set|
13
- yield Ruleset.new(rule_set)
14
- end
15
- end
16
-
17
- class Ruleset
18
-
19
- def initialize(ruleset)
20
- @ruleset = ruleset
21
- end
22
-
23
- def each_selector
24
- @ruleset.each_selector do |sel, dec, spe|
25
- normalize_for_test! dec
26
- yield sel, dec, spe
27
- end
28
- end
29
-
30
- private
31
-
32
- # To make testings work for both parsers we need to conform to
33
- # csspool normalizing attributes. This means all numbers have a
34
- # decimal place. It also means that urls do not have quotes.
35
- #
36
- # NOTE: This really doesn't have anything to do with the
37
- # correctness of the parser. It just makes testing easier since
38
- # each parser can run against the same tests.
39
- def normalize_for_test!(dec)
40
-
41
- # Give all numbers a decimal if they do not already have it
42
- dec.gsub! '0;', '0.0;'
43
- dec.gsub! ' 0 ', ' 0.0 '
44
- dec.gsub! /([^\.0-9]\d+)px/, '\1.0px'
45
- dec.gsub! /([^\.0-9]\d+)%/, '\1.0%'
46
-
47
- # Remove any quotes in url()
48
- dec.gsub! "url('", 'url('
49
- dec.gsub! "')", ')'
50
-
51
- end
52
-
53
- end
54
-
55
- end
56
- end
@@ -1,38 +0,0 @@
1
- require "#{ File.dirname __FILE__ }/spec_helper"
2
-
3
- describe InlineStyle::CssParsers do
4
-
5
- before do
6
- @orig_env = ENV['CSS_PARSER']
7
- ENV['CSS_PARSER'] = nil
8
- InlineStyle::CssParsers.parser = nil # Clear out any cached value
9
- end
10
- after do
11
- ENV['CSS_PARSER'] = @orig_env
12
- InlineStyle::CssParsers.parser = nil # Clear out any cached value
13
- end
14
-
15
- it 'should load css_parser by default' do
16
- InlineStyle::CssParsers.parser.name.
17
- should == 'InlineStyle::CssParsers::CssParser'
18
- end
19
-
20
- it "should obey ENV['CSS_PARSER']" do
21
- ENV['CSS_PARSER'] = 'css_parser'
22
- InlineStyle::CssParsers.parser.name.
23
- should == 'InlineStyle::CssParsers::CssParser'
24
- end
25
-
26
- it 'should be able to load csspool' do
27
- InlineStyle::CssParsers.parser = :csspool
28
- InlineStyle::CssParsers.parser.name.
29
- should == 'InlineStyle::CssParsers::Csspool'
30
- end
31
-
32
- it 'should be able to load css_parser' do
33
- InlineStyle::CssParsers.parser = :css_parser
34
- InlineStyle::CssParsers.parser.name.
35
- should == 'InlineStyle::CssParsers::CssParser'
36
- end
37
-
38
- end