Module: Adapter::Hpricot

Defined in:
lib/premailer/adapter/hpricot.rb

Instance Method Summary collapse

Instance Method Details

#load_html(input) ⇒ Object

Returns an Hpricot document.



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/premailer/adapter/hpricot.rb', line 162

def load_html(input) # :nodoc:
	thing = nil
	
	# TODO: duplicate options
	if @options[:with_html_string] or @options[:inline] or input.respond_to?(:read)
		thing = input
		elsif @is_local_file
		@base_dir = File.dirname(input)
		thing = File.open(input, 'r')
		else
		thing = open(input)
	end

	# TODO: deal with Hpricot seg faults on empty input
	thing ? Hpricot(thing) : nil  
end

#to_inline_cssObject

Returns a string.



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
# File 'lib/premailer/adapter/hpricot.rb', line 8

def to_inline_css
  doc = @processed_doc
  @unmergable_rules = CssParser::Parser.new
  
  # Give all styles already in style attributes a specificity of 1000 
  # per http://www.w3.org/TR/CSS21/cascade.html#specificity
  doc.search("*[@style]").each do |el| 
    el['style'] = '[SPEC=1000[' + el.attributes['style'] + ']]'
  end

  # Iterate through the rules and merge them into the HTML
  @css_parser.each_selector(:all) do |selector, declaration, specificity|
    # Save un-mergable rules separately
    selector.gsub!(/:link([\s]*)+/i) {|m| $1 }

    # Convert element names to lower case
    selector.gsub!(/([\s]|^)([\w]+)/) {|m| $1.to_s + $2.to_s.downcase }
    
    if selector =~ Premailer::RE_UNMERGABLE_SELECTORS
      @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless @options[:preserve_styles]
    else
      begin
        # Change single ID CSS selectors into xpath so that we can match more 
        # than one element.  Added to work around dodgy generated code.
        selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')

        doc.search(selector).each do |el|
          if el.elem? and (el.name != 'head' and el.parent.name != 'head')
            # Add a style attribute or append to the existing one  
            block = "[SPEC=#{specificity}[#{declaration}]]"
            el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block
          end
        end
      rescue Hpricot::Error, RuntimeError, ArgumentError
        $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
        next
      end
    end
  end

  # Read STYLE attributes and perform folding
  doc.search("*[@style]").each do |el|
    style = el.attributes['style'].to_s
    
    declarations = []

    style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration|
      rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i)
      declarations << rs
    end

    # Perform style folding
    merged = CssParser.merge(declarations)
    merged.expand_shorthand!
         
    # Duplicate CSS attributes as HTML attributes
    if Premailer::RELATED_ATTRIBUTES.has_key?(el.name)       
      Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
        el[html_att] = merged[css_att].gsub(/;$/, '').strip if el[html_att].nil? and not merged[css_att].empty?
      end
    end
    
    merged.create_dimensions_shorthand!

    # write the inline STYLE attribute
    el['style'] = Premailer.escape_string(merged.declarations_to_s)
  end

  doc = write_unmergable_css_rules(doc, @unmergable_rules)

  if @options[:remove_classes] or @options[:remove_comments]
    doc.search('*').each do |el|
      if el.comment? and @options[:remove_comments]
        lst = el.parent.children
        el.parent = nil
        lst.delete(el)
      elsif el.elem?
        el.remove_attribute('class') if @options[:remove_classes]
      end
    end
  end

  if @options[:remove_ids]
    # find all anchor's targets and hash them
    targets = []
    doc.search("a[@href^='#']").each do |el|
      target = el.get_attribute('href')[1..-1]
      targets << target
      el.set_attribute('href', "#" + Digest::MD5.hexdigest(target))
    end
    # hash ids that are links target, delete others
    doc.search("*[@id]").each do |el|
      id = el.get_attribute('id')
      if targets.include?(id)
        el.set_attribute('id', Digest::MD5.hexdigest(id))
      else
        el.remove_attribute('id')
      end
    end
  end

  @processed_doc = doc

  @processed_doc.to_original_html
end

#to_plain_textObject

If present, uses the <body> element as its base; otherwise uses the whole document.

Returns a string.



143
144
145
146
147
148
149
150
151
# File 'lib/premailer/adapter/hpricot.rb', line 143

def to_plain_text
	html_src = ''
	begin
		html_src = @doc.search("body").inner_html
	rescue; end

	html_src = @doc.to_html unless html_src and not html_src.empty?
	convert_to_text(html_src, @options[:line_length], @html_encoding)
end

#to_sObject

Returns the original HTML as a string.



155
156
157
# File 'lib/premailer/adapter/hpricot.rb', line 155

def to_s
	@doc.to_original_html
end

#write_unmergable_css_rules(doc, unmergable_rules) ⇒ Object

and write it into the body.

doc is an Hpricot document and unmergable_css_rules is a Css::RuleSet.

Returns an Hpricot document.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/premailer/adapter/hpricot.rb', line 120

def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
  if head = doc.search('head')
    styles = ''
    unmergable_rules.each_selector(:all, :force_important => true) do |selector, declarations, specificity|
      styles += "#{selector} { #{declarations} }\n"
    end    

    unless styles.empty?
      style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
      head.html.empty? ? head.inner_html(style_tag) : head.append(style_tag)
    end
  else
    $stderr.puts "Unable to write unmergable CSS rules: no <head> was found" if @options[:verbose]
  end
  doc
end