├── lib ├── html_press │ ├── version.rb │ ├── html_entities.rb │ └── html.rb └── html_press.rb ├── .gitignore ├── .travis.yml ├── Gemfile ├── Rakefile ├── profile └── profile.rb ├── html_press.gemspec ├── LICENSE.txt ├── Readme.md └── spec └── html_press_spec.rb /lib/html_press/version.rb: -------------------------------------------------------------------------------- 1 | module HtmlPress 2 | VERSION = "0.8.2" 3 | end 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | pkg/* 5 | profile/reports/* 6 | /.project 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.2 5 | - 1.9.3 6 | - jruby-18mode 7 | - rbx-18mode 8 | # - jruby-19mode 9 | # - rbx-19mode -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Specify your gem's dependencies in html_press.gemspec 4 | gemspec 5 | 6 | gem "ruby-prof", :platforms => :mri 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | 3 | [:build, :install, :release].each do |task_name| 4 | Rake::Task[task_name].prerequisites << :spec 5 | end 6 | 7 | require "rspec/core/rake_task" 8 | RSpec::Core::RakeTask.new 9 | 10 | task :default => :spec 11 | -------------------------------------------------------------------------------- /lib/html_press.rb: -------------------------------------------------------------------------------- 1 | require "html_press/version" 2 | require "html_press/html_entities" 3 | require "html_press/html" 4 | 5 | require 'multi_css' 6 | require 'multi_js' 7 | 8 | module HtmlPress 9 | def self.press(text, options = {}) 10 | HtmlPress::Html.new(options).press text 11 | end 12 | 13 | # for backward compatibility 14 | def self.compress(text, options = {}) 15 | HtmlPress::Html.new(options).press text 16 | end 17 | 18 | def self.js_compressor (text, options = nil) 19 | options ||= {} 20 | options[:output] ||= {} 21 | options[:output][:inline_script] = true 22 | MultiJs.compile(text, options).gsub(/;$/,'') 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/html_press/html_entities.rb: -------------------------------------------------------------------------------- 1 | require 'htmlentities' 2 | 3 | module HtmlPress 4 | class Entities 5 | def initialize 6 | @replacement_hash = 'MINIFYENTITY' + Time.now.to_i.to_s 7 | @placeholders = [] 8 | end 9 | 10 | def reserve(content) 11 | @placeholders.push content 12 | '%' + @replacement_hash + '%' + (@placeholders.size - 1).to_s + '%' 13 | end 14 | 15 | def minify text 16 | out = text.dup 17 | 18 | out.gsub! /<|<|>|>|&|&/ do |m| 19 | reserve m 20 | end 21 | 22 | out = HTMLEntities.new.decode(out) 23 | 24 | re = Regexp.new('%' + @replacement_hash + '%(\d+)%') 25 | out.gsub! re do |m| 26 | m.gsub!(re, "\\1") 27 | @placeholders[m.to_i] 28 | end 29 | 30 | out 31 | end 32 | end 33 | 34 | def self.entities_compressor (text) 35 | Entities.new.minify(text) 36 | end 37 | end -------------------------------------------------------------------------------- /profile/profile.rb: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | Bundler.setup 3 | 4 | require 'ruby-prof' 5 | require 'html_press' 6 | 7 | file_path = File.expand_path("../index.html", __FILE__) 8 | html = File.open(file_path, "r:UTF-8").read 9 | 10 | # require 'open-uri' 11 | # html = open('http://www.amazon.com/') {|f| f.read } 12 | 13 | before = html.bytesize 14 | html.force_encoding "UTF-8" if html.respond_to?(:force_encoding) 15 | 16 | RubyProf.start 17 | html = HtmlPress.press html 18 | result = RubyProf.stop 19 | 20 | after = html.bytesize 21 | puts "Economy: " + ((before - after).to_f/1024).round(2).to_s + "kb (" + 22 | (100*(before - after).to_f/before).round(2).to_s + "%)" 23 | 24 | report_path = File.expand_path("../reports", __FILE__) 25 | FileUtils.rm_rf(report_path) 26 | Dir.mkdir(report_path) unless File.exist?(report_path) 27 | printer = RubyProf::MultiPrinter.new(result) 28 | printer.print(:path => report_path, :profile => "profile") 29 | -------------------------------------------------------------------------------- /html_press.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require "html_press/version" 4 | 5 | Gem::Specification.new do |s| 6 | s.name = "html_press" 7 | s.version = HtmlPress::VERSION 8 | s.authors = ["stereobooster"] 9 | s.email = ["stereobooster@gmail.com"] 10 | s.homepage = "https://github.com/stereobooster/html_press" 11 | s.summary = %q{Compress html} 12 | s.description = %q{Ruby gem for compressing html} 13 | s.license = "MIT" 14 | 15 | s.files = `git ls-files`.split("\n") 16 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 17 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 18 | s.require_paths = ["lib"] 19 | 20 | s.add_development_dependency "rspec" 21 | s.add_development_dependency "rake" 22 | 23 | s.add_dependency "multi_css", ">= 0.1.0" 24 | s.add_dependency "multi_js", ">= 0.1.0" 25 | s.add_dependency "htmlentities" 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 sterebooster 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # HtmlPress ![Gem Version](https://fury-badge.herokuapp.com/rb/html_press.png) [![Build Status](https://travis-ci.org/stereobooster/html_press.png?branch=master)](https://travis-ci.org/stereobooster/html_press) [![Dependency Status](https://gemnasium.com/stereobooster/html_press.png?travis)](https://gemnasium.com/stereobooster/html_press) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/stereobooster/html_press) 2 | 3 | ## How it works 4 | 5 | Remove all whitespace junk. Leave only HTML 6 | 7 | ``` 8 | 1. ┌――――――――――――――――――╖ 2. ┌――――――――――――――――――╖ 9 | ●――――――――――――――├―╢ ws junk ║ ●――――――――├―――――――――╢ ║ 10 | └――――――――――――――――――╜ └――――――――――――――――――╜ 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Ruby 16 | ```ruby 17 | require 'html_press' 18 | compressed_html = HtmlPress.press html 19 | ``` 20 | 21 | ### Jekyll 22 | see [jekyll_press](https://github.com/stereobooster/jekyll_press) 23 | 24 | ### Rails 25 | TODO :exclamation: 26 | 27 | ### Sinatra 28 | TODO :exclamation: 29 | 30 | ### Command line 31 | TODO :exclamation: 32 | 33 | ## TODO 34 | - use parser ([鋸](https://github.com/tenderlove/nokogiri)) instead of regexp's 35 | - add option to convert relative urls to absolute urls (for SEO) 36 | - [ambigious ampersands](http://mathiasbynens.be/notes/ambiguous-ampersands) for compression? 37 | - Support other js/css minifiers (Closure, YUI compressor) 38 | - htmlTydi 39 | - Rack plugin 40 | - add script to benchmark real projects like amazon or stackoverflow 41 | - support html5 tags 42 | - add more options 43 | - Optimization: make substring replace based on substring length and its position in initial string 44 | 45 | ## Alternatives 46 | - [html-minifier](https://github.com/kangax/html-minifier) (js), [test suite](https://github.com/kangax/html-minifier/blob/gh-pages/tests/index.html), ruby wrapper - [html_minifier](https://github.com/stereobooster/html_minifier) 47 | - [htmlcompressor](http://code.google.com/p/htmlcompressor/) (java), [test suite](http://code.google.com/p/htmlcompressor/source/browse/#svn%2Ftrunk%2Fsrc%2Ftest%2Fresources%2Fhtml%253Fstate%253Dclosed) 48 | - PHPTal compress (php), [test suite](https://svn.motion-twin.com/phptal/trunk/tests/CompressTest.php) 49 | - [W3 total cache](http://wordpress.org/extend/plugins/w3-total-cache/) - WP plugin from smashingmagazine contains html minifier (php) 50 | 51 | ## Additional tools 52 | - [jeanny](https://github.com/gfranco/jeanny) - rename css classes and ids in css and html files 53 | - make shorter pathes for images in css 54 | - [deadweight](https://github.com/aanand/deadweight) - remove unused css rules from css files 55 | - [csscss](http://zmoazeni.github.com/csscss/) will parse any CSS files you give it and let you know which rulesets have duplicated declarations. 56 | - [css-spriter](https://github.com/aberant/css-spriter), [sprite-factory](https://github.com/jakesgordon/sprite-factory) - combine images in sprites 57 | - resize images by size defined in html and vice versa embed size of images in html 58 | - [#1](http://habrahabr.ru/post/90761/), [#2](http://ap-project.org/English/Article/View/53/) - inline small images in css 59 | - [smusher](https://github.com/grosser/smusher), jpegtran, optipng - losslessly minify images 60 | - [sprockets](https://github.com/sstephenson/sprockets), [jammit](https://github.com/documentcloud/jammit) - asset bundlers 61 | - [w3c_validators](https://github.com/alexdunae/w3c_validators) 62 | - [reduce](https://github.com/grosser/reduce) 63 | 64 | ## Resources 65 | 66 | ### Minimize HTML 67 | - http://perfectionkills.com/experimenting-with-html-minifier 68 | - http://perfectionkills.com/optimizing-html 69 | - https://developers.google.com/speed/articles/optimizing-html 70 | 71 | ### Front-end optimization 72 | - https://developers.google.com/speed/docs/insights/rules 73 | - http://developer.yahoo.com/performance/rules.html 74 | -------------------------------------------------------------------------------- /lib/html_press/html.rb: -------------------------------------------------------------------------------- 1 | module HtmlPress 2 | class Html 3 | 4 | DEFAULTS = { 5 | :logger => false, 6 | :unquoted_attributes => false, 7 | :drop_empty_values => false, 8 | :strip_crlf => false, 9 | :js_minifier_options => false 10 | } 11 | 12 | def initialize (options = {}) 13 | @options = DEFAULTS.merge(options) 14 | if @options.keys.include? :dump_empty_values 15 | @options[:drop_empty_values] = @options.delete(:dump_empty_values) 16 | warn "dump_empty_values deprecated use drop_empty_values" 17 | end 18 | if @options[:logger] && !@options[:logger].respond_to?(:error) 19 | raise ArgumentError, 'Logger has no error method' 20 | end 21 | end 22 | 23 | def press (html) 24 | out = html.respond_to?(:read) ? html.read : html.dup 25 | 26 | @replacement_hash = 'MINIFYHTML' + Time.now.to_i.to_s 27 | @placeholders = [] 28 | 29 | out = process_ie_conditional_comments out 30 | out = process_scripts out 31 | out = process_styles out 32 | out = process_html_comments out 33 | out = process_pres out 34 | 35 | out = HtmlPress.entities_compressor out 36 | 37 | out = trim_lines out 38 | out = process_block_elements out 39 | out = process_textareas out 40 | 41 | # use newlines before 1st attribute in open tags (to limit line lengths) 42 | # out.gsub!(/(<[a-z\-:]+)\s+([^>]+>)/i, "\\1\n\\2") 43 | 44 | out = process_attributes out 45 | out = process_whitespaces out 46 | out = fill_placeholders out 47 | 48 | out 49 | end 50 | 51 | # for backward compatibility 52 | alias :compile :press 53 | 54 | protected 55 | 56 | # IE conditional comments 57 | def process_ie_conditional_comments (out) 58 | out.gsub /()\s*/ do 59 | m = $1 60 | comment = $2 61 | comment_compressed = Html.new.press(comment) 62 | m.gsub!(comment, comment_compressed) 63 | reserve m 64 | end 65 | end 66 | 67 | # replace SCRIPTs (and minify) with placeholders 68 | def process_scripts (out) 69 | out.gsub /(]*?>([\s\S]*?)<\/script>)\s*/i do 70 | js = $2 71 | m = $1.gsub /^]+)>/i do |m| 72 | attrs(m, 'script', true) 73 | end 74 | begin 75 | js_compressed = HtmlPress.js_compressor js, @options[:js_minifier_options] 76 | m.gsub!(">#{js}<\/script>", ">#{js_compressed}<\/script>") 77 | rescue MultiJs::ParseError => e 78 | log e.message 79 | end 80 | reserve m 81 | end 82 | end 83 | 84 | # replace STYLEs (and minify) with placeholders 85 | def process_styles (out) 86 | out.gsub /(]*?>([\s\S]*?)<\/style>)\s*/i do 87 | css = $2 88 | m = $1.gsub /^]+)>/i do |m| 89 | attrs(m, 'style', true) 90 | end 91 | begin 92 | css_compressed = MultiCss.min css 93 | m.gsub!(css, css_compressed) 94 | rescue Exception => e 95 | log e.message 96 | end 97 | reserve m 98 | end 99 | end 100 | 101 | # remove html comments (not IE conditional comments) 102 | def process_html_comments (out) 103 | out.gsub //, '' 104 | end 105 | 106 | # replace PREs with placeholders 107 | def process_pres (out) 108 | out.gsub /(]*?>([\s\S]*?)<\/pre>)\s*/i do 109 | pre = $2 110 | m = $1 111 | pre_compressed = pre.lines.map{ |l| l.gsub(/\s+$/, '') }.join("\n") 112 | pre_compressed = HtmlPress.entities_compressor pre_compressed 113 | m.gsub!(pre, pre_compressed) 114 | reserve m 115 | end 116 | end 117 | 118 | # trim each line 119 | def trim_lines (out) 120 | out.gsub(/^\s+|\s+$/m, '') 121 | end 122 | 123 | # remove whitespaces outside of block elements 124 | def process_block_elements (out) 125 | re = '\\s+(<\\/?(?:area|base(?:font)?|blockquote|body' + 126 | '|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form' + 127 | '|frame(?:set)?|h[1-6]|head|hr|html|legend|li|link|map|menu|meta' + 128 | '|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h|r|foot|itle)' + 129 | '|ul)\\b[^>]*>)' 130 | 131 | re = Regexp.new(re) 132 | out.gsub!(re, '\\1') 133 | 134 | # remove whitespaces outside of all elements 135 | out.gsub! />([^<]+)]*?>[\s\S]*?<\/textarea>)\s*/i do |m| 145 | reserve m 146 | end 147 | end 148 | 149 | # attributes 150 | def process_attributes (out) 151 | out.gsub /<[a-z\-:]+\s([^>]+)>/i do |m| 152 | reserve attrs(m, '[a-z\-:]+', true) 153 | end 154 | end 155 | 156 | # replace two or more whitespaces with one 157 | def process_whitespaces (out) 158 | out.gsub!(/[\r\n]+/, @options[:strip_crlf] ? ' ' : "\n") 159 | out.gsub!(/\s+/, ' ') 160 | out 161 | end 162 | 163 | # fill placeholders 164 | def fill_placeholders (out) 165 | re = Regexp.new('%' + @replacement_hash + '%(\d+)%') 166 | out.gsub re do |m| 167 | m.gsub!(re, "\\1") 168 | @placeholders[m.to_i] 169 | end 170 | end 171 | 172 | def log (text) 173 | @options[:logger].error text if @options[:logger] 174 | end 175 | 176 | def reserve (content) 177 | @placeholders.push content 178 | '%' + @replacement_hash + '%' + (@placeholders.size - 1).to_s + '%' 179 | end 180 | 181 | def attrs (m, tag_name, r) 182 | re = "<(" + tag_name + ")(\s[^>]+)?>" 183 | re = Regexp.new(re, true) 184 | attributes = m.gsub(re, "\\2") 185 | if r 186 | tag = m.gsub(re, "\\1") 187 | else 188 | tag = tag_name 189 | end 190 | 191 | if attributes.size > 0 192 | attributes_compressed = attributes.gsub(/([a-z\-_:]+(="[^"]*")?(='[^']*')?)\s*/i, " \\1") 193 | 194 | attributes_compressed.gsub! /([a-z\-_:]+="[^"]*")/i do |k| 195 | attr k, "\"", tag 196 | end 197 | 198 | attributes_compressed.gsub! /([a-z\-_:]+='[^']*')/i do |k| 199 | attr k, "'", tag 200 | end 201 | 202 | attributes_compressed = " " + attributes_compressed.strip 203 | 204 | if attributes_compressed == " /" 205 | attributes_compressed = "/" 206 | elsif attributes_compressed == " " 207 | attributes_compressed = "" 208 | end 209 | m.gsub(attributes, attributes_compressed) 210 | else 211 | m 212 | end 213 | end 214 | 215 | def attr(attribute, delimiter, tag) 216 | re = "([a-z\\-_:]+)(=" + delimiter + "[^" + delimiter + "]*" + delimiter + ")?" 217 | re = Regexp.new re, true 218 | value_original = attribute.gsub(re, "\\2") 219 | value = value_original.downcase 220 | name_original = attribute.gsub(re, "\\1") 221 | name = name_original.downcase 222 | tag_name = tag.downcase 223 | 224 | if value.size > 0 225 | re = "^=" + delimiter + "|" + delimiter + "$" 226 | re = Regexp.new re 227 | value_original.gsub!(re, "") 228 | end 229 | 230 | case tag_name 231 | when "script" 232 | if (name == "type" && value_original == "text/javascript") || (name == "language" && value_original == "JavaScript") 233 | return "" 234 | elsif name == "async" || name == "defer" 235 | return name_original 236 | end 237 | when "form" 238 | if name == "method" && value_original == "get" 239 | return "" 240 | end 241 | when /link|style/ 242 | if name == "type" && value_original == "text/stylesheet" 243 | return "" 244 | end 245 | when /input|textarea|button|select|option|optgroup/ 246 | if name == "disabled" 247 | return name_original 248 | end 249 | if (tag_name == "input" || tag_name == "textarea") && name == "readonly" 250 | return name_original 251 | end 252 | if tag_name == "option" && name == "selected" 253 | return name_original 254 | end 255 | if tag_name == "input" 256 | if name == "type" && value_original == "text" 257 | return "" 258 | end 259 | if name == "checked" 260 | return name_original 261 | end 262 | # if name == "value" && (value == "=\"\"" || value == "=''") 263 | # return '' 264 | # end 265 | end 266 | end 267 | 268 | if value.size > 0 269 | 270 | if name == "style" 271 | begin 272 | value_original = MultiCss.min_attr value_original 273 | # TODO what about escaped attribute values? 274 | if delimiter == "\"" 275 | value_original.gsub!("\"", "'") 276 | else 277 | value_original.gsub!("'", "\"") 278 | end 279 | rescue MultiCss::ParseError => e 280 | log e.message 281 | end 282 | end 283 | 284 | if name == "class" 285 | value_original.gsub!(/\s+/, " ") 286 | value_original.gsub!(/^\s+|\s+$/, "") 287 | end 288 | 289 | events = %w[onfocus onblur onselect onchange onclick 290 | ondblclick onmousedown onmouseup onmouseover onmousemove 291 | onmouseout onkeypress onkeydown onkeyup] 292 | 293 | if events.include? name 294 | value_original.gsub! /^javascript:\s+|;$/, '' 295 | begin 296 | value_original = HtmlPress.js_compressor value_original, @options[:js_minifier_options] 297 | # TODO what about escaped attribute values? 298 | if delimiter == "\"" 299 | value_original.gsub! "\"", "'" 300 | end 301 | rescue MultiJs::ParseError => e 302 | log e.message 303 | end 304 | end 305 | 306 | if value_original.size == 0 307 | #attribute without value may be dropped by IE7 308 | if @options[:drop_empty_values] 309 | attribute = name_original 310 | else 311 | attribute = name_original + "=" + delimiter + delimiter 312 | end 313 | elsif @options[:unquoted_attributes] && !(value_original =~ /[ \t\r\n\f"'`=<>]/) 314 | attribute = name_original + "=" + value_original 315 | else 316 | attribute = name_original + "=" + delimiter + value_original + delimiter 317 | end 318 | 319 | end 320 | 321 | attribute 322 | end 323 | 324 | end 325 | end 326 | -------------------------------------------------------------------------------- /spec/html_press_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | 3 | require File.expand_path("../lib/html_press", File.dirname(__FILE__)) 4 | 5 | class LoggerStub 6 | attr_accessor :errors 7 | def initialize 8 | @errors = [] 9 | end 10 | def error text 11 | @errors.push text 12 | end 13 | end 14 | 15 | describe HtmlPress do 16 | before :each do 17 | end 18 | 19 | it "should leave only one whitespace between inline tags" do 20 | HtmlPress.press("

lorem ipsum dolor

").should eql "

lorem ipsum dolor

" 21 | end 22 | 23 | it "should leave no whitespaces between block tags" do 24 | HtmlPress.press("
\t\r\n
").should eql "
" 25 | HtmlPress.press("
\t\r\n
").should eql "
" 26 | end 27 | 28 | it "should leave only one whitespace in text" do 29 | HtmlPress.press("

a a

").should eql "

a a

" 30 | end 31 | 32 | it "should leave newlines in pre tags and remove trailing spaces" do 33 | HtmlPress.press("
a \t 
").should eql "
a
" 34 | HtmlPress.press("
qwe   \r\nasd   
").should eql "
qwe\nasd
" 35 | HtmlPress.press("
   qwe   \n\r\n   asd   
").should eql "
   qwe\n\n   asd
" 36 | end 37 | 38 | it "should leave textareas as is" do 39 | text = "" 40 | HtmlPress.press(text).should eql text 41 | end 42 | 43 | it "should compress js in script tags" do 44 | script = " (function(undefined){ \t\n var long_name = ' '; }()) \n \r" 45 | pressed_script = "" 46 | script = " " 47 | HtmlPress.press(script).should eql pressed_script 48 | 49 | script = %q{} 50 | HtmlPress.press(script).should eql script 51 | end 52 | 53 | it "should compress css in style tags" do 54 | style = " div { margin: 0px 0px; \n} " 55 | pressed_style = "" 56 | style = " " 57 | HtmlPress.press(style).should eql pressed_style 58 | end 59 | 60 | it "should remove html comments" do 61 | HtmlPress.press("

").should eql "

" 62 | end 63 | 64 | it "should leave IE conditional comments" do 65 | text = "
" 66 | HtmlPress.press(text).should eql text 67 | end 68 | 69 | it "should work with special utf-8 symbols" do 70 | HtmlPress.press("✪

").should eql "✪

" 71 | end 72 | 73 | it "should work with tags in upper case" do 74 | HtmlPress.press("

").should eql "

" 75 | end 76 | 77 | it "should remove whitespaces between IE conditional comments" do 78 | text = "

" 79 | text2 = "

" 80 | # TODO ↑ remove this whitespace 81 | HtmlPress.press(text).should eql text2 82 | end 83 | 84 | it "should remove whitespaces between script tags" do 85 | text = "

\t " 86 | text2 = "

" 87 | HtmlPress.press(text).should eql text2 88 | end 89 | 90 | it "should concatenate adjacent script tags" do 91 | pending "Not implemented yet" do 92 | text = "

\t " 93 | text2 = "

" 94 | HtmlPress.press(text).should eql text2 95 | end 96 | end 97 | 98 | it "should treat text inside IE conditional comments as it was without comments" do 99 | text = "

" 100 | text2 = HtmlPress.press(text) 101 | text = "" 102 | text2 = "" 103 | HtmlPress.press(text).should eql text2 104 | text = "" 105 | text2 = HtmlPress.press(text) 106 | text = "" 107 | text2 = "" 108 | HtmlPress.press(text).should eql text2 109 | end 110 | 111 | it "should remove unnecessary whitespaces inside tag" do 112 | HtmlPress.press("

").should eql "

" 113 | HtmlPress.press("

").should eql "

" 114 | HtmlPress.press("").should eql "" 115 | HtmlPress.press("
").should eql "
" 116 | end 117 | 118 | it "should work with 'badly' formatted attributes" do 119 | HtmlPress.press("

").should eql "

" 120 | # HtmlPress.press("

").should eql "

" 121 | # HtmlPress.press("

").should eql "

" 122 | end 123 | 124 | it "should work with different case attributes" do 125 | text = '' 126 | HtmlPress.press(text).should eql text 127 | end 128 | 129 | it "should optimize attributes" do 130 | HtmlPress.press("

").should eql "

" 131 | # TODO http(s):// to // 132 | end 133 | 134 | it "should compress css in style attributes" do 135 | HtmlPress.press("

").should eql "

" 136 | HtmlPress.press("

").should eql "

" 137 | #FIX those tests can be broken if algorithm of css_press will be changed 138 | HtmlPress.press("

").should eql "

" 139 | HtmlPress.press("

").should eql "

" 140 | end 141 | 142 | it "should work with namespaces" do 143 | text = "like" 144 | HtmlPress.press(text).should eql text 145 | end 146 | 147 | it "should compress namespaces" do 148 | pending "Not implemented yet" do 149 | text = "like" 150 | text1 = "like" 151 | HtmlPress.press(text).should eql text1 152 | end 153 | end 154 | 155 | it "should not modify input value" do 156 | text = "
" 157 | text1 = text.dup 158 | HtmlPress.press(text).should_not eql text 159 | text.should eql text1 160 | end 161 | 162 | it "should leave whitespaces inside other attributes" do 163 | text = "a" 164 | HtmlPress.press(text).should eql text 165 | end 166 | 167 | it "should report javascript errors" do 168 | ["", ""].each do |script_with_error| 169 | log = LoggerStub.new 170 | HtmlPress.press(script_with_error, {:logger => log}).should eql script_with_error 171 | log.errors.size.should eql 1 172 | end 173 | end 174 | 175 | it "should report css errors" do 176 | ["", "link"].each do |style_with_error| 177 | log = LoggerStub.new 178 | HtmlPress.press(style_with_error, {:logger => log}).should eql style_with_error 179 | log.errors.size.should eql 1 180 | end 181 | end 182 | 183 | it "should remove values of boolean attributes" do 184 | HtmlPress.press("").should eql "" 185 | HtmlPress.press("").should eql "" 186 | HtmlPress.press("").should eql "" 187 | # disabled (input, textarea, button, select, option, optgroup) 188 | HtmlPress.press("").should eql "" 189 | # readonly (input type=text/password, textarea) 190 | HtmlPress.press("").should eql "" 191 | pending "Not implemented yet" do 192 | HtmlPress.press("").should eql "" 193 | HtmlPress.press("").should eql "" 194 | HtmlPress.press("" 195 | # ismap isMap (img, input type=image) 196 | # declare (object; never used) 197 | # noresize noResize (frame) 198 | # nowrap noWrap (td, th; deprecated) 199 | # noshade noShade (hr; deprecated) 200 | # compact (ul, ol, dl, menu, dir; deprecated) 201 | end 202 | end 203 | 204 | it "should remove attributes with default values" do 205 | HtmlPress.press("").should eql "" 206 | HtmlPress.press(""). 207 | should eql "" 208 | HtmlPress.press("").should eql "" 209 | HtmlPress.press("").should eql "" 210 | HtmlPress.press("").should eql "" 211 | HtmlPress.press("
").should eql "
" 212 | HtmlPress.press("").should eql "" 213 | # input value "" ? 214 | end 215 | 216 | it "should convert html entities to utf-8 symbols" do 217 | HtmlPress.press("< < > > & &").should eql "< < > > & &" 218 | HtmlPress.press("élan").should eql "élan" 219 | %W{textarea pre}.each do |t| 220 | HtmlPress.press("<#{t}>'").should eql "<#{t}>'" 221 | end 222 | end 223 | 224 | it "should remove unnecessary quotes for attribute values" do 225 | HtmlPress.press("", {:unquoted_attributes => true}).should eql "" 226 | HtmlPress.press("

", {:unquoted_attributes => true}).should eql "

" 227 | text = "

" 228 | HtmlPress.press(text, {:unquoted_attributes => true}).should eql text 229 | text = "

" 230 | HtmlPress.press(text, {:unquoted_attributes => true}).should eql text 231 | text = "

" 232 | HtmlPress.press(text, {:unquoted_attributes => true}).should eql text 233 | text = "

" 234 | HtmlPress.press(text, {:unquoted_attributes => true}).should eql text 235 | text = "

" 236 | HtmlPress.press(text, {:unquoted_attributes => true}).should eql text 237 | text = "

" 238 | HtmlPress.press(text, {:unquoted_attributes => true}).should eql text 239 | end 240 | 241 | it "should remove empty attribute values" do 242 | HtmlPress.press("", {:drop_empty_values => true}).should eql "" 243 | end 244 | 245 | it "should compress javascript in event attributes" do 246 | %w[onfocus onblur onselect onchange onclick 247 | ondblclick onmousedown onmouseup onmouseover onmousemove 248 | onmouseout onkeypress onkeydown onkeyup 249 | ].each do |evt| 250 | HtmlPress.press("").should eql "" 251 | HtmlPress.press("").should eql "" 252 | end 253 | end 254 | 255 | it "should concatenate adjecent style tags" do 256 | pending "Not implemented yet" 257 | # all stylle tags can be collected, concatneated and placed in header 258 | end 259 | end --------------------------------------------------------------------------------