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.
Files changed (130) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +13 -0
  4. data/README.md +83 -0
  5. data/Rakefile +2 -0
  6. data/htmlcompressor.gemspec +21 -0
  7. data/lib/htmlcompressor.rb +6 -0
  8. data/lib/htmlcompressor/compressor.rb +785 -0
  9. data/lib/htmlcompressor/rack.rb +54 -0
  10. data/lib/htmlcompressor/version.rb +3 -0
  11. data/test/compressor_test.rb +254 -0
  12. data/test/resources/html/.svn/all-wcprops +275 -0
  13. data/test/resources/html/.svn/entries +1558 -0
  14. data/test/resources/html/.svn/prop-base/testRemoveHttpProtocol.html.svn-base +5 -0
  15. data/test/resources/html/.svn/prop-base/testRemoveHttpProtocolResult.html.svn-base +5 -0
  16. data/test/resources/html/.svn/prop-base/testRemoveHttpsProtocol.html.svn-base +5 -0
  17. data/test/resources/html/.svn/prop-base/testRemoveHttpsProtocolResult.html.svn-base +5 -0
  18. data/test/resources/html/.svn/prop-base/testRemoveSpacesInsideTags.html.svn-base +5 -0
  19. data/test/resources/html/.svn/prop-base/testRemoveSpacesInsideTagsResult.html.svn-base +5 -0
  20. data/test/resources/html/.svn/prop-base/testSurroundingSpaces.html.svn-base +5 -0
  21. data/test/resources/html/.svn/prop-base/testSurroundingSpacesResult.html.svn-base +5 -0
  22. data/test/resources/html/.svn/text-base/testCompress.html.svn-base +10 -0
  23. data/test/resources/html/.svn/text-base/testCompressCss.html.svn-base +11 -0
  24. data/test/resources/html/.svn/text-base/testCompressCssResult.html.svn-base +6 -0
  25. data/test/resources/html/.svn/text-base/testCompressJavaScript.html.svn-base +24 -0
  26. data/test/resources/html/.svn/text-base/testCompressJavaScriptClosureResult.html.svn-base +7 -0
  27. data/test/resources/html/.svn/text-base/testCompressJavaScriptYuiResult.html.svn-base +7 -0
  28. data/test/resources/html/.svn/text-base/testCompressResult.html.svn-base +1 -0
  29. data/test/resources/html/.svn/text-base/testEnabled.html.svn-base +1 -0
  30. data/test/resources/html/.svn/text-base/testEnabledResult.html.svn-base +1 -0
  31. data/test/resources/html/.svn/text-base/testPreserveLineBreaks.html.svn-base +11 -0
  32. data/test/resources/html/.svn/text-base/testPreserveLineBreaksResult.html.svn-base +5 -0
  33. data/test/resources/html/.svn/text-base/testPreservePatterns.html.svn-base +7 -0
  34. data/test/resources/html/.svn/text-base/testPreservePatternsResult.html.svn-base +1 -0
  35. data/test/resources/html/.svn/text-base/testRemoveComments.html.svn-base +17 -0
  36. data/test/resources/html/.svn/text-base/testRemoveCommentsResult.html.svn-base +4 -0
  37. data/test/resources/html/.svn/text-base/testRemoveFormAttributes.html.svn-base +1 -0
  38. data/test/resources/html/.svn/text-base/testRemoveFormAttributesResult.html.svn-base +1 -0
  39. data/test/resources/html/.svn/text-base/testRemoveHttpProtocol.html.svn-base +1 -0
  40. data/test/resources/html/.svn/text-base/testRemoveHttpProtocolResult.html.svn-base +1 -0
  41. data/test/resources/html/.svn/text-base/testRemoveHttpsProtocol.html.svn-base +1 -0
  42. data/test/resources/html/.svn/text-base/testRemoveHttpsProtocolResult.html.svn-base +1 -0
  43. data/test/resources/html/.svn/text-base/testRemoveInputAttributes.html.svn-base +1 -0
  44. data/test/resources/html/.svn/text-base/testRemoveInputAttributesResult.html.svn-base +1 -0
  45. data/test/resources/html/.svn/text-base/testRemoveIntertagSpaces.html.svn-base +5 -0
  46. data/test/resources/html/.svn/text-base/testRemoveIntertagSpacesResult.html.svn-base +1 -0
  47. data/test/resources/html/.svn/text-base/testRemoveJavaScriptProtocol.html.svn-base +1 -0
  48. data/test/resources/html/.svn/text-base/testRemoveJavaScriptProtocolResult.html.svn-base +1 -0
  49. data/test/resources/html/.svn/text-base/testRemoveLinkAttributes.html.svn-base +5 -0
  50. data/test/resources/html/.svn/text-base/testRemoveLinkAttributesResult.html.svn-base +1 -0
  51. data/test/resources/html/.svn/text-base/testRemoveMultiSpaces.html.svn-base +1 -0
  52. data/test/resources/html/.svn/text-base/testRemoveMultiSpacesResult.html.svn-base +1 -0
  53. data/test/resources/html/.svn/text-base/testRemoveQuotes.html.svn-base +7 -0
  54. data/test/resources/html/.svn/text-base/testRemoveQuotesResult.html.svn-base +1 -0
  55. data/test/resources/html/.svn/text-base/testRemoveScriptAttributes.html.svn-base +6 -0
  56. data/test/resources/html/.svn/text-base/testRemoveScriptAttributesResult.html.svn-base +1 -0
  57. data/test/resources/html/.svn/text-base/testRemoveSpacesInsideTags.html.svn-base +1 -0
  58. data/test/resources/html/.svn/text-base/testRemoveSpacesInsideTagsResult.html.svn-base +1 -0
  59. data/test/resources/html/.svn/text-base/testRemoveStyleAttributes.html.svn-base +3 -0
  60. data/test/resources/html/.svn/text-base/testRemoveStyleAttributesResult.html.svn-base +1 -0
  61. data/test/resources/html/.svn/text-base/testSimpleBooleanAttributes.html.svn-base +1 -0
  62. data/test/resources/html/.svn/text-base/testSimpleBooleanAttributesResult.html.svn-base +1 -0
  63. data/test/resources/html/.svn/text-base/testSimpleDoctype.html.svn-base +2 -0
  64. data/test/resources/html/.svn/text-base/testSimpleDoctypeResult.html.svn-base +1 -0
  65. data/test/resources/html/.svn/text-base/testSurroundingSpaces.html.svn-base +1 -0
  66. data/test/resources/html/.svn/text-base/testSurroundingSpacesResult.html.svn-base +1 -0
  67. data/test/resources/html/testCompress.html +10 -0
  68. data/test/resources/html/testCompressCss.html +11 -0
  69. data/test/resources/html/testCompressCssResult.html +6 -0
  70. data/test/resources/html/testCompressJavaScript.html +24 -0
  71. data/test/resources/html/testCompressJavaScriptClosureResult.html +7 -0
  72. data/test/resources/html/testCompressJavaScriptYuiResult.html +7 -0
  73. data/test/resources/html/testCompressResult.html +1 -0
  74. data/test/resources/html/testEnabled.html +1 -0
  75. data/test/resources/html/testEnabledResult.html +1 -0
  76. data/test/resources/html/testPreserveLineBreaks.html +11 -0
  77. data/test/resources/html/testPreserveLineBreaksResult.html +5 -0
  78. data/test/resources/html/testPreservePatterns.html +7 -0
  79. data/test/resources/html/testPreservePatternsResult.html +1 -0
  80. data/test/resources/html/testRemoveComments.html +17 -0
  81. data/test/resources/html/testRemoveCommentsResult.html +4 -0
  82. data/test/resources/html/testRemoveFormAttributes.html +1 -0
  83. data/test/resources/html/testRemoveFormAttributesResult.html +1 -0
  84. data/test/resources/html/testRemoveHttpProtocol.html +1 -0
  85. data/test/resources/html/testRemoveHttpProtocolResult.html +1 -0
  86. data/test/resources/html/testRemoveHttpsProtocol.html +1 -0
  87. data/test/resources/html/testRemoveHttpsProtocolResult.html +1 -0
  88. data/test/resources/html/testRemoveInputAttributes.html +1 -0
  89. data/test/resources/html/testRemoveInputAttributesResult.html +1 -0
  90. data/test/resources/html/testRemoveIntertagSpaces.html +5 -0
  91. data/test/resources/html/testRemoveIntertagSpacesResult.html +1 -0
  92. data/test/resources/html/testRemoveJavaScriptProtocol.html +1 -0
  93. data/test/resources/html/testRemoveJavaScriptProtocolResult.html +1 -0
  94. data/test/resources/html/testRemoveLinkAttributes.html +5 -0
  95. data/test/resources/html/testRemoveLinkAttributesResult.html +1 -0
  96. data/test/resources/html/testRemoveMultiSpaces.html +1 -0
  97. data/test/resources/html/testRemoveMultiSpacesResult.html +1 -0
  98. data/test/resources/html/testRemoveQuotes.html +7 -0
  99. data/test/resources/html/testRemoveQuotesResult.html +1 -0
  100. data/test/resources/html/testRemoveScriptAttributes.html +6 -0
  101. data/test/resources/html/testRemoveScriptAttributesResult.html +1 -0
  102. data/test/resources/html/testRemoveSpacesInsideTags.html +1 -0
  103. data/test/resources/html/testRemoveSpacesInsideTagsResult.html +1 -0
  104. data/test/resources/html/testRemoveStyleAttributes.html +3 -0
  105. data/test/resources/html/testRemoveStyleAttributesResult.html +1 -0
  106. data/test/resources/html/testSimpleBooleanAttributes.html +1 -0
  107. data/test/resources/html/testSimpleBooleanAttributesResult.html +1 -0
  108. data/test/resources/html/testSimpleDoctype.html +2 -0
  109. data/test/resources/html/testSimpleDoctypeResult.html +1 -0
  110. data/test/resources/html/testSurroundingSpaces.html +1 -0
  111. data/test/resources/html/testSurroundingSpacesResult.html +1 -0
  112. data/test/resources/xml/.svn/all-wcprops +53 -0
  113. data/test/resources/xml/.svn/entries +300 -0
  114. data/test/resources/xml/.svn/text-base/testCompress.xml.svn-base +11 -0
  115. data/test/resources/xml/.svn/text-base/testCompressResult.xml.svn-base +5 -0
  116. data/test/resources/xml/.svn/text-base/testEnabled.xml.svn-base +1 -0
  117. data/test/resources/xml/.svn/text-base/testEnabledResult.xml.svn-base +1 -0
  118. data/test/resources/xml/.svn/text-base/testRemoveComments.xml.svn-base +5 -0
  119. data/test/resources/xml/.svn/text-base/testRemoveCommentsResult.xml.svn-base +4 -0
  120. data/test/resources/xml/.svn/text-base/testRemoveIntertagSpaces.xml.svn-base +11 -0
  121. data/test/resources/xml/.svn/text-base/testRemoveIntertagSpacesResult.xml.svn-base +5 -0
  122. data/test/resources/xml/testCompress.xml +11 -0
  123. data/test/resources/xml/testCompressResult.xml +5 -0
  124. data/test/resources/xml/testEnabled.xml +1 -0
  125. data/test/resources/xml/testEnabledResult.xml +1 -0
  126. data/test/resources/xml/testRemoveComments.xml +5 -0
  127. data/test/resources/xml/testRemoveCommentsResult.xml +4 -0
  128. data/test/resources/xml/testRemoveIntertagSpaces.xml +11 -0
  129. data/test/resources/xml/testRemoveIntertagSpacesResult.xml +5 -0
  130. metadata +332 -0
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in htmlcompressor.gemspec
4
+ gemspec
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.
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -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,6 @@
1
+ require "htmlcompressor/version"
2
+ require "htmlcompressor/compressor"
3
+ require "htmlcompressor/rack"
4
+
5
+ module HtmlCompressor
6
+ 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>&lt;?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>&lt;% ... %></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>&lt;--# ... --></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>&lt;div></code> and <code>&lt;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