html_press 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/Readme.md +66 -0
- data/html_press.gemspec +28 -0
- data/lib/html_press/html.rb +181 -0
- data/lib/html_press/rainpress.rb +12 -0
- data/lib/html_press/uglifier.rb +12 -0
- data/lib/html_press/version.rb +3 -0
- data/lib/html_press.rb +10 -0
- data/spec/html_press_spec.rb +98 -0
- metadata +100 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/Readme.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#html_press
|
2
|
+
|
3
|
+
## Alternatives
|
4
|
+
|
5
|
+
###Ruby
|
6
|
+
|
7
|
+
- https://github.com/completelynovel/html_compressor
|
8
|
+
- https://github.com/MadRabbit/frontcompiler
|
9
|
+
|
10
|
+
###Other
|
11
|
+
|
12
|
+
- http://code.google.com/p/htmlcompressor/
|
13
|
+
- smarty `strip` tag
|
14
|
+
- W3 total cache (WP plugin from smashingmagazine contains html minifier)
|
15
|
+
|
16
|
+
## TODO
|
17
|
+
|
18
|
+
- bin
|
19
|
+
- Support other minifiers (Closure, YUI compressor)
|
20
|
+
- htmlTydi
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Jekyll
|
25
|
+
|
26
|
+
Gemfile
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
gem "jekyll"
|
30
|
+
gem "html_press"
|
31
|
+
```
|
32
|
+
|
33
|
+
_plugins/strip_tag.rb
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
module Jekyll
|
37
|
+
class StripTag < Liquid::Block
|
38
|
+
begin
|
39
|
+
require 'html_press'
|
40
|
+
def render(context)
|
41
|
+
text = super
|
42
|
+
HtmlPress.compress text
|
43
|
+
end
|
44
|
+
rescue LoadError => e
|
45
|
+
p "Unable to load 'html_press'"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Liquid::Template.register_tag('strip', Jekyll::StripTag)
|
51
|
+
```
|
52
|
+
|
53
|
+
In templates
|
54
|
+
|
55
|
+
```liquid
|
56
|
+
{% strip %}
|
57
|
+
here goes text...
|
58
|
+
{% endstrip %}
|
59
|
+
```
|
60
|
+
|
61
|
+
Run
|
62
|
+
|
63
|
+
```
|
64
|
+
bundle install
|
65
|
+
bundle exec jelyll
|
66
|
+
```
|
data/html_press.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
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
|
+
|
14
|
+
s.rubyforge_project = "html_press"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# s.add_dependency "nokogiri"
|
22
|
+
|
23
|
+
s.add_development_dependency "rspec"
|
24
|
+
s.add_development_dependency "rake"
|
25
|
+
|
26
|
+
s.add_runtime_dependency "rainpress"
|
27
|
+
s.add_runtime_dependency "uglifier"
|
28
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module HtmlPress
|
2
|
+
class Html
|
3
|
+
|
4
|
+
def self.compress text
|
5
|
+
Html.new.compile text
|
6
|
+
end
|
7
|
+
|
8
|
+
def compile (html)
|
9
|
+
|
10
|
+
out = html + ""
|
11
|
+
|
12
|
+
@replacement_hash = 'MINIFYHTML' + Time.now.to_i.to_s
|
13
|
+
@placeholders = []
|
14
|
+
@strip_crlf = false
|
15
|
+
|
16
|
+
# replace SCRIPTs (and minify) with placeholders
|
17
|
+
out.gsub! /\s*(<script\b[^>]*?>[\s\S]*?<\/script>)\s*/i do |m|
|
18
|
+
m.gsub!(/^\s+|\s+$/, '')
|
19
|
+
js = m.gsub(/\s*<script\b[^>]*?>([\s\S]*?)<\/script>\s*/i , "\\1")
|
20
|
+
begin
|
21
|
+
js_compressed = HtmlPress.js_compressor js
|
22
|
+
m.gsub!(js, js_compressed)
|
23
|
+
rescue Exception => e
|
24
|
+
# p e.message
|
25
|
+
end
|
26
|
+
reserve m
|
27
|
+
end
|
28
|
+
|
29
|
+
# replace STYLEs (and minify) with placeholders
|
30
|
+
out.gsub! /\s*(<style\b[^>]*?>[\s\S]*?<\/style>)\s*/i do |m|
|
31
|
+
m.gsub!(/^\s+|\s+$/, '')
|
32
|
+
css = m.gsub(/\s*<style\b[^>]*?>([\s\S]*?)<\/style>\s*/i, "\\1")
|
33
|
+
begin
|
34
|
+
css_compressed = HtmlPress.css_compressor css
|
35
|
+
m.gsub!(css, css_compressed)
|
36
|
+
rescue Exception => e
|
37
|
+
# p e.message
|
38
|
+
end
|
39
|
+
reserve m
|
40
|
+
end
|
41
|
+
|
42
|
+
# IE conditional comments
|
43
|
+
out.gsub! /\s*(<!--\[[^\]]+\]>[\s\S]*?<!\[[^\]]+\]-->)\s*/ do |m|
|
44
|
+
m.gsub!(/^\s+|\s+$/, '')
|
45
|
+
comment = m.gsub(/\s*<!--\[[^\]]+\]>([\s\S]*?)<!\[[^\]]+\]-->\s*/, "\\1")
|
46
|
+
comment_compressed = Html.new.compile(comment)
|
47
|
+
m.gsub!(comment, comment_compressed)
|
48
|
+
reserve m
|
49
|
+
end
|
50
|
+
|
51
|
+
# remove out comments (not containing IE conditional comments).
|
52
|
+
out.gsub! /<!--([\s\S]*?)-->/ do |m|
|
53
|
+
''
|
54
|
+
end
|
55
|
+
|
56
|
+
# replace PREs with placeholders
|
57
|
+
out.gsub! /\s*(<pre\b[^>]*?>[\s\S]*?<\/pre>)\s*/i do |m|
|
58
|
+
pre = m.gsub(/\s*<pre\b[^>]*?>([\s\S]*?)<\/pre>\s*/i, "\\1")
|
59
|
+
pre_compressed = pre.gsub(/\s+$/, '')
|
60
|
+
m.gsub!(pre, pre_compressed)
|
61
|
+
reserve m
|
62
|
+
end
|
63
|
+
|
64
|
+
# replace TEXTAREAs with placeholders
|
65
|
+
out.gsub! /\s*(<textarea\b[^>]*?>[\s\S]*?<\/textarea>)\s*/i do |m|
|
66
|
+
reserve m
|
67
|
+
end
|
68
|
+
|
69
|
+
# trim each line.
|
70
|
+
# @todo take into account attribute values that span multiple lines.
|
71
|
+
out.gsub!(/^\s+|\s+$/m, '')
|
72
|
+
|
73
|
+
re = '\\s+(<\\/?(?:area|base(?:font)?|blockquote|body' +
|
74
|
+
'|caption|center|cite|col(?:group)?|dd|dir|div|dl|dt|fieldset|form' +
|
75
|
+
'|frame(?:set)?|h[1-6]|head|hr|out|legend|li|link|map|menu|meta' +
|
76
|
+
'|ol|opt(?:group|ion)|p|param|t(?:able|body|head|d|h|r|foot|itle)' +
|
77
|
+
'|ul)\\b[^>]*>)'
|
78
|
+
|
79
|
+
re = Regexp.new(re)
|
80
|
+
out.gsub!(re, '\\1')
|
81
|
+
|
82
|
+
# remove ws outside of all elements
|
83
|
+
out.gsub! />([^<]+)</ do |m|
|
84
|
+
m.gsub(/^\s+|\s+$/, ' ')
|
85
|
+
end
|
86
|
+
|
87
|
+
# use newlines before 1st attribute in open tags (to limit line lengths)
|
88
|
+
# out.gsub!(/(<[a-z\-:]+)\s+([^>]+>)/i, "\\1\n\\2")
|
89
|
+
|
90
|
+
# match attributes
|
91
|
+
out.gsub! /<[a-z\-:]+\s([^>]+)>/i do |m|
|
92
|
+
reserve attrs(m, '[a-z\-:]+', true)
|
93
|
+
end
|
94
|
+
|
95
|
+
out.gsub!(/[\r\n]+/, @strip_crlf ? ' ' : "\n")
|
96
|
+
|
97
|
+
out.gsub!(/\s+/, ' ')
|
98
|
+
|
99
|
+
# fill placeholders
|
100
|
+
re = Regexp.new('%' + @replacement_hash + '%(\d+)%')
|
101
|
+
out.gsub! re do |m|
|
102
|
+
m.gsub!(re, "\\1")
|
103
|
+
@placeholders[m.to_i]
|
104
|
+
end
|
105
|
+
|
106
|
+
out
|
107
|
+
end
|
108
|
+
|
109
|
+
def reserve(content)
|
110
|
+
@placeholders.push content
|
111
|
+
'%' + @replacement_hash + '%' + (@placeholders.size - 1).to_s + '%'
|
112
|
+
end
|
113
|
+
|
114
|
+
def attrs (m, tag_name, r)
|
115
|
+
re = "<(" + tag_name + ")(\s[^>]+)?>"
|
116
|
+
re = Regexp.new(re, true)
|
117
|
+
attributes = m.gsub(re, "\\2")
|
118
|
+
if r
|
119
|
+
tag = m.gsub(re, "\\1")
|
120
|
+
else
|
121
|
+
tag = tag_name
|
122
|
+
end
|
123
|
+
|
124
|
+
if attributes.size > 0
|
125
|
+
attributes_compressed = attributes.gsub(/\s*([a-z\-_]+(="[^"]*")?(='[^']*')?)\s*/i, " \\1")
|
126
|
+
|
127
|
+
attributes_compressed.gsub! /([a-z\-_]+="[^"]*")/ do |k|
|
128
|
+
attr k, "\"", tag
|
129
|
+
end
|
130
|
+
|
131
|
+
attributes_compressed.gsub! /([a-z\-_]+='[^']*')/ do |k|
|
132
|
+
attr k, "'", tag
|
133
|
+
end
|
134
|
+
|
135
|
+
return m.gsub(attributes, attributes_compressed)
|
136
|
+
end
|
137
|
+
|
138
|
+
return m
|
139
|
+
end
|
140
|
+
|
141
|
+
def attr(attribute, delimiter, tag)
|
142
|
+
re = "([a-z\\-_]+)(=" + delimiter + "[^" + delimiter + "]*" + delimiter + ")?"
|
143
|
+
re = Regexp.new re
|
144
|
+
value = attribute.gsub(re, "\\2")
|
145
|
+
|
146
|
+
if tag == "script"
|
147
|
+
name = attribute.gsub(re, "\\1")
|
148
|
+
if name == "type" || name == "language"
|
149
|
+
return ""
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if value.size != 0
|
154
|
+
|
155
|
+
name = attribute.gsub(re, "\\1")
|
156
|
+
|
157
|
+
re = "^=" + delimiter + "|" + delimiter + "$"
|
158
|
+
re = Regexp.new re
|
159
|
+
value.gsub!(re, "")
|
160
|
+
|
161
|
+
if name == "style"
|
162
|
+
value = HtmlPress.css_compressor value
|
163
|
+
end
|
164
|
+
|
165
|
+
if name == "class"
|
166
|
+
value.gsub!(/\s+/, " ")
|
167
|
+
value.gsub!(/^\s+|\s+$/, "")
|
168
|
+
end
|
169
|
+
|
170
|
+
# if name == "onclick"
|
171
|
+
# value = HtmlPress.js_compressor value
|
172
|
+
# end
|
173
|
+
|
174
|
+
attribute = name + "=" + delimiter + value + delimiter
|
175
|
+
end
|
176
|
+
|
177
|
+
attribute
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
data/lib/html_press.rb
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative "../lib/html_press"
|
4
|
+
|
5
|
+
describe HtmlPress do
|
6
|
+
before :each do
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should leave only one whitespace between inline tags" do
|
10
|
+
HtmlPress.compress("<p>lorem <b>ipsum</b> <i>dolor</i> </p>").should eql "<p>lorem <b>ipsum</b> <i>dolor</i></p>"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should leave no whitespaces between block tags" do
|
14
|
+
HtmlPress.compress("<div></div> \t\r\n <div></div>").should eql "<div></div><div></div>"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should leave only one whitespace in text" do
|
18
|
+
HtmlPress.compress("<p>a a</p>").should eql "<p>a a</p>"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should leave newlines in pre tags and remove trailing spaces" do
|
22
|
+
HtmlPress.compress("<pre>a \t </pre>").should eql "<pre>a</pre>"
|
23
|
+
HtmlPress.compress("<pre>qwe \nasd </pre>").should eql "<pre>qwe\nasd</pre>"
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should leave textareas as is" do
|
27
|
+
text = "<textarea> \t </textarea>"
|
28
|
+
HtmlPress.compress(text).should eql text
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should compress js in script tags" do
|
32
|
+
script = " (function(undefined){ \t\n var long_name = ' '; }()) \n \r"
|
33
|
+
compressed_script = "<script>" + HtmlPress.js_compressor(script) + "</script>"
|
34
|
+
script = " <script>" + script + "</script> "
|
35
|
+
HtmlPress.compress(script).should eql compressed_script
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should compress css in style tags" do
|
39
|
+
style = " div { margin: 0px 0px; \n} "
|
40
|
+
compressed_style = "<style>" + HtmlPress.css_compressor(style) + "</style>"
|
41
|
+
style = " <style>" + style + "</style> "
|
42
|
+
HtmlPress.compress(style).should eql compressed_style
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should remove html comments" do
|
46
|
+
HtmlPress.compress("<p></p><!-- comment --><p></p>").should eql "<p></p><p></p>"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should leave IE conditional comments" do
|
50
|
+
text = "<!--[if IE]><html class=\"ie\"><![endif]--><div></div>"
|
51
|
+
HtmlPress.compress(text).should eql text
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should remove unnecessary whitespaces between attributes" do
|
55
|
+
HtmlPress.compress("<p class=\"a\" id=\"b\"></p>").should eql "<p class=\"a\" id=\"b\"></p>"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should leave whitespaces everywhere else" do
|
59
|
+
text = "<a onclick=\"alert(' ')\" unknown_attr=' a a'>a</a>"
|
60
|
+
HtmlPress.compress(text).should eql text
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should work with special utf-8 symbols" do
|
64
|
+
HtmlPress.compress("✪<p></p> <p></p>").should eql "✪<p></p><p></p>"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should work with tags in upper case" do
|
68
|
+
HtmlPress.compress("<P> </p>").should eql "<P></p>"
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should remove whitespaces between IE conditional comments" do
|
72
|
+
text = "<p></p> <!--[if IE]><html class=\"ie\"> <![endif]--> <!--[if IE]><html class=\"ie1\"><![endif]-->"
|
73
|
+
text2 = "<p></p><!--[if IE]><html class=\"ie\"><![endif]--><!--[if IE]><html class=\"ie1\"><![endif]-->"
|
74
|
+
HtmlPress.compress(text).should eql text2
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should treat text inside IE conditional comments as it was without comments" do
|
78
|
+
text = "<div class=\"a\" id=\"b\"> </div> <p></p>"
|
79
|
+
text2 = HtmlPress.compress(text)
|
80
|
+
text = "<!--[if IE]>" + text + "<![endif]-->"
|
81
|
+
text2 = "<!--[if IE]>" + text2 + "<![endif]-->"
|
82
|
+
HtmlPress.compress(text).should eql text2
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should remove unnecessary whitespaces in html attributes (class)" do
|
86
|
+
HtmlPress.compress("<p class=\"a b\"></p>").should eql "<p class=\"a b\"></p>"
|
87
|
+
HtmlPress.compress("<p class='a b'></p>").should eql "<p class='a b'></p>"
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should remove unnecessary whitespaces in html attributes (style)" do
|
91
|
+
HtmlPress.compress("<p style=\"display: none;\"></p>").should eql "<p style=\"display:none;\"></p>"
|
92
|
+
end
|
93
|
+
|
94
|
+
# it "should remove unnecessary attributes" do
|
95
|
+
# HtmlPress.compress("<script type=\"text/javascript\">var a;</script>").should eql "<script>var a;</script>"
|
96
|
+
# end
|
97
|
+
|
98
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: html_press
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- stereobooster
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-04 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &19832364 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *19832364
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &20531592 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *20531592
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rainpress
|
38
|
+
requirement: &20756364 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *20756364
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: uglifier
|
49
|
+
requirement: &21077148 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *21077148
|
58
|
+
description: Ruby gem for compressing html
|
59
|
+
email:
|
60
|
+
- stereobooster@gmail.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- .gitignore
|
66
|
+
- Gemfile
|
67
|
+
- Rakefile
|
68
|
+
- Readme.md
|
69
|
+
- html_press.gemspec
|
70
|
+
- lib/html_press.rb
|
71
|
+
- lib/html_press/html.rb
|
72
|
+
- lib/html_press/rainpress.rb
|
73
|
+
- lib/html_press/uglifier.rb
|
74
|
+
- lib/html_press/version.rb
|
75
|
+
- spec/html_press_spec.rb
|
76
|
+
homepage: https://github.com/stereobooster/html_press
|
77
|
+
licenses: []
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project: html_press
|
96
|
+
rubygems_version: 1.8.15
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: Compress html
|
100
|
+
test_files: []
|