inline-style-umanni 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ *.swp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in inline-style.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ inline-style (0.4.10)
5
+ css_parser
6
+ facets
7
+ maca-fork-csspool
8
+ nokogiri
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ activesupport (3.0.4)
14
+ css_parser (1.1.5)
15
+ diff-lcs (1.1.2)
16
+ facets (2.9.1)
17
+ ffi (1.0.7)
18
+ rake (>= 0.8.7)
19
+ i18n (0.5.0)
20
+ maca-fork-csspool (2.0.2)
21
+ ffi
22
+ mail (2.2.15)
23
+ activesupport (>= 2.3.6)
24
+ i18n (>= 0.4.0)
25
+ mime-types (~> 1.16)
26
+ treetop (~> 1.4.8)
27
+ mime-types (1.16)
28
+ nokogiri (1.4.4)
29
+ polyglot (0.3.1)
30
+ rack (1.2.1)
31
+ rake (0.8.7)
32
+ rspec (2.4.0)
33
+ rspec-core (~> 2.4.0)
34
+ rspec-expectations (~> 2.4.0)
35
+ rspec-mocks (~> 2.4.0)
36
+ rspec-core (2.4.0)
37
+ rspec-expectations (2.4.0)
38
+ diff-lcs (~> 1.1.2)
39
+ rspec-mocks (2.4.0)
40
+ treetop (1.4.9)
41
+ polyglot (>= 0.3.1)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ css_parser
48
+ facets
49
+ inline-style!
50
+ maca-fork-csspool
51
+ mail
52
+ nokogiri
53
+ rack
54
+ rspec
55
+ rspec-core
data/README.rdoc ADDED
@@ -0,0 +1,153 @@
1
+ = inline-style
2
+
3
+ http://github.com/umanni/inline-style
4
+
5
+
6
+ == Description
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
9
+ each refered element taking selector specificity and declarator order.
10
+
11
+ Useful for html email: some clients (gmail, et all) won't render non inline styles.
12
+
13
+ * Includes a Rack middleware for using with Rails, Sinatra, etc...
14
+ * Includes a interceptor for the mail gem which allows automatic
15
+ inline processing for both mail as well as ActionMailer.
16
+ * It takes into account selector specificity.
17
+
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
+
25
+ require 'inline-style'
26
+
27
+ html = File.read("./index.html")
28
+ puts InlineStyle.process(html, :stylesheets_paths => "./styles")
29
+
30
+ index.html contains:
31
+
32
+ <style type="text/css" media="screen">
33
+ * {
34
+ font-family: "Lucida Grande", Lucida, Verdana, sans-serif;
35
+ margin: 4px 3px 2px 1px;
36
+ padding: 0;
37
+ }
38
+
39
+ #list {
40
+ margin: 10;
41
+ }
42
+
43
+ #list li {
44
+ font-family: Arial;
45
+ }
46
+
47
+ .element {
48
+ padding: 10;
49
+ }
50
+
51
+ .odd {
52
+ background-color: black;
53
+ }
54
+
55
+ .pair {
56
+ background-color: red;
57
+ }
58
+
59
+ </style>
60
+
61
+
62
+ <ul id='number' class='listing inlined' style='background-color: yellow'>
63
+ <li class='list-element odd'>
64
+ <span>1</span>
65
+ </li>
66
+ <li class='list-element pair'>
67
+ <span>2</span>
68
+ </li>
69
+ <li class='list-element odd'>
70
+ <span>3</span>
71
+ </li>
72
+ <li class='list-element pair'>
73
+ <span>4</span>
74
+ </li>
75
+ </ul>
76
+
77
+ Will become:
78
+
79
+ <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;'>
80
+ <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;'>
81
+ <span style='font-family: "Lucida Grande", Lucida, Verdana, sans-serif;margin: 4.0px 3.0px 2.0px 1.0px;padding: 0.0;'>1</span>
82
+ </li>
83
+ <li class="list-element pair" style='font-family: "Lucida Grande", Lucida, Verdana, sans-serif;margin: 4.0px 3.0px 2.0px 1.0px;padding: 0.0;background-color: red;'>
84
+ <span style='font-family: "Lucida Grande", Lucida, Verdana, sans-serif;margin: 4.0px 3.0px 2.0px 1.0px;padding: 0.0;'>2</span>
85
+ </li>
86
+ <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;'>
87
+ <span style='font-family: "Lucida Grande", Lucida, Verdana, sans-serif;margin: 4.0px 3.0px 2.0px 1.0px;padding: 0.0;'>3</span>
88
+ </li>
89
+ <li class="list-element pair" style='font-family: "Lucida Grande", Lucida, Verdana, sans-serif;margin: 4.0px 3.0px 2.0px 1.0px;padding: 0.0;background-color: red;'>
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>
91
+ </li>
92
+ </ul>
93
+
94
+
95
+ == Rack Middleware:
96
+
97
+ # Process all routes:
98
+ use InlineStyle::Rack::Middleware
99
+
100
+ # Restrict processing to single route:
101
+ use InlineStyle::Rack::Middleware, :paths => %r(/mails/.*)
102
+
103
+ # Restrict processing to some routes:
104
+ use InlineStyle::Rack::Middleware, :paths => [%r(/mails/.*), "/somepath"]
105
+
106
+
107
+ == Mail Interceptor
108
+
109
+ If using the mail library the following code will work:
110
+
111
+ Mail.register_interceptor \
112
+ InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
113
+
114
+ If using ActionMailer (which wraps mail):
115
+
116
+ ActionMailer::Base.register_interceptor \
117
+ InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
118
+
119
+
120
+ == Requirements:
121
+
122
+ nokogiri && (css_parser || maca-fork-csspool)
123
+
124
+
125
+ == Install:
126
+
127
+ sudo gem install inline-style
128
+
129
+
130
+ == License:
131
+
132
+ (The MIT License)
133
+
134
+ Copyright (c) 2009 Macario Ortega
135
+
136
+ Permission is hereby granted, free of charge, to any person obtaining
137
+ a copy of this software and associated documentation files (the
138
+ 'Software'), to deal in the Software without restriction, including
139
+ without limitation the rights to use, copy, modify, merge, publish,
140
+ distribute, sublicense, and/or sell copies of the Software, and to
141
+ permit persons to whom the Software is furnished to do so, subject to
142
+ the following conditions:
143
+
144
+ The above copyright notice and this permission notice shall be
145
+ included in all copies or substantial portions of the Software.
146
+
147
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
148
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
149
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
150
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
151
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
152
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
153
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ require "rspec/core/rake_task"
3
+
4
+ task :default => [:spec]
5
+
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ Bundler::GemHelper.install_tasks
data/example.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require "#{ dir = File.dirname(__FILE__) }/lib/inline-style"
3
+
4
+ html = File.read("#{ fixtures = dir + '/spec/fixtures' }/boletin.html")
5
+ puts InlineStyle.process(html)
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "inline-style/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "inline-style-umanni"
7
+ s.version = InlineStyle::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Umanni"]
10
+ s.email = ["ygor@umanni.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{Inlines CSS for html email delivery}
13
+ s.description = %q{Inlines CSS for html email delivery}
14
+ s.post_install_message = %{Please read documentation for changes on the default css parser gem, specifically if you use csspool}
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency 'rspec'
22
+ s.add_development_dependency 'rack'
23
+ s.add_development_dependency 'rspec-core'
24
+ s.add_development_dependency 'mail'
25
+
26
+ s.add_dependency 'nokogiri'
27
+ s.add_dependency 'css_parser'
28
+ s.add_dependency 'maca-fork-csspool'
29
+ end
@@ -0,0 +1,98 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+
4
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
5
+
6
+ require "inline-style/rule"
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
22
+ #
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
+ def self.process html, opts = {}
26
+ new(html, opts).process
27
+ end
28
+
29
+ def initialize html, opts = {}
30
+ @stylesheets_path = opts[:stylesheets_path] || ENV['DOCUMENT_ROOT'] || '.'
31
+ @html = html
32
+ @dom = String === html ? Nokogiri.HTML(html) : html
33
+ end
34
+
35
+ def process
36
+ nodes_with_rules.each_pair do |node, rules|
37
+ rules = rules.sort_by{ |sel| "#{sel.specificity}%04d" % rules.index(sel) }
38
+
39
+ styles = []
40
+ rules.each do |rule|
41
+ next if rule.dynamic_pseudo_class
42
+ rule.declarations.each do |declaration|
43
+ if defined = styles.assoc(declaration.first)
44
+ styles[styles.index(defined)] = declaration # overrides defined declaration
45
+ else
46
+ styles << declaration
47
+ end
48
+ end
49
+ end
50
+
51
+ style = styles.map{ |declaration| declaration.join(': ') }.join('; ')
52
+ node['style'] = "#{style};" unless style.empty?
53
+ end
54
+ pre_parsed? ? @dom : @dom.to_s
55
+ end
56
+
57
+ private
58
+ def nodes_with_rules
59
+ nodes, body = {}, @dom.css('body')
60
+
61
+ parse_css.rules.each do |rule|
62
+ body.css(rule.selector).each do |node|
63
+ nodes[node] ||= []
64
+ nodes[node].push rule
65
+ end
66
+ end
67
+
68
+ body.css('[style]').each do |node|
69
+ nodes[node] ||= []
70
+ nodes[node].push Rule.new ':inline', node['style'], '1000' # :inline is not really a pseudoclass
71
+ end
72
+
73
+ nodes
74
+ end
75
+
76
+ def pre_parsed?
77
+ @html == @dom
78
+ end
79
+
80
+ # Returns parsed CSS
81
+ def extract_css
82
+ @dom.css('style, link[rel=stylesheet]').collect do |node|
83
+ next unless /^$|screen|all/ === node['media'].to_s
84
+ node.remove
85
+
86
+ if node.name == 'style'
87
+ node.content
88
+ else
89
+ uri = %r{^https?://} === node['href'] ? node['href'] : File.join(@stylesheets_path, node['href'].sub(/\?.+$/,''))
90
+ open(uri).read
91
+ end
92
+ end.join("\n")
93
+ end
94
+
95
+ def parse_css
96
+ CSSParser.new extract_css
97
+ end
98
+ end
@@ -0,0 +1,15 @@
1
+ require 'css_parser'
2
+
3
+ class InlineStyle
4
+ class CssParserWrapper
5
+ attr_accessor :rules
6
+
7
+ def initialize(css_code)
8
+ parser, @rules = CssParser::Parser.new, []
9
+ parser.add_block! css_code
10
+ parser.each_rule_set do |rule_set|
11
+ rule_set.each_selector { |sel, dec, spec| @rules << Rule.new(sel, dec, '%04d' % spec.to_i) }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ class InlineStyle
2
+ class CSSPoolWrapper
3
+ attr_accessor :rules
4
+
5
+ def initialize css_code
6
+ parser = CSSPool.CSS css_code
7
+ @rules = parser.rule_sets.map do |rule_set|
8
+ rule_set.selectors.map { |sel| Rule.new(sel.to_s, sel.declarations.join, "0#{sel.specificity.join}") }
9
+ end.flatten
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ # A interceptor for +mail+ (https://github.com/mikel/mail) to
2
+ # automatically inline the styles of outgoing e-mails. To use:
3
+ #
4
+ # Mail.register_interceptor \
5
+ # InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
6
+ #
7
+ # Rails 3's ActionMailer wraps around the +mail+ and also supports
8
+ # interceptors. Example usage:
9
+ #
10
+ # ActionMailer::Base.register_interceptor \
11
+ # InlineStyle::Mail::Interceptor.new(:stylesheets_path => 'public')
12
+ #
13
+ module InlineStyle::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)
18
+
19
+ # Save the options to later pass to InlineStyle.process
20
+ def initialize(options={})
21
+ @options = options
22
+ end
23
+
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)
32
+ end
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,33 @@
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
21
+
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]]
30
+ end
31
+ end
32
+ end
33
+