inline-style 0.4.6 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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