kivanio (owner)

Fork Of

Revisions

gist: 227912 Download_button fork
public
Public Clone URL: git://gist.github.com/227912.git
Embed All Files: show embed
Ruby #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
require 'nokogiri'
require 'open-uri'
 
gem 'maca-fork-csspool'
require 'csspool'
 
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
      # env['DOCUMENT_ROOT']
      #
      # +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.
      # 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]
        @pseudo = opts[:pseudo]
        @paths = /^(?:#{ [*opts[:paths]].join('|') })/
      end
 
      def call env
        response = @app.call env
        return response unless @paths === env['PATH_INFO']
        
        status, headers, content = response
        response = ::Rack::Response.new '', status, headers
        body = content.respond_to?(:body) ? content.body : content
        
        
        response.write InlineStyle.process(body, :stylesheets_path => @document_root || env['DOCUMENT_ROOT'], :pseudo => @pseudo)
        response.finish
      end
    end
  end
  
  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, stylesheets_path
    nodes = {}
 
    css.rule_sets.each do |rule_set|
      rule_set.selectors.each do |selector|
        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
          
          next unless node['style']
          
          path = node.css_path
          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 }
        end
      end
    end
 
    nodes.each_pair do |node, style|
      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
  
  # Returns CSSPool::Document
  def self.extract_css html, stylesheets_path = ''
    CSSPool.CSS html.css('style, link').collect { |e|
      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
      
      uri = %r{^https?://} === e['href'] ? e['href'] : File.join(stylesheets_path, e['href'].sub(/\?\d+$/,''))
      open(uri).read rescue nil
    }.join("\n")
  end
end