pax_global_header00006660000000000000000000000064137321376300014517gustar00rootroot0000000000000052 comment=be1634c542910f729d0070f5be2a36de97eb0d48 premailer-1.14.2/000077500000000000000000000000001373213763000135645ustar00rootroot00000000000000premailer-1.14.2/.editorconfig000066400000000000000000000002461373213763000162430ustar00rootroot00000000000000# editorconfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true premailer-1.14.2/.gitignore000066400000000000000000000001401373213763000155470ustar00rootroot00000000000000.DS_Store /*.gem /bin/*.html /html/ /vendor/ /doc/ /.yardoc/ *.sw? /pkg/ /.bundle/ /*.sublime-* premailer-1.14.2/.jrubyrc000066400000000000000000000000221373213763000152370ustar00rootroot00000000000000cext.enabled=true premailer-1.14.2/.travis.yml000066400000000000000000000002571373213763000157010ustar00rootroot00000000000000cache: bundler branches: only: master matrix: fast_finish: true language: ruby before_install: - gem update --system - gem update bundler rvm: - 2.5 - 2.6 - 2.7 premailer-1.14.2/.yardopts000066400000000000000000000002271373213763000154330ustar00rootroot00000000000000--markup markdown --markup-provider redcarpet --charset utf-8 --no-private --readme README.md --title "Premailer Documentation" - README.md LICENSE.md premailer-1.14.2/CHANGELOG.md000066400000000000000000000020451373213763000153760ustar00rootroot00000000000000## Premailer CHANGELOG ### Verion 1.14.1 * Fix to converting inline html 100px to 100 ### Verion 1.14.0 * Convert inline html 100px to 100 ### Verion 1.13.1 * Replace deprecated File.exists? with File.exist? (fixes Ruby 2.8 deprecation warning) ### Verion 1.13.0 * Fix URI.open deprecation warnings ### Version 1.12.1 * Fix greedy script regex. ### Version 1.11.1 * Fix input encoding in nokogiri adapters. ### Version 1.11.0 * Support for HTML fragments rendering (without enforcing of doctype, head, body). See :html_fragment option. * Depends on css_parser 1.6.0. ### Version 1.10.4 * Exponential regexp in convert_to_text fixed. ### Version 1.10.3 * Keep consecutive whitespaces. * Depends on css_parser 1.5.0. ### Version 1.10.2 * Fix LoadError addressable with Addressable 2.3.8 ### Version 1.10.1 * Depends on css_parser 1.4.10. * Drops wrong destructive sorting of attributes (`css_parser` already does it correctly) * Replace obsolete `URI` calls with `Addressable::URI`. * Drop last semicolon from attributes. * Update tests. premailer-1.14.2/Gemfile000066400000000000000000000003461373213763000150620ustar00rootroot00000000000000# Keep Gemfile.lock from repo. Reason: https://grosser.it/2015/08/14/check-in-your-gemfile-lock/ source 'https://rubygems.org' platforms :jruby do gem 'jruby-openssl' end group :development, :test do gem 'pry' end gemspec premailer-1.14.2/Gemfile.lock000066400000000000000000000030101373213763000160000ustar00rootroot00000000000000PATH remote: . specs: premailer (1.14.2) addressable css_parser (>= 1.6.0) htmlentities (>= 4.0.0) GEM remote: https://rubygems.org/ specs: addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) bump (0.9.0) coderay (1.1.2) coveralls (0.8.23) json (>= 1.8, < 3) simplecov (~> 0.16.1) term-ansicolor (~> 1.3) thor (>= 0.19.4, < 2.0) tins (~> 1.6) crack (0.4.3) safe_yaml (~> 1.0.0) css_parser (1.7.1) addressable docile (1.3.2) hashdiff (1.0.0) htmlentities (4.3.4) json (2.2.0) maxitest (3.3.0) minitest (>= 5.0.0, < 5.12.0) method_source (0.9.2) mini_portile2 (2.3.0) minitest (5.11.3) nokogiri (1.8.5) mini_portile2 (~> 2.3.0) nokogumbo (2.0.1) nokogiri (~> 1.8, >= 1.8.4) pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) public_suffix (4.0.1) rake (12.3.3) redcarpet (3.5.0) safe_yaml (1.0.5) simplecov (0.16.1) docile (~> 1.1) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) term-ansicolor (1.7.1) tins (~> 1.0) thor (0.20.3) tins (1.21.1) webmock (3.7.5) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) PLATFORMS ruby DEPENDENCIES bump bundler (>= 1.3) coveralls jruby-openssl maxitest nokogiri (~> 1.8.2) nokogumbo premailer! pry rake (> 0.8, != 0.9.0) redcarpet (~> 3.0) webmock BUNDLED WITH 2.1.4 premailer-1.14.2/LICENSE.md000066400000000000000000000027411373213763000151740ustar00rootroot00000000000000# Premailer License Copyright (c) 2007-2017, 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. premailer-1.14.2/README.md000066400000000000000000000114361373213763000150500ustar00rootroot00000000000000# Premailer README [![Build Status](https://travis-ci.org/premailer/premailer.svg?branch=master)](https://travis-ci.org/premailer/premailer) [![Gem Version](https://badge.fury.io/rb/premailer.svg)](https://badge.fury.io/rb/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](https://premailer.github.io/premailer/HtmlToPlainText.html) is created (optional) ## Installation Install the Premailer gem from RubyGems. ```bash gem install premailer ``` or add it to your `Gemfile` and run `bundle`. ## Example ```ruby require 'premailer' premailer = Premailer.new('http://example.com/myfile.html', :warn_level => Premailer::Warnings::SAFE) # Write the plain-text output # This must come before to_inline_css (https://github.com/premailer/premailer/issues/201) File.open("output.txt", "w") do |fout| fout.puts premailer.to_plain_text end # Write the HTML output File.open("output.html", "w") do |fout| fout.puts premailer.to_inline_css end # Output any CSS warnings premailer.warnings.each do |w| puts "#{w[:message]} (#{w[:level]}) may not render properly in #{w[:clients]}" end ``` ## Adapters Premailer's default adapter is nokogiri if both nokogiri and nokogumbo are included in the Gemfile list. However, if you want to use a different adapter, you can choose to. There are three adapters in total (as of premailer 1.10.0) 1. nokogiri (default) 2. nokogiri_fast 3. nokogumbo hpricot adapter removed due to its EOL, please use `~>1.9.0` version if You still need it.. `NokogiriFast` adapter improves the Algorithmic complexity of the running time by 20x with a slight compensation on memory. To switch to any of these adapters, add the following line. For example, if you want to include the `NokogiriFast` adapter, ```ruby Premailer::Adapter.use = :nokogiri_fast ``` ## Ruby Compatibility See .travis.yml for which ruby versions are tested. 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 | | -premailer-align | 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 ``` ## Configuration options The behavior of Premailer can be configured by passing options in the initializer. For example, the following will accept HTML from a string and will exclude unmergeable css from being added to the `` of the output document. ```ruby premailer = Premailer.new(html_string, with_html_string: true, drop_unmergeable_css_rules: true) ``` [See here for a full list of the available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method). ## 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 source code can be found on [GitHub](https://github.com/premailer/premailer). Copyright by Alex Dunae (dunae.ca, e-mail 'code' at the same domain), 2007-2017. See [LICENSE.md](https://github.com/premailer/premailer/blob/master/LICENSE.md) for license details. premailer-1.14.2/Rakefile000066400000000000000000000027021373213763000152320ustar00rootroot00000000000000require 'bundler/setup' require 'rake/testtask' require "bundler/gem_tasks" require 'bump/tasks' 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 task :default => [:test] premailer-1.14.2/bin/000077500000000000000000000000001373213763000143345ustar00rootroot00000000000000premailer-1.14.2/bin/premailer000077500000000000000000000001751373213763000162450ustar00rootroot00000000000000#!/usr/bin/env ruby # This binary used in rubygems environment only as part of installed gem require 'premailer/executor' premailer-1.14.2/lib/000077500000000000000000000000001373213763000143325ustar00rootroot00000000000000premailer-1.14.2/lib/premailer.rb000066400000000000000000000003751373213763000166440ustar00rootroot00000000000000require 'yaml' require 'open-uri' require 'digest/md5' require 'cgi' require 'addressable/uri' require 'css_parser' require 'premailer/adapter' require 'premailer/adapter/rgb_to_hex' require 'premailer/html_to_plain_text' require 'premailer/premailer' premailer-1.14.2/lib/premailer/000077500000000000000000000000001373213763000163125ustar00rootroot00000000000000premailer-1.14.2/lib/premailer/adapter.rb000066400000000000000000000035051373213763000202620ustar00rootroot00000000000000class Premailer # Manages the adapter classes. Currently supports: # # * nokogiri # * nokogiri_fast # * nokogumbo module Adapter autoload :Nokogiri, 'premailer/adapter/nokogiri' autoload :NokogiriFast, 'premailer/adapter/nokogiri_fast' autoload :Nokogumbo, 'premailer/adapter/nokogumbo' # adapter to required file mapping. REQUIREMENT_MAP = [ ["nokogiri", :nokogiri], ["nokogiri", :nokogiri_fast], ["nokogumbo", :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 :nokogiri if defined?(::Nokogiri) return :nokogiri_fast if defined?(::NokogiriFast) 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 nokogiri or nokogumbo") 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 premailer-1.14.2/lib/premailer/adapter/000077500000000000000000000000001373213763000177325ustar00rootroot00000000000000premailer-1.14.2/lib/premailer/adapter/nokogiri.rb000066400000000000000000000233321373213763000221030ustar00rootroot00000000000000require 'nokogiri' class Premailer module Adapter # Nokogiri adapter module Nokogiri include AdapterHelper::RgbToHex # 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 doc.search("script").remove if @options[:remove_scripts] # Read STYLE attributes and perform folding doc.search("*[@style]").each do |el| style = el.attributes['style'].to_s declarations = [] style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration| rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i) declarations << rs end # Perform style folding merged = CssParser.merge(declarations) merged.expand_shorthand! # Duplicate CSS attributes as HTML attributes if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes] Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr| if el[html_attr].nil? and not merged[css_attr].empty? new_val = merged[css_attr].dup # Remove url() function wrapper new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2') # Remove !important, trailing semi-colon, and leading/trailing whitespace new_val.gsub!(/;$|\s*!important/, '').strip! # For width and height tags, remove px units new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr) # For color-related tags, convert RGB to hex if specified by options new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes] el[html_attr] = new_val end unless @options[:preserve_style_attribute] merged.instance_variable_get("@declarations").tap do |declarations| declarations.delete(css_attr) end end end end # Collapse multiple rules into one as much as possible. merged.create_shorthand! if @options[:create_shorthands] # write the inline STYLE attribute el['style'] = merged.declarations_to_s end doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules] if @options[:remove_classes] or @options[:remove_comments] doc.traverse do |el| if el.comment? and @options[:remove_comments] el.remove elsif el.element? el.remove_attribute('class') if @options[:remove_classes] end end end if @options[:remove_ids] # find all anchor's targets and hash them targets = [] doc.search("a[@href^='#']").each do |el| target = el.get_attribute('href')[1..-1] targets << target el.set_attribute('href', "#" + Digest::MD5.hexdigest(target)) end # hash ids that are links target, delete others doc.search("*[@id]").each do |el| id = el.get_attribute('id') if targets.include?(id) el.set_attribute('id', Digest::MD5.hexdigest(id)) else el.remove_attribute('id') end end end if @options[:reset_contenteditable] doc.search('*[@contenteditable]').each do |el| el.remove_attribute('contenteditable') end end @processed_doc = doc if is_xhtml? # we don't want to encode carriage returns @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r") else @processed_doc.to_html(:encoding => @options[:output_encoding]) end end # Create a style element with un-mergable rules (e.g. :hover) # and write it into the head. # # doc is an Nokogiri document and unmergable_css_rules 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? if @options[:html_fragment] style_tag = ::Nokogiri::XML::Node.new("style", doc) style_tag.content = styles doc.add_child(style_tag) else style_tag = doc.create_element "style", "#{styles}" head = doc.at_css('head') head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child head ||= doc.add_child(doc.create_element "head") head << style_tag end end doc end # Converts the HTML document to a format suitable for plain-text e-mail. # # If present, uses the 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 = URI.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. encoding = if thing.is_a?(String) and RUBY_VERSION =~ /1.9/ thing = thing.force_encoding(@options[:input_encoding]).encode! @options[:input_encoding] else @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY') end doc = if @options[:html_fragment] ::Nokogiri::HTML.fragment(thing, encoding) else ::Nokogiri::HTML(thing, nil, 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 premailer-1.14.2/lib/premailer/adapter/nokogiri_fast.rb000066400000000000000000000345601373213763000231250ustar00rootroot00000000000000require 'nokogiri' class Premailer module Adapter # NokogiriFast adapter module NokogiriFast include AdapterHelper::RgbToHex # 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 # Create an index for nodes by tag name/id/class # Also precompute the map of nodes to descendants index, all_nodes, descendants = make_index(doc) # 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 # Try the new index based technique. If not supported, fall back to the old brute force one. nodes = match_selector(index, all_nodes, descendants, selector) || doc.search(selector) nodes.each do |el| if el.elem? and (el.name != 'head' and el.parent.name != 'head') # Add a style attribute or append to the existing one block = "[SPEC=#{specificity}[#{declaration}]]" el['style'] = (el.attributes['style'].to_s ||= '') + ' ' + block end end rescue ::Nokogiri::SyntaxError, RuntimeError, ArgumentError $stderr.puts "CSS syntax error with selector: #{selector}" if @options[:verbose] next end end end # Remove script tags doc.search("script").remove if @options[:remove_scripts] # Read STYLE attributes and perform folding doc.search("*[@style]").each do |el| style = el.attributes['style'].to_s declarations = [] style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration| rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i) declarations << rs end # Perform style folding merged = CssParser.merge(declarations) merged.expand_shorthand! # Duplicate CSS attributes as HTML attributes if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes] Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr| if el[html_attr].nil? and not merged[css_attr].empty? new_val = merged[css_attr].dup # Remove url() function wrapper new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2') # Remove !important, trailing semi-colon, and leading/trailing whitespace new_val.gsub!(/;$|\s*!important/, '').strip! # For width and height tags, remove px units new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr) # For color-related tags, convert RGB to hex if specified by options new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes] el[html_attr] = new_val end unless @options[:preserve_style_attribute] merged.instance_variable_get("@declarations").tap do |declarations| declarations.delete(css_attr) end end end end # Collapse multiple rules into one as much as possible. merged.create_shorthand! if @options[:create_shorthands] # write the inline STYLE attribute el['style'] = merged.declarations_to_s end doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules] if @options[:remove_classes] or @options[:remove_comments] doc.traverse do |el| if el.comment? and @options[:remove_comments] el.remove elsif el.element? el.remove_attribute('class') if @options[:remove_classes] end end end if @options[:remove_ids] # find all anchor's targets and hash them targets = [] doc.search("a[@href^='#']").each do |el| target = el.get_attribute('href')[1..-1] targets << target el.set_attribute('href', "#" + Digest::MD5.hexdigest(target)) end # hash ids that are links target, delete others doc.search("*[@id]").each do |el| id = el.get_attribute('id') if targets.include?(id) el.set_attribute('id', Digest::MD5.hexdigest(id)) else el.remove_attribute('id') end end end if @options[:reset_contenteditable] doc.search('*[@contenteditable]').each do |el| el.remove_attribute('contenteditable') end end @processed_doc = doc if is_xhtml? # we don't want to encode carriage returns @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r") else @processed_doc.to_html(:encoding => @options[:output_encoding]) end end # Create a style element with un-mergable rules (e.g. :hover) # and write it into the head. # # doc is an Nokogiri document and unmergable_css_rules 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? if @options[:html_fragment] style_tag = ::Nokogiri::XML::Node.new("style", doc) style_tag.content = styles doc.add_child(style_tag) else style_tag = doc.create_element "style", styles head = doc.at_css('head') head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child head ||= doc.add_child(doc.create_element "head") head << style_tag end end doc end # Converts the HTML document to a format suitable for plain-text e-mail. # # If present, uses the 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 = URI.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. encoding = if thing.is_a?(String) and RUBY_VERSION =~ /1.9/ thing = thing.force_encoding(@options[:input_encoding]).encode! @options[:input_encoding] else @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY') end doc = if @options[:html_fragment] ::Nokogiri::HTML.fragment(thing, encoding) else ::Nokogiri::HTML(thing, nil, 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 private # For very large documents, it is useful to trade off some memory for performance. # We can build an index of the nodes so we can quickly select by id/class/tagname # instead of search the tree again and again. # # @param page The Nokogiri HTML document to index. # @return [index, set_of_all_nodes, descendants] The index is a hash from key to set of nodes. # The "descendants" is a hash mapping a node to the set of its descendant nodes. def make_index(page) index = {} # Contains a map of tag/class/id names to set of nodes. all_nodes = [] # A plain array of all nodes in the doc. The superset. descendants = {} # Maps node -> set of descendants page.traverse do |node| all_nodes.push(node) if node != page then index_ancestry(page, node, node.parent, descendants) end # Index the node by tag name. This is the least selective # of the three index types empirically. index[node.name] = (index[node.name] || Set.new).add(node) # Index the node by all class attributes it possesses. # Classes are modestly selective. Usually more than tag names # but less selective than ids. if node.has_attribute?("class") then node.get_attribute("class").split(/\s+/).each do |c| c = '.' + c index[c] = (index[c] || Set.new).add(node) end end # Index the node by its "id" attribute if it has one. # This is usually the most selective of the three. if node.has_attribute?("id") then id = '#' + node.get_attribute("id") index[id] = (index[id] || Set.new).add(node) end end # If an index key isn't there, then we should treat it as an empty set. # This makes the index total and we don't need to special case presence. # Note that the default value will never be modified. So we don't need # default_proc. index.default = Set.new descendants.default = Set.new return index, Set.new(all_nodes), descendants end # @param doc The top level document # @param elem The element whose ancestry is to be captured # @param parent the current parent in the process of capturing. Should be set to elem.parent for starters. # @param descendants The running hash map of node -> set of nodes that maps descendants of a node. # @return The descendants argument after updating it. def index_ancestry(doc, elem, parent, descendants) if parent then descendants[parent] = (descendants[parent] || Set.new).add(elem) if doc != parent then index_ancestry(doc, elem, parent.parent, descendants) end end descendants end # @param index An index hash returned by make_index # @param base The base set of nodes within which the given spec is to be matched. # @param intersection_selector A CSS intersection selector string of the form # "hello.world" or "#blue.diamond". This should not contain spaces. # @return Set of nodes matching the given spec that are present in the base set. def narrow_down_nodes(index, base, intersection_selector) intersection_selector.split(/(?=[.#])/).reduce(base) do |acc, sel| acc = index[sel].intersection(acc) acc end end # @param index An index returned by make_index # @param allNodes The set of all nodes in the DOM to search # @param selector A simple CSS tree matching selector of the form "div.container p.item span" # @return Set of matching nodes # # Note that fancy CSS selector syntax is not supported. Anything # not matching the regex /^[-a-zA-Z0-9\s_.#]*$/ should not be passed. # It will return nil when such a selector is passed, so you can take # action on the falsity of the return value. def match_selector(index, all_nodes, descendants, selector) if /[^-a-zA-Z0-9_\s.#]/.match(selector) then return nil end take_children = false selector.split(/\s+/).reduce(all_nodes) do |base, spec| desc = base if take_children then desc = Set.new base.each do |n| desc.merge(descendants[n]) end else take_children = true end narrow_down_nodes(index, desc, spec) end end end end end premailer-1.14.2/lib/premailer/adapter/nokogumbo.rb000066400000000000000000000230351373213763000222620ustar00rootroot00000000000000require 'nokogumbo' class Premailer module Adapter # Nokogiri adapter module Nokogumbo include AdapterHelper::RgbToHex # 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 doc.search("script").remove if @options[:remove_scripts] # Read STYLE attributes and perform folding doc.search("*[@style]").each do |el| style = el.attributes['style'].to_s declarations = [] style.scan(/\[SPEC\=([\d]+)\[(.[^\]\]]*)\]\]/).each do |declaration| rs = CssParser::RuleSet.new(nil, declaration[1].to_s, declaration[0].to_i) declarations << rs end # Perform style folding merged = CssParser.merge(declarations) merged.expand_shorthand! # Duplicate CSS attributes as HTML attributes if Premailer::RELATED_ATTRIBUTES.has_key?(el.name) && @options[:css_to_attributes] Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr| if el[html_attr].nil? and not merged[css_attr].empty? new_val = merged[css_attr].dup # Remove url() function wrapper new_val.gsub!(/url\((['"])(.*?)\1\)/, '\2') # Remove !important, trailing semi-colon, and leading/trailing whitespace new_val.gsub!(/;$|\s*!important/, '').strip! # For width and height tags, remove px units new_val.gsub!(/(\d+)px/, '\1') if %w[width height].include?(html_attr) # For color-related tags, convert RGB to hex if specified by options new_val = ensure_hex(new_val) if css_attr.end_with?('color') && @options[:rgb_to_hex_attributes] el[html_attr] = new_val end unless @options[:preserve_style_attribute] merged.instance_variable_get("@declarations").tap do |declarations| declarations.delete(css_attr) end end end end # Collapse multiple rules into one as much as possible. merged.create_shorthand! if @options[:create_shorthands] # write the inline STYLE attribute el['style'] = merged.declarations_to_s end doc = write_unmergable_css_rules(doc, @unmergable_rules) unless @options[:drop_unmergeable_css_rules] if @options[:remove_classes] or @options[:remove_comments] doc.traverse do |el| if el.comment? and @options[:remove_comments] el.remove elsif el.element? el.remove_attribute('class') if @options[:remove_classes] end end end if @options[:remove_ids] # find all anchor's targets and hash them targets = [] doc.search("a[@href^='#']").each do |el| target = el.get_attribute('href')[1..-1] targets << target el.set_attribute('href', "#" + Digest::MD5.hexdigest(target)) end # hash ids that are links target, delete others doc.search("*[@id]").each do |el| id = el.get_attribute('id') if targets.include?(id) el.set_attribute('id', Digest::MD5.hexdigest(id)) else el.remove_attribute('id') end end end if @options[:reset_contenteditable] doc.search('*[@contenteditable]').each do |el| el.remove_attribute('contenteditable') end end @processed_doc = doc if is_xhtml? # we don't want to encode carriage returns @processed_doc.to_xhtml(:encoding => @options[:output_encoding]).gsub(/&\#(xD|13);/i, "\r") else @processed_doc.to_html(:encoding => @options[:output_encoding]) end end # Create a style element with un-mergable rules (e.g. :hover) # and write it into the head. # # doc is an Nokogiri document and unmergable_css_rules 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? if @options[:html_fragment] style_tag = ::Nokogiri::XML::Node.new("style", doc) style_tag.content = styles doc.add_child(style_tag) else style_tag = doc.create_element "style", styles head = doc.at_css('head') head ||= doc.root.first_element_child.add_previous_sibling(doc.create_element "head") if doc.root && doc.root.first_element_child head ||= doc.add_child(doc.create_element "head") head << style_tag end end doc end # Converts the HTML document to a format suitable for plain-text e-mail. # # If present, uses the 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 = URI.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! end doc = if @options[:html_fragment] ::Nokogiri::HTML5.fragment(thing) else ::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 premailer-1.14.2/lib/premailer/adapter/rgb_to_hex.rb000066400000000000000000000014731373213763000224040ustar00rootroot00000000000000# RGB helper for adapters, currently only nokogiri supported module AdapterHelper module RgbToHex def to_hex(str) str.to_i.to_s(16).rjust(2, '0').upcase end def is_rgb?(color) pattern = %r{ rgb \(\s* # literal open, with optional whitespace (\d{1,3}) # capture 1-3 digits \s*,\s* # comma, with optional whitespace (\d{1,3}) # capture 1-3 digits \s*,\s* # comma, with optional whitespace (\d{1,3}) # capture 1-3 digits \s*\) # literal close, with optional whitespace }x pattern.match(color) end def ensure_hex(color) match_data = is_rgb?(color) if match_data "#{to_hex(match_data[1])}#{to_hex(match_data[2])}#{to_hex(match_data[3])}" else color end end end end premailer-1.14.2/lib/premailer/executor.rb000066400000000000000000000056461373213763000205100ustar00rootroot00000000000000require 'optparse' require 'premailer' # defaults options = { :base_url => nil, :link_query_string => nil, :remove_classes => false, :verbose => false, :line_length => 65, :adapter => :nokogiri, } 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 [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("--adapter ADAPTER", [:nokogiri, :nokogiri_fast, :nokogumbo], "Adapter: nokogiri, nokogiri_fast or nokogumbo (default: #{options[:adapter]}") do |v| options[:adapter] = 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 options[:remove_classes] = true end opts.on("-j", "--remove-scripts", "Remove END_HTML premailer = Premailer.new(html, :with_html_string => true) assert_empty premailer.to_plain_text end def test_specialchars assert_plaintext 'cédille garçon & à ñ', 'cédille garçon & à ñ' 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 " assert_plaintext "test text", "test text" end def test_wrapping_spans html = <

Test line 2

END_HTML premailer = Premailer.new(html, :adapter => :nokogiri, :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", "
  • item 1
  • item 2
  • \n" assert_plaintext "* item 1\n* item 2\n* item 3", "
  • item 1
  • \t\n
  • item 2
  • item 3
  • \n" end def test_stripping_html assert_plaintext 'test text', "

    test text\n" end def test_stripping_ignored_blocks html = <test

    logo

    text

    END_HTML premailer = Premailer.new(html, :adapter => :nokogiri, :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", "

    Test text

    Test text

    " assert_plaintext "Test text\n\nTest text", "\n

    Test text

    \n\n\n\t

    Test text

    \n" assert_plaintext "Test text\nTest text", "\n

    Test text
    Test text

    \n" assert_plaintext "Test text\nTest text", "\n

    Test text
    \tTest text

    \n" assert_plaintext "Test text\n\nTest text", "Test text

    Test text" end def test_headings assert_plaintext "****\nTest\n****", "

    Test

    " assert_plaintext "****\nTest\n****", "\t

    \nTest

    " assert_plaintext "***********\nTest line 1\nTest 2\n***********", "\t

    \nTest line 1
    Test 2

    " assert_plaintext "****\nTest\n****\n\n****\nTest\n****", "

    Test

    Test

    " assert_plaintext "----\nTest\n----", "

    Test

    " assert_plaintext "Test\n----", "

    Test

    " 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_many_spaces assert_plaintext "Long line\nnext line", "Long line next line", nil ,14 end def test_img_alt_tags # ensure html img tags that aren't self-closed are parsed, # along with accepting both '' and "" as attribute quotes # assert_plaintext 'Example ( http://example.com/ )', 'Example' # assert_plaintext 'Example ( http://example.com/ )', 'Example' # assert_plaintext 'Example ( http://example.com/ )', "Example" # assert_plaintext 'Example ( http://example.com/ )', "Example" # ensure that img alt text is handled properly for multiple # img tags on the same line # before then after assert_plaintext "before then after", 'before then after' # just after assert_plaintext "just after", ' just after' end def test_links # basic assert_plaintext 'Link ( http://example.com/ )', 'Link' # nested html assert_plaintext 'Link ( http://example.com/ )', 'Link' # nested html with new line assert_plaintext 'Link ( http://example.com/ )', "\n\tLink\n\t" # mailto assert_plaintext 'Contact Us ( contact@example.org )', "Contact Us" # complex link assert_plaintext 'Link ( http://example.com:80/~user?aaa=bb&c=d,e,f#foo )', 'Link' # attributes assert_plaintext 'Link ( http://example.com/ )', 'Link' # spacing assert_plaintext 'Link ( http://example.com/ )', ' Link ' # multiple assert_plaintext 'Link A ( http://example.com/a/ ) Link B ( http://example.com/b/ )', 'Link A Link B' # merge links assert_plaintext 'Link ( %%LINK%% )', 'Link' assert_plaintext 'Link ( [LINK] )', 'Link' assert_plaintext 'Link ( {LINK} )', 'Link' # unsubscribe assert_plaintext 'Link ( [[!unsubscribe]] )', 'Link' # empty link gets dropped, and shouldn't run forever assert_plaintext(("This is some more text\n\n" * 14 + "This is some more text"), "#{"\n

    This is some more text

    " * 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 LINK TEXT and then more text that does not wrap' # same text and link assert_plaintext 'http://example.com', 'http://example.com' # link that includes a single quote assert_plaintext "King's Gambit ( https://en.wikipedia.org/wiki/King's_Gambit )", "King's Gambit" # link that includes double quotes assert_plaintext '"Weird Al" Yankovic ( https://en.wikipedia.org/wiki/%22Weird_Al%22_Yankovic )', '"Weird Al" Yankovic', nil, 100 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.', '

    This is link1 and link2 is next.

    ', nil, 10000 end # see https://github.com/alexdunae/premailer/issues/72 def test_links_within_headings assert_plaintext "****************************\nTest ( http://example.com/ )\n****************************", "

    Test

    " end def test_unterminated_anchor_tag assert_plaintext("Example -->", <<-HTML)
    HTML end def assert_plaintext(out, raw, msg = nil, line_length = 65) assert_equal out, convert_to_text(raw, line_length), msg end end premailer-1.14.2/test/test_links.rb000066400000000000000000000170171373213763000172550ustar00rootroot00000000000000# encoding: UTF-8 require File.expand_path(File.dirname(__FILE__)) + '/helper' class TestLinks < Premailer::TestCase def test_empty_query_string premailer = Premailer.new('

    Test

    ', :adapter => :nokogiri, :with_html_string => true, :link_query_string => ' ') premailer.to_inline_css end def test_appending_link_query_string qs = 'utm_source=1234&tracking=good&doublescape' opts = {:base_url => 'http://example.com/', :link_query_string => qs, :with_html_string => true, :adapter => :nokogiri} appendable = [ '/', opts[:base_url], 'https://example.com/tester', 'images/', "#{opts[:base_url]}test.html?cn=tf&c=20&ord=random", '?query=string' ] not_appendable = [ '%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| "Link" } 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 = Addressable::URI.parse(href) assert_match qs, uri.query, "missing query string for #{el.to_s}" end html = not_appendable.collect {|url| "Link" } 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("Link Link", :adapter => :nokogiri, :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("Link", :adapter => :nokogiri, :link_query_string => qs, :with_html_string => true) premailer.to_inline_css assert_equal '/test/?123&456&utm_source=1234', premailer.processed_doc.at('a')['href'] end def test_unescape_ampersand qs = 'utm_source=1234' premailer = Premailer.new("Link", :adapter => :nokogiri, :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 = "Link" premailer = Premailer.new(html.to_s, :adapter => :nokogiri, :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 = "Link" premailer = Premailer.new(html.to_s, :adapter => :nokogiri, :link_query_string => 'qs', :with_html_string => true) premailer.to_inline_css assert_equal 'http://example.com/index.php?pram1=one&pram2=two&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 = Addressable::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 = Addressable::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 = Addressable::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 = Addressable::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 = "\nhttp://example.com/\n" ['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 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| "Link" } premailer = Premailer.new(html.to_s, :adapter => :nokogiri, :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]', '', '{@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| "Link" } premailer = Premailer.new(html.to_s, :adapter => :nokogiri, :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 premailer-1.14.2/test/test_misc.rb000066400000000000000000000314011373213763000170610ustar00rootroot00000000000000# 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 def test_styles_in_the_body html = <

    Test

    END_HTML premailer = Premailer.new(html, :adapter => :nokogiri, :with_html_string => true) premailer.to_inline_css assert_match /color\:\s*red/i, premailer.processed_doc.at('p')['style'] end def test_commented_out_styles_in_the_body html = <

    Test

    END_HTML premailer = Premailer.new(html, :adapter => :nokogiri, :with_html_string => true) premailer.to_inline_css assert_match /color\:\s*red/i, premailer.processed_doc.at('p')['style'] end def test_not_applying_styles_to_the_head html = < Title

    Test

    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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

    Test

    Test

    END_HTML premailer = Premailer.new(html, :adapter => :nokogiri, :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 = <

    Test

    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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('head style').inner_html assert_match /a:hover/i, premailer.processed_doc.at('style').inner_html end end def test_unmergable_rules html = <

    Test

    END_HTML premailer = Premailer.new(html, :adapter => :nokogiri, :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('head style').inner_html # red should remain in

    Test

    END_HTML premailer = Premailer.new(html, :with_html_string => true, :verbose => true, :drop_unmergeable_css_rules => true) premailer.to_inline_css # no

    Test

    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| premailer = Premailer.new(html, :with_html_string => true, :adapter => adapter) premailer.to_inline_css style_tag = premailer.processed_doc.at('head style') assert style_tag, "#{adapter} failed to add a head 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 = <

    Test

    END_HTML premailer = Premailer.new(html, :adapter => :nokogiri, :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 premailer = Premailer.new(html, :adapter => :nokogiri, :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 premailer = Premailer.new(html, :adapter => :nokogiri, :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 def test_preserve_original_style_attribute html = < END_HTML premailer = Premailer.new( html, adapter: :nokogiri, with_html_string: true, preserve_style_attribute: true ) premailer.to_inline_css style5 = premailer.processed_doc.search('.style5').first style5_style = style5.attributes['style'].to_s assert_match /font-size: xx-large/, premailer.processed_doc.search('.style3').first.attributes['style'].to_s assert_match /background-color: #000080/, style5_style assert_match /text-align: center/, style5_style assert_match /#000080/, style5.attributes['bgcolor'].to_s end # in response to https://github.com/alexdunae/premailer/issues/56 def test_inline_important html = <

    test

    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 = <

    test

    END_HTML premailer = Premailer.new(html, :adapter => :nokogiri, :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_removing_scripts html = < content END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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, :nokogiri_fast, :nokogumbo].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 = <
    Test
    PROMOCION CURSOS PRESENCIALES
    PROMOCION CURSOS PRESENCIALES
    red
    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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 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 [:nokogiri, :nokogiri_fast, :nokogumbo].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 [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| premailer = Premailer.new(html, :with_html_string => true, :adapter => adapter) assert_match /content:\s*url\(data:image\/png;base64,LOTSOFSTUFF\)/, premailer.to_inline_css end end end premailer-1.14.2/test/test_premailer.rb000066400000000000000000000361211373213763000201120ustar00rootroot00000000000000# -*- encoding: UTF-8 -*- require File.expand_path(File.dirname(__FILE__)) + '/helper' class TestPremailer < Premailer::TestCase def test_special_characters_nokogiri html = '

    cédille cé & garçon garçon à à   & ©

    ' premailer = Premailer.new(html, :with_html_string => true, :adapter => :nokogiri) premailer.to_inline_css assert_equal 'cédille cé & garçon garçon à à   & ©', 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édille cé & garçon garçon à à   & ©', @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 def test_detecting_html [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| remote_setup('base.html', :adapter => adapter) refute @premailer.is_xhtml?, "Using: #{adapter}" end end def test_detecting_xhtml [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| remote_setup('xhtml.html', :adapter => adapter) assert @premailer.is_xhtml?, "Using: #{adapter}" end end def test_detecting_plain_text [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| remote_setup('plain.txt', :adapter => adapter) refute @premailer.is_xhtml?, "Using: #{adapter}" end end def test_self_closing_xhtml_tags [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| remote_setup('xhtml.html', :adapter => adapter) assert_match //, @premailer.to_s, "Using: #{adapter}" assert_match //, @premailer.to_inline_css, "Using: #{adapter}" end end def test_non_self_closing_html_tags [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| remote_setup('html4.html', :adapter => adapter) assert_match /
    /, @premailer.to_s, "Using: #{adapter}" assert_match /
    /, @premailer.to_inline_css, "Using: #{adapter}" end end def test_mailtos_with_query_strings html = < Test END_HTML qs = 'testing=123' [:nokogiri, :nokogiri_fast, :nokogumbo].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, "Using: #{adapter}" end end def test_preserving_ignored_style_elements [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| local_setup('ignore.html', :adapter => adapter) assert_nil @doc.at('h1')['style'], "Using: #{adapter}" end end def test_preserving_ignored_link_elements [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| local_setup('ignore.html', :adapter => adapter) assert_nil @doc.at('body')['style'], "Using: #{adapter}" end end def test_importing_local_css [:nokogiri, :nokogiri_fast, :nokogumbo].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, "Using: #{adapter}" # import.css sets .hide to { display: none } assert_match /display: none/, @doc.at('#hide01').attributes['style'].to_s, "Using: #{adapter}" end end def test_css_to_attributes [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| html = '
    ' 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, "Using: #{adapter}" assert_equal '#FFF', premailer.processed_doc.search('td').first.attributes['bgcolor'].to_s, "Using: #{adapter}" end end def test_avoid_changing_css_to_attributes [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| html = '
    ' premailer = Premailer.new(html, {:with_html_string => true, :adapter => adapter, :css_to_attributes => false}) premailer.to_inline_css assert_match /background-color: #FFF/, premailer.processed_doc.at_css('td').attributes['style'].to_s, "Using: #{adapter}" end end def test_importing_remote_css [:nokogiri, :nokogiri_fast, :nokogumbo].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'], "Using: #{adapter}" # import.css sets .hide to { display: none } assert_match /display: none/, @doc.at('#hide01')['style'], "Using: #{adapter}" 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, :nokogiri_fast, :nokogumbo].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'], "Using: #{adapter}" 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'), :adapter => :nokogiri ) silence_stderr do assert premailer.local_uri?( '/path/' ) end end def test_initialize_can_accept_io_object [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| io = StringIO.new('hi mom') premailer = Premailer.new(io, :adapter => adapter) assert_match /hi mom/, premailer.to_inline_css, "Using: #{adapter}" end end def test_initialize_can_accept_html_string [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| premailer = Premailer.new('

    test

    ', :with_html_string => true, :adapter => adapter) assert_match /test/, premailer.to_inline_css, "Using: #{adapter}" end end def test_initialize_no_escape_attributes_option html = < Google Link END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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', "Using: #{adapter}" assert_equal doc.at('#noescape')['href'], '{{link_url}}', "Using: #{adapter}" end end def test_remove_ids html = <

    Test

    Test

    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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'), "Using: #{adapter}" assert_nil doc.at('#keep'), "Using: #{adapter}" hashed_id = doc.at('a')['href'][1..-1] refute_nil doc.at("\##{hashed_id}"), "Using: #{adapter}" end end def test_reset_contenteditable html = <<-___
    Test
    ___ [:nokogiri, :nokogiri_fast, :nokogumbo].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_css('#editable')['contenteditable'], "Using: #{adapter}" end end def test_carriage_returns_as_entities html = <<-html \n\r

    test

    \n\r

    test

    html [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| pm = Premailer.new(html, :with_html_string => true, :adapter => adapter) assert_match /\n/, pm.to_inline_css, "Using: #{adapter}" 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'] end def test_premailer_related_attributes html = <
    Test
    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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'], "Using: #{adapter}" assert_equal '20', doc.at('td')['height'], "Using: #{adapter}" end end def test_empty_css_att html = <<-END_HTML
    Test
    END_HTML pm = Premailer.new(html, :with_html_string => true, :rgb_to_hex_attributes => true, :remove_scripts => true, :adapter => :nokogiri) pm.to_inline_css doc = pm.processed_doc assert_match /]+550px.+bgcolor="FAFAFA"/, doc.at('table').to_s end def test_rgb_color html = <<-END_HTML
    Test
    END_HTML pm = Premailer.new(html, :with_html_string => true, :rgb_to_hex_attributes => true, :remove_scripts => true, :adapter => :nokogiri) pm.to_inline_css doc = pm.processed_doc assert_equal 'FAFAFA', doc.at('table')['bgcolor'] end def test_non_rgb_color html = <<-END_HTML
    Test
    END_HTML pm = Premailer.new(html, :with_html_string => true, :rgb_to_hex_attributes => true, :adapter => :nokogiri) pm.to_inline_css doc = pm.processed_doc assert_equal 'red', doc.at('table')['bgcolor'] 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 = "Ää, Öö, Üü" pm = Premailer.new(html_special_characters, :with_html_string => true, :adapter => :nokogiri, :input_encoding => "UTF-8") assert_match /#{html_special_characters}/, 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 = /©/ expected_html = /#{html_entities_characters}/ pm = Premailer.new(html_special_characters, :output_encoding => "US-ASCII", :with_html_string => true, :adapter => :nokogiri, :input_encoding => "UTF-8"); assert_match expected_html, pm.to_inline_css end def test_meta_encoding_downcase meta_encoding = '' expected_html = Regexp.new(Regexp.escape(''), 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 = '' expected_html = Regexp.new(Regexp.escape(''), 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 = "’" pm = Premailer.new(html_entities, :with_html_string => true, :adapter => :nokogiri, :replace_html_entities => true) assert_match /'/, 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, :adapter => :nokogiri, :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, input_encoding: 'UTF-8') 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 premailer-1.14.2/test/test_warnings.rb000066400000000000000000000052101373213763000177550ustar00rootroot00000000000000# encoding: UTF-8 require File.expand_path(File.dirname(__FILE__)) + '/helper' class TestWarnings < Premailer::TestCase def test_element_warnings html = <
    Test
    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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 = <
    Test
    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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 = <
    Test
    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].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 [:nokogiri, :nokogiri_fast, :nokogumbo].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 = <
    Test
    END_HTML [:nokogiri, :nokogiri_fast, :nokogumbo].each do |adapter| warnings = get_warnings(html, adapter, Premailer::Warnings::SAFE) assert_equal 2, warnings.length end [:nokogiri, :nokogiri_fast, :nokogumbo].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, {:adapter => adapter, :with_html_string => true, :warn_level => warn_level}) pm.to_inline_css pm.check_client_support end end