Full Code of alexdunae/premailer for AI

master be30faeeec0a cached
41 files
125.5 KB
36.9k tokens
163 symbols
1 requests
Download .txt
Repository: alexdunae/premailer
Branch: master
Commit: be30faeeec0a
Files: 41
Total size: 125.5 KB

Directory structure:
gitextract_7t_qp4dq/

├── .gitignore
├── .jrubyrc
├── .travis.yml
├── .yardopts
├── Gemfile
├── LICENSE.md
├── README.md
├── Rakefile
├── bin/
│   └── premailer
├── lib/
│   ├── premailer/
│   │   ├── adapter/
│   │   │   ├── hpricot.rb
│   │   │   ├── nokogiri.rb
│   │   │   └── nokogumbo.rb
│   │   ├── adapter.rb
│   │   ├── executor.rb
│   │   ├── html_to_plain_text.rb
│   │   ├── premailer.rb
│   │   └── version.rb
│   └── premailer.rb
├── misc/
│   └── client_support.yaml
├── premailer.gemspec
└── test/
    ├── files/
    │   ├── base.html
    │   ├── chars.html
    │   ├── html4.html
    │   ├── html_with_uri.html
    │   ├── ignore.css
    │   ├── ignore.html
    │   ├── import.css
    │   ├── iso-8859-2.html
    │   ├── iso-8859-5.html
    │   ├── no_css.html
    │   ├── noimport.css
    │   ├── styles.css
    │   └── xhtml.html
    ├── future_tests.rb
    ├── helper.rb
    ├── test_adapter.rb
    ├── test_html_to_plain_text.rb
    ├── test_links.rb
    ├── test_misc.rb
    ├── test_premailer.rb
    └── test_warnings.rb

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.DS_Store
*.gem
bin/*.html
html/
vendor/
doc/
.yardoc/
*.sw?
pkg/
.bundle/
*.sublime-*


================================================
FILE: .jrubyrc
================================================
cext.enabled=true


================================================
FILE: .travis.yml
================================================
cache: bundler
sudo: false
branches:
  only: master
matrix:
  fast_finish: true
before_install: rm Gemfile.lock
rvm:
  - 2.0.0
  - 2.1.0
  - 2.2.0
  - 2.3.0


================================================
FILE: .yardopts
================================================
--markup markdown
--markup-provider redcarpet
--charset utf-8
--no-private
--readme README.md
--title "Premailer Documentation"
-
README.md
LICENSE.md


================================================
FILE: Gemfile
================================================
source "https://rubygems.org"

gem 'css_parser', :git => 'git://github.com/premailer/css_parser.git'

platforms :jruby do
  gem 'jruby-openssl'
end

gemspec


================================================
FILE: LICENSE.md
================================================
# Premailer License

Copyright (c) 2007-2012, Alex Dunae.  All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Premailer, Alex Dunae nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
# Premailer README [![Build Status](https://travis-ci.org/premailer/premailer.png?branch=master)](https://travis-ci.org/premailer/premailer)

## What is this?

For the best HTML e-mail delivery results, CSS should be inline. This is a 
huge pain and a simple newsletter becomes un-managable very quickly. This 
script is my solution.

* CSS styles are converted to inline style attributes
  - Checks `style` and `link[rel=stylesheet]` tags and preserves existing inline attributes
* Relative paths are converted to absolute paths
  - Checks links in `href`, `src` and CSS `url('')`
* CSS properties are checked against e-mail client capabilities
  - Based on the Email Standards Project's guides
* A plain text version is created (optional)

## Premailer 2.0 is coming

I'm looking for input on a version 2.0 update to Premailer. Please visit the [Premailer 2.0 Planning Page](https://github.com/premailer/premailer/wiki/New-Premailer-2.0-Planning) and give me your feedback.

## Installation

Install the Premailer gem from RubyGems.

```bash
gem install premailer
```

or add it to your `Gemfile` and run `bundle`.

## Example

```ruby
require 'rubygems' # optional for Ruby 1.9 or above.
require 'premailer'

premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE)

# Write the HTML output
File.open("output.html", "w") do |fout|
  fout.puts premailer.to_inline_css
end

# Write the plain-text output
File.open("output.txt", "w") do |fout|
  fout.puts premailer.to_plain_text
end

# Output any CSS warnings
premailer.warnings.each do |w|
  puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
end
```

## Ruby Compatibility

Premailer is tested on Ruby 1.8.7, Ruby 1.9.2, Ruby 1.9.3, and Ruby 2.x.0 . It also works on REE. JRuby support is close; contributors are welcome.  Checkout the latest build status on the [Travis CI dashboard](https://travis-ci.org/#!/premailer/premailer).

## Premailer-specific CSS

Premailer looks for a few CSS attributes that make working with tables a bit easier.

| CSS Attribute | Availability |
| ------------- | ------------ |
| -premailer-width | Available on `table`, `th` and `td` elements |
| -premailer-height | Available on `table`, `tr`, `th` and `td` elements |
| -premailer-cellpadding | Available on `table` elements |
| -premailer-cellspacing | Available on `table` elements |
| data-premailer="ignore" | Available on `link` and `style` elements. Premailer will ignore these elements entirely. |

Each of these CSS declarations will be copied to appropriate element's attribute.

For example

```css
table { -premailer-cellspacing: 5; -premailer-width: 500; }
```

will result in 

```html
<table cellspacing='5' width='500'>
```

## Contributions

Contributions are most welcome.  Premailer was rotting away in a private SVN repository for too long and could use some TLC.  Fork and patch to your heart's content.  Please don't increment the version numbers, though.

A few areas that are particularly in need of love:

* Improved test coverage
* Move un-repeated background images defined in CSS for Outlook

## Credits and code

Thanks to [all the wonderful contributors](https://github.com/premailer/premailer/contributors) for their updates.

Thanks to [Greenhood + Company](http://www.greenhood.com/) for sponsoring some of the 1.5.6 updates,
and to [Campaign Monitor](https://www.campaignmonitor.com/) for supporting the web interface.

The web interface can be found at [premailer.dialect.ca](http://premailer.dialect.ca).

The source code can be found on [GitHub](https://github.com/alexdunae/premailer).

Copyright by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2007-2012.  See [LICENSE.md](https://github.com/alexdunae/premailer/blob/master/LICENSE.md) for license details.



================================================
FILE: Rakefile
================================================
require 'bundler/setup'
require 'rake/testtask'
require "bundler/gem_tasks"
require 'yard'

GEM_ROOT = File.dirname(__FILE__).freeze  unless defined?(GEM_ROOT)

lib_path = File.expand_path('lib', GEM_ROOT)
$LOAD_PATH.unshift(lib_path)  unless $LOAD_PATH.include? lib_path

require 'premailer/version'

desc 'Parse a URL and write out the output.'
task :inline do
  require 'premailer'

  url = ENV['url']
  output = ENV['output']

  if !url or url.empty? or !output or output.empty?
    puts 'Usage: rake inline url=http://example.com/ output=output.html'
    exit
  end

  premailer = Premailer.new(url, :warn_level => Premailer::Warnings::SAFE, :verbose => true, :adapter => :nokogiri)
  File.open(output, "w") do |fout|
    fout.puts premailer.to_inline_css
  end

  puts "Succesfully parsed '#{url}' into '#{output}'"
  puts premailer.warnings.length.to_s + ' CSS warnings were found'
end

task :text do
  require 'premailer'

  url = ENV['url']
  output = ENV['output']

  if !url or url.empty? or !output or output.empty?
    puts 'Usage: rake text url=http://example.com/ output=output.txt'
    exit
  end

  premailer = Premailer.new(url, :warn_level => Premailer::Warnings::SAFE)
  File.open(output, "w") do |fout|
    fout.puts premailer.to_plain_text
  end

  puts "Succesfully parsed '#{url}' into '#{output}'"
end

Rake::TestTask.new do |t|
  t.test_files = FileList['test/test_*.rb']
  t.verbose = false
  t.warning = false
end

YARD::Rake::YardocTask.new do |yard|
  yard.options << "--title='Premailer #{Premailer::VERSION} Documentation'"
end

task :default => [:test]


================================================
FILE: bin/premailer
================================================
#!/usr/bin/env ruby

# This binary used in rubygems environment only as part of installed gem

require 'premailer/executor'



================================================
FILE: lib/premailer/adapter/hpricot.rb
================================================
require 'hpricot'

class Premailer
  module Adapter
    # Hpricot adapter
    module Hpricot

      # Merge CSS into the HTML document.
      # @return [String] HTML.
      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]')

              # convert attribute selectors to hpricot's format
              selector.gsub!(/\[([\w]+)\]/, '[@\1]')
              selector.gsub!(/\[([\w]+)([\=\~\^\$\*]+)([\w\s]+)\]/, '[@\1\2\'\3\']')

              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

        # Remove script tags
        if @options[:remove_scripts]
          doc.search("script").remove
        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!
          merged.create_shorthand! if @options[:create_shorthands]

          # 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_att, html_att|
              el[html_att] = merged[css_att].gsub(/url\('(.*)'\)/,'\1').gsub(/;$|\s*!important/, '').strip if el[html_att].nil? and not merged[css_att].empty?
              merged.instance_variable_get("@declarations").tap do |declarations|
                declarations.delete(css_att)
              end
            end
          end

          # 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[:reset_contenteditable]
          doc.search('*[@contenteditable]').each do |el|
            el.remove_attribute('contenteditable')
          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

      # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
      # and write it into the <tt>body</tt>.
      #
      # <tt>doc</tt> is an Hpricot document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
      #
      # @return [::Hpricot] a document.
      def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
        styles = unmergable_rules.to_s

        unless styles.empty?
          style_tag = "\n<style type=\"text/css\">\n#{styles}</style>\n"
          if head = doc.search('head')
            head.append(style_tag)
          elsif body = doc.search('body')
            body.append(style_tag)
          else
            doc.inner_html= doc.inner_html << style_tag
          end
        end
        doc
      end


      # 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] Plain text.
      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


      # Gets the original HTML as a string.
      # @return [String] HTML.
      def to_s
        @doc.to_original_html
      end

      # Load the HTML file and convert it into an Hpricot document.
      #
      # @return [::Hpricot] a document.
      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

    end
  end
end



================================================
FILE: lib/premailer/adapter/nokogiri.rb
================================================
require 'nokogiri'

class Premailer
  module Adapter
    # Nokogiri adapter
    module Nokogiri

      # Merge CSS into the HTML document.
      #
      # @return [String] an HTML.
      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
        if @options[:remove_scripts]
          doc.search("script").remove
        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) && @options[:css_to_attributes]
            Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
              el[html_att] = merged[css_att].gsub(/url\(['|"](.*)['|"]\)/, '\1').gsub(/;$|\s*!important/, '').strip if el[html_att].nil? and not merged[css_att].empty?
              merged.instance_variable_get("@declarations").tap do |declarations|
                declarations.delete(css_att)
              end
            end
          end
          # Collapse multiple rules into one as much as possible.
          merged.create_shorthand! if @options[:create_shorthands]

          # write the inline STYLE attribute
          # split by ';' but ignore those in brackets
          attributes = Premailer.escape_string(merged.declarations_to_s).split(/;(?![^(]*\))/).map(&:strip)
          attributes = attributes.map { |attr| [attr.split(':').first, attr] }.sort_by { |pair| pair.first }.map { |pair| pair[1] }
          el['style'] = attributes.join('; ') + ";"
        end

        doc = write_unmergable_css_rules(doc, @unmergable_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

      # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
      # and write it into the <tt>body</tt>.
      #
      # <tt>doc</tt> is an Nokogiri document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
      #
      # @return [::Nokogiri::XML] a document.
      def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
        styles = unmergable_rules.to_s

        unless styles.empty?
          style_tag = "<style type=\"text/css\">\n#{styles}</style>"
          unless (body = doc.search('body')).empty?
            if doc.at_css('body').children && !doc.at_css('body').children.empty?
              doc.at_css('body').children.before(::Nokogiri::XML.fragment(style_tag))
            else
              doc.at_css('body').add_child(::Nokogiri::XML.fragment(style_tag))
            end
          else
            doc.inner_html = style_tag += doc.inner_html
          end
        end
        doc
      end


      # 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.
      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

      # Gets the original HTML as a string.
      # @return [String] HTML.
      def to_s
        if is_xhtml?
          @doc.to_xhtml(:encoding => nil)
        else
          @doc.to_html(:encoding => nil)
        end
      end

      # Load the HTML file and convert it into an Nokogiri document.
      #
      # @return [::Nokogiri::XML] a document.
      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

        if thing.respond_to?(:read)
          thing = thing.read
        end

        return nil unless thing
        doc = nil

        # Handle HTML entities
        if @options[:replace_html_entities] == true and thing.is_a?(String)
          HTML_ENTITIES.map do |entity, replacement|
            thing.gsub! entity, replacement
          end
        end
        # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
        # However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
        if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
          thing = thing.force_encoding(@options[:input_encoding]).encode!
          doc = ::Nokogiri::HTML(thing, nil, @options[:input_encoding]) { |c| c.recover }
        else
          default_encoding = RUBY_PLATFORM == 'java' ? nil : 'BINARY'
          doc = ::Nokogiri::HTML(thing, nil, @options[:input_encoding] || default_encoding) { |c| c.recover }
        end

        # Fix for removing any CDATA tags from both style and script tags inserted per
        # https://github.com/sparklemotion/nokogiri/issues/311 and
        # https://github.com/premailer/premailer/issues/199
        %w(style script).each do |tag|
          doc.search(tag).children.each do |child|
            child.swap(child.text()) if child.cdata?
          end
        end

        doc
      end

    end
  end
end


================================================
FILE: lib/premailer/adapter/nokogumbo.rb
================================================
require 'nokogumbo'

class Premailer
  module Adapter
    # Nokogiri adapter
    module Nokogumbo

      # Merge CSS into the HTML document.
      #
      # @return [String] an HTML.
      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
        if @options[:remove_scripts]
          doc.search("script").remove
        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) && @options[:css_to_attributes]
            Premailer::RELATED_ATTRIBUTES[el.name].each do |css_att, html_att|
              el[html_att] = merged[css_att].gsub(/url\(['|"](.*)['|"]\)/, '\1').gsub(/;$|\s*!important/, '').strip if el[html_att].nil? and not merged[css_att].empty?
              merged.instance_variable_get("@declarations").tap do |declarations|
                declarations.delete(css_att)
              end
            end
          end
          # Collapse multiple rules into one as much as possible.
          merged.create_shorthand! if @options[:create_shorthands]

          # write the inline STYLE attribute
          attributes = Premailer.escape_string(merged.declarations_to_s).split(';').map(&:strip)
          attributes = attributes.map { |attr| [attr.split(':').first, attr] }.sort_by { |pair| pair.first }.map { |pair| pair[1] }
          el['style'] = attributes.join('; ') + ";"
        end

        doc = write_unmergable_css_rules(doc, @unmergable_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

      # Create a <tt>style</tt> element with un-mergable rules (e.g. <tt>:hover</tt>)
      # and write it into the <tt>body</tt>.
      #
      # <tt>doc</tt> is an Nokogiri document and <tt>unmergable_css_rules</tt> is a Css::RuleSet.
      #
      # @return [::Nokogiri::XML] a document.
      def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
        styles = unmergable_rules.to_s

        unless styles.empty?
          style_tag = "<style type=\"text/css\">\n#{styles}</style>"
          unless (body = doc.search('body')).empty?
            if doc.at_css('body').children && !doc.at_css('body').children.empty?
              doc.at_css('body').children.before(::Nokogiri::XML.fragment(style_tag))
            else
              doc.at_css('body').add_child(::Nokogiri::XML.fragment(style_tag))
            end
          else
            doc.inner_html = style_tag += doc.inner_html
          end
        end
        doc
      end


      # 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.
      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

      # Gets the original HTML as a string.
      # @return [String] HTML.
      def to_s
        if is_xhtml?
          @doc.to_xhtml(:encoding => nil)
        else
          @doc.to_html(:encoding => nil)
        end
      end

      # Load the HTML file and convert it into an Nokogiri document.
      #
      # @return [::Nokogiri::XML] a document.
      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

        if thing.respond_to?(:read)
          thing = thing.read
        end

        return nil unless thing
        doc = nil

        # Handle HTML entities
        if @options[:replace_html_entities] == true and thing.is_a?(String)
          HTML_ENTITIES.map do |entity, replacement|
            thing.gsub! entity, replacement
          end
        end
        # Default encoding is ASCII-8BIT (binary) per http://groups.google.com/group/nokogiri-talk/msg/0b81ef0dc180dc74
        # However, we really don't want to hardcode this. ASCII-8BIT should be the default, but not the only option.
        if thing.is_a?(String) and RUBY_VERSION =~ /1.9/
          thing = thing.force_encoding(@options[:input_encoding]).encode!
          doc = ::Nokogiri::HTML5(thing)
        else
          default_encoding = RUBY_PLATFORM == 'java' ? nil : 'BINARY'
          doc = ::Nokogiri::HTML5(thing)
        end

        # Fix for removing any CDATA tags from both style and script tags inserted per
        # https://github.com/sparklemotion/nokogiri/issues/311 and
        # https://github.com/premailer/premailer/issues/199
        %w(style script).each do |tag|
          doc.search(tag).children.each do |child|
            child.swap(child.text()) if child.cdata?
          end
        end

        doc
      end

    end
  end
end


================================================
FILE: lib/premailer/adapter.rb
================================================


class Premailer
  # Manages the adapter classes. Currently supports:
  #
  # * nokogiri
  # * hpricot
  module Adapter

    autoload :Hpricot, 'premailer/adapter/hpricot'
    autoload :Nokogiri, 'premailer/adapter/nokogiri'
    autoload :Nokogumbo, 'premailer/adapter/nokogumbo'

    # adapter to required file mapping.
    REQUIREMENT_MAP = [
      ["hpricot",  :hpricot],
      ["nokogiri", :nokogiri],
      ["nokogumbi", :nokogumbo],
    ]

    # Returns the adapter to use.
    def self.use
      return @use if @use
      self.use = self.default
      @use
    end

    # The default adapter based on what you currently have loaded and
    # installed. First checks to see if any adapters are already loaded,
    # then checks to see which are installed if none are loaded.
    # @raise [RuntimeError] unless suitable adapter found.
    def self.default
      return :hpricot  if defined?(::Hpricot)
      return :nokogiri if defined?(::Nokogiri)
      return :nokogumbo if defined?(::Nokogumbo)

      REQUIREMENT_MAP.each do |(library, adapter)|
        begin
          require library
          return adapter
        rescue LoadError
          next
        end
      end

      raise RuntimeError.new("No suitable adapter for Premailer was found, please install hpricot or nokogiri")
    end

    # Sets the adapter to use.
    # @raise [ArgumentError] unless the adapter exists.
    def self.use=(new_adapter)
      @use = find(new_adapter)
    end

    # Returns an adapter.
    # @raise [ArgumentError] unless the adapter exists.
    def self.find(adapter)
      return adapter if adapter.is_a?(Module)

      Premailer::Adapter.const_get("#{adapter.to_s.split('_').map{|s| s.capitalize}.join('')}")
    rescue NameError
      raise ArgumentError, "Invalid adapter: #{adapter}"
    end

  end
end


================================================
FILE: lib/premailer/executor.rb
================================================
require 'optparse'
require 'premailer'

# defaults
options = {
  :base_url => nil,
  :link_query_string => nil,
  :remove_classes => false,
  :verbose => false,
  :line_length => 65
}

mode = :html

opts = OptionParser.new do |opts|
  opts.banner = "Improve the rendering of HTML emails by making CSS inline among other things. Takes a path to a local file, a URL or a pipe as input.\n\n"
  opts.define_head "Usage: premailer <optional uri|optional path> [options]"
  opts.separator ""
  opts.separator "Examples:"
  opts.separator "  premailer http://example.com/ > out.html"
  opts.separator "  premailer http://example.com/ --mode txt > out.txt"
  opts.separator "  cat input.html | premailer -q src=email > out.html"
  opts.separator "  premailer ./public/index.html"
  opts.separator ""
  opts.separator "Options:"

  opts.on("--mode MODE", [:html, :txt], "Output: html or txt") do |v|
    mode = v
  end

  opts.on("-b", "--base-url STRING", String, "Base URL, useful for local files") do |v|
    options[:base_url] = v
  end

  opts.on("-q", "--query-string STRING", String, "Query string to append to links") do |v|
    options[:link_query_string] = v
  end

  opts.on("--css FILE,FILE", Array, "Additional CSS stylesheets") do |v|
    options[:css] = v
  end

  opts.on("-r", "--remove-classes", "Remove HTML classes") do |v|
    options[:remove_classes] = v
  end

  opts.on("-j", "--remove-scripts", "Remove <script> elements") do |v|
    options[:remove_classes] = v
  end

  opts.on("-l", "--line-length N", Integer, "Line length for plaintext (default: #{options[:line_length].to_s})") do |v|
    options[:line_length] = v
  end

  opts.on("-e", "--entities", "Output HTML entities instead of UTF-8 when using Nokogiri") do |v|
    options[:output_encoding] = "US-ASCII"
  end

  opts.on("-d", "--io-exceptions", "Abort on I/O errors") do |v|
    options[:io_exceptions] = v
  end

  opts.on("-v", "--verbose", "Print additional information at runtime") do |v|
    options[:verbose] = v
  end

  opts.on_tail("-?", "--help", "Show this message") do
    puts opts
    exit
  end

  opts.on_tail("-V", "--version", "Show version") do
    puts "Premailer #{Premailer::VERSION} (c) 2008-2010 Alex Dunae"
    exit
  end
end
opts.parse!

$stderr.puts "Processing in #{mode} mode with options #{options.inspect}" if options[:verbose]

premailer = nil
input = nil

if ARGV.size > 0
  # Executed via command line or shell script
  input = ARGV.shift
else
  # Called in piped command
  input = $stdin.read
  options[:with_html_string] = true
end

if input
  premailer = Premailer.new(input, options)
else
  puts opts
  exit 1
end

if mode == :txt
  print premailer.to_plain_text
else
  print premailer.to_inline_css
end

exit


================================================
FILE: lib/premailer/html_to_plain_text.rb
================================================
# coding: utf-8
require 'htmlentities'

# Support functions for Premailer
module HtmlToPlainText

  # Returns the text in UTF-8 format with all HTML tags removed
  #
  # TODO: add support for DL, OL
  def convert_to_text(html, line_length = 65, from_charset = 'UTF-8')
    txt = html

    # strip text ignored html. Useful for removing
    # headers and footers that aren't needed in the
    # text version
    txt.gsub!(/<!-- start text\/html -->.*?<!-- end text\/html -->/m, '')

    # replace images with their alt attributes
    # for img tags with "" for attribute quotes
    # with or without closing tag
    # eg. the following formats:
    # <img alt="" />
    # <img alt="">
    txt.gsub!(/<img.+?alt=\"([^\"]*)\"[^>]*\>/i, '\1')

    # for img tags with '' for attribute quotes
    # with or without closing tag
    # eg. the following formats:
    # <img alt='' />
    # <img alt=''>
    txt.gsub!(/<img.+?alt=\'([^\']*)\'[^>]*\>/i, '\1')

    # links
    txt.gsub!(/<a\s.*?href=["'](mailto:)?([^"']*)["'][^>]*>((.|\s)*?)<\/a>/i) do |s|
      if $3.empty?
        ''
      else
        $3.strip + ' ( ' + $2.strip + ' )'
      end
    end

    # handle headings (H1-H6)
    txt.gsub!(/(<\/h[1-6]>)/i, "\n\\1") # move closing tags to new lines
    txt.gsub!(/[\s]*<h([1-6]+)[^>]*>[\s]*(.*)[\s]*<\/h[1-6]+>/i) do |s|
      hlevel = $1.to_i

      htext = $2
      htext.gsub!(/<br[\s]*\/?>/i, "\n") # handle <br>s
      htext.gsub!(/<\/?[^>]*>/i, '') # strip tags

      # determine maximum line length
      hlength = 0
      htext.each_line { |l| llength = l.strip.length; hlength = llength if llength > hlength }
      hlength = line_length if hlength > line_length

      case hlevel
        when 1   # H1, asterisks above and below
          htext = ('*' * hlength) + "\n" + htext + "\n" + ('*' * hlength)
        when 2   # H1, dashes above and below
          htext = ('-' * hlength) + "\n" + htext + "\n" + ('-' * hlength)
        else     # H3-H6, dashes below
          htext = htext + "\n" + ('-' * hlength)
      end

      "\n\n" + htext + "\n\n"
    end

    # wrap spans
    txt.gsub!(/(<\/span>)[\s]+(<span)/mi, '\1 \2')

    # lists -- TODO: should handle ordered lists
    txt.gsub!(/[\s]*(<li[^>]*>)[\s]*/i, '* ')
    # list not followed by a newline
    txt.gsub!(/<\/li>[\s]*(?![\n])/i, "\n")

    # paragraphs and line breaks
    txt.gsub!(/<\/p>/i, "\n\n")
    txt.gsub!(/<br[\/ ]*>/i, "\n")

    # strip remaining tags
    txt.gsub!(/<\/?[^>]*>/, '')

    # decode HTML entities
    he = HTMLEntities.new
    txt = he.decode(txt)

    # no more than two consecutive spaces
    txt.gsub!(/ {2,}/, " ")

    txt = word_wrap(txt, line_length)

    # remove linefeeds (\r\n and \r -> \n)
    txt.gsub!(/\r\n?/, "\n")

    # strip extra spaces
    txt.gsub!(/[ \t]*\302\240+[ \t]*/, " ") # non-breaking spaces -> spaces
    txt.gsub!(/\n[ \t]+/, "\n") # space at start of lines
    txt.gsub!(/[ \t]+\n/, "\n") # space at end of lines

    # no more than two consecutive newlines
    txt.gsub!(/[\n]{3,}/, "\n\n")

    # the word messes up the parens
    txt.gsub!(/\(([ \n])(http[^)]+)([\n ])\)/) do |s|
      ($1 == "\n" ? $1 : '' ) + '( ' + $2 + ' )' + ($3 == "\n" ? $1 : '' )
    end

    txt.strip
  end

  # Taken from Rails' word_wrap helper (http://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-word_wrap)
  def word_wrap(txt, line_length)
    txt.split("\n").collect do |line|
      line.length > line_length ? line.gsub(/(.{1,#{line_length}})(\s+|$)/, "\\1\n").strip : line
    end * "\n"
  end
end


================================================
FILE: lib/premailer/premailer.rb
================================================
# Premailer processes HTML and CSS to improve e-mail deliverability.
#
# Premailer's main function is to render all CSS as inline <tt>style</tt>
# attributes. It also converts relative links to absolute links and checks
# the 'safety' of CSS properties against a CSS support chart.
#
# ## Example of use
#
# ```ruby
# premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE)
#
# # Write the HTML output
# fout = File.open("output.html", "w")
# fout.puts premailer.to_inline_css
# fout.close
#
# # Write the plain-text output
# fout = File.open("ouput.txt", "w")
# fout.puts premailer.to_plain_text
# fout.close
#
# # List any CSS warnings
# puts premailer.warnings.length.to_s + ' warnings found'
# premailer.warnings.each do |w|
#   puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}"
# end
#
# premailer = Premailer.new(html_file, :warn_level => Premailer::Warnings::SAFE)
# puts premailer.to_inline_css
# ```
#
require 'premailer/version'

class Premailer
  include HtmlToPlainText
  include CssParser

  CLIENT_SUPPORT_FILE = File.dirname(__FILE__) + '/../../misc/client_support.yaml'

  # Unmergable selectors regexp.
  RE_UNMERGABLE_SELECTORS = /(\:(visited|active|hover|focus|after|before|selection|target|first\-(line|letter))|^\@)/i
  # Reset selectors regexp.
  RE_RESET_SELECTORS = /^(\:\#outlook|body.*|\.ReadMsgBody|\.ExternalClass|img|\#backgroundTable)$/

  # list of HTMLEntities to fix
  # source: http://stackoverflow.com/questions/2812781/how-to-convert-webpage-apostrophe-8217-to-ascii-39-in-ruby-1-
  HTML_ENTITIES = {
    "&#8217;" => "'",
    "&#8230;" => "...",
    "&#8216;" => "'",
    "&#8218;" => ',',
    "&#8219;" => "'",
    "&#8220;" => '"',
    "&#8221;" => '"',
    "&#8208;" => '-',
    "&#8211;" => '-',
    "&#8212;" => '--',
    "&#8213;" => '--'
  }

  # list of CSS attributes that can be rendered as HTML attributes
  #
  # @todo too much repetition
  # @todo background=""
  RELATED_ATTRIBUTES = {
    'h1' => {'text-align' => 'align'},
    'h2' => {'text-align' => 'align'},
    'h3' => {'text-align' => 'align'},
    'h4' => {'text-align' => 'align'},
    'h5' => {'text-align' => 'align'},
    'h6' => {'text-align' => 'align'},
    'p' => {'text-align' => 'align'},
    'div' => {'text-align' => 'align'},
    'blockquote' => {'text-align' => 'align'},
    'body' => {'background-color' => 'bgcolor'},
    'table' => {
      '-premailer-align' => 'align',
      'background-color' => 'bgcolor',
      'background-image' => 'background',
      '-premailer-width' => 'width',
      '-premailer-height' => 'height',
      '-premailer-cellpadding' => 'cellpadding',
      '-premailer-cellspacing' => 'cellspacing'
    },
    'tr' => {
      'text-align' => 'align',
      'background-color' => 'bgcolor',
      '-premailer-height' => 'height'
    },
    'th' => {
      'text-align' => 'align',
      'background-color' => 'bgcolor',
      'vertical-align' => 'valign',
      '-premailer-width' => 'width',
      '-premailer-height' => 'height'
    },
    'td' => {
      'text-align' => 'align',
      'background-color' => 'bgcolor',
      'vertical-align' => 'valign',
      '-premailer-width' => 'width',
      '-premailer-height' => 'height'
    },
    'img' => {
      'float' => 'align',
      '-premailer-width' => 'width',
      '-premailer-height' => 'height'
    }
  }

  # URI of the HTML file used
  attr_reader   :html_file

  # base URL used to resolve links
  attr_reader   :base_url

  # base directory used to resolve links for local files
  # @return [String] base directory
  attr_reader   :base_dir

  # unmergeable CSS rules to be preserved in the head (CssParser)
  attr_reader   :unmergable_rules

  # processed HTML document (Hpricot/Nokogiri)
  attr_reader   :processed_doc

  # source HTML document (Hpricot/Nokogiri)
  attr_reader   :doc

  # Warning levels
  module Warnings
    # No warnings
    NONE = 0
    # Safe
    SAFE = 1
    # Poor
    POOR = 2
    # Risky
    RISKY = 3
  end
  include Warnings

  # Waning level names
  WARN_LABEL = %w(NONE SAFE POOR RISKY)

  # Create a new Premailer object.
  #
  # @param html is the HTML data to process. It can be either an IO object, the URL of a
  #   remote file, a local path or a raw HTML string.  If passing an HTML string you
  #   must set the with_html_string option to true.
  #
  # @param [Hash] options the options to handle html with.
  # @option options [Fixnum] :line_length Line length used by to_plain_text. Default is 65.
  # @option options [Fixnum] :warn_level What level of CSS compatibility warnings to show (see {Premailer::Warnings}).
  # @option options [String] :link_query_string A string to append to every <tt>a href=""</tt> link. Do not include the initial <tt>?</tt>.
  # @option options [String] :base_url Used to calculate absolute URLs for local files.
  # @option options [Array(String)] :css Manually specify CSS stylesheets.
  # @option options [Boolean] :css_to_attributes Copy related CSS attributes into HTML attributes (e.g. background-color to bgcolor)
  # @option options [String] :css_string Pass CSS as a string
  # @option options [Boolean] :remove_ids Remove ID attributes whenever possible and convert IDs used as anchors to hashed to avoid collisions in webmail programs.  Default is false.
  # @option options [Boolean] :remove_classes Remove class attributes. Default is false.
  # @option options [Boolean] :remove_comments Remove html comments. Default is false.
  # @option options [Boolean] :remove_scripts Remove <tt>script</tt> elements. Default is true.
  # @option options [Boolean] :reset_contenteditable Remove <tt>contenteditable</tt> attributes. Default is true.
  # @option options [Boolean] :preserve_styles Whether to preserve any <tt>link rel=stylesheet</tt> and <tt>style</tt> elements.  Default is false.
  # @option options [Boolean] :preserve_reset Whether to preserve styles associated with the MailChimp reset code. Default is true.
  # @option options [Boolean] :with_html_string Whether the html param should be treated as a raw string. Default is false.
  # @option options [Boolean] :verbose Whether to print errors and warnings to <tt>$stderr</tt>.  Default is false.
  # @option options [Boolean] :include_link_tags Whether to include css from <tt>link rel=stylesheet</tt> tags.  Default is true.
  # @option options [Boolean] :include_style_tags Whether to include css from <tt>style</tt> tags.  Default is true.
  # @option options [String] :input_encoding Manually specify the source documents encoding. This is a good idea. Default is ASCII-8BIT.
  # @option options [Boolean] :replace_html_entities Convert HTML entities to actual characters. Default is false.
  # @option options [Boolean] :escape_url_attributes URL Escapes href, src, and background attributes on elements. Default is true.
  # @option options [Symbol] :adapter Which HTML parser to use, either <tt>:nokogiri</tt> or <tt>:hpricot</tt>.  Default is <tt>:hpricot</tt>.
  # @option options [String] :output_encoding Output encoding option for Nokogiri adapter. Should be set to "US-ASCII" to output HTML entities instead of Unicode characters.
  # @option options [Boolean] :create_shorthands Combine several properties into a shorthand one, e.g. font: style weight size. Default is true.
  def initialize(html, options = {})
    @options = {:warn_level => Warnings::SAFE,
                :line_length => 65,
                :link_query_string => nil,
                :base_url => nil,
                :remove_classes => false,
                :remove_ids => false,
                :remove_comments => false,
                :remove_scripts => true,
                :reset_contenteditable => true,
                :css => [],
                :css_to_attributes => true,
                :with_html_string => false,
                :css_string => nil,
                :preserve_styles => false,
                :preserve_reset => true,
                :verbose => false,
                :debug => false,
                :io_exceptions => false,
                :include_link_tags => true,
                :include_style_tags => true,
                :input_encoding => 'ASCII-8BIT',
                :output_encoding => nil,
                :replace_html_entities => false,
                :escape_url_attributes => true,
                :unescaped_ampersand => false,
                :create_shorthands => true,
                :adapter => Adapter.use,
                }.merge(options)

    @html_file = html
    @is_local_file = @options[:with_html_string] || Premailer.local_data?(html)

    @css_files = [@options[:css]].flatten

    @css_warnings = []

    @base_url = nil
    @base_dir = nil
    @unmergable_rules = nil

    if @options[:base_url]
      @base_url = URI.parse(@options.delete(:base_url))
    elsif not @is_local_file
      @base_url = URI.parse(@html_file)
    end

    @css_parser = CssParser::Parser.new({
      :absolute_paths => true,
      :import => true,
      :io_exceptions => @options[:io_exceptions]
    })

    @adapter_class = Adapter.find @options[:adapter]

    self.class.send(:include, @adapter_class)

    @doc = load_html(@html_file)

    @processed_doc = @doc
    @processed_doc = convert_inline_links(@processed_doc, @base_url) if @base_url
    if options[:link_query_string]
      @processed_doc = append_query_string(@processed_doc, options[:link_query_string])
    end
    load_css_from_options!
    load_css_from_html!
  end

  # CSS warnings.
  # @return [Array(Hash)] Array of warnings.
  def warnings
    return [] if @options[:warn_level] == Warnings::NONE
    @css_warnings = check_client_support if @css_warnings.empty?
    @css_warnings
  end

protected
  def load_css_from_local_file!(path)
    css_block = ''
    path.gsub!(/\Afile:/, '')
    begin
      File.open(path, "r") do |file|
        while line = file.gets
          css_block << line
        end
      end

      load_css_from_string(css_block)
    rescue; end
  end

  def load_css_from_string(css_string)
    @css_parser.add_block!(css_string, {:base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld]})
  end

  # @private
  def load_css_from_options! # :nodoc:
    load_css_from_string(@options[:css_string]) if @options[:css_string]

    @css_files.each do |css_file|
      if Premailer.local_data?(css_file)
        load_css_from_local_file!(css_file)
      else
        @css_parser.load_uri!(css_file)
      end
    end
  end

    # Load CSS included in <tt>style</tt> and <tt>link</tt> tags from an HTML document.
  def load_css_from_html! # :nodoc:
    tags = @doc.search("link[@rel='stylesheet']:not([@data-premailer='ignore']), style:not([@data-premailer='ignore'])")
    if tags
      tags.each do |tag|
        if tag.to_s.strip =~ /^\<link/i && tag.attributes['href'] && media_type_ok?(tag.attributes['media']) && @options[:include_link_tags]
          # A user might want to <link /> to a local css file that is also mirrored on the site
          # but the local one is different (e.g. newer) than the live file, premailer will now choose the local file

          if tag.attributes['href'].to_s.include? @base_url.to_s and @html_file.kind_of?(String)
            if @options[:with_html_string]
              link_uri = tag.attributes['href'].to_s.sub(@base_url.to_s, '')
            else
              link_uri = File.join(File.dirname(@html_file), tag.attributes['href'].to_s.sub!(@base_url.to_s, ''))
              # if the file does not exist locally, try to grab the remote reference
              unless File.exists?(link_uri)
                link_uri = Premailer.resolve_link(tag.attributes['href'].to_s, @html_file)
              end
            end
          else
            link_uri = tag.attributes['href'].to_s
          end

          if Premailer.local_data?(link_uri)
            $stderr.puts "Loading css from local file: " + link_uri if @options[:verbose]
            load_css_from_local_file!(link_uri)
          else
            $stderr.puts "Loading css from uri: " + link_uri if @options[:verbose]
            @css_parser.load_uri!(link_uri, {:only_media_types => [:screen, :handheld]})
          end

        elsif tag.to_s.strip =~ /^\<style/i && @options[:include_style_tags]
          @css_parser.add_block!(tag.inner_html, :base_uri => @base_url, :base_dir => @base_dir, :only_media_types => [:screen, :handheld])
        end
      end
      tags.remove unless @options[:preserve_styles]
    end
  end



# here be deprecated methods
public
  # @private
  # @deprecated
  def local_uri?(uri) # :nodoc:
    warn "[DEPRECATION] `local_uri?` is deprecated.  Please use `Premailer.local_data?` instead."
    Premailer.local_data?(uri)
  end

# here be instance methods

  # @private
  def media_type_ok?(media_types)
    media_types = media_types.to_s
    return true if media_types.nil? or media_types.empty?
    media_types.split(/[\s]+|,/).any? { |media_type| media_type.strip =~ /screen|handheld|all/i }
  end

  def append_query_string(doc, qs)
    return doc if qs.nil?

    qs.to_s.gsub!(/^[\?]*/, '').strip!
    return doc if qs.empty?

    begin
      current_host = @base_url.host
    rescue
      current_host = nil
    end

    $stderr.puts "Attempting to append_query_string: #{qs}" if @options[:verbose]

    doc.search('a').each do|el|
      href = el.attributes['href'].to_s.strip
      next if href.nil? or href.empty?

      next if href[0,1] =~ /[\#\{\[\<\%]/ # don't bother with anchors or special-looking links

      begin
        href = URI.parse(href)

        if current_host and href.host != nil and href.host != current_host
          $stderr.puts "Skipping append_query_string for: #{href.to_s} because host is no good" if @options[:verbose]
          next
        end

        if href.scheme and href.scheme != 'http' and href.scheme != 'https'
          puts "Skipping append_query_string for: #{href.to_s} because scheme is no good" if @options[:verbose]
          next
        end

        if href.query and not href.query.empty?
          amp = @options[:unescaped_ampersand] ? '&' : '&amp;'
          href.query = href.query + amp + qs
        else
          href.query = qs
        end

        el['href'] = href.to_s
      rescue URI::Error => e
        $stderr.puts "Skipping append_query_string for: #{href.to_s} (#{e.message})" if @options[:verbose]
        next
      end

    end
    doc
  end

  # Check for an XHTML doctype
  def is_xhtml?
    intro = @doc.to_html.strip.split("\n")[0..2].join(' ')
    is_xhtml = !!(intro =~ /w3c\/\/[\s]*dtd[\s]+xhtml/i)
    $stderr.puts "Is XHTML? #{is_xhtml.inspect}\nChecked:\n#{intro}" if @options[:debug]
    is_xhtml
  end

  # Convert relative links to absolute links.
  #
  # Processes <tt>href</tt> <tt>src</tt> and <tt>background</tt> attributes
  # as well as CSS <tt>url()</tt> declarations found in inline <tt>style</tt> attributes.
  #
  # <tt>doc</tt> is an Hpricot document and <tt>base_uri</tt> is either a string or a URI.
  #
  # Returns an Hpricot document.
  def convert_inline_links(doc, base_uri) # :nodoc:
    base_uri = URI.parse(base_uri) unless base_uri.kind_of?(URI)

    append_qs = @options[:link_query_string] || ''
    escape_attrs = @options[:escape_url_attributes]

    ['href', 'src', 'background'].each do |attribute|
      tags = doc.search("*[@#{attribute}]")

      next if tags.empty?

      tags.each do |tag|
        # skip links that look like they have merge tags
        # and mailto, ftp, etc...
        if tag.attributes[attribute].to_s =~ /^([\%\<\{\#\[]|data:|tel:|file:|sms:|callto:|facetime:|mailto:|ftp:|gopher:|cid:)/i
          next
        end

        if tag.attributes[attribute].to_s =~ /^http/i
          begin
            merged = URI.parse(tag.attributes[attribute])
          rescue; next; end
        else
          begin
            merged = Premailer.resolve_link(tag.attributes[attribute].to_s, base_uri)
          rescue
            begin
              next unless escape_attrs
              merged = Premailer.resolve_link(URI.escape(tag.attributes[attribute].to_s), base_uri)
            rescue; end
          end
        end

        # make sure 'merged' is a URI
        merged = URI.parse(merged.to_s) unless merged.kind_of?(URI)
        tag[attribute] = merged.to_s
      end # end of each tag
    end # end of each attrs

    doc.search("*[@style]").each do |el|
      el['style'] = CssParser.convert_uris(el.attributes['style'].to_s, base_uri)
    end
    doc
  end

  # @private
  def self.is_media_query?(media_types)
    media_types && media_types.any?{|mt| mt.to_s.count('()') >= 2 }
  end

  # @private
  def self.escape_string(str) # :nodoc:
    str.gsub(/"/ , "'")
  end

  # @private
  def self.resolve_link(path, base_path) # :nodoc:
    path.strip!
    resolved = nil
    if path =~ /\A(?:(https?|ftp|file):)\/\//i
      resolved = path
      Premailer.canonicalize(resolved)
    elsif base_path.kind_of?(URI)
      resolved = base_path.merge(path)
      Premailer.canonicalize(resolved)
    elsif base_path.kind_of?(String) and base_path =~ /\A(?:(?:https?|ftp|file):)\/\//i
      resolved = URI.parse(base_path)
      resolved = resolved.merge(path)
      Premailer.canonicalize(resolved)
    else
      File.expand_path(path, File.dirname(base_path))
    end
  end

  # Test the passed variable to see if we are in local or remote mode.
  #
  # IO objects return true, as do strings that look like URLs.
  def self.local_data?(data)
    return true   if data.is_a?(IO) || data.is_a?(StringIO)
    return true   if data =~ /\Afile:\/\//i
    return false  if data =~ /\A(?:(https?|ftp):)\/\//i
    true
  end

  # from http://www.ruby-forum.com/topic/140101
  def self.canonicalize(uri) # :nodoc:
    u = uri.kind_of?(URI) ? uri : URI.parse(uri.to_s)
    u.normalize!
    newpath = u.path
    while newpath.gsub!(%r{([^/]+)/\.\./?}) { |match|
        $1 == '..' ? match : ''
      } do end
      newpath = newpath.gsub(%r{/\./}, '/').sub(%r{/\.\z}, '/')
      u.path = newpath
      u.to_s
    end

  # Check <tt>CLIENT_SUPPORT_FILE</tt> for any CSS warnings
  def check_client_support # :nodoc:
    @client_support ||= YAML::load(File.open(CLIENT_SUPPORT_FILE))

    warnings = []
    properties = []

    # Get a list off CSS properties
    @processed_doc.search("*[@style]").each do |el|
      style_url = el.attributes['style'].to_s.gsub(/([\w\-]+)[\s]*\:/i) do |s|
        properties.push($1)
      end
    end

    properties.uniq!

    property_support = @client_support['css_properties']
    properties.each do |prop|
      if property_support.include?(prop) and
          property_support[prop].include?('support') and
          property_support[prop]['support'] >= @options[:warn_level]
        warnings.push({:message => "#{prop} CSS property",
            :level => WARN_LABEL[property_support[prop]['support']],
            :clients => property_support[prop]['unsupported_in'].join(', ')})
      end
    end

    @client_support['attributes'].each do |attribute, data|
      next unless data['support'] >= @options[:warn_level]
      if @doc.search("*[@#{attribute}]").length > 0
        warnings.push({:message => "#{attribute} HTML attribute",
            :level => WARN_LABEL[data['support']],
            :clients => data['unsupported_in'].join(', ')})
      end
    end

    @client_support['elements'].each do |element, data|
      next unless data['support'] >= @options[:warn_level]
      if @doc.search(element).length > 0
        warnings.push({:message => "#{element} HTML element",
            :level => WARN_LABEL[data['support']],
            :clients => data['unsupported_in'].join(', ')})
      end
    end

    warnings
  end
end


================================================
FILE: lib/premailer/version.rb
================================================
class Premailer
  # Premailer version.
  VERSION = '1.8.6'.freeze
end


================================================
FILE: lib/premailer.rb
================================================
require 'yaml'
require 'open-uri'
require 'digest/md5'
require 'cgi'
require 'css_parser'

require 'premailer/adapter'
require 'premailer/html_to_plain_text'
require 'premailer/premailer'


================================================
FILE: misc/client_support.yaml
================================================
# Capabilities of e-mail clients
#
# Sources
# * http://campaignmonitor.com/css/
# * http://www.campaignmonitor.com/blog/archives/2007/04/a_guide_to_css_support_in_emai_2.html
# * http://www.campaignmonitor.com/blog/archives/2007/11/do_image_maps_work_in_html_ema.html
# * http://www.campaignmonitor.com/blog/archives/2007/11/how_forms_perform_in_html_emai.html
# * http://www.xavierfrenette.com/articles/css-support-in-webmail/
# * http://www.email-standards.org/
# Updated 2008-08-26
#
# Support: 1 = SAFE,  2 = POOR,  3 = RISKY
elements:
  map:
    support: 2
    unsupported_in: [GMail]
  area:
    support: 2
    unsupported_in: [GMail]
  form:
    support: 3
    unsupported_in: [Mobile Me, Old Yahoo, AOL, Live Mail, Outlook 07, Outlook 03]
  link:
    support: 2
    unsupported_in: [GMail, Hotmail, Old Yahoo]
attributes:
  ismap:
    support: 2
    unsupported_in: [GMail]
css_properties:
  color:
    unsupported_in: [Eudora]
    support_level: 92%
    support: 1
  font-size:
    unsupported_in: [Eudora]
    support_level: 92%
    support: 1
  font-style:
    unsupported_in: [Eudora]
    support_level: 92%
    support: 1
  font-weight:
    unsupported_in: [Eudora]
    support_level: 92%
    support: 1
  text-align:
    unsupported_in: [Eudora]
    support_level: 92%
    support: 1
  text-decoration:
    unsupported_in: [Eudora]
    support_level: 92%
    support: 1
  background-color:
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  border: &border_shorthand
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  border-bottom: *border_shorthand
  border-left: *border_shorthand
  border-right: *border_shorthand
  border-top: *border_shorthand
  display:
    unsupported_in: [Outlook 07, Eudora]
    support_level: 85%
    support: 2
  font-family:
    unsupported_in: [Eudora, Old GMail, New GMail]
    support_level: 92%
    support: 2
  font-variant:
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  letter-spacing:
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  line-height:
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  padding: &padding_shorthand
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  padding-bottom: *padding_shorthand
  padding-left: *padding_shorthand
  padding-right: *padding_shorthand
  padding-top: *padding_shorthand
  table-layout:
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  text-indent:
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  text-transform:
    unsupported_in: [Notes 6, Eudora]
    support_level: 85%
    support: 2
  border-collapse:
    unsupported_in: [Entourage 2004, Notes 6, Eudora]
    support_level: 77%
    support: 3
  clear:
    unsupported_in: [Outlook 07, Notes 6, Eudora]
    support_level: 77%
    support: 3
  direction:
    unsupported_in: [Outlook 07, Entourage 2004, Eudora, New GMail]
    support_level: 77%
    support: 3
  float:
    unsupported_in: [Outlook 07, Eudora, Old GMail]
    support_level: 85%
    support: 3
  vertical-align:
    unsupported_in: [Outlook 07, Notes 6, Eudora]
    support_level: 77%
    support: 3
  width:
    unsupported_in: [Outlook 07, Notes 6, Eudora]
    support_level: 77%
    support: 3
  word-spacing:
    unsupported_in: [Outlook 07, Notes 6, Eudora]
    support_level: 77%
    support: 3
  height:
    unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail]
    support_level: 77%
    support: 3
  list-style-type:
    unsupported_in: [Outlook 07, Eudora, Hotmail]
    support_level: 85%
    support: 3
  overflow:
    unsupported_in: [Outlook 07, Entourage 2004, Notes 6, Eudora]
    support_level: 69%
    support: 3
  visibility:
    unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail, New GMail, aolWeb]
    support_level: 77%
    support: 3
  white-space:
    unsupported_in: [Outlook 03, Windows Mail, AOL 9, AOL 10, Notes 6, Eudora, Mobile Me]
    support_level: 54%
    support: 3
  background-image:
    unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail, New GMail, Live Mail]
    support_level: 77%
    support: 3
  background-repeat:
    unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail, New GMail, Live Mail]
    support_level: 77%
    support: 3
  clip:
    unsupported_in: [Outlook 07, Notes 6, Eudora, New Yahoo, New GMail, Live Mail, Mobile Me]
    support_level: 77%
    support: 3
  cursor:
    unsupported_in: [Outlook 07, Entourage 2004, Notes 6, Eudora, Old GMail, New GMail]
    support_level: 69%
    support: 3
  list-style-image:
    unsupported_in: [Outlook 07, Notes 6, Eudora, Old GMail, New GMail, Live Mail]
    support_level: 77%
    support: 3
  list-style-position:
    unsupported_in: [Outlook 07, Notes 6, Eudora, Old Yahoo, Hotmail]
    support_level: 77%
    support: 3
  margin: &margin_shorthand
    unsupported_in: [AOL 9, Notes 6, Eudora, Live Mail, Hotmail]
    support_level: 77%
    support: 3
  margin-bottom: *margin_shorthand
  margin-left: *margin_shorthand
  margin-right: *margin_shorthand
  margin-top: *margin_shorthand
  z-index:
    unsupported_in: [Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
    support_level: 85%
    support: 3
  left:
    unsupported_in: [Outlook 07, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
    support_level: 77%
    support: 3
  right:
    unsupported_in: [Outlook 07, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
    support_level: 77%
    support: 3
  top:
    unsupported_in: [Outlook 07, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
    support_level: 77%
    support: 3
  background-position:
    unsupported_in: [Outlook 07, Notes 6, Eudora, Old Yahoo, Old GMail, New GMail, Live Mail, Hotmail]
    support_level: 77%
    support: 3
  border-spacing:
    unsupported_in: [Outlook 03, Outlook 07, Windows Mail, Entourage 2004, AOL 10, Notes 6, Eudora, Live Mail, Hotmail]
    support_level: 46%
    support: 3
  bottom:
    unsupported_in: [Outlook 07, AOL 9, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail]
    support_level: 69%
    support: 3
  empty-cells:
    unsupported_in: [Outlook 03, Outlook 07, Windows Mail, Entourage 2004, AOL 9, AOL 10, Notes 6, Eudora, Hotmail]
    support_level: 38%
    support: 3
  position:
    unsupported_in: [Outlook 07, Notes 6, Eudora, Old Yahoo, New Yahoo, Old GMail, New GMail, Live Mail, Hotmail, Mobile Me]
    support_level: 77%
    support: 3
  caption-side:
    unsupported_in: [Outlook 03, Outlook 07, Windows Mail, Mac Mail, Entourage 2004, Entourage 2008, AOL 9, AOL 10, AOL Desktop for Mac, Notes 6, Eudora, New Yahoo, Hotmail]
    support_level: 15%
    support: 3
  opacity:
    unsupported_in: [Outlook 03, Outlook 07, Windows Mail, Entourage 2004, Notes 6, Eudora, New Yahoo, Old GMail, New GMail, Live Mail, Hotmail]
    support_level: 54%
    support: 3


================================================
FILE: premailer.gemspec
================================================
require './lib/premailer/version'

Gem::Specification.new "premailer", Premailer::VERSION do |s|
  s.summary  = "Preflight for HTML e-mail."
  s.email    = "code@dunae.ca"
  s.homepage = "http://premailer.dialect.ca/"
  s.description = "Improve the rendering of HTML emails by making CSS inline, converting links and warning about unsupported code."
  s.has_rdoc = true
  s.author  = "Alex Dunae"
  s.files            = `git ls-files lib misc LICENSE.md README.md`.split("\n")
  s.executables      = ['premailer']
  s.required_ruby_version = '>= 2.0.0'

  s.add_dependency('css_parser', '>= 1.3.7')
  s.add_dependency('htmlentities', ['>= 4.0.0'])
  s.add_development_dependency "bundler", "~> 1.3"
  s.add_development_dependency('rake', ['~> 0.8',  '!= 0.9.0'])
  s.add_development_dependency('hpricot', '>= 0.8.3')
  s.add_development_dependency('nokogiri', '>= 1.4.4')
  s.add_development_dependency('yard', '~> 0.8.7.6')
  s.add_development_dependency('redcarpet', '~> 3.0')
  s.add_development_dependency('maxitest')
  s.add_development_dependency('coveralls')
  s.add_development_dependency('webmock')
  s.add_development_dependency('nokogumbo')
end



================================================
FILE: test/files/base.html
================================================
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<!--

  You can read this newsletter online at
  [webversion]

-->
<html>
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Premailer Test</title>
	<link rel="stylesheet" type="text/css" href="styles.css">
	<style type="text/css">
		@import "import.css" screen, handheld;
	</style>
	<style type="text/css">
		@import "noimport.css" print;
	</style>
	<style type="text/css">
		#iphone { display: block; }
	</style>
</head>
<body>
<div id="wrapper">
<p class="hide" id="hide01">This line should be hidden.</p>
<p class="hide" id="iphone">This is an iPhone style.</p>
<table width="646" class="container" cellspacing="0" cellpadding="0">
<tr><td id="webversion" colspan="6">Having trouble reading this newsletter? <webversion>Click here to see it in your browser</webversion></td></tr>

<tr><td height="13" colspan="6" class="frame">&#x00a0;</td></tr>



<table width="646" class="container" cellspacing="0" cellpadding="0">

<tr>
	<td class="frame" width="13">&#x00a0;</td>
	<td class="gutter" width="60">&#x00a0;</td>

	<td class="content" colspan="2" width="500">

<h1><span>Premailer Test</span></h1>

<table width="500" cellpadding="0" cellspacing="0">
<tr>
	<td width="20">&#x00a0;</td>
	<td colspan="2" width="460">
		<h2>Lorem ipsum dolor</h2>
		<h3>Suspendisse id velit vitae ligula volutpat condimentum</h3>
		<p class="dt">Morbi commodo, ipsum sed</p>


		<p class="unaligned"><img src="2009-placeholder.png" alt="Image" align="right" class="right">Lorem&nbsp;ipsum&#x00a0;dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis. Nullam sit amet enim. Suspendisse id velit vitae ligula volutpat condimentum. Aliquam erat volutpat. Sed quis velit. <a href="http://premailer.dialect.ca/">Nulla facilisi</a>. Nulla libero.</p>

		<p attr="another quote">Here&rsquo;s a quote.  Here’s a quote.  &#x201c;Here’s a quote in quotes”.</p>

		<p>Nullam sit amet enim. Suspendisse id velit vitae ligula volutpat condimentum. Aliquam erat volutpat. Sed quis velit. Nulla facilisi.</p>
		<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis. Nullam sit amet enim. Suspendisse id velit vitae ligula volutpat condimentum. Aliquam erat volutpat. Sed quis velit. Nulla facilisi. Nulla libero.</p>
		<blockquote><p>“Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis.”</p></blockquote>
		<p>Aliquam erat volutpat. Sed quis velit. Nulla facilisi. Nulla libero.</p>

		<h3>Link tests</h3>
		<ul>
			<li><a id="l01" href="/">Relative path to root</a></li>
			<li><a id="l02" href="http://premailer.dialect.ca/">Absolute path to root</a></li>
			<li><a id="l03" href="http://example.com/">Different domain</a></li>
			<li><a id="l04" href="images/">Relative path to sub-directory</a></li>
			<li><a id="l05" href="#relative">Link is not converted</a></li>
			<li><a id="l06" href="http://example.com/test.html?cn=tf&amp;c=20&amp;ord=%%RANDOM%%">Funky ASP URL</a></li>
			<li><a id="l07" href="?query=string">Appends tracking query string</a></li>
			<li><a id="l08" href="{DONOTCONVERT}">Link is not converted</a></li>
			<li><a id="l09" href="[DONOTCONVERT]">Link is not converted</a></li>
			<li><a id="l10" href="<DONOTCONVERT>">Link is not converted</a></li>
			<li><a id="l11" href="mailto:premailer@example.com">mailto link</a></li>
			<li><a id="l12" href="ftp://example.com">FTP link</a></li>
			<li><a id="l13" href="gopher://gopher.floodgap.com/1/fun/twitpher">Gopher link</a></li>
		</ul>



		<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi commodo, ipsum sed pharetra gravida, orci magna rhoncus neque, id pulvinar odio lorem non turpis.</p>

		<p>&nbsp;</p>


	</td>
	<td width="20">&#x00a0;</td>
</tr>
</table>

<p class="section"><img src="dots_end.png" alt="---" width="499" height="75"></p>
	</td><!-- /#content -->
	<td class="gutter" width="60">&#x00a0;</td>

	<td class="frame" width="13">&#x00a0;</td>
</tr>

<tr>
	<td class="frame" width="13">&#x00a0;</td>
	<td colspan="4" width="620">
		<table summary="Contact information" cellspacing="0" cellpadding="0" width="620">
			<tr><td height="4" class="hairline">&#x00a0;</td></tr>
			<tr><td height="34"class="contact">&#x00a0;</td></tr>
			<tr>

				<td align="center" class="contact" id="contact_info">
					<p id="address">Premailer Test<br>
					<a href="http://dialect.ca/?utm_source=Premailer&utm_medium=Test+Suite&utm_campaign=Premailer">by Dialect</a><br>
					Vancouver Island, British Columbia<br>
					250 555.2222</p>
				</td>

			</tr>
			<tr><td height="34"class="contact">&#x00a0;</td></tr>
			<tr><td height="4" class="hairline">&#x00a0;</td></tr>
		</table>
	</td>
	<td class="frame" width="13">&#x00a0;</td>
</tr>

<tr>
	<td class="frame" width="13">&#x00a0;</td>

	<td colspan="4" class="content" height="60">&#x00a0;</td>
	<td class="frame" width="13">&#x00a0;</td>
</tr>





<tr><td height="13" colspan="6" class="frame">&#x00a0;</td></tr>
<tr><td height="22" colspan="6" >&#x00a0;</td></tr>
<tr><td id="credit" colspan="6">Newsletter communications by<br><a href="http://dialect.ca/dialogue/?utm_source=Dialogue&utm_medium=Credit&utm_campaign=South+Hollow"><img src="inc/dialect.png" alt="Dialect" width="60" height="35" border="0"></a><br><unsubscribe>Click here to unsubscribe</unsubscribe></td></tr>

</table>

</div>
</body>
</html>


================================================
FILE: test/files/chars.html
================================================
<!DOCTYPE html>
<html>
<body>
<p>cédille c&eacute; & garçon gar&#231;on à &agrave; &nbsp; &amp; &copy;</p>
</body>
</html>

================================================
FILE: test/files/html4.html
================================================
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <title>Title</title>
</head>
<body>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  <br>
  Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</body>
</html>


================================================
FILE: test/files/html_with_uri.html
================================================
<html>
  <link rel="stylesheet" type="text/css" href="styles.css">
  <body>
    <p>
      The following line should not make this html be identified as a URI
http://foobar.com
    </p>
  </body>
</html>

================================================
FILE: test/files/ignore.css
================================================
body {
  color: orange;
}


================================================
FILE: test/files/ignore.html
================================================
<!doctype html>
<html>
  <head>
    <title>Should ignore link and style elements with data attribute</title>
    <link rel="stylesheet" type="text/css" href="ignore.css" data-premailer="ignore" />
    <style type="text/css" data-premailer="ignore">
      h1 {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1>Content</h1>
  </body>
</html>


================================================
FILE: test/files/import.css
================================================
/*
 * Premailer styles - should import
 *
 * $Package: Premailer $
 * $Date: 2009-02-09 17:15:56 -0800 (Mon, 09 Feb 2009) $WCDATE$ $
 * $Rev: 95 $WCREV$ $
 */

.hide {
	display: none;
}


================================================
FILE: test/files/iso-8859-2.html
================================================
<body>In Hungary we use some special accented characters:    .</body>


================================================
FILE: test/files/iso-8859-5.html
================================================
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=iso-8859-5">
</head>
<body>
<p> </p>
</body>
</html>

================================================
FILE: test/files/no_css.html
================================================
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
	<title>Premailer No CSS Test</title>
	</style>
</head>
<body>
<p class="hide" id="hide01">This line should be hidden.</p>
</body>
</html>


================================================
FILE: test/files/noimport.css
================================================
/*
 * Premailer styles - should not import
 *
 * $Package: Premailer $
 * $Date: 2009-02-09 17:15:56 -0800 (Mon, 09 Feb 2009) $WCDATE$ $
 * $Rev: 95 $WCREV$ $
 */

body {
	background: none red !important;
}


================================================
FILE: test/files/styles.css
================================================
/*
 * Premailer styles
 *
 * $Package: Premailer $
 * $Date: 2009-02-09 17:15:56 -0800 (Mon, 09 Feb 2009) $WCDATE$ $
 * $Rev: 95 $WCREV$ $
 */

@import "noimport.css" print;


/*** structure and table cells ***/
body{font:13px/1.231 "Arial",sans-serif;color: #fff;background-color: #9EBF00;}

 #wrapper { width: 100%; margin: 2em 0; background-color: #9EBF00; color: #fff; }

.container { margin: 0 auto; line-height: 130%; color: #4d4d4d; text-align: left; }

.frame { background-color: #b6d93f; font-size: 1px; line-height: 1px;  }

.hairline { background-color: #9ebf00; font-size: 1px; line-height: 1px; }

.masthead, .gutter { color: #999; background-color: #fff; }

.content { line-height: 158%; color: #999; background-color: #fff; }

#webversion, #footer { margin: 0 auto; text-align: center; font-size: 85%; }


/*** general styles ***/
h1, h1 a, h2, h2 a {  color: #9ebf00; }

h1 span { padding: 0 .5em; background: #fff; }

h1 {
	margin: 32px 0 19px;
	font: bold 85% "Verdana", sans-serif;
	text-transform: uppercase;
	text-align: center;
	letter-spacing: .1em;
	background: transparent url("dots_h.gif") repeat-x 0 55%;
}

h2 { margin: 0 0 .4em; font: normal 205%/109% "Arial", sans-serif; }

h3, h3 a { color: #808080; }

h3 { margin: 0 0 .1em; font: normal 165%/109% "Arial", sans-serif; }

table{ border-collapse:collapse;border-spacing:0; border: 0; }

caption,th,td {text-align:left;font-weight:normal; margin: 0; padding: 0; }

pre,code,kbd,samp,tt{font-family:monospace;line-height:100%;}

th, td { vertical-align: top; }

a { text-decoration: none; }

blockquote { margin: 0; padding: 5px 30px; text-align: center; }

blockquote, blockquote p { font: italic 16px/145% "Georgia", serif; }

p, blockquote { color: #999; }

p { margin: 0 0 1em; font: normal 100%/158% "Arial", sans-serif; vertical-align: top; }


.hide { text-align: center; color: red; font-size: 150%; }

/*** specific elements ***/
h2 + h3 { font-style: italic;}
p[attr~=quote]  { font-style: italic;}
ul li:first-of-type { font-style: italic;}

.content p a, .content li a { color: #8AAD09; text-decoration: underline; }

.content p.dt { margin-bottom: .8em; font: italic 95%/135% "Arial", sans-serif; }

img.right { float: right; margin: 0 0 30px 20px; }

.contact { text-align: center; background: #9EC03B url("contact_bg.png") repeat 0 0; }

.contact, .contact p, .contact a { color: #fff; text-decoration: none; }

.contact p { margin-bottom: 0; line-height: 140%; }

.contact a:hover { text-decoration: underline; }

#webversion, #webversion a {
	font: bold 12px/28px "Trebuchet", "Trebuchet MS", serif;
	color: #fff;
	background: #9ebf00;
}

#credit { padding-bottom: 20px; }

#credit, #credit a {
	color: #fff;
	font: normal 10px/13px "Verdana", sans-serif;
	text-align: center;
}

#contact_info { padding: 5px; }


================================================
FILE: test/files/xhtml.html
================================================
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
  <title>Title</title>
</head>
<body>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
  <br/>
  Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</body>
</html>


================================================
FILE: test/future_tests.rb
================================================
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__)) + '/helper'

class TestPremailer < Premailer::TestCase
  def test_related_attributes
    flunk 'Not implemented'
    local_setup

    # h1 { text-align: center; }
    assert_equal 'center', @doc.at('h1')['align']

    # td { vertical-align: top; }
    assert_equal 'top', @doc.at('td')['valign']

    # p { vertical-align: top; } -- not allowed
    assert_nil @doc.at('p')['valign']

    # no align attr is specified for <p> elements, so it should not appear
    assert_nil @doc.at('p.unaligned')['align']

    # .contact { background: #9EC03B url("contact_bg.png") repeat 0 0; }
    assert_equal '#9EC03B', @doc.at('td.contact')['bgcolor']

    # body { background-color: #9EBF00; }
    assert_equal '#9EBF00', @doc.at('body')['bgcolor']
  end

  def test_merging_cellpadding
    flunk 'Not implemented'
    local_setup('cellpadding.html', {:prefer_cellpadding => true})
    assert_equal '0', @doc.at('#t1')['cellpadding']
    assert_match /padding\:/i, @doc.at('#t1 td')['style']

    assert_equal '5', @doc.at('#t2')['cellpadding']
    refute_match /padding\:/i, @doc.at('#t2 td')['style']

    assert_nil @doc.at('#t3')['cellpadding']
    assert_match /padding\:/i, @doc.at('#t3 td')['style']

    assert_nil @doc.at('#t4')['cellpadding']
    assert_match /padding\:/i, @doc.at('#t4a')['style']
    assert_match /padding\:/i, @doc.at('#t4b')['style']
  end

  def test_preserving_media_queries
    flunk 'Not implemented'
    local_setup
    assert_match /display\: none/i, @doc.at('#iphone')['style']
  end
end


================================================
FILE: test/helper.rb
================================================
require 'bundler/setup'
require 'maxitest/autorun'
require 'webmock/minitest'
require 'premailer'

class Premailer::TestCase < Minitest::Test
  BASE_URI  = 'http://premailer.dev/'
  BASE_PATH =  File.expand_path(File.dirname(__FILE__)) + '/files'

  def setup
    stub_request(:any, /premailer\.dev\/*/).to_return do |request|
      file_path = BASE_PATH + URI.parse(request.uri).path
      if File.exists?(file_path)
        { :status => 200, :body => File.open(file_path) }
      else
        { :status => 404, :body => "#{file_path} not found" }
      end
    end

    stub_request(:get, /my\.example\.com\:8080\/*/).to_return(:status => 200, :body => "", :headers => {})
  end

  def default_test; end

  protected
  def local_setup(f = 'base.html', opts = {})
    base_file = BASE_PATH + '/' + f
    premailer = Premailer.new(base_file, opts)
    premailer.to_inline_css
    @doc = premailer.processed_doc
  end

  def remote_setup(f = 'base.html', opts = {})
    @premailer = Premailer.new(BASE_URI + "#{f}", opts)
    @premailer.to_inline_css
    @doc = @premailer.processed_doc
  end

end


================================================
FILE: test/test_adapter.rb
================================================
require File.expand_path(File.dirname(__FILE__)) + '/helper'

class TestAdapter < Premailer::TestCase

  def test_default_to_best_available
    require 'hpricot'
    assert_equal 'Premailer::Adapter::Hpricot', Premailer::Adapter.use.name
  end

  def test_settable_via_symbol
    Premailer::Adapter.use = :hpricot
    assert_equal 'Premailer::Adapter::Hpricot', Premailer::Adapter.use.name
  end

  def test_adapters_are_findable_by_symbol
    assert_equal 'Premailer::Adapter::Hpricot', Premailer::Adapter.find(:hpricot).name
  end

  def test_adapters_are_findable_by_class
    assert_equal 'Premailer::Adapter::Hpricot', Premailer::Adapter.find(Premailer::Adapter::Hpricot).name
  end

  def test_raises_argument_error
    assert_raises(ArgumentError, "Invalid adapter: unknown") {
      Premailer::Adapter.find(:unknown)
    }
  end

end


================================================
FILE: test/test_html_to_plain_text.rb
================================================
# encoding: utf-8
require File.expand_path(File.dirname(__FILE__)) + '/helper'

class TestHtmlToPlainText < Premailer::TestCase
  include HtmlToPlainText

  def test_to_plain_text_with_fragment
    premailer = Premailer.new('<p>Test</p>', :with_html_string => true)
    assert_match /Test/, premailer.to_plain_text
  end

  def test_to_plain_text_with_body
    html = <<END_HTML
    <html>
    <title>Ignore me</title>
    <body>
		<p>Test</p>
		</body>
		</html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    assert_match /Test/, premailer.to_plain_text
  end

  def test_to_plain_text_with_malformed_body
    html = <<END_HTML
    <html>
    <title>Ignore me</title>
    <body>
		<p>Test
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    assert_match /Test/, premailer.to_plain_text
  end

  def test_specialchars
    assert_plaintext 'cédille garçon & à ñ', 'c&eacute;dille gar&#231;on &amp; &agrave; &ntilde;'
  end

  def test_stripping_whitespace
    assert_plaintext "text\ntext", "  \ttext\ntext\n"
    assert_plaintext "a\na", "  \na \n a \t"
    assert_plaintext "a\n\na", "  \na \n\t \n \n a \t"
    assert_plaintext "test text", "test text&nbsp;"
    assert_plaintext "test text", "test        text"
  end

  def test_wrapping_spans
    html = <<END_HTML
    <html>
    <body>
		<p><span>Test</span>
		<span>line 2</span>
		</p>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    assert_match /Test line 2/, premailer.to_plain_text
  end

  def test_line_breaks
    assert_plaintext "Test text\nTest text", "Test text\r\nTest text"
    assert_plaintext "Test text\nTest text", "Test text\rTest text"
  end

  def test_lists
    assert_plaintext "* item 1\n* item 2", "<li class='123'>item 1</li> <li>item 2</li>\n"
    assert_plaintext "* item 1\n* item 2\n* item 3", "<li>item 1</li> \t\n <li>item 2</li> <li> item 3</li>\n"
  end

  def test_stripping_html
    assert_plaintext 'test text', "<p class=\"123'45 , att\" att=tester>test <span class='te\"st'>text</span>\n"
  end

  def test_stripping_ignored_blocks
    html = <<END_HTML
    <p>test</p>
    <!-- start text/html -->
      <img src="logo.png" alt="logo">
    <!-- end text/html -->
    <p>text</p>
END_HTML
    premailer = Premailer.new(html, :with_html_string => true)
    assert_match /test\n\ntext/, premailer.to_plain_text
  end

  def test_paragraphs_and_breaks
    assert_plaintext "Test text\n\nTest text", "<p>Test text</p><p>Test text</p>"
    assert_plaintext "Test text\n\nTest text", "\n<p>Test text</p>\n\n\n\t<p>Test text</p>\n"
    assert_plaintext "Test text\nTest text", "\n<p>Test text<br/>Test text</p>\n"
    assert_plaintext "Test text\nTest text", "\n<p>Test text<br> \tTest text<br></p>\n"
    assert_plaintext "Test text\n\nTest text", "Test text<br><BR />Test text"
  end

  def test_headings
    assert_plaintext "****\nTest\n****", "<h1>Test</h1>"
    assert_plaintext "****\nTest\n****", "\t<h1>\nTest</h1> "
    assert_plaintext "***********\nTest line 1\nTest 2\n***********", "\t<h1>\nTest line 1<br>Test 2</h1> "
    assert_plaintext "****\nTest\n****\n\n****\nTest\n****", "<h1>Test</h1> <h1>Test</h1>"
    assert_plaintext "----\nTest\n----", "<h2>Test</h2>"
    assert_plaintext "Test\n----", "<h3> <span class='a'>Test </span></h3>"
  end

  def test_wrapping_lines
    raw = ''
    100.times { raw += 'test ' }

    txt = convert_to_text(raw, 20)

    lens = []
    txt.each_line { |l| lens << l.length }
    assert lens.max <= 20
  end

  def test_wrapping_lines_with_spaces
    assert_plaintext "Long line\nnew line", 'Long     line new line', nil ,10
  end

  def test_img_alt_tags
    # ensure html imag tags that aren't self-closed are parsed,
    # along with accepting both '' and "" as attribute quotes

    # <img alt="" />
    assert_plaintext 'Example ( http://example.com/ )', '<a href="http://example.com/"><img src="http://example.ru/hello.jpg" alt="Example"/></a>'
    # <img alt="">
    assert_plaintext 'Example ( http://example.com/ )', '<a href="http://example.com/"><img src="http://example.ru/hello.jpg" alt="Example"></a>'
    # <img alt='' />
    assert_plaintext 'Example ( http://example.com/ )', "<a href='http://example.com/'><img src='http://example.ru/hello.jpg' alt='Example'/></a>"
    # <img alt=''>
    assert_plaintext 'Example ( http://example.com/ )', "<a href='http://example.com/'><img src='http://example.ru/hello.jpg' alt='Example'></a>"
  end

  def test_links
    # basic
    assert_plaintext 'Link ( http://example.com/ )', '<a href="http://example.com/">Link</a>'

    # nested html
    assert_plaintext 'Link ( http://example.com/ )', '<a href="http://example.com/"><span class="a">Link</span></a>'

    # nested html with new line
    assert_plaintext 'Link ( http://example.com/ )', "<a href='http://example.com/'>\n\t<span class='a'>Link</span>\n\t</a>"

    # mailto
    assert_plaintext 'Contact Us ( contact@example.org )', "<a href='mailto:contact@example.org'>Contact Us</a>"

    # complex link
    assert_plaintext 'Link ( http://example.com:80/~user?aaa=bb&c=d,e,f#foo )', '<a href="http://example.com:80/~user?aaa=bb&amp;c=d,e,f#foo">Link</a>'

    # attributes
    assert_plaintext 'Link ( http://example.com/ )', '<a title=\'title\' href="http://example.com/">Link</a>'

    # spacing
    assert_plaintext 'Link ( http://example.com/ )', '<a href="   http://example.com/ "> Link </a>'

    # multiple
    assert_plaintext 'Link A ( http://example.com/a/ ) Link B ( http://example.com/b/ )', '<a href="http://example.com/a/">Link A</a> <a href="http://example.com/b/">Link B</a>'

    # merge links
    assert_plaintext 'Link ( %%LINK%% )', '<a href="%%LINK%%">Link</a>'
    assert_plaintext 'Link ( [LINK] )', '<a href="[LINK]">Link</a>'
    assert_plaintext 'Link ( {LINK} )', '<a href="{LINK}">Link</a>'

    # unsubscribe
    assert_plaintext 'Link ( [[!unsubscribe]] )', '<a href="[[!unsubscribe]]">Link</a>'

    # empty link gets dropped, and shouldn't run forever
    assert_plaintext(("This is some more text\n\n" * 14 + "This is some more text"), "<a href=\"test\"></a>#{"\n<p>This is some more text</p>" * 15}")

    # links that go outside of line should wrap nicely
    assert_plaintext "Long text before the actual link and then LINK TEXT \n( http://www.long.link ) and then more text that does not wrap", 'Long text before the actual link and then <a href="http://www.long.link"/>LINK TEXT</a> and then more text that does not wrap'
  end

  # see https://github.com/alexdunae/premailer/issues/72
  def test_multiple_links_per_line
    assert_plaintext 'This is link1 ( http://www.google.com ) and link2 ( http://www.google.com ) is next.',
                     '<p>This is <a href="http://www.google.com" >link1</a> and <a href="http://www.google.com" >link2 </a> is next.</p>',
                     nil, 10000
  end

  # see https://github.com/alexdunae/premailer/issues/72
  def test_links_within_headings
    assert_plaintext "****************************\nTest ( http://example.com/ )\n****************************",
                     "<h1><a href='http://example.com/'>Test</a></h1>"
  end

  def assert_plaintext(out, raw, msg = nil, line_length = 65)
    assert_equal out, convert_to_text(raw, line_length), msg
  end
end


================================================
FILE: test/test_links.rb
================================================
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__)) + '/helper'

class TestLinks < Premailer::TestCase
  def test_empty_query_string
    premailer = Premailer.new('<p>Test</p>', :with_html_string => true, :link_query_string => ' ')
    premailer.to_inline_css
  end

  def test_appending_link_query_string
    qs = 'utm_source=1234&tracking=good&amp;doublescape'
    opts = {:base_url => 'http://example.com/',  :link_query_string => qs, :with_html_string => true, :adapter => :hpricot}

    appendable = [
        '/',
        opts[:base_url],
        'https://example.com/tester',
        'images/',
        "#{opts[:base_url]}test.html?cn=tf&amp;c=20&amp;ord=random",
        '?query=string'
    ]

    not_appendable = [
        '%DONOTCONVERT%',
        '{DONOTCONVERT}',
        '[DONOTCONVERT]',
        '<DONOTCONVERT>',
        '{@msg-txturl}',
        '[[!unsubscribe]]',
        '#relative',
        'tel:5555551212',
        'http://example.net/',
        'mailto:premailer@example.com',
        'ftp://example.com',
        'gopher://gopher.floodgap.com/1/fun/twitpher'
    ]

    html = appendable.collect {|url| "<a href='#{url}'>Link</a>" }

    premailer = Premailer.new(html.to_s, opts)
    premailer.to_inline_css

    premailer.processed_doc.search('a').each do |el|
      href = el.attributes['href'].to_s
      next if href.nil? or href.empty?
      uri = URI.parse(href)
      assert_match qs, uri.query, "missing query string for #{el.to_s}"
    end

    html = not_appendable.collect {|url| "<a href='#{url}'>Link</a>" }

    premailer = Premailer.new(html.to_s, opts)
    premailer.to_inline_css

    premailer.processed_doc.search('a').each do |el|
      href = el['href']
      next if href.nil? or href.empty?
      assert not_appendable.include?(href), "link #{href} should not be converted: see #{not_appendable.to_s}"
    end
  end

  def test_stripping_extra_question_marks_from_query_string
    qs = '??utm_source=1234'

    premailer = Premailer.new("<a href='/test/?'>Link</a> <a href='/test/'>Link</a>", :link_query_string => qs, :with_html_string => true)
    premailer.to_inline_css

    premailer.processed_doc.search('a').each do |a|
      assert_equal '/test/?utm_source=1234', a['href'].to_s
    end

    premailer = Premailer.new("<a href='/test/?123&456'>Link</a>", :link_query_string => qs, :with_html_string => true)
    premailer.to_inline_css

    assert_equal '/test/?123&456&amp;utm_source=1234', premailer.processed_doc.at('a')['href']
  end

  def test_unescape_ampersand
    qs = 'utm_source=1234'

    premailer = Premailer.new("<a href='/test/?q=query'>Link</a>", :link_query_string => qs, :with_html_string => true, :unescaped_ampersand => true)
    premailer.to_inline_css

    premailer.processed_doc.search('a').each do |a|
      assert_equal '/test/?q=query&utm_source=1234', a['href'].to_s
    end
  end

  def test_preserving_links
    html = "<a href='http://example.com/index.php?pram1=one&pram2=two'>Link</a>"
    premailer = Premailer.new(html.to_s, :link_query_string => '', :with_html_string => true)
    premailer.to_inline_css

    assert_equal 'http://example.com/index.php?pram1=one&pram2=two', premailer.processed_doc.at('a')['href']

    html = "<a href='http://example.com/index.php?pram1=one&pram2=two'>Link</a>"
    premailer = Premailer.new(html.to_s, :link_query_string => 'qs', :with_html_string => true)
    premailer.to_inline_css

    assert_equal 'http://example.com/index.php?pram1=one&pram2=two&amp;qs', premailer.processed_doc.at('a')['href']

  end

  def test_resolving_urls_from_string
    ['test.html', '/test.html', './test.html',
     'test/../test.html', 'test/../test/../test.html'].each do |q|
      assert_equal 'http://example.com/test.html', Premailer.resolve_link(q, 'http://example.com/'), q
    end

    assert_equal 'https://example.net:80/~basedir/test.html?var=1#anchor', Premailer.resolve_link('test/../test/../test.html?var=1#anchor', 'https://example.net:80/~basedir/')
  end

  def test_resolving_urls_from_uri
    base_uri = URI.parse('http://example.com/')
    ['test.html', '/test.html', './test.html',
     'test/../test.html', 'test/../test/../test.html'].each do |q|
      assert_equal 'http://example.com/test.html', Premailer.resolve_link(q, base_uri), q
    end

    base_uri = URI.parse('https://example.net:80/~basedir/')
    assert_equal 'https://example.net:80/~basedir/test.html?var=1#anchor', Premailer.resolve_link('test/../test/../test.html?var=1#anchor', base_uri)

    # base URI with a query string
    base_uri = URI.parse('http://example.com/dir/index.cfm?newsletterID=16')
    assert_equal 'http://example.com/dir/index.cfm?link=15', Premailer.resolve_link('?link=15', base_uri)

    # URI preceded by a space
    base_uri = URI.parse('http://example.com/')
    assert_equal 'http://example.com/path', Premailer.resolve_link(' path', base_uri)
  end

  def test_resolving_urls_from_html_string
    # The inner URI is on its own line to ensure that the impl doesn't match
    # URIs based on start of line.
    base_uri = "<html><head></head><body>\nhttp://example.com/\n</body>"
    ['test.html', '/test.html', './test.html',
     'test/../test.html', 'test/../test/../test.html'].each do |q|
      Premailer.resolve_link(q, base_uri)
    end
  end

  def test_resolving_urls_in_doc
    # force Nokogiri since this consistenly segfaults with Hpricot
    base_file = File.dirname(__FILE__) + '/files/base.html'
    base_url = 'https://my.example.com:8080/test-path.html'
    premailer = Premailer.new(base_file, :base_url => base_url, :adapter => :nokogiri)
    premailer.to_inline_css
    pdoc = premailer.processed_doc
    doc = premailer.doc

    # unchanged links
    ['#l02', '#l03', '#l05', '#l06', '#l07', '#l08',
     '#l09', '#l10', '#l11', '#l12', '#l13'].each do |link_id|
      assert_equal doc.at(link_id).attributes['href'], pdoc.at(link_id).attributes['href'], link_id
    end

    assert_equal 'https://my.example.com:8080/', pdoc.at('#l01').attributes['href'].to_s
    assert_equal 'https://my.example.com:8080/images/', pdoc.at('#l04').attributes['href'].to_s
  end

  def test_convertable_inline_links
    convertable = [
        'my/path/to',
        'other/path',
        '/'
    ]

    html = convertable.collect {|url| "<a href='#{url}'>Link</a>" }
    premailer = Premailer.new(html.to_s, :base_url => "http://example.com", :with_html_string => true)

    premailer.processed_doc.search('a').each do |el|
      href = el.attributes['href'].to_s
      assert(href =~ /http:\/\/example.com/, "link #{href} is not absolute")
    end
  end

  def test_non_convertable_inline_links
    not_convertable = [
        '%DONOTCONVERT%',
        '{DONOTCONVERT}',
        '[DONOTCONVERT]',
        '<DONOTCONVERT>',
        '{@msg-txturl}',
        '[[!unsubscribe]]',
        '#relative',
        'tel:5555551212',
        'mailto:premailer@example.com',
        'ftp://example.com',
        'gopher://gopher.floodgap.com/1/fun/twitpher',
        'cid:13443452066.10392logo.jpeg@inline_attachment'
    ]

    html = not_convertable.collect {|url| "<a href='#{url}'>Link</a>" }

    premailer = Premailer.new(html.to_s, :base_url => "example.com", :with_html_string => true)
    premailer.to_inline_css

    premailer.processed_doc.search('a').each do |el|
      href = el.attributes['href'].to_s
      assert not_convertable.include?(href), "link #{href} should not be converted: see #{not_convertable.inspect}"
    end
  end
end


================================================
FILE: test/test_misc.rb
================================================
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__)) + '/helper'

# Random tests for specific issues.
#
# The test suite will be cleaned up at some point soon.
class TestMisc < Premailer::TestCase

  # in response to http://github.com/alexdunae/premailer/issues#issue/4
  #
  # NB: 2010-11-16 -- after reverting to Hpricot this test can no longer pass.
  # It's too much of an edge case to get any dev time.
  def test_parsing_extra_quotes
    io = StringIO.new('<p></p>
    <h3 "id="WAR"><a name="WAR"></a>Writes and Resources</h3>
    <table></table>')
    premailer = Premailer.new(io, :adapter => :nokogiri)
    assert_match /<h3>[\s]*<a name="WAR">[\s]*<\/a>[\s]*Writes and Resources[\s]*<\/h3>/i, premailer.to_inline_css
  end

  def test_styles_in_the_body
    html = <<END_HTML
    <html>
    <body>
    <style type="text/css"> p { color: red; } </style>
		<p>Test</p>
		</body>
		</html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    premailer.to_inline_css

    assert_match /color\: red/i,  premailer.processed_doc.at('p')['style']
  end

  def test_commented_out_styles_in_the_body
    html = <<END_HTML
    <html>
    <body>
    <style type="text/css"> <!-- p { color: red; } --> </style>
		<p>Test</p>
		</body>
		</html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    premailer.to_inline_css

    assert_match /color\: red/i,  premailer.processed_doc.at('p')['style']
  end

  def test_not_applying_styles_to_the_head
    html = <<END_HTML
    <html>
    <head>
    <title>Title</title>
    <style type="text/css"> * { color: red; } </style>
    </head>
    <body>
		<p><a>Test</a></p>
		</body>
		</html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :adapter => adapter)
      premailer.to_inline_css

      h = premailer.processed_doc.at('head')
      assert_nil h['style']

      t = premailer.processed_doc.at('title')
      assert_nil t['style']
    end
  end

  def test_multiple_identical_ids
    html = <<-END_HTML
    <html>
    <head>
    <style type="text/css"> #the_id { color: red; } </style>
    </head>
    <body>
		<p id="the_id">Test</p>
		<p id="the_id">Test</p>
		</body>
		</html>
    END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    premailer.to_inline_css
    premailer.processed_doc.search('p').each do |el|
      assert_match /red/i, el['style']
    end
  end

  def test_preserving_styles
    html = <<END_HTML
    <html>
    <head>
    <link rel="stylesheet" href="#"/>
    <style type="text/css"> a:hover { color: red; } </style>
    </head>
    <body>
		<p><a>Test</a></p>
		</body>
		</html>
END_HTML
    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :preserve_styles => true,  :adapter => adapter)
      premailer.to_inline_css
      assert_equal 1, premailer.processed_doc.search('head link').length
      assert_equal 1, premailer.processed_doc.search('head style').length

      premailer = Premailer.new(html, :with_html_string => true, :preserve_styles => false, :adapter => adapter)
      premailer.to_inline_css
      assert_nil premailer.processed_doc.at('body link')

      # should be preserved as unmergeable

      assert_match /color: red/i, premailer.processed_doc.at('body style').inner_html

      assert_match /a:hover/i, premailer.processed_doc.at('style').inner_html

    end
  end

  def test_unmergable_rules
    html = <<END_HTML
    <html> <head> <style type="text/css"> a { color:blue; } a:hover { color: red; } </style> </head>
		<p><a>Test</a></p>
		</body> </html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true, :verbose => true)
    premailer.to_inline_css

    # blue should be inlined
    refute_match /a\:hover[\s]*\{[\s]*color\:[\s]*blue[\s]*;[\s]*\}/i, premailer.processed_doc.at('body style').inner_html
    # red should remain in <style> block
    assert_match /a\:hover[\s]*\{[\s]*color\:[\s]*red;[\s]*\}/i, premailer.processed_doc.at('body style').inner_html
  end

  def test_unmergable_media_queries
    html = <<END_HTML
    <html> <head>
    <style type="text/css">
    a { color: blue; }
    @media (min-width:500px) {
      a { color: red; }
    }
    @media screen and (orientation: portrait) {
      a { color: green; }
    }
    </style>
    </head>
    <body>
    <p><a>Test</a></p>
    </body> </html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :adapter => adapter)
      premailer.to_inline_css

      style_tag = premailer.processed_doc.at('body style')
      assert style_tag, "#{adapter} failed to add a body style tag"

      style_tag_contents = style_tag.inner_html

      assert_equal "color: blue;", premailer.processed_doc.at('a').attributes['style'].to_s,
                   "#{adapter}: Failed to inline the default style"
      assert_match /@media \(min-width:500px\) \{.*?a \{.*?color: red;.*?\}.*?\}/m, style_tag_contents,
                   "#{adapter}: Failed to add media query with no type to style"
      assert_match /@media screen and \(orientation: portrait\) \{.*?a \{.*?color: green;.*?\}.*?\}/m, style_tag_contents,
                   "#{adapter}: Failed to add media query with type to style"
    end

  end

  def test_unmergable_rules_with_no_body
    html = <<END_HTML
    <html>
    <style type="text/css"> a:hover { color: red; } </style>
		<p><a>Test</a></p>
		</html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    premailer.to_inline_css
    assert_match /a\:hover[\s]*\{[\s]*color\:[\s]*red;[\s]*\}/i, premailer.processed_doc.at('style').inner_html
  end

  # in response to https://github.com/alexdunae/premailer/issues#issue/7
  def test_ignoring_link_pseudo_selectors
    html = <<END_HTML
    <html>
    <style type="text/css"> td a:link.top_links { color: red; } </style>
    <body>
		<td><a class="top_links">Test</a></td>
		</body>
		</html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    premailer.to_inline_css
    assert_match /color: red/, premailer.processed_doc.at('a').attributes['style'].to_s
  end

  # in response to https://github.com/alexdunae/premailer/issues#issue/7
  #
  # fails sometimes in JRuby, see https://github.com/alexdunae/premailer/issues/79
  def test_parsing_bad_markup_around_tables
    html = <<END_HTML
    <html>
    <style type="text/css">
      .style3 { font-size: xx-large; }
      .style5 { background-color: #000080; }
    </style>
		<tr>
						<td valign="top" class="style3">
						<!-- MSCellType="ContentHead" -->
						<strong>PROMOCION CURSOS PRESENCIALES</strong></td>
						<strong>
						<td valign="top" style="height: 125px" class="style5">
						<!-- MSCellType="DecArea" -->
						<img alt="" src="../../images/CertisegGold.GIF" width="608" height="87" /></td>
		</tr>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    premailer.to_inline_css
    assert_match /font-size: xx-large/, premailer.processed_doc.search('.style3').first.attributes['style'].to_s
    refute_match /background: #000080/, premailer.processed_doc.search('.style5').first.attributes['style'].to_s
    assert_match /#000080/, premailer.processed_doc.search('.style5').first.attributes['bgcolor'].to_s
  end

  # in response to https://github.com/alexdunae/premailer/issues/56
  def test_inline_important
    html = <<END_HTML
    <html>
    <style type="text/css">
      p { color: red !important; }
    </style>
    <body>
      <p style='color: green !important;'>test</p></div>
    </body>
    </html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true, :adapter => :nokogiri)
    premailer.to_inline_css
    assert_equal 'color: green !important;', premailer.processed_doc.search('p').first.attributes['style'].to_s
  end

  # in response to https://github.com/alexdunae/premailer/issues/28
  def test_handling_shorthand_auto_properties
    html = <<END_HTML
    <html>
    <style type="text/css">
      #page { margin: 0; margin-left: auto; margin-right: auto; }
      p { border: 1px solid black; border-right: none; }

    </style>
    <body>
      <div id='page'><p>test</p></div>
    </body>
    </html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    premailer.to_inline_css

    assert_match /margin: 0 auto/, premailer.processed_doc.search('#page').first.attributes['style'].to_s
    assert_match /border-style: solid none solid solid;/, premailer.processed_doc.search('p').first.attributes['style'].to_s
  end

  def test_sorting_style_attributes
    html = <<END_HTML
    <html>
    <style type="text/css">
      #page { right: 10px; left: 5px }
    </style>
    <body>
      <div id='page'>test</div>
    </body>
    </html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true)
    premailer.to_inline_css
    assert_equal "left: 5px; right: 10px;", premailer.processed_doc.search('#page').first.attributes['style'].to_s
  end

  def test_removing_scripts
    html = <<END_HTML
    <html>
    <head>
      <script>script to be removed</script>
    </head>
    <body>
      content
    </body>
    </html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :remove_scripts => true, :adapter => adapter)
      premailer.to_inline_css
      assert_equal 0, premailer.processed_doc.search('script').length
    end

    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :remove_scripts => false, :adapter => adapter)
      premailer.to_inline_css
      assert_equal 1, premailer.processed_doc.search('script').length
    end
  end

  def test_strip_important_from_attributes
    html = <<END_HTML
    <html>
    <head>
      <style>td { background-color: #FF0000 !important; }</style>
    </head>
    <body>
      <table><tr><td>red</td></tr></table>
    </body>
    </html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :adapter => adapter)
      assert_match 'bgcolor="#FF0000"', premailer.to_inline_css
    end
  end

  def test_scripts_with_nokogiri
    html = <<END_HTML
    <html>
    <body>
    <script type="application/ld+json">
    {
      "@context": "http://schema.org",
      "@type": "Person",
      "name": "John Doe",
      "jobTitle": "Graduate research assistant",
      "affiliation": "University of Dreams",
      "additionalName": "Johnny",
      "url": "http://www.example.com",
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "1234 Peach Drive",
        "addressLocality": "Wonderland",
        "addressRegion": "Georgia"
      }
    }
    </script
    </body>
    </html>
END_HTML

    premailer = Premailer.new(html, :with_html_string => true, :remove_scripts => false, :adapter => :nokogiri)
    premailer.to_inline_css

    assert !premailer.processed_doc.css('script[type="application/ld+json"]').first.children.first.cdata?
  end

  def test_style_without_data_in_content
    html = <<END_HTML
    <html>
    <head>
      <style>#logo {content:url(good.png)};}</style>
    </head>
    <body>
      <image id="logo"/>
    </body>
    </html>
END_HTML
    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :adapter => adapter)
      assert_match 'content: url(good.png)', premailer.to_inline_css
    end
  end

  def test_style_with_data_in_content
    html = <<END_HTML
    <html>
    <head>
      <style>#logo {content: url(data:image/png;base64,LOTSOFSTUFF)};}</style>
    </head>
    <body>
      <image id="logo"/>
    </body>
    </html>
END_HTML
    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :adapter => adapter)
      assert_match 'content: url(data:image/png;base64,LOTSOFSTUFF)', premailer.to_inline_css
    end
  end

end


================================================
FILE: test/test_premailer.rb
================================================
# -*- encoding: UTF-8 -*-

require File.expand_path(File.dirname(__FILE__)) + '/helper'

class TestPremailer < Premailer::TestCase
  def test_special_characters_nokogiri
    html = 	'<p>cédille c&eacute; & garçon gar&#231;on à &agrave; &nbsp; &amp; &copy;</p>'
    premailer = Premailer.new(html, :with_html_string => true, :adapter => :nokogiri)
    premailer.to_inline_css
    assert_equal 'c&eacute;dille c&eacute; &amp; gar&ccedil;on gar&ccedil;on &agrave; &agrave; &nbsp; &amp; &copy;', premailer.processed_doc.at('p').inner_html
  end

  def test_special_characters_nokogiri_remote
    remote_setup('chars.html', :adapter => :nokogiri)
    @premailer.to_inline_css
    assert_equal 'c&eacute;dille c&eacute; &amp; gar&ccedil;on gar&ccedil;on &agrave; &agrave; &nbsp; &amp; &copy;', @premailer.processed_doc.at('p').inner_html
  end

  #def test_cyrillic_nokogiri_remote
  #  if RUBY_VERSION =~ /1.9/
  #    remote_setup('iso-8859-5.html', :adapter => :nokogiri) #, :encoding => 'iso-8859-5')
  #  	@premailer.to_inline_css
  #    assert_equal Encoding.find('ISO-8859-5'), @premailer.processed_doc.at('p').inner_html.encoding
  #  end
  #end

  # TODO: this passes when run from rake but not when run from:
  #  ruby -Itest test/test_premailer.rb -n test_special_characters_hpricot
  def test_special_characters_hpricot
    html = 	'<p>cédille c&eacute; & garçon gar&#231;on à &agrave; &nbsp; &amp;</p>'
    premailer = Premailer.new(html, :with_html_string => true, :adapter => :hpricot)
    premailer.to_inline_css
    assert_equal 'c&eacute;dille c&eacute; &amp; gar&ccedil;on gar&ccedil;on &agrave; &agrave; &nbsp; &amp;', premailer.processed_doc.at('p').inner_html
  end

  def test_detecting_html
    [:nokogiri, :hpricot].each do |adapter|
      remote_setup('base.html', :adapter => adapter)
      assert !@premailer.is_xhtml?
    end
  end

  def test_detecting_xhtml
    [:nokogiri, :hpricot].each do |adapter|
      remote_setup('xhtml.html', :adapter => adapter)
      assert @premailer.is_xhtml?
    end
  end

  def test_self_closing_xhtml_tags
    [:nokogiri, :hpricot].each do |adapter|
      remote_setup('xhtml.html', :adapter => adapter)
      assert_match /<br[\s]*\/>/, @premailer.to_s
      assert_match /<br[\s]*\/>/, @premailer.to_inline_css
    end
  end

  def test_non_self_closing_html_tags
    [:nokogiri, :hpricot].each do |adapter|
      remote_setup('html4.html', :adapter => adapter)
      assert_match /<br>/, @premailer.to_s
      assert_match /<br>/, @premailer.to_inline_css
    end
  end

  def test_mailtos_with_query_strings
    html = <<END_HTML
    <html>
		<a href="mailto:info@example.com?subject=Programmübersicht&amp;body=Lorem ipsum dolor sit amet.">Test</a>
		</html>
END_HTML

    qs = 'testing=123'

    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(html, :with_html_string => true, :link_query_string => qs, :adapter => adapter)
      premailer.to_inline_css
      refute_match /testing=123/, premailer.processed_doc.search('a').first.attributes['href'].to_s
    end
  end

  def test_escaping_strings
    local_setup

    str = %q{url("/images/test.png");}
    assert_equal("url(\'/images/test.png\');", Premailer.escape_string(str))
  end

  def test_preserving_ignored_style_elements
    [:nokogiri, :hpricot].each do |adapter|
      local_setup('ignore.html', :adapter => adapter)

      assert_nil @doc.at('h1')['style']
    end
  end

  def test_preserving_ignored_link_elements
    [:nokogiri, :hpricot].each do |adapter|
      local_setup('ignore.html', :adapter => adapter)

      assert_nil @doc.at('body')['style']
    end
  end

  def test_importing_local_css
    # , :hpricot
    [:nokogiri].each do |adapter|
      local_setup('base.html', :adapter => adapter)

      # noimport.css (print stylesheet) sets body { background } to red
      refute_match /red/, @doc.at('body').attributes['style'].to_s

      # import.css sets .hide to { display: none }
      assert_match /display: none/, @doc.at('#hide01').attributes['style'].to_s
    end
  end

  def test_css_to_attributes
    [:nokogiri, :hpricot].each do |adapter|
      html = '<td style="background-color: #FFF;"></td>'
      premailer = Premailer.new(html, {:with_html_string => true, :adapter => adapter, :css_to_attributes => true})
      premailer.to_inline_css
      assert_equal ';', premailer.processed_doc.search('td').first.attributes['style'].to_s
      assert_equal '#FFF', premailer.processed_doc.search('td').first.attributes['bgcolor'].to_s
    end
  end

  def test_avoid_changing_css_to_attributes
    [:nokogiri, :hpricot].each do |adapter|
      html = '<td style="background-color: #FFF;"></td>'
      premailer = Premailer.new(html, {:with_html_string => true, :adapter => adapter, :css_to_attributes => false})
      premailer.to_inline_css
      assert_match /background: #FFF/, premailer.processed_doc.search('td').first.attributes['style'].to_s
    end
  end

  def test_importing_remote_css
    [:nokogiri, :hpricot].each do |adapter|
      remote_setup('base.html', :adapter => adapter)

      # noimport.css (print stylesheet) sets body { background } to red
      refute_match /red/, @doc.at('body')['style']

      # import.css sets .hide to { display: none }
      assert_match /display: none/, @doc.at('#hide01')['style']
    end
  end

  def test_importing_css_as_string
    files_base = File.expand_path(File.dirname(__FILE__)) + '/files/'

    css_string = IO.read(File.join(files_base, 'import.css'))

    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new(File.join(files_base, 'no_css.html'), {:css_string => css_string, :adapter => adapter})
      premailer.to_inline_css
      @doc = premailer.processed_doc

      # import.css sets .hide to { display: none }
      assert_match /display: none/, @doc.at('#hide01')['style']
    end
  end

  def test_local_remote_check
    assert Premailer.local_data?( StringIO.new('a') )
    assert Premailer.local_data?( '/path/' )
    assert !Premailer.local_data?( 'http://example.com/path/' )

    # the old way is deprecated but should still work
    premailer = Premailer.new( StringIO.new('a') )
    silence_stderr do
      assert premailer.local_uri?( '/path/' )
    end
  end

  def test_initialize_can_accept_io_object
    [:nokogiri, :hpricot].each do |adapter|
      io = StringIO.new('hi mom')
      premailer = Premailer.new(io, :adapter => adapter)
      assert_match /hi mom/, premailer.to_inline_css
    end
  end

  def test_initialize_can_accept_html_string
    [:nokogiri, :hpricot].each do |adapter|
      premailer = Premailer.new('<p>test</p>', :with_html_string => true, :adapter => adapter)
      assert_match /test/, premailer.to_inline_css
    end
  end

  def test_initialize_no_escape_attributes_option
    html = <<END_HTML
    <html> <body>
    <a id="google" href="http://google.com">Google</a>
    <a id="noescape" href="{{link_url}}">Link</a>
		</body> </html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      pm = Premailer.new(html, :with_html_string => true, :adapter => adapter, :escape_url_attributes => false)
      pm.to_inline_css
      doc = pm.processed_doc
      assert_equal doc.at('#google')['href'], 'http://google.com'
      assert_equal doc.at('#noescape')['href'], '{{link_url}}'
    end
  end

  def test_remove_ids
    html = <<END_HTML
    <html> <head> <style type="text/css"> #remove { color:blue; } </style> </head>
    <body>
		<p id="remove"><a href="#keep">Test</a></p>
		<p id="keep">Test</p>
		</body> </html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      pm = Premailer.new(html, :with_html_string => true, :remove_ids => true, :adapter => adapter)
      pm.to_inline_css
      doc = pm.processed_doc
      assert_nil doc.at('#remove')
      assert_nil doc.at('#keep')
      hashed_id = doc.at('a')['href'][1..-1]
      refute_nil doc.at("\##{hashed_id}")
    end
  end

  def test_reset_contenteditable
    html = <<-___
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html> <head> <style type="text/css"> #remove { color:blue; } </style> </head>
    <body>
    <div contenteditable="true" id="editable"> Test </div>
    </body> </html>
    ___
    [:nokogiri, :hpricot].each do |adapter|
      pm = Premailer.new(html, :with_html_string => true, :reset_contenteditable => true, :adapter => adapter)
      pm.to_inline_css
      doc = pm.processed_doc
      assert_nil doc.at('#editable')['contenteditable'],
                 "#{adapter}: contenteditable attribute not removed"
    end
  end

  def test_carriage_returns_as_entities
    html = <<-html
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
    <body>\n\r<p>test</p>\n\r<p>test</p>
    </body></html>
    html

    [:nokogiri, :hpricot].each do |adapter|
      pm = Premailer.new(html, :with_html_string => true, :adapter => adapter)
      assert_match /\r/, pm.to_inline_css
    end
  end


  def test_advanced_selectors
    remote_setup('base.html', :adapter => :nokogiri)
    assert_match /italic/, @doc.at('h2 + h3')['style']
    assert_match /italic/, @doc.at('p[attr~=quote]')['style']
    assert_match /italic/, @doc.at('ul li:first-of-type')['style']

    remote_setup('base.html', :adapter => :hpricot)
    assert_match /italic/, @doc.at('p[@attr~="quote"]')['style']
    assert_match /italic/, @doc.at('ul li:first-of-type')['style']
  end

  def test_premailer_related_attributes
    html = <<END_HTML
    <html> <head> <style>table { -premailer-width: 500; } td { -premailer-height: 20}; </style>
    <body>
		<table> <tr> <td> Test </td> </tr> </table>
		</body> </html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      pm = Premailer.new(html, :with_html_string => true, :adapter => adapter)
      pm.to_inline_css
      doc = pm.processed_doc
      assert_equal '500', doc.at('table')['width']
      assert_equal '20', doc.at('td')['height']
    end
  end

  def test_include_link_tags_option
    local_setup('base.html', :adapter => :nokogiri, :include_link_tags => true)
    assert_match /1\.231/, @doc.at('body').attributes['style'].to_s
    assert_match /display: none/, @doc.at('.hide').attributes['style'].to_s

    local_setup('base.html', :adapter => :nokogiri, :include_link_tags => false)
    refute_match /1\.231/, @doc.at('body').attributes['style'].to_s
    assert_match /display: none/, @doc.at('.hide').attributes['style'].to_s
  end

  def test_include_style_tags_option
    local_setup('base.html', :adapter => :nokogiri, :include_style_tags => true)
    assert_match /1\.231/, @doc.at('body').attributes['style'].to_s
    assert_match /display: block/, @doc.at('#iphone').attributes['style'].to_s

    local_setup('base.html', :adapter => :nokogiri, :include_style_tags => false)
    assert_match /1\.231/, @doc.at('body').attributes['style'].to_s
    refute_match /display: block/, @doc.at('#iphone').attributes['style'].to_s
  end

  def test_input_encoding
    html_special_characters = "Ää, Öö, Üü"
    expected_html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>" + html_special_characters + "</p></body></html>\n"
    pm = Premailer.new(html_special_characters, :with_html_string => true, :adapter => :nokogiri, :input_encoding => "UTF-8")
    assert_equal expected_html, pm.to_inline_css
  end

  # output_encoding option should return HTML Entities when set to US-ASCII
  def test_output_encoding
    html_special_characters = "©"
    html_entities_characters = "&#169;"
    expected_html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>" + html_entities_characters + "</p></body></html>\n"
    pm = Premailer.new(html_special_characters, :output_encoding => "US-ASCII", :with_html_string => true, :adapter => :nokogiri, :input_encoding => "UTF-8");
    assert_equal expected_html, pm.to_inline_css
  end

  def test_meta_encoding_downcase
    meta_encoding = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
    expected_html = Regexp.new(Regexp.escape('<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'), Regexp::IGNORECASE)
    pm = Premailer.new(meta_encoding, :with_html_string => true, :adapter => :nokogiri, :input_encoding => "utf-8")
    assert_match expected_html, pm.to_inline_css
  end

  def test_meta_encoding_upcase
    meta_encoding = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
    expected_html = Regexp.new(Regexp.escape('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'), Regexp::IGNORECASE)
    pm = Premailer.new(meta_encoding, :with_html_string => true, :adapter => :nokogiri, :input_encoding => "UTF-8")
    assert_match expected_html, pm.to_inline_css
  end

  def test_htmlentities
    html_entities = "&#8217;"
    expected_html = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n<html><body><p>'</p></body></html>\n"
    pm = Premailer.new(html_entities, :with_html_string => true, :adapter => :nokogiri, :replace_html_entities => true)
    assert_equal expected_html, pm.to_inline_css
  end

  # If a line other than the first line in the html string begins with a URI
  # Premailer should not identify the html string as a URI. Otherwise the following
  # exception would be raised: ActionView::Template::Error: bad URI(is not URI?)
  def test_line_starting_with_uri_in_html_with_linked_css
    files_base = File.expand_path(File.dirname(__FILE__)) + '/files/'
    html_string = IO.read(File.join(files_base, 'html_with_uri.html'))

    premailer = Premailer.new(html_string, :with_html_string => true)
    premailer.to_inline_css
  end

  def test_empty_html_nokogiri
    html = ""
    css = "a:hover {color:red;}"

    pm = Premailer.new(html, :with_html_string => true, :css_string => css, :adapter => :nokogiri)
    pm.to_inline_css
  end

  def silence_stderr(&block)
    orig_stderr = $stderr
    $stderr = File.open(File::NULL, 'w')
    block.call
  ensure
    $stderr = orig_stderr
  end
end


================================================
FILE: test/test_warnings.rb
================================================
# encoding: UTF-8
require File.expand_path(File.dirname(__FILE__)) + '/helper'

class TestWarnings < Premailer::TestCase
  def test_element_warnings
    html = <<END_HTML
    <!DOCTYPE html>
    <html>
    <head><link rel="alternate" href="http://example.com/"></head>
    <body>
    <form method="post"> Test </form>
    </body>
		</html>
END_HTML
    
    [:nokogiri, :hpricot].each do |adapter|
      warnings = get_warnings(html, adapter)
      assert_equal 2, warnings.length
      assert warnings.any? { |w| w[:message] == 'form HTML element'}
      assert warnings.any? { |w| w[:message] == 'link HTML element'}
    end
  end

  def test_css_warnings
    html = <<END_HTML
    <!DOCTYPE html>
    <html><body>
    <div style="margin: 5px; height: 100px;">Test</div>
    </body></html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      warnings = get_warnings(html, adapter)
      assert_equal 2, warnings.length
      assert warnings.any? { |w| w[:message] == 'height CSS property'}
      assert warnings.any? { |w| w[:message] == 'margin CSS property'}
    end
  end

  def test_css_aliased_warnings
    html = <<END_HTML
    <!DOCTYPE html>
    <html><body>
    <div style="margin-top: 5px;">Test</div>
    </body></html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      warnings = get_warnings(html, adapter)
      assert_equal 1, warnings.length
      assert warnings.any? { |w| w[:message] == 'margin-top CSS property'}
    end
  end

  def test_attribute_warnings
    html = <<END_HTML
    <!DOCTYPE html>
    <html><body>
    <img src="#" ismap>
    </body></html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      warnings = get_warnings(html, adapter)
      assert_equal 1, warnings.length
      assert warnings.any? { |w| w[:message] == 'ismap HTML attribute'}
    end
  end

  def test_warn_level
    html = <<END_HTML
    <!DOCTYPE html>
    <html><body>
    <div style="color: red; font-family: sans-serif;">Test</div>
    </body></html>
END_HTML

    [:nokogiri, :hpricot].each do |adapter|
      warnings = get_warnings(html, adapter, Premailer::Warnings::SAFE)
      assert_equal 2, warnings.length
    end

    [:nokogiri, :hpricot].each do |adapter|
      warnings = get_warnings(html, adapter, Premailer::Warnings::POOR)
      assert_equal 1, warnings.length
    end
  end
  
protected
  def get_warnings(html, adapter = :nokogiri, warn_level = Premailer::Warnings::SAFE)
    pm = Premailer.new(html, {:adpater => adapter, :with_html_string => true, :warn_level => warn_level})
    pm.to_inline_css
    pm.check_client_support  
  end
end
Download .txt
gitextract_7t_qp4dq/

├── .gitignore
├── .jrubyrc
├── .travis.yml
├── .yardopts
├── Gemfile
├── LICENSE.md
├── README.md
├── Rakefile
├── bin/
│   └── premailer
├── lib/
│   ├── premailer/
│   │   ├── adapter/
│   │   │   ├── hpricot.rb
│   │   │   ├── nokogiri.rb
│   │   │   └── nokogumbo.rb
│   │   ├── adapter.rb
│   │   ├── executor.rb
│   │   ├── html_to_plain_text.rb
│   │   ├── premailer.rb
│   │   └── version.rb
│   └── premailer.rb
├── misc/
│   └── client_support.yaml
├── premailer.gemspec
└── test/
    ├── files/
    │   ├── base.html
    │   ├── chars.html
    │   ├── html4.html
    │   ├── html_with_uri.html
    │   ├── ignore.css
    │   ├── ignore.html
    │   ├── import.css
    │   ├── iso-8859-2.html
    │   ├── iso-8859-5.html
    │   ├── no_css.html
    │   ├── noimport.css
    │   ├── styles.css
    │   └── xhtml.html
    ├── future_tests.rb
    ├── helper.rb
    ├── test_adapter.rb
    ├── test_html_to_plain_text.rb
    ├── test_links.rb
    ├── test_misc.rb
    ├── test_premailer.rb
    └── test_warnings.rb
Download .txt
SYMBOL INDEX (163 symbols across 15 files)

FILE: lib/premailer/adapter.rb
  class Premailer (line 3) | class Premailer
    type Adapter (line 8) | module Adapter
      function use (line 22) | def self.use
      function default (line 32) | def self.default
      function use= (line 51) | def self.use=(new_adapter)
      function find (line 57) | def self.find(adapter)

FILE: lib/premailer/adapter/hpricot.rb
  class Premailer (line 3) | class Premailer
    type Adapter (line 4) | module Adapter
      type Hpricot (line 6) | module Hpricot
        function to_inline_css (line 10) | def to_inline_css
        function write_unmergable_css_rules (line 144) | def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
        function to_plain_text (line 166) | def to_plain_text
        function to_s (line 179) | def to_s
        function load_html (line 186) | def load_html(input) # :nodoc:

FILE: lib/premailer/adapter/nokogiri.rb
  class Premailer (line 3) | class Premailer
    type Adapter (line 4) | module Adapter
      type Nokogiri (line 6) | module Nokogiri
        function to_inline_css (line 11) | def to_inline_css
        function write_unmergable_css_rules (line 146) | def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
        function to_plain_text (line 170) | def to_plain_text
        function to_s (line 183) | def to_s
        function load_html (line 194) | def load_html(input) # :nodoc:

FILE: lib/premailer/adapter/nokogumbo.rb
  class Premailer (line 3) | class Premailer
    type Adapter (line 4) | module Adapter
      type Nokogumbo (line 6) | module Nokogumbo
        function to_inline_css (line 11) | def to_inline_css
        function write_unmergable_css_rules (line 145) | def write_unmergable_css_rules(doc, unmergable_rules) # :nodoc:
        function to_plain_text (line 169) | def to_plain_text
        function to_s (line 182) | def to_s
        function load_html (line 193) | def load_html(input) # :nodoc:

FILE: lib/premailer/html_to_plain_text.rb
  type HtmlToPlainText (line 5) | module HtmlToPlainText
    function convert_to_text (line 10) | def convert_to_text(html, line_length = 65, from_charset = 'UTF-8')
    function word_wrap (line 112) | def word_wrap(txt, line_length)

FILE: lib/premailer/premailer.rb
  class Premailer (line 34) | class Premailer
    type Warnings (line 131) | module Warnings
    method initialize (line 177) | def initialize(html, options = {})
    method warnings (line 247) | def warnings
    method load_css_from_local_file! (line 254) | def load_css_from_local_file!(path)
    method load_css_from_string (line 268) | def load_css_from_string(css_string)
    method load_css_from_options! (line 273) | def load_css_from_options! # :nodoc:
    method load_css_from_html! (line 286) | def load_css_from_html! # :nodoc:
    method local_uri? (line 330) | def local_uri?(uri) # :nodoc:
    method media_type_ok? (line 338) | def media_type_ok?(media_types)
    method append_query_string (line 344) | def append_query_string(doc, qs)
    method is_xhtml? (line 395) | def is_xhtml?
    method convert_inline_links (line 410) | def convert_inline_links(doc, base_uri) # :nodoc:
    method is_media_query? (line 456) | def self.is_media_query?(media_types)
    method escape_string (line 461) | def self.escape_string(str) # :nodoc:
    method resolve_link (line 466) | def self.resolve_link(path, base_path) # :nodoc:
    method local_data? (line 487) | def self.local_data?(data)
    method canonicalize (line 495) | def self.canonicalize(uri) # :nodoc:
    method check_client_support (line 508) | def check_client_support # :nodoc:

FILE: lib/premailer/version.rb
  class Premailer (line 1) | class Premailer

FILE: test/future_tests.rb
  class TestPremailer (line 4) | class TestPremailer < Premailer::TestCase
    method test_related_attributes (line 5) | def test_related_attributes
    method test_merging_cellpadding (line 28) | def test_merging_cellpadding
    method test_preserving_media_queries (line 45) | def test_preserving_media_queries

FILE: test/helper.rb
  class Premailer::TestCase (line 6) | class Premailer::TestCase < Minitest::Test
    method setup (line 10) | def setup
    method default_test (line 23) | def default_test; end
    method local_setup (line 26) | def local_setup(f = 'base.html', opts = {})
    method remote_setup (line 33) | def remote_setup(f = 'base.html', opts = {})

FILE: test/test_adapter.rb
  class TestAdapter (line 3) | class TestAdapter < Premailer::TestCase
    method test_default_to_best_available (line 5) | def test_default_to_best_available
    method test_settable_via_symbol (line 10) | def test_settable_via_symbol
    method test_adapters_are_findable_by_symbol (line 15) | def test_adapters_are_findable_by_symbol
    method test_adapters_are_findable_by_class (line 19) | def test_adapters_are_findable_by_class
    method test_raises_argument_error (line 23) | def test_raises_argument_error

FILE: test/test_html_to_plain_text.rb
  class TestHtmlToPlainText (line 4) | class TestHtmlToPlainText < Premailer::TestCase
    method test_to_plain_text_with_fragment (line 7) | def test_to_plain_text_with_fragment
    method test_to_plain_text_with_body (line 12) | def test_to_plain_text_with_body
    method test_to_plain_text_with_malformed_body (line 26) | def test_to_plain_text_with_malformed_body
    method test_specialchars (line 38) | def test_specialchars
    method test_stripping_whitespace (line 42) | def test_stripping_whitespace
    method test_wrapping_spans (line 50) | def test_wrapping_spans
    method test_line_breaks (line 63) | def test_line_breaks
    method test_lists (line 68) | def test_lists
    method test_stripping_html (line 73) | def test_stripping_html
    method test_stripping_ignored_blocks (line 77) | def test_stripping_ignored_blocks
    method test_paragraphs_and_breaks (line 89) | def test_paragraphs_and_breaks
    method test_headings (line 97) | def test_headings
    method test_wrapping_lines (line 106) | def test_wrapping_lines
    method test_wrapping_lines_with_spaces (line 117) | def test_wrapping_lines_with_spaces
    method test_img_alt_tags (line 121) | def test_img_alt_tags
    method test_links (line 135) | def test_links
    method test_multiple_links_per_line (line 176) | def test_multiple_links_per_line
    method test_links_within_headings (line 183) | def test_links_within_headings
    method assert_plaintext (line 188) | def assert_plaintext(out, raw, msg = nil, line_length = 65)

FILE: test/test_links.rb
  class TestLinks (line 4) | class TestLinks < Premailer::TestCase
    method test_empty_query_string (line 5) | def test_empty_query_string
    method test_appending_link_query_string (line 10) | def test_appending_link_query_string
    method test_stripping_extra_question_marks_from_query_string (line 62) | def test_stripping_extra_question_marks_from_query_string
    method test_unescape_ampersand (line 78) | def test_unescape_ampersand
    method test_preserving_links (line 89) | def test_preserving_links
    method test_resolving_urls_from_string (line 104) | def test_resolving_urls_from_string
    method test_resolving_urls_from_uri (line 113) | def test_resolving_urls_from_uri
    method test_resolving_urls_from_html_string (line 132) | def test_resolving_urls_from_html_string
    method test_resolving_urls_in_doc (line 142) | def test_resolving_urls_in_doc
    method test_convertable_inline_links (line 161) | def test_convertable_inline_links
    method test_non_convertable_inline_links (line 177) | def test_non_convertable_inline_links

FILE: test/test_misc.rb
  class TestMisc (line 7) | class TestMisc < Premailer::TestCase
    method test_parsing_extra_quotes (line 13) | def test_parsing_extra_quotes
    method test_styles_in_the_body (line 21) | def test_styles_in_the_body
    method test_commented_out_styles_in_the_body (line 37) | def test_commented_out_styles_in_the_body
    method test_not_applying_styles_to_the_head (line 53) | def test_not_applying_styles_to_the_head
    method test_multiple_identical_ids (line 78) | def test_multiple_identical_ids
    method test_preserving_styles (line 98) | def test_preserving_styles
    method test_unmergable_rules (line 129) | def test_unmergable_rules
    method test_unmergable_media_queries (line 145) | def test_unmergable_media_queries
    method test_unmergable_rules_with_no_body (line 182) | def test_unmergable_rules_with_no_body
    method test_ignoring_link_pseudo_selectors (line 196) | def test_ignoring_link_pseudo_selectors
    method test_parsing_bad_markup_around_tables (line 214) | def test_parsing_bad_markup_around_tables
    method test_inline_important (line 240) | def test_inline_important
    method test_handling_shorthand_auto_properties (line 258) | def test_handling_shorthand_auto_properties
    method test_sorting_style_attributes (line 279) | def test_sorting_style_attributes
    method test_removing_scripts (line 296) | def test_removing_scripts
    method test_strip_important_from_attributes (line 321) | def test_strip_important_from_attributes
    method test_scripts_with_nokogiri (line 339) | def test_scripts_with_nokogiri
    method test_style_without_data_in_content (line 370) | def test_style_without_data_in_content
    method test_style_with_data_in_content (line 387) | def test_style_with_data_in_content

FILE: test/test_premailer.rb
  class TestPremailer (line 5) | class TestPremailer < Premailer::TestCase
    method test_special_characters_nokogiri (line 6) | def test_special_characters_nokogiri
    method test_special_characters_nokogiri_remote (line 13) | def test_special_characters_nokogiri_remote
    method test_special_characters_hpricot (line 29) | def test_special_characters_hpricot
    method test_detecting_html (line 36) | def test_detecting_html
    method test_detecting_xhtml (line 43) | def test_detecting_xhtml
    method test_self_closing_xhtml_tags (line 50) | def test_self_closing_xhtml_tags
    method test_non_self_closing_html_tags (line 58) | def test_non_self_closing_html_tags
    method test_mailtos_with_query_strings (line 66) | def test_mailtos_with_query_strings
    method test_escaping_strings (line 82) | def test_escaping_strings
    method test_preserving_ignored_style_elements (line 89) | def test_preserving_ignored_style_elements
    method test_preserving_ignored_link_elements (line 97) | def test_preserving_ignored_link_elements
    method test_importing_local_css (line 105) | def test_importing_local_css
    method test_css_to_attributes (line 118) | def test_css_to_attributes
    method test_avoid_changing_css_to_attributes (line 128) | def test_avoid_changing_css_to_attributes
    method test_importing_remote_css (line 137) | def test_importing_remote_css
    method test_importing_css_as_string (line 149) | def test_importing_css_as_string
    method test_local_remote_check (line 164) | def test_local_remote_check
    method test_initialize_can_accept_io_object (line 176) | def test_initialize_can_accept_io_object
    method test_initialize_can_accept_html_string (line 184) | def test_initialize_can_accept_html_string
    method test_initialize_no_escape_attributes_option (line 191) | def test_initialize_no_escape_attributes_option
    method test_remove_ids (line 208) | def test_remove_ids
    method test_reset_contenteditable (line 228) | def test_reset_contenteditable
    method test_carriage_returns_as_entities (line 245) | def test_carriage_returns_as_entities
    method test_advanced_selectors (line 260) | def test_advanced_selectors
    method test_premailer_related_attributes (line 271) | def test_premailer_related_attributes
    method test_include_link_tags_option (line 288) | def test_include_link_tags_option
    method test_include_style_tags_option (line 298) | def test_include_style_tags_option
    method test_input_encoding (line 308) | def test_input_encoding
    method test_output_encoding (line 316) | def test_output_encoding
    method test_meta_encoding_downcase (line 324) | def test_meta_encoding_downcase
    method test_meta_encoding_upcase (line 331) | def test_meta_encoding_upcase
    method test_htmlentities (line 338) | def test_htmlentities
    method test_line_starting_with_uri_in_html_with_linked_css (line 348) | def test_line_starting_with_uri_in_html_with_linked_css
    method test_empty_html_nokogiri (line 356) | def test_empty_html_nokogiri
    method silence_stderr (line 364) | def silence_stderr(&block)

FILE: test/test_warnings.rb
  class TestWarnings (line 4) | class TestWarnings < Premailer::TestCase
    method test_element_warnings (line 5) | def test_element_warnings
    method test_css_warnings (line 24) | def test_css_warnings
    method test_css_aliased_warnings (line 40) | def test_css_aliased_warnings
    method test_attribute_warnings (line 55) | def test_attribute_warnings
    method test_warn_level (line 70) | def test_warn_level
    method get_warnings (line 90) | def get_warnings(html, adapter = :nokogiri, warn_level = Premailer::Wa...
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (136K chars).
[
  {
    "path": ".gitignore",
    "chars": 87,
    "preview": ".DS_Store\n*.gem\nbin/*.html\nhtml/\nvendor/\ndoc/\n.yardoc/\n*.sw?\npkg/\n.bundle/\n*.sublime-*\n"
  },
  {
    "path": ".jrubyrc",
    "chars": 18,
    "preview": "cext.enabled=true\n"
  },
  {
    "path": ".travis.yml",
    "chars": 157,
    "preview": "cache: bundler\nsudo: false\nbranches:\n  only: master\nmatrix:\n  fast_finish: true\nbefore_install: rm Gemfile.lock\nrvm:\n  -"
  },
  {
    "path": ".yardopts",
    "chars": 151,
    "preview": "--markup markdown\n--markup-provider redcarpet\n--charset utf-8\n--no-private\n--readme README.md\n--title \"Premailer Documen"
  },
  {
    "path": "Gemfile",
    "chars": 157,
    "preview": "source \"https://rubygems.org\"\n\ngem 'css_parser', :git => 'git://github.com/premailer/css_parser.git'\n\nplatforms :jruby d"
  },
  {
    "path": "LICENSE.md",
    "chars": 1505,
    "preview": "# Premailer License\n\nCopyright (c) 2007-2012, Alex Dunae.  All rights reserved.\n\nRedistribution and use in source and bi"
  },
  {
    "path": "README.md",
    "chars": 3821,
    "preview": "# Premailer README [![Build Status](https://travis-ci.org/premailer/premailer.png?branch=master)](https://travis-ci.org/"
  },
  {
    "path": "Rakefile",
    "chars": 1586,
    "preview": "require 'bundler/setup'\nrequire 'rake/testtask'\nrequire \"bundler/gem_tasks\"\nrequire 'yard'\n\nGEM_ROOT = File.dirname(__FI"
  },
  {
    "path": "bin/premailer",
    "chars": 125,
    "preview": "#!/usr/bin/env ruby\n\n# This binary used in rubygems environment only as part of installed gem\n\nrequire 'premailer/execut"
  },
  {
    "path": "lib/premailer/adapter/hpricot.rb",
    "chars": 7436,
    "preview": "require 'hpricot'\n\nclass Premailer\n  module Adapter\n    # Hpricot adapter\n    module Hpricot\n\n      # Merge CSS into the"
  },
  {
    "path": "lib/premailer/adapter/nokogiri.rb",
    "chars": 9374,
    "preview": "require 'nokogiri'\n\nclass Premailer\n  module Adapter\n    # Nokogiri adapter\n    module Nokogiri\n\n      # Merge CSS into "
  },
  {
    "path": "lib/premailer/adapter/nokogumbo.rb",
    "chars": 9193,
    "preview": "require 'nokogumbo'\n\nclass Premailer\n  module Adapter\n    # Nokogiri adapter\n    module Nokogumbo\n\n      # Merge CSS int"
  },
  {
    "path": "lib/premailer/adapter.rb",
    "chars": 1812,
    "preview": "\n\nclass Premailer\n  # Manages the adapter classes. Currently supports:\n  #\n  # * nokogiri\n  # * hpricot\n  module Adapter"
  },
  {
    "path": "lib/premailer/executor.rb",
    "chars": 2730,
    "preview": "require 'optparse'\nrequire 'premailer'\n\n# defaults\noptions = {\n  :base_url => nil,\n  :link_query_string => nil,\n  :remov"
  },
  {
    "path": "lib/premailer/html_to_plain_text.rb",
    "chars": 3562,
    "preview": "# coding: utf-8\nrequire 'htmlentities'\n\n# Support functions for Premailer\nmodule HtmlToPlainText\n\n  # Returns the text i"
  },
  {
    "path": "lib/premailer/premailer.rb",
    "chars": 19754,
    "preview": "# Premailer processes HTML and CSS to improve e-mail deliverability.\n#\n# Premailer's main function is to render all CSS "
  },
  {
    "path": "lib/premailer/version.rb",
    "chars": 70,
    "preview": "class Premailer\n  # Premailer version.\n  VERSION = '1.8.6'.freeze\nend\n"
  },
  {
    "path": "lib/premailer.rb",
    "chars": 188,
    "preview": "require 'yaml'\nrequire 'open-uri'\nrequire 'digest/md5'\nrequire 'cgi'\nrequire 'css_parser'\n\nrequire 'premailer/adapter'\nr"
  },
  {
    "path": "misc/client_support.yaml",
    "chars": 6957,
    "preview": "# Capabilities of e-mail clients\n#\n# Sources\n# * http://campaignmonitor.com/css/\n# * http://www.campaignmonitor.com/blog"
  },
  {
    "path": "premailer.gemspec",
    "chars": 1157,
    "preview": "require './lib/premailer/version'\n\nGem::Specification.new \"premailer\", Premailer::VERSION do |s|\n  s.summary  = \"Preflig"
  },
  {
    "path": "test/files/base.html",
    "chars": 5676,
    "preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<!--\n\n  You can read this new"
  },
  {
    "path": "test/files/chars.html",
    "chars": 122,
    "preview": "<!DOCTYPE html>\n<html>\n<body>\n<p>cédille c&eacute; & garçon gar&#231;on à &agrave; &nbsp; &amp; &copy;</p>\n</body>\n</htm"
  },
  {
    "path": "test/files/html4.html",
    "chars": 625,
    "preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n   \"http://www.w3.org/TR/html4/strict.dtd\">\n<html>\n<head>\n  <title>Tit"
  },
  {
    "path": "test/files/html_with_uri.html",
    "chars": 202,
    "preview": "<html>\n  <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\">\n  <body>\n    <p>\n      The following line should not "
  },
  {
    "path": "test/files/ignore.css",
    "chars": 26,
    "preview": "body {\n  color: orange;\n}\n"
  },
  {
    "path": "test/files/ignore.html",
    "chars": 359,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <title>Should ignore link and style elements with data attribute</title>\n    <link r"
  },
  {
    "path": "test/files/import.css",
    "chars": 186,
    "preview": "/*\n * Premailer styles - should import\n *\n * $Package: Premailer $\n * $Date: 2009-02-09 17:15:56 -0800 (Mon, 09 Feb 2009"
  },
  {
    "path": "test/files/iso-8859-2.html",
    "chars": 70,
    "preview": "<body>In Hungary we use some special accented characters:    .</body>\n"
  },
  {
    "path": "test/files/iso-8859-5.html",
    "chars": 126,
    "preview": "<html>\n<head>\n<meta http-equiv=\"Content-type\" content=\"text/html; charset=iso-8859-5\">\n</head>\n<body>\n<p> </p>\n</body>\n<"
  },
  {
    "path": "test/files/no_css.html",
    "chars": 313,
    "preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html>\n<head>\n\t<meta http-equ"
  },
  {
    "path": "test/files/noimport.css",
    "chars": 207,
    "preview": "/*\n * Premailer styles - should not import\n *\n * $Package: Premailer $\n * $Date: 2009-02-09 17:15:56 -0800 (Mon, 09 Feb "
  },
  {
    "path": "test/files/styles.css",
    "chars": 2809,
    "preview": "/*\n * Premailer styles\n *\n * $Package: Premailer $\n * $Date: 2009-02-09 17:15:56 -0800 (Mon, 09 Feb 2009) $WCDATE$ $\n * "
  },
  {
    "path": "test/files/xhtml.html",
    "chars": 654,
    "preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
  },
  {
    "path": "test/future_tests.rb",
    "chars": 1583,
    "preview": "# encoding: UTF-8\nrequire File.expand_path(File.dirname(__FILE__)) + '/helper'\n\nclass TestPremailer < Premailer::TestCas"
  },
  {
    "path": "test/helper.rb",
    "chars": 1097,
    "preview": "require 'bundler/setup'\nrequire 'maxitest/autorun'\nrequire 'webmock/minitest'\nrequire 'premailer'\n\nclass Premailer::Test"
  },
  {
    "path": "test/test_adapter.rb",
    "chars": 842,
    "preview": "require File.expand_path(File.dirname(__FILE__)) + '/helper'\n\nclass TestAdapter < Premailer::TestCase\n\n  def test_defaul"
  },
  {
    "path": "test/test_html_to_plain_text.rb",
    "chars": 7304,
    "preview": "# encoding: utf-8\nrequire File.expand_path(File.dirname(__FILE__)) + '/helper'\n\nclass TestHtmlToPlainText < Premailer::T"
  },
  {
    "path": "test/test_links.rb",
    "chars": 7491,
    "preview": "# encoding: UTF-8\nrequire File.expand_path(File.dirname(__FILE__)) + '/helper'\n\nclass TestLinks < Premailer::TestCase\n  "
  },
  {
    "path": "test/test_misc.rb",
    "chars": 12068,
    "preview": "# encoding: UTF-8\nrequire File.expand_path(File.dirname(__FILE__)) + '/helper'\n\n# Random tests for specific issues.\n#\n# "
  },
  {
    "path": "test/test_premailer.rb",
    "chars": 14267,
    "preview": "# -*- encoding: UTF-8 -*-\n\nrequire File.expand_path(File.dirname(__FILE__)) + '/helper'\n\nclass TestPremailer < Premailer"
  },
  {
    "path": "test/test_warnings.rb",
    "chars": 2596,
    "preview": "# encoding: UTF-8\nrequire File.expand_path(File.dirname(__FILE__)) + '/helper'\n\nclass TestWarnings < Premailer::TestCase"
  }
]

About this extraction

This page contains the full source code of the alexdunae/premailer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (125.5 KB), approximately 36.9k tokens, and a symbol index with 163 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!