htmlcompressor 0.0.2
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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/README.md +83 -0
- data/Rakefile +2 -0
- data/htmlcompressor.gemspec +21 -0
- data/lib/htmlcompressor.rb +6 -0
- data/lib/htmlcompressor/compressor.rb +785 -0
- data/lib/htmlcompressor/rack.rb +54 -0
- data/lib/htmlcompressor/version.rb +3 -0
- data/test/compressor_test.rb +254 -0
- data/test/resources/html/.svn/all-wcprops +275 -0
- data/test/resources/html/.svn/entries +1558 -0
- data/test/resources/html/.svn/prop-base/testRemoveHttpProtocol.html.svn-base +5 -0
- data/test/resources/html/.svn/prop-base/testRemoveHttpProtocolResult.html.svn-base +5 -0
- data/test/resources/html/.svn/prop-base/testRemoveHttpsProtocol.html.svn-base +5 -0
- data/test/resources/html/.svn/prop-base/testRemoveHttpsProtocolResult.html.svn-base +5 -0
- data/test/resources/html/.svn/prop-base/testRemoveSpacesInsideTags.html.svn-base +5 -0
- data/test/resources/html/.svn/prop-base/testRemoveSpacesInsideTagsResult.html.svn-base +5 -0
- data/test/resources/html/.svn/prop-base/testSurroundingSpaces.html.svn-base +5 -0
- data/test/resources/html/.svn/prop-base/testSurroundingSpacesResult.html.svn-base +5 -0
- data/test/resources/html/.svn/text-base/testCompress.html.svn-base +10 -0
- data/test/resources/html/.svn/text-base/testCompressCss.html.svn-base +11 -0
- data/test/resources/html/.svn/text-base/testCompressCssResult.html.svn-base +6 -0
- data/test/resources/html/.svn/text-base/testCompressJavaScript.html.svn-base +24 -0
- data/test/resources/html/.svn/text-base/testCompressJavaScriptClosureResult.html.svn-base +7 -0
- data/test/resources/html/.svn/text-base/testCompressJavaScriptYuiResult.html.svn-base +7 -0
- data/test/resources/html/.svn/text-base/testCompressResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testEnabled.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testEnabledResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testPreserveLineBreaks.html.svn-base +11 -0
- data/test/resources/html/.svn/text-base/testPreserveLineBreaksResult.html.svn-base +5 -0
- data/test/resources/html/.svn/text-base/testPreservePatterns.html.svn-base +7 -0
- data/test/resources/html/.svn/text-base/testPreservePatternsResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveComments.html.svn-base +17 -0
- data/test/resources/html/.svn/text-base/testRemoveCommentsResult.html.svn-base +4 -0
- data/test/resources/html/.svn/text-base/testRemoveFormAttributes.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveFormAttributesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveHttpProtocol.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveHttpProtocolResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveHttpsProtocol.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveHttpsProtocolResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveInputAttributes.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveInputAttributesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveIntertagSpaces.html.svn-base +5 -0
- data/test/resources/html/.svn/text-base/testRemoveIntertagSpacesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveJavaScriptProtocol.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveJavaScriptProtocolResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveLinkAttributes.html.svn-base +5 -0
- data/test/resources/html/.svn/text-base/testRemoveLinkAttributesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveMultiSpaces.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveMultiSpacesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveQuotes.html.svn-base +7 -0
- data/test/resources/html/.svn/text-base/testRemoveQuotesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveScriptAttributes.html.svn-base +6 -0
- data/test/resources/html/.svn/text-base/testRemoveScriptAttributesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveSpacesInsideTags.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveSpacesInsideTagsResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testRemoveStyleAttributes.html.svn-base +3 -0
- data/test/resources/html/.svn/text-base/testRemoveStyleAttributesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testSimpleBooleanAttributes.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testSimpleBooleanAttributesResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testSimpleDoctype.html.svn-base +2 -0
- data/test/resources/html/.svn/text-base/testSimpleDoctypeResult.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testSurroundingSpaces.html.svn-base +1 -0
- data/test/resources/html/.svn/text-base/testSurroundingSpacesResult.html.svn-base +1 -0
- data/test/resources/html/testCompress.html +10 -0
- data/test/resources/html/testCompressCss.html +11 -0
- data/test/resources/html/testCompressCssResult.html +6 -0
- data/test/resources/html/testCompressJavaScript.html +24 -0
- data/test/resources/html/testCompressJavaScriptClosureResult.html +7 -0
- data/test/resources/html/testCompressJavaScriptYuiResult.html +7 -0
- data/test/resources/html/testCompressResult.html +1 -0
- data/test/resources/html/testEnabled.html +1 -0
- data/test/resources/html/testEnabledResult.html +1 -0
- data/test/resources/html/testPreserveLineBreaks.html +11 -0
- data/test/resources/html/testPreserveLineBreaksResult.html +5 -0
- data/test/resources/html/testPreservePatterns.html +7 -0
- data/test/resources/html/testPreservePatternsResult.html +1 -0
- data/test/resources/html/testRemoveComments.html +17 -0
- data/test/resources/html/testRemoveCommentsResult.html +4 -0
- data/test/resources/html/testRemoveFormAttributes.html +1 -0
- data/test/resources/html/testRemoveFormAttributesResult.html +1 -0
- data/test/resources/html/testRemoveHttpProtocol.html +1 -0
- data/test/resources/html/testRemoveHttpProtocolResult.html +1 -0
- data/test/resources/html/testRemoveHttpsProtocol.html +1 -0
- data/test/resources/html/testRemoveHttpsProtocolResult.html +1 -0
- data/test/resources/html/testRemoveInputAttributes.html +1 -0
- data/test/resources/html/testRemoveInputAttributesResult.html +1 -0
- data/test/resources/html/testRemoveIntertagSpaces.html +5 -0
- data/test/resources/html/testRemoveIntertagSpacesResult.html +1 -0
- data/test/resources/html/testRemoveJavaScriptProtocol.html +1 -0
- data/test/resources/html/testRemoveJavaScriptProtocolResult.html +1 -0
- data/test/resources/html/testRemoveLinkAttributes.html +5 -0
- data/test/resources/html/testRemoveLinkAttributesResult.html +1 -0
- data/test/resources/html/testRemoveMultiSpaces.html +1 -0
- data/test/resources/html/testRemoveMultiSpacesResult.html +1 -0
- data/test/resources/html/testRemoveQuotes.html +7 -0
- data/test/resources/html/testRemoveQuotesResult.html +1 -0
- data/test/resources/html/testRemoveScriptAttributes.html +6 -0
- data/test/resources/html/testRemoveScriptAttributesResult.html +1 -0
- data/test/resources/html/testRemoveSpacesInsideTags.html +1 -0
- data/test/resources/html/testRemoveSpacesInsideTagsResult.html +1 -0
- data/test/resources/html/testRemoveStyleAttributes.html +3 -0
- data/test/resources/html/testRemoveStyleAttributesResult.html +1 -0
- data/test/resources/html/testSimpleBooleanAttributes.html +1 -0
- data/test/resources/html/testSimpleBooleanAttributesResult.html +1 -0
- data/test/resources/html/testSimpleDoctype.html +2 -0
- data/test/resources/html/testSimpleDoctypeResult.html +1 -0
- data/test/resources/html/testSurroundingSpaces.html +1 -0
- data/test/resources/html/testSurroundingSpacesResult.html +1 -0
- data/test/resources/xml/.svn/all-wcprops +53 -0
- data/test/resources/xml/.svn/entries +300 -0
- data/test/resources/xml/.svn/text-base/testCompress.xml.svn-base +11 -0
- data/test/resources/xml/.svn/text-base/testCompressResult.xml.svn-base +5 -0
- data/test/resources/xml/.svn/text-base/testEnabled.xml.svn-base +1 -0
- data/test/resources/xml/.svn/text-base/testEnabledResult.xml.svn-base +1 -0
- data/test/resources/xml/.svn/text-base/testRemoveComments.xml.svn-base +5 -0
- data/test/resources/xml/.svn/text-base/testRemoveCommentsResult.xml.svn-base +4 -0
- data/test/resources/xml/.svn/text-base/testRemoveIntertagSpaces.xml.svn-base +11 -0
- data/test/resources/xml/.svn/text-base/testRemoveIntertagSpacesResult.xml.svn-base +5 -0
- data/test/resources/xml/testCompress.xml +11 -0
- data/test/resources/xml/testCompressResult.xml +5 -0
- data/test/resources/xml/testEnabled.xml +1 -0
- data/test/resources/xml/testEnabledResult.xml +1 -0
- data/test/resources/xml/testRemoveComments.xml +5 -0
- data/test/resources/xml/testRemoveCommentsResult.xml +4 -0
- data/test/resources/xml/testRemoveIntertagSpaces.xml +11 -0
- data/test/resources/xml/testRemoveIntertagSpacesResult.xml +5 -0
- metadata +332 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright (c) 2012 Paolo Chiodi
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Htmlcompressor
|
|
2
|
+
|
|
3
|
+
## Put your html on a diet
|
|
4
|
+
|
|
5
|
+
Htmlcompressor provides tools to minify html code.
|
|
6
|
+
It includes
|
|
7
|
+
- HtmlCompressor::Compressor class which is a raw port of [google's htmlcompressor](http://code.google.com/p/htmlcompressor/)
|
|
8
|
+
- HtmlCompressor::Rack a rack middleware to compress html pages on the fly
|
|
9
|
+
|
|
10
|
+
Please note that Htmlcompressor is still in alpha version and need some additional love.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
Using the compressor class is straightforward:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
compressor = HtmlCompressor.Compressor.new
|
|
18
|
+
compressor.compile('<html><body><div id="compress_me"></div></body></html>')
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The compressor ships with some default option that may be overwritten passing the options hash to the constructor:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
option = {
|
|
25
|
+
:enabled => true
|
|
26
|
+
:remove_multi_spaces => true,
|
|
27
|
+
:remove_comments => true,
|
|
28
|
+
:remove_intertag_spaces => true,
|
|
29
|
+
:remove_quotes => true,
|
|
30
|
+
:compress_css => false,
|
|
31
|
+
:compress_javascript => false,
|
|
32
|
+
:simple_doctype => false,
|
|
33
|
+
:remove_script_attributes => true,
|
|
34
|
+
:remove_style_attributes => true,
|
|
35
|
+
:remove_link_attributes => true,
|
|
36
|
+
:remove_form_attributes => false,
|
|
37
|
+
:remove_input_attributes => true,
|
|
38
|
+
:remove_javascript_protocol => true,
|
|
39
|
+
:remove_http_protocol => true,
|
|
40
|
+
:remove_https_protocol => false,
|
|
41
|
+
:preserve_line_breaks => false,
|
|
42
|
+
:simple_boolean_attributes => true
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Using rack middleware is as easy as:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
config.middleware.use HtmlCompressor::Rack
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
It is not yet possible to override default options when using the rack middleware.
|
|
53
|
+
|
|
54
|
+
Rails 2.3 users may need to add
|
|
55
|
+
```ruby
|
|
56
|
+
require 'htmlcompressor'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Statistics
|
|
60
|
+
|
|
61
|
+
As of now the statistic framework hasn't been ported. Refer to original [htmlcompressor documentation](http://code.google.com/p/htmlcompressor/) for statistics on minified pages.
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
66
|
+
you may not use this file except in compliance with the License.
|
|
67
|
+
You may obtain a copy of the License at
|
|
68
|
+
|
|
69
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
70
|
+
|
|
71
|
+
Unless required by applicable law or agreed to in writing, software
|
|
72
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
73
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
74
|
+
See the License for the specific language governing permissions and
|
|
75
|
+
limitations under the License.
|
|
76
|
+
|
|
77
|
+
## Contributing
|
|
78
|
+
|
|
79
|
+
1. Fork it
|
|
80
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
81
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
|
82
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
83
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/htmlcompressor/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.authors = ["Paolo Chiodi"]
|
|
6
|
+
gem.email = ["chiodi84@gmail.com"]
|
|
7
|
+
gem.description = %q{Put your html on a diet}
|
|
8
|
+
gem.summary = %q{htmlcompressor provides a class and a rack middleware to minify html pages}
|
|
9
|
+
gem.homepage = ""
|
|
10
|
+
|
|
11
|
+
gem.add_dependency 'yui-compressor', '~> 0.9.6'
|
|
12
|
+
gem.add_development_dependency 'closure-compiler', '~> 1.1.5'
|
|
13
|
+
gem.add_development_dependency 'rake'
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($\)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.name = "htmlcompressor"
|
|
19
|
+
gem.require_paths = ["lib"]
|
|
20
|
+
gem.version = HtmlCompressor::VERSION
|
|
21
|
+
end
|
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
require "yui/compressor"
|
|
2
|
+
|
|
3
|
+
module HtmlCompressor
|
|
4
|
+
class Compressor
|
|
5
|
+
|
|
6
|
+
JS_COMPRESSOR_YUI = "yui";
|
|
7
|
+
JS_COMPRESSOR_CLOSURE = "closure";
|
|
8
|
+
|
|
9
|
+
# Predefined pattern that matches <code><?php ... ?></code> tags.
|
|
10
|
+
# Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method.
|
|
11
|
+
PHP_TAG_PATTERN = /<\?php.*?\?>/im
|
|
12
|
+
|
|
13
|
+
# Predefined pattern that matches <code><% ... %></code> tags.
|
|
14
|
+
# Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method.
|
|
15
|
+
SERVER_SCRIPT_TAG_PATTERN = /<%.*?%>/m
|
|
16
|
+
|
|
17
|
+
# Predefined pattern that matches <code><--# ... --></code> tags.
|
|
18
|
+
# Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method.
|
|
19
|
+
SERVER_SIDE_INCLUDE_PATTERN = /<!--\s*#.*?-->/m
|
|
20
|
+
|
|
21
|
+
# Predefined list of tags that are very likely to be block-level.
|
|
22
|
+
#Could be passed to {@link #setRemoveSurroundingSpaces(String) setRemoveSurroundingSpaces} method.
|
|
23
|
+
BLOCK_TAGS_MIN = "html,head,body,br,p"
|
|
24
|
+
|
|
25
|
+
# Predefined list of tags that are block-level by default, excluding <code><div></code> and <code><li></code> tags.
|
|
26
|
+
#Table tags are also included.
|
|
27
|
+
#Could be passed to {@link #setRemoveSurroundingSpaces(String) setRemoveSurroundingSpaces} method.
|
|
28
|
+
BLOCK_TAGS_MAX = BLOCK_TAGS_MIN + ",h1,h2,h3,h4,h5,h6,blockquote,center,dl,fieldset,form,frame,frameset,hr,noframes,ol,table,tbody,tr,td,th,tfoot,thead,ul"
|
|
29
|
+
|
|
30
|
+
# Could be passed to {@link #setRemoveSurroundingSpaces(String) setRemoveSurroundingSpaces} method
|
|
31
|
+
# to remove all surrounding spaces (not recommended).
|
|
32
|
+
ALL_TAGS = "all"
|
|
33
|
+
|
|
34
|
+
# temp replacements for preserved blocks
|
|
35
|
+
TEMP_COND_COMMENT_BLOCK = "%%%~COMPRESS~COND~{0,number,#}~%%%"
|
|
36
|
+
TEMP_PRE_BLOCK = "%%%~COMPRESS~PRE~{0,number,#}~%%%"
|
|
37
|
+
TEMP_TEXT_AREA_BLOCK = "%%%~COMPRESS~TEXTAREA~{0,number,#}~%%%"
|
|
38
|
+
TEMP_SCRIPT_BLOCK = "%%%~COMPRESS~SCRIPT~{0,number,#}~%%%"
|
|
39
|
+
TEMP_STYLE_BLOCK = "%%%~COMPRESS~STYLE~{0,number,#}~%%%"
|
|
40
|
+
TEMP_EVENT_BLOCK = "%%%~COMPRESS~EVENT~{0,number,#}~%%%"
|
|
41
|
+
TEMP_LINE_BREAK_BLOCK = "%%%~COMPRESS~LT~{0,number,#}~%%%"
|
|
42
|
+
TEMP_SKIP_BLOCK = "%%%~COMPRESS~SKIP~{0,number,#}~%%%"
|
|
43
|
+
TEMP_USER_BLOCK = "%%%~COMPRESS~USER{0,number,#}~{1,number,#}~%%%"
|
|
44
|
+
|
|
45
|
+
# compiled regex patterns
|
|
46
|
+
EMPTY_PATTERN = Regexp.new("\\s")
|
|
47
|
+
SKIP_PATTERN = Regexp.new("<!--\\s*\\{\\{\\{\\s*-->(.*?)<!--\\s*\\}\\}\\}\\s*-->", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
48
|
+
COND_COMMENT_PATTERN = Regexp.new("(<!(?:--)?\\[[^\\]]+?\\]>)(.*?)(<!\\[[^\\]]+\\]-->)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
49
|
+
COMMENT_PATTERN = Regexp.new("<!---->|<!--[^\\[].*?-->", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
50
|
+
INTERTAG_PATTERN_TAG_TAG = Regexp.new(">\\s+<", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
51
|
+
INTERTAG_PATTERN_TAG_CUSTOM = Regexp.new(">\\s+%%%~", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
52
|
+
INTERTAG_PATTERN_CUSTOM_TAG = Regexp.new("~%%%\\s+<", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
53
|
+
INTERTAG_PATTERN_CUSTOM_CUSTOM = Regexp.new("~%%%\\s+%%%~", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
54
|
+
MULTISPACE_PATTERN = Regexp.new("\\s+", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
55
|
+
TAG_END_SPACE_PATTERN = Regexp.new("(<(?:[^>]+?))(?:\\s+?)(/?>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
56
|
+
TAG_LAST_UNQUOTED_VALUE_PATTERN = Regexp.new("=\\s*[a-z0-9\\-_]+$", Regexp::IGNORECASE)
|
|
57
|
+
TAG_QUOTE_PATTERN = Regexp.new("\\s*=\\s*([\"'])([a-z0-9\\-_]+?)\\1(/?)(?=[^<]*?>)", Regexp::IGNORECASE)
|
|
58
|
+
PRE_PATTERN = Regexp.new("(<pre[^>]*?>)(.*?)(</pre>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
59
|
+
TA_PATTERN = Regexp.new("(<textarea[^>]*?>)(.*?)(</textarea>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
60
|
+
SCRIPT_PATTERN = Regexp.new("(<script[^>]*?>)(.*?)(</script>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
61
|
+
STYLE_PATTERN = Regexp.new("(<style[^>]*?>)(.*?)(</style>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
62
|
+
TAG_PROPERTY_PATTERN = Regexp.new("(\\s\\w+)\\s*=\\s*(?=[^<]*?>)", Regexp::IGNORECASE)
|
|
63
|
+
CDATA_PATTERN = Regexp.new("\\s*<!\\[CDATA\\[(.*?)\\]\\]>\\s*", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
64
|
+
DOCTYPE_PATTERN = Regexp.new("<!DOCTYPE[^>]*>", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
65
|
+
TYPE_ATTR_PATTERN = Regexp.new("type\\s*=\\s*([\\\"']*)(.+?)\\1", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
66
|
+
JS_TYPE_ATTR_PATTERN = Regexp.new("(<script[^>]*)type\\s*=\\s*([\"']*)(?:text|application)\/javascript\\2([^>]*>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
67
|
+
JS_LANG_ATTR_PATTERN = Regexp.new("(<script[^>]*)language\\s*=\\s*([\"']*)javascript\\2([^>]*>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
68
|
+
STYLE_TYPE_ATTR_PATTERN = Regexp.new("(<style[^>]*)type\\s*=\\s*([\"']*)text/style\\2([^>]*>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
69
|
+
LINK_TYPE_ATTR_PATTERN = Regexp.new("(<link[^>]*)type\\s*=\\s*([\"']*)text/(?:css|plain)\\2([^>]*>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
70
|
+
LINK_REL_ATTR_PATTERN = Regexp.new("<link(?:[^>]*)rel\\s*=\\s*([\"']*)(?:alternate\\s+)?stylesheet\\1(?:[^>]*)>", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
71
|
+
FORM_METHOD_ATTR_PATTERN = Regexp.new("(<form[^>]*)method\\s*=\\s*([\"']*)get\\2([^>]*>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
72
|
+
INPUT_TYPE_ATTR_PATTERN = Regexp.new("(<input[^>]*)type\\s*=\\s*([\"']*)text\\2([^>]*>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
73
|
+
BOOLEAN_ATTR_PATTERN = Regexp.new("(<\\w+[^>]*)(checked|selected|disabled|readonly)\\s*=\\s*([\"']*)\\w*\\3([^>]*>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
74
|
+
EVENT_JS_PROTOCOL_PATTERN = Regexp.new("^javascript:\\s*(.+)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
75
|
+
HTTP_PROTOCOL_PATTERN = Regexp.new("(<[^>]+?(?:href|src|cite|action)\\s*=\\s*['\"])http:(//[^>]+?>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
76
|
+
HTTPS_PROTOCOL_PATTERN = Regexp.new("(<[^>]+?(?:href|src|cite|action)\\s*=\\s*['\"])https:(//[^>]+?>)", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
77
|
+
REL_EXTERNAL_PATTERN = Regexp.new("<(?:[^>]*)rel\\s*=\\s*([\"']*)(?:alternate\\s+)?external\\1(?:[^>]*)>", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
78
|
+
EVENT_PATTERN1 = Regexp.new("(\\son[a-z]+\\s*=\\s*\")([^\"\\\\\\r\\n]*(?:\\\\.[^\"\\\\\\r\\n]*)*)(\")", Regexp::IGNORECASE) # unmasked: \son[a-z]+\s*=\s*"[^"\\\r\n]*(?:\\.[^"\\\r\n]*)*""
|
|
79
|
+
EVENT_PATTERN2 = Regexp.new("(\\son[a-z]+\\s*=\\s*')([^'\\\\\\r\\n]*(?:\\\\.[^'\\\\\\r\\n]*)*)(')", Regexp::IGNORECASE)
|
|
80
|
+
LINE_BREAK_PATTERN = Regexp.new("(?:\\p{Blank}*(\\r?\\n)\\p{Blank}*)+")
|
|
81
|
+
SURROUNDING_SPACES_MIN_PATTERN = Regexp.new("\\s*(</?(?:" + BLOCK_TAGS_MIN.gsub(",", "|") + ")(?:>|[\\s/][^>]*>))\\s*", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
82
|
+
SURROUNDING_SPACES_MAX_PATTERN = Regexp.new("\\s*(</?(?:" + BLOCK_TAGS_MAX.gsub(",", "|") + ")(?:>|[\\s/][^>]*>))\\s*", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
83
|
+
SURROUNDING_SPACES_ALL_PATTERN = Regexp.new("\\s*(<[^>]+>)\\s*", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
84
|
+
|
|
85
|
+
# patterns for searching for temporary replacements
|
|
86
|
+
TEMP_COND_COMMENT_PATTERN = Regexp.new("%%%~COMPRESS~COND~(\\d+?)~%%%")
|
|
87
|
+
TEMP_PRE_PATTERN = Regexp.new("%%%~COMPRESS~PRE~(\\d+?)~%%%")
|
|
88
|
+
TEMP_TEXT_AREA_PATTERN = Regexp.new("%%%~COMPRESS~TEXTAREA~(\\d+?)~%%%")
|
|
89
|
+
TEMP_SCRIPT_PATTERN = Regexp.new("%%%~COMPRESS~SCRIPT~(\\d+?)~%%%")
|
|
90
|
+
TEMP_STYLE_PATTERN = Regexp.new("%%%~COMPRESS~STYLE~(\\d+?)~%%%")
|
|
91
|
+
TEMP_EVENT_PATTERN = Regexp.new("%%%~COMPRESS~EVENT~(\\d+?)~%%%")
|
|
92
|
+
TEMP_SKIP_PATTERN = Regexp.new("%%%~COMPRESS~SKIP~(\\d+?)~%%%")
|
|
93
|
+
TEMP_LINE_BREAK_PATTERN = Regexp.new("%%%~COMPRESS~LT~(\\d+?)~%%%")
|
|
94
|
+
|
|
95
|
+
DEFAULT_OPTIONS = {
|
|
96
|
+
:enabled => true,
|
|
97
|
+
|
|
98
|
+
# default settings
|
|
99
|
+
:remove_comments => true,
|
|
100
|
+
:remove_multi_spaces => true,
|
|
101
|
+
|
|
102
|
+
# optional settings
|
|
103
|
+
:remove_intertag_spaces => false,
|
|
104
|
+
:remove_quotes => false,
|
|
105
|
+
:compress_javascript => false,
|
|
106
|
+
:compress_css => false,
|
|
107
|
+
:simple_doctype => false,
|
|
108
|
+
:remove_script_attributes => false,
|
|
109
|
+
:remove_style_attributes => false,
|
|
110
|
+
:remove_link_attributes => false,
|
|
111
|
+
:remove_form_attributes => false,
|
|
112
|
+
:remove_input_attributes => false,
|
|
113
|
+
:simple_boolean_attributes => false,
|
|
114
|
+
:remove_javascript_protocol => false,
|
|
115
|
+
:remove_http_protocol => false,
|
|
116
|
+
:remove_https_protocol => false,
|
|
117
|
+
:preserve_line_breaks => false,
|
|
118
|
+
:remove_surrounding_spaces => nil,
|
|
119
|
+
|
|
120
|
+
:preserve_patterns => nil,
|
|
121
|
+
:javascript_compressor => nil,
|
|
122
|
+
:css_compressor => nil
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def initialize(options = {})
|
|
126
|
+
|
|
127
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
|
128
|
+
|
|
129
|
+
# YUICompressor settings
|
|
130
|
+
@yuiCssLineBreak = -1
|
|
131
|
+
@yuiJsNoMunge = false
|
|
132
|
+
@yuiJsPreserveAllSemiColons = false
|
|
133
|
+
@yuiJsDisableOptimizations = false
|
|
134
|
+
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def compress html
|
|
138
|
+
if not @options[:enabled] or html.nil? or html.length == 0
|
|
139
|
+
return html
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# preserved block containers
|
|
143
|
+
condCommentBlocks = []
|
|
144
|
+
preBlocks = []
|
|
145
|
+
taBlocks = []
|
|
146
|
+
scriptBlocks = []
|
|
147
|
+
styleBlocks = []
|
|
148
|
+
eventBlocks = []
|
|
149
|
+
skipBlocks = []
|
|
150
|
+
lineBreakBlocks = []
|
|
151
|
+
userBlocks = []
|
|
152
|
+
|
|
153
|
+
# preserve blocks
|
|
154
|
+
html = preserve_blocks(html, preBlocks, taBlocks, scriptBlocks, styleBlocks, eventBlocks, condCommentBlocks, skipBlocks, lineBreakBlocks, userBlocks)
|
|
155
|
+
|
|
156
|
+
# process pure html
|
|
157
|
+
html = process_html(html)
|
|
158
|
+
|
|
159
|
+
# process preserved blocks
|
|
160
|
+
process_preserved_blocks(preBlocks, taBlocks, scriptBlocks, styleBlocks, eventBlocks, condCommentBlocks, skipBlocks, lineBreakBlocks, userBlocks)
|
|
161
|
+
|
|
162
|
+
# put preserved blocks back
|
|
163
|
+
html = return_blocks(html, preBlocks, taBlocks, scriptBlocks, styleBlocks, eventBlocks, condCommentBlocks, skipBlocks, lineBreakBlocks, userBlocks)
|
|
164
|
+
|
|
165
|
+
html
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def preserve_blocks(html, preBlocks, taBlocks, scriptBlocks, styleBlocks, eventBlocks, condCommentBlocks, skipBlocks, lineBreakBlocks, userBlocks)
|
|
171
|
+
|
|
172
|
+
# preserve user blocks
|
|
173
|
+
preservePatterns = @options[:preserve_patterns]
|
|
174
|
+
unless (preservePatterns.nil?)
|
|
175
|
+
preservePatterns.each_with_index do |preservePattern, i|
|
|
176
|
+
userBlock = []
|
|
177
|
+
index = -1
|
|
178
|
+
|
|
179
|
+
html = html.gsub(preservePattern) do |match|
|
|
180
|
+
if match.strip.length > 0
|
|
181
|
+
userBlock << match
|
|
182
|
+
index += 1
|
|
183
|
+
message_format(TEMP_USER_BLOCK, i, index)
|
|
184
|
+
else
|
|
185
|
+
''
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
userBlocks << userBlock
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# preserve <!-- {{{ ---><!-- }}} ---> skip blocks
|
|
194
|
+
skipBlockIndex = -1
|
|
195
|
+
|
|
196
|
+
html = html.gsub(SKIP_PATTERN) do |match|
|
|
197
|
+
if $1.strip.length > 0
|
|
198
|
+
skipBlocks << match
|
|
199
|
+
skipBlockIndex += 1
|
|
200
|
+
message_format(TEMP_SKIP_BLOCK, skipBlockIndex)
|
|
201
|
+
else
|
|
202
|
+
match
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# preserve conditional comments
|
|
207
|
+
condCommentCompressor = self.clone
|
|
208
|
+
index = -1
|
|
209
|
+
|
|
210
|
+
html = html.gsub(COND_COMMENT_PATTERN) do |match|
|
|
211
|
+
if $2.strip.length > 0
|
|
212
|
+
index += 1
|
|
213
|
+
condCommentBlocks << ($1 + condCommentCompressor.compress($2) + $3)
|
|
214
|
+
message_format(TEMP_COND_COMMENT_BLOCK, index)
|
|
215
|
+
else
|
|
216
|
+
''
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# preserve inline events
|
|
221
|
+
index = -1
|
|
222
|
+
|
|
223
|
+
html = html.gsub(EVENT_PATTERN1) do |match|
|
|
224
|
+
if $2.strip.length > 0
|
|
225
|
+
eventBlocks << $2
|
|
226
|
+
index += 1
|
|
227
|
+
$1 + message_format(TEMP_EVENT_BLOCK, index) + $3
|
|
228
|
+
else
|
|
229
|
+
''
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
html = html.gsub(EVENT_PATTERN2) do |match|
|
|
234
|
+
if $2.strip.length > 0
|
|
235
|
+
eventBlocks << $2
|
|
236
|
+
index += 1
|
|
237
|
+
$1 + message_format(TEMP_EVENT_BLOCK, index) + $3
|
|
238
|
+
else
|
|
239
|
+
''
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# preserve PRE tags
|
|
244
|
+
index = -1
|
|
245
|
+
html = html.gsub PRE_PATTERN do |match|
|
|
246
|
+
if $2.strip.length > 0
|
|
247
|
+
index += 1
|
|
248
|
+
preBlocks << $2
|
|
249
|
+
$1 + message_format(TEMP_PRE_BLOCK, index) + $3
|
|
250
|
+
else
|
|
251
|
+
''
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# preserve SCRIPT tags
|
|
256
|
+
index = -1
|
|
257
|
+
|
|
258
|
+
html = html.gsub(SCRIPT_PATTERN) do |match|
|
|
259
|
+
group_1 = $1
|
|
260
|
+
group_2 = $2
|
|
261
|
+
group_3 = $3
|
|
262
|
+
# ignore empty scripts
|
|
263
|
+
if group_2.strip.length > 0
|
|
264
|
+
# check type
|
|
265
|
+
type = ""
|
|
266
|
+
if group_1 =~ TYPE_ATTR_PATTERN
|
|
267
|
+
type = $2.downcase
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
if type.length == 0 or type == 'text/javascript' or type == 'application/javascript'
|
|
271
|
+
# javascript block, preserve and compress with js compressor
|
|
272
|
+
scriptBlocks << group_2
|
|
273
|
+
index += 1
|
|
274
|
+
group_1 + message_format(TEMP_SCRIPT_BLOCK, index) + group_3
|
|
275
|
+
elsif type == 'text/x-jquery-tmpl'
|
|
276
|
+
# jquery template, ignore so it gets compressed with the rest of html
|
|
277
|
+
match
|
|
278
|
+
else
|
|
279
|
+
# some custom script, preserve it inside "skip blocks" so it won't be compressed with js compressor
|
|
280
|
+
skipBlocks << group_2
|
|
281
|
+
skipBlockIndex += 1
|
|
282
|
+
group_1 + message_format(TEMP_SKIP_BLOCK, skipBlockIndex) + group_3
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
else
|
|
286
|
+
match
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# preserve STYLE tags
|
|
291
|
+
index = -1
|
|
292
|
+
|
|
293
|
+
html = html.gsub(STYLE_PATTERN) do |match|
|
|
294
|
+
if $2.strip.length > 0
|
|
295
|
+
styleBlocks << $2
|
|
296
|
+
index += 1
|
|
297
|
+
$1 + message_format(TEMP_STYLE_BLOCK, index) + $3
|
|
298
|
+
else
|
|
299
|
+
match
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# preserve TEXTAREA tags
|
|
304
|
+
index = -1
|
|
305
|
+
html = html.gsub(TA_PATTERN) do |match|
|
|
306
|
+
if $2.strip.length > 0
|
|
307
|
+
taBlocks << $2
|
|
308
|
+
index += 1
|
|
309
|
+
$1 + message_format(TEMP_TEXT_AREA_BLOCK, index) + $3
|
|
310
|
+
else
|
|
311
|
+
''
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# preserve line breaks
|
|
316
|
+
if @options[:preserve_line_breaks]
|
|
317
|
+
index = -1
|
|
318
|
+
html = html.gsub(LINE_BREAK_PATTERN) do |match|
|
|
319
|
+
lineBreakBlocks << $1
|
|
320
|
+
index += 1
|
|
321
|
+
message_format(TEMP_LINE_BREAK_BLOCK, index)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
html
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def return_blocks(html, preBlocks, taBlocks, scriptBlocks, styleBlocks, eventBlocks, condCommentBlocks, skipBlocks, lineBreakBlocks, userBlocks)
|
|
329
|
+
|
|
330
|
+
# put line breaks back
|
|
331
|
+
if @options[:preserve_line_breaks]
|
|
332
|
+
html = html.gsub(TEMP_LINE_BREAK_PATTERN) do |match|
|
|
333
|
+
i = $1.to_i
|
|
334
|
+
if lineBreakBlocks.size > i
|
|
335
|
+
lineBreakBlocks[i]
|
|
336
|
+
else
|
|
337
|
+
''
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# put TEXTAREA blocks back
|
|
343
|
+
html = html.gsub(TEMP_TEXT_AREA_PATTERN) do |match|
|
|
344
|
+
i = $1.to_i
|
|
345
|
+
if taBlocks.size > i
|
|
346
|
+
taBlocks[i]
|
|
347
|
+
else
|
|
348
|
+
''
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# put STYLE blocks back
|
|
353
|
+
html = html.gsub(TEMP_STYLE_PATTERN) do |match|
|
|
354
|
+
i = $1.to_i
|
|
355
|
+
if styleBlocks.size > i
|
|
356
|
+
styleBlocks[i]
|
|
357
|
+
else
|
|
358
|
+
''
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# put SCRIPT blocks back
|
|
363
|
+
html = html.gsub(TEMP_SCRIPT_PATTERN) do |match|
|
|
364
|
+
i = $1.to_i
|
|
365
|
+
if scriptBlocks.size > i
|
|
366
|
+
scriptBlocks[i]
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# put PRE blocks back
|
|
371
|
+
html = html.gsub TEMP_PRE_PATTERN do |match|
|
|
372
|
+
i = $1.to_i
|
|
373
|
+
if preBlocks.size > i
|
|
374
|
+
preBlocks[i] # quoteReplacement ?
|
|
375
|
+
else
|
|
376
|
+
''
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# put event blocks back
|
|
381
|
+
html = html.gsub(TEMP_EVENT_PATTERN) do |match|
|
|
382
|
+
i = $1.to_i
|
|
383
|
+
if eventBlocks.size > i
|
|
384
|
+
eventBlocks[i]
|
|
385
|
+
else
|
|
386
|
+
''
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# put conditional comments back
|
|
391
|
+
html = html.gsub(TEMP_COND_COMMENT_PATTERN) do |match|
|
|
392
|
+
i = $1.to_i
|
|
393
|
+
if condCommentBlocks.size > i
|
|
394
|
+
condCommentBlocks[i] # quoteReplacement ?
|
|
395
|
+
else
|
|
396
|
+
''
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# put skip blocks back
|
|
401
|
+
html = html.gsub(TEMP_SKIP_PATTERN) do |match|
|
|
402
|
+
i = $1.to_i
|
|
403
|
+
if skipBlocks.size > i
|
|
404
|
+
skipBlocks[i]
|
|
405
|
+
else
|
|
406
|
+
''
|
|
407
|
+
end
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
# put user blocks back
|
|
411
|
+
unless @options[:preserve_patterns].nil?
|
|
412
|
+
@options[:preserve_patterns].each_with_index do |preservePattern, p|
|
|
413
|
+
tempUserPattern = Regexp.new("%%%~COMPRESS~USER#{p}~(\\d+?)~%%%")
|
|
414
|
+
html = html.gsub(tempUserPattern).each do |match|
|
|
415
|
+
i = $1.to_i
|
|
416
|
+
if userBlocks.size > p and userBlocks[p].size > i
|
|
417
|
+
userBlocks[p][i]
|
|
418
|
+
else
|
|
419
|
+
''
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
html
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def process_preserved_blocks(preBlocks, taBlocks, scriptBlocks, styleBlocks, eventBlocks, condCommentBlocks, skipBlocks, lineBreakBlocks, userBlocks)
|
|
429
|
+
# processPreBlocks(preBlocks)
|
|
430
|
+
# processTextAreaBlocks(taBlocks)
|
|
431
|
+
process_script_blocks(scriptBlocks)
|
|
432
|
+
process_style_blocks(styleBlocks)
|
|
433
|
+
process_event_blocks(eventBlocks)
|
|
434
|
+
# processCondCommentBlocks(condCommentBlocks)
|
|
435
|
+
# processSkipBlocks(skipBlocks)
|
|
436
|
+
# processUserBlocks(userBlocks)
|
|
437
|
+
# processLineBreakBlocks(lineBreakBlocks)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def process_script_blocks(scriptBlocks)
|
|
441
|
+
if @options[:compress_javascript]
|
|
442
|
+
scriptBlocks.map! do |block|
|
|
443
|
+
compress_javascript(block)
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def process_style_blocks(styleBlocks)
|
|
449
|
+
if @options[:compress_css]
|
|
450
|
+
styleBlocks.map! do |block|
|
|
451
|
+
compress_css_styles(block)
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def process_event_blocks(eventBlocks)
|
|
457
|
+
if @options[:remove_javascript_protocol]
|
|
458
|
+
eventBlocks.map! do |block|
|
|
459
|
+
remove_javascript_protocol(block)
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def compress_javascript(source)
|
|
465
|
+
# set default javascript compressor
|
|
466
|
+
javaScriptCompressor = @options[:javascript_compressor]
|
|
467
|
+
|
|
468
|
+
if javaScriptCompressor.nil?
|
|
469
|
+
javaScriptCompressor = YUI::JavaScriptCompressor.new(
|
|
470
|
+
:munge => !@yuiJsNoMunge,
|
|
471
|
+
:preserve_semicolons => !@yuiJsDisableOptimizations,
|
|
472
|
+
:optimize => !@yuiJsDisableOptimizations,
|
|
473
|
+
:line_break => @yuiJsLineBreak
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# detect CDATA wrapper
|
|
478
|
+
cdataWrapper = false
|
|
479
|
+
if source =~ CDATA_PATTERN
|
|
480
|
+
cdataWrapper = true
|
|
481
|
+
source = $1
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
result = javaScriptCompressor.compress(source).strip
|
|
485
|
+
|
|
486
|
+
if cdataWrapper
|
|
487
|
+
result = "<![CDATA[" + result + "]]>"
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
result
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def compress_css_styles(source)
|
|
494
|
+
# set default css compressor
|
|
495
|
+
cssCompressor = @options[:css_compressor]
|
|
496
|
+
|
|
497
|
+
if cssCompressor.nil?
|
|
498
|
+
cssCompressor = YUI::CssCompressor.new(:line_break => @yuiCssLineBreak)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# detect CDATA wrapper
|
|
502
|
+
cdataWrapper = false
|
|
503
|
+
if source =~ CDATA_PATTERN
|
|
504
|
+
cdataWrapper = true
|
|
505
|
+
source = $1
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
result = cssCompressor.compress(source)
|
|
509
|
+
|
|
510
|
+
if cdataWrapper
|
|
511
|
+
result = "<![CDATA[" + result + "]]>"
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
result
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def remove_javascript_protocol(source)
|
|
518
|
+
# remove javascript: from inline events
|
|
519
|
+
source.sub(EVENT_JS_PROTOCOL_PATTERN, '\1')
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def process_html(html)
|
|
523
|
+
|
|
524
|
+
# remove comments
|
|
525
|
+
html = remove_comments(html)
|
|
526
|
+
|
|
527
|
+
# simplify doctype
|
|
528
|
+
html = simple_doctype(html)
|
|
529
|
+
|
|
530
|
+
# remove script attributes
|
|
531
|
+
html = remove_script_attributes(html)
|
|
532
|
+
|
|
533
|
+
# remove style attributes
|
|
534
|
+
html = remove_style_attributes(html)
|
|
535
|
+
|
|
536
|
+
# remove link attributes
|
|
537
|
+
html = remove_link_attributes(html)
|
|
538
|
+
|
|
539
|
+
# remove form attributes
|
|
540
|
+
html = remove_form_attributes(html)
|
|
541
|
+
|
|
542
|
+
# remove input attributes
|
|
543
|
+
html = remove_input_attributes(html)
|
|
544
|
+
|
|
545
|
+
# simplify boolean attributes
|
|
546
|
+
html = simple_boolean_attributes(html)
|
|
547
|
+
|
|
548
|
+
# remove http from attributes
|
|
549
|
+
html = remove_http_protocol(html)
|
|
550
|
+
|
|
551
|
+
# remove https from attributes
|
|
552
|
+
html = remove_https_protocol(html)
|
|
553
|
+
|
|
554
|
+
# remove inter-tag spaces
|
|
555
|
+
html = remove_intertag_spaces(html)
|
|
556
|
+
|
|
557
|
+
# remove multi whitespace characters
|
|
558
|
+
html = remove_multi_spaces(html)
|
|
559
|
+
|
|
560
|
+
# remove spaces around equals sign and ending spaces
|
|
561
|
+
html = remove_spaces_inside_tags(html)
|
|
562
|
+
|
|
563
|
+
# remove quotes from tag attributes
|
|
564
|
+
html = remove_quotes_inside_tags(html)
|
|
565
|
+
|
|
566
|
+
# # remove surrounding spaces
|
|
567
|
+
html = remove_surrounding_spaces(html)
|
|
568
|
+
|
|
569
|
+
html.strip
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
def remove_comments(html)
|
|
573
|
+
|
|
574
|
+
# remove comments
|
|
575
|
+
if @options[:remove_comments]
|
|
576
|
+
html = html.gsub(COMMENT_PATTERN, '')
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
html
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def simple_doctype(html)
|
|
583
|
+
# simplify doctype
|
|
584
|
+
if @options[:simple_doctype]
|
|
585
|
+
html = html.gsub(DOCTYPE_PATTERN, '<!DOCTYPE html>')
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
html
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
def remove_script_attributes(html)
|
|
592
|
+
if @options[:remove_script_attributes]
|
|
593
|
+
#remove type from script tags
|
|
594
|
+
html = html.gsub(JS_TYPE_ATTR_PATTERN, '\1\3')
|
|
595
|
+
|
|
596
|
+
#remove language from script tags
|
|
597
|
+
html = html.gsub(JS_LANG_ATTR_PATTERN, '\1\3')
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
html
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
def remove_style_attributes(html)
|
|
604
|
+
# remove type from style tags
|
|
605
|
+
if @options[:remove_style_attributes]
|
|
606
|
+
html = html.gsub(STYLE_TYPE_ATTR_PATTERN, '\1\3')
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
html
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def remove_link_attributes(html)
|
|
613
|
+
# remove type from link tags with rel=stylesheet
|
|
614
|
+
if @options[:remove_link_attributes]
|
|
615
|
+
html = html.gsub(LINK_TYPE_ATTR_PATTERN) do |match|
|
|
616
|
+
group_1 = $1
|
|
617
|
+
group_3 = $3
|
|
618
|
+
# if rel=stylesheet
|
|
619
|
+
if match =~ LINK_REL_ATTR_PATTERN
|
|
620
|
+
group_1 + group_3
|
|
621
|
+
else
|
|
622
|
+
match
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
html
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def remove_form_attributes(html)
|
|
631
|
+
# remove method from form tags
|
|
632
|
+
if @options[:remove_form_attributes]
|
|
633
|
+
html = html.gsub(FORM_METHOD_ATTR_PATTERN, '\1\3')
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
html
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def remove_input_attributes(html)
|
|
640
|
+
# remove type from input tags
|
|
641
|
+
if @options[:remove_input_attributes]
|
|
642
|
+
html = html.gsub(INPUT_TYPE_ATTR_PATTERN, '\1\3')
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
html
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def remove_http_protocol(html)
|
|
649
|
+
# remove http protocol from tag attributes
|
|
650
|
+
if @options[:remove_http_protocol]
|
|
651
|
+
html = html.gsub(HTTP_PROTOCOL_PATTERN) do |match|
|
|
652
|
+
group_1 = $1
|
|
653
|
+
group_2 = $2
|
|
654
|
+
|
|
655
|
+
if match =~ REL_EXTERNAL_PATTERN
|
|
656
|
+
match
|
|
657
|
+
else
|
|
658
|
+
"#{group_1}#{group_2}"
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
html
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def remove_https_protocol(html)
|
|
667
|
+
# remove https protocol from tag attributes
|
|
668
|
+
if @options[:remove_https_protocol]
|
|
669
|
+
html = html.gsub(HTTPS_PROTOCOL_PATTERN) do |match|
|
|
670
|
+
group_1 = $1
|
|
671
|
+
group_2 = $2
|
|
672
|
+
|
|
673
|
+
if match =~ REL_EXTERNAL_PATTERN
|
|
674
|
+
match
|
|
675
|
+
else
|
|
676
|
+
"#{group_1}#{group_2}"
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
html
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def remove_intertag_spaces(html)
|
|
685
|
+
|
|
686
|
+
# remove inter-tag spaces
|
|
687
|
+
if @options[:remove_intertag_spaces]
|
|
688
|
+
html = html.gsub(INTERTAG_PATTERN_TAG_TAG, '><')
|
|
689
|
+
html = html.gsub(INTERTAG_PATTERN_TAG_CUSTOM, '>%%%~')
|
|
690
|
+
html = html.gsub(INTERTAG_PATTERN_CUSTOM_TAG, '~%%%<')
|
|
691
|
+
html = html.gsub(INTERTAG_PATTERN_CUSTOM_CUSTOM, '~%%%%%%~')
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
html
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def remove_spaces_inside_tags(html)
|
|
698
|
+
#remove spaces around equals sign inside tags
|
|
699
|
+
|
|
700
|
+
html = html.gsub(TAG_PROPERTY_PATTERN, '\1=')
|
|
701
|
+
|
|
702
|
+
#remove ending spaces inside tags
|
|
703
|
+
|
|
704
|
+
html.gsub!(TAG_END_SPACE_PATTERN) do |match|
|
|
705
|
+
|
|
706
|
+
group_1 = $1
|
|
707
|
+
group_2 = $2
|
|
708
|
+
|
|
709
|
+
# keep space if attribute value is unquoted before trailing slash
|
|
710
|
+
if group_2.start_with?("/") and (TAG_LAST_UNQUOTED_VALUE_PATTERN =~ group_1)
|
|
711
|
+
"#{group_1} #{group_2}"
|
|
712
|
+
else
|
|
713
|
+
"#{group_1}#{group_2}"
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
html
|
|
719
|
+
end
|
|
720
|
+
|
|
721
|
+
def remove_quotes_inside_tags(html)
|
|
722
|
+
if @options[:remove_quotes]
|
|
723
|
+
html = html.gsub(TAG_QUOTE_PATTERN) do |match|
|
|
724
|
+
# if quoted attribute is followed by "/" add extra space
|
|
725
|
+
if $3.strip.length == 0
|
|
726
|
+
"=#{$2}"
|
|
727
|
+
else
|
|
728
|
+
"=#{$2} #{$3}"
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
html
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
def remove_multi_spaces(html)
|
|
737
|
+
# collapse multiple spaces
|
|
738
|
+
if @options[:remove_multi_spaces]
|
|
739
|
+
html = html.gsub(MULTISPACE_PATTERN, ' ')
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
html
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
def simple_boolean_attributes(html)
|
|
746
|
+
# simplify boolean attributes
|
|
747
|
+
if @options[:simple_boolean_attributes]
|
|
748
|
+
html = html.gsub(BOOLEAN_ATTR_PATTERN, '\1\2\4')
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
html
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
def remove_surrounding_spaces(html)
|
|
755
|
+
# remove spaces around provided tags
|
|
756
|
+
|
|
757
|
+
unless @options[:remove_surrounding_spaces].nil?
|
|
758
|
+
pattern = case @options[:remove_surrounding_spaces].downcase
|
|
759
|
+
when BLOCK_TAGS_MIN
|
|
760
|
+
SURROUNDING_SPACES_MIN_PATTERN
|
|
761
|
+
when BLOCK_TAGS_MAX
|
|
762
|
+
SURROUNDING_SPACES_MAX_PATTERN
|
|
763
|
+
when ALL_TAGS
|
|
764
|
+
SURROUNDING_SPACES_ALL_PATTERN
|
|
765
|
+
else
|
|
766
|
+
Regexp.new("\\s*(</?(?:" + @options[:remove_surrounding_spaces].gsub(",", "|") + ")(?:>|[\\s/][^>]*>))\\s*", Regexp::MULTILINE | Regexp::IGNORECASE)
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
html = html.gsub(pattern, '\1')
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
html
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
private
|
|
776
|
+
|
|
777
|
+
def message_format(message, *params)
|
|
778
|
+
message.gsub(/\{(\d+),number,#\}/) do
|
|
779
|
+
params[$1.to_i]
|
|
780
|
+
end
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
end
|