module Premailer::Adapter::Nokogiri

Nokogiri adapter

Public Instance Methods

to_inline_css() click to toggle source

Merge CSS into the HTML document.

@return [String] an HTML.

# File lib/premailer/adapter/nokogiri.rb, line 12
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, media_types|
    # 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 Premailer.is_media_query?(media_types) || selector =~ Premailer::RE_UNMERGABLE_SELECTORS
      @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration), media_types) unless @options[:preserve_styles]
    else
      begin
        if selector =~ Premailer::RE_RESET_SELECTORS
          # this is in place to preserve the MailChimp CSS reset: http://github.com/mailchimp/Email-Blueprints/
          # however, this doesn't mean for testing pur
          @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selector, declaration)) unless !@options[:preserve_reset]
        end

        # 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 ::Nokogiri::SyntaxError, RuntimeError, ArgumentError
        $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose]
        next
      end
    end
  end

  # Remove script tags
  doc.search("script").remove if @options[:remove_scripts]

  # 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) && @options[:css_to_attributes]
      Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
        if el[html_attr].nil? and not merged[css_attr].empty?
          new_val = merged[css_attr].dup

          # Remove url() function wrapper
          new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2')

          # Remove !important, trailing semi-colon, and leading/trailing whitespace
          new_val.gsub!(/;$|\s*!important/, '').strip!

          # For width and height tags, remove px units
          new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr)

          # For color-related tags, convert RGB to hex if specified by options
          new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes]

          el[html_attr] = new_val
        end

        unless @options[:preserve_style_attribute]
          merged.instance_variable_get("@declarations").tap do |declarations|
            declarations.delete(css_attr)
          end
        end
      end
    end

    # Collapse multiple rules into one as much as possible.
    merged.create_shorthand! if @options[:create_shorthands]

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

  doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules]

  if @options[:remove_classes] or @options[:remove_comments]
    doc.traverse do |el|
      if el.comment? and @options[:remove_comments]
        el.remove
      elsif el.element?
        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

  if @options[:reset_contenteditable]
    doc.search('*[@contenteditable]').each do |el|
      el.remove_attribute('contenteditable')
    end
  end

  @processed_doc = doc
  if is_xhtml?
    # we don't want to encode carriage returns
    @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r")
  else
    @processed_doc.to_html(:encoding => @options[:output_encoding])
  end
end
to_plain_text() click to toggle source

Converts the HTML document to a format suitable for plain-text e-mail.

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

@return [String] a plain text.

# File lib/premailer/adapter/nokogiri.rb, line 186
def to_plain_text
  html_src = ''
  begin
    html_src = @doc.at("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_s() click to toggle source

Gets the original HTML as a string. @return [String] HTML.

# File lib/premailer/adapter/nokogiri.rb, line 199
def to_s
  if is_xhtml?
    @doc.to_xhtml(:encoding => nil)
  else
    @doc.to_html(:encoding => nil)
  end
end