htmlcompressor 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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