html_press 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|