PluginProbe
WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript / trunk
WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript vtrunk
trunk 1.0 1.1 1.2 1.3 1.3.1 1.3.2 1.4 1.5 1.5.1 1.6 2.0 2.0.1
wp-super-minify / includes / min / lib / Minify / HTML.php

HTML.php in WP Super Minify • Minify, Compress and Cache HTML, CSS & JavaScript trunk, at includes/min/lib/Minify/HTML.php

269 lines 8.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class Minify_HTML
4 * @package Minify
5 */
6
7 /**
8 * Compress HTML
9 *
10 * This is a heavy regex-based removal of whitespace, unnecessary comments and
11 * tokens. IE conditional comments are preserved. There are also options to have
12 * STYLE and SCRIPT blocks compressed by callback functions.
13 *
14 * A test suite is available.
15 *
16 * @package Minify
17 * @author Stephen Clay <steve@mrclay.org>
18 */
19 class Minify_HTML
20 {
21 /**
22 * @var boolean
23 */
24 protected $_jsCleanComments = true;
25
26 /**
27 * @var string
28 */
29 protected $_html;
30
31 /**
32 * "Minify" an HTML page
33 *
34 * @param string $html
35 *
36 * @param array $options
37 *
38 * 'cssMinifier' : (optional) callback function to process content of STYLE
39 * elements.
40 *
41 * 'jsMinifier' : (optional) callback function to process content of SCRIPT
42 * elements. Note: the type attribute is ignored.
43 *
44 * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
45 * unset, minify will sniff for an XHTML doctype.
46 *
47 * @return string
48 */
49 public static function minify($html, $options = array())
50 {
51 $min = new self($html, $options);
52
53 return $min->process();
54 }
55
56 /**
57 * Create a minifier object
58 *
59 * @param string $html
60 *
61 * @param array $options
62 *
63 * 'cssMinifier' : (optional) callback function to process content of STYLE
64 * elements.
65 *
66 * 'jsMinifier' : (optional) callback function to process content of SCRIPT
67 * elements. Note: the type attribute is ignored.
68 *
69 * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block
70 *
71 * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If
72 * unset, minify will sniff for an XHTML doctype.
73 */
74 public function __construct($html, $options = array())
75 {
76 $this->_html = str_replace("\r\n", "\n", trim($html));
77 if (isset($options['xhtml'])) {
78 $this->_isXhtml = (bool)$options['xhtml'];
79 }
80 if (isset($options['cssMinifier'])) {
81 $this->_cssMinifier = $options['cssMinifier'];
82 }
83 if (isset($options['jsMinifier'])) {
84 $this->_jsMinifier = $options['jsMinifier'];
85 }
86 if (isset($options['jsCleanComments'])) {
87 $this->_jsCleanComments = (bool)$options['jsCleanComments'];
88 }
89 }
90
91 /**
92 * Minify the markeup given in the constructor
93 *
94 * @return string
95 */
96 public function process()
97 {
98 if ($this->_isXhtml === null) {
99 $this->_isXhtml = (false !== strpos($this->_html, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
100 }
101
102 $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
103 $this->_placeholders = array();
104
105 // replace SCRIPTs (and minify) with placeholders
106 $this->_html = preg_replace_callback(
107 '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/iu',
108 array($this, '_removeScriptCB'),
109 $this->_html
110 );
111
112 // replace STYLEs (and minify) with placeholders
113 $this->_html = preg_replace_callback(
114 '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/iu',
115 array($this, '_removeStyleCB'),
116 $this->_html
117 );
118
119 // remove HTML comments (not containing IE conditional comments).
120 $this->_html = preg_replace_callback(
121 '/<!--([\\s\\S]*?)-->/u',
122 array($this, '_commentCB'),
123 $this->_html
124 );
125
126 // replace PREs with placeholders
127 $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/iu', array($this, '_removePreCB'), $this->_html);
128
129 // replace TEXTAREAs with placeholders
130 $this->_html = preg_replace_callback(
131 '/\\s*<textarea(\\b[^>]*?>[\\s\\S]*?<\\/textarea>)\\s*/iu',
132 array($this, '_removeTextareaCB'),
133 $this->_html
134 );
135
136 // trim each line.
137 // @todo take into account attribute values that span multiple lines.
138 $this->_html = preg_replace('/^\\s+|\\s+$/mu', '', $this->_html);
139
140 // remove ws around block/undisplayed elements
141 $this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body'
142 .'|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form'
143 .'|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav'
144 .'|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)'
145 .'|ul|video)\\b[^>]*>)/iu', '$1', $this->_html);
146
147 // remove ws outside of all elements
148 $this->_html = preg_replace(
149 '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?</u',
150 '>$1$2$3<',
151 $this->_html
152 );
153
154 // use newlines before 1st attribute in open tags (to limit line lengths)
155 $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/iu', "$1\n$2", $this->_html);
156
157 // fill placeholders
158 $this->_html = str_replace(
159 array_keys($this->_placeholders),
160 array_values($this->_placeholders),
161 $this->_html
162 );
163 // issue 229: multi-pass to catch scripts that didn't get replaced in textareas
164 $this->_html = str_replace(
165 array_keys($this->_placeholders),
166 array_values($this->_placeholders),
167 $this->_html
168 );
169
170 return $this->_html;
171 }
172
173 protected function _commentCB($m)
174 {
175 return (0 === strpos($m[1], '[') || false !== strpos($m[1], '<![') || 0 === strpos($m[1], '#'))
176 ? $m[0]
177 : '';
178 }
179
180 protected function _reservePlace($content)
181 {
182 $placeholder = '%' . $this->_replacementHash . count($this->_placeholders) . '%';
183 $this->_placeholders[$placeholder] = $content;
184
185 return $placeholder;
186 }
187
188 protected $_isXhtml;
189 protected $_replacementHash;
190 protected $_placeholders = array();
191 protected $_cssMinifier;
192 protected $_jsMinifier;
193
194 protected function _removePreCB($m)
195 {
196 return $this->_reservePlace("<pre{$m[1]}");
197 }
198
199 protected function _removeTextareaCB($m)
200 {
201 return $this->_reservePlace("<textarea{$m[1]}");
202 }
203
204 protected function _removeStyleCB($m)
205 {
206 $openStyle = "<style{$m[1]}";
207 $css = $m[2];
208 // remove HTML comments
209 $css = preg_replace('/(?:^\\s*<!--|-->\\s*$)/u', '', $css);
210
211 // remove CDATA section markers
212 $css = $this->_removeCdata($css);
213
214 // minify
215 $minifier = $this->_cssMinifier
216 ? $this->_cssMinifier
217 : 'trim';
218 $css = call_user_func($minifier, $css);
219
220 return $this->_reservePlace(
221 $this->_needsCdata($css)
222 ? "{$openStyle}/*<![CDATA[*/{$css}/*]]>*/</style>"
223 : "{$openStyle}{$css}</style>"
224 );
225 }
226
227 protected function _removeScriptCB($m)
228 {
229 $openScript = "<script{$m[2]}";
230 $js = $m[3];
231
232 // whitespace surrounding? preserve at least one space
233 $ws1 = ($m[1] === '') ? '' : ' ';
234 $ws2 = ($m[4] === '') ? '' : ' ';
235
236 // remove HTML comments (and ending "//" if present)
237 if ($this->_jsCleanComments) {
238 $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\s*-->\\s*$)/u', '', $js);
239 }
240
241 // remove CDATA section markers
242 $js = $this->_removeCdata($js);
243
244 // minify
245 $minifier = $this->_jsMinifier
246 ? $this->_jsMinifier
247 : 'trim';
248 $js = call_user_func($minifier, $js);
249
250 return $this->_reservePlace(
251 $this->_needsCdata($js)
252 ? "{$ws1}{$openScript}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
253 : "{$ws1}{$openScript}{$js}</script>{$ws2}"
254 );
255 }
256
257 protected function _removeCdata($str)
258 {
259 return (false !== strpos($str, '<![CDATA['))
260 ? str_replace(array('<![CDATA[', ']]>'), '', $str)
261 : $str;
262 }
263
264 protected function _needsCdata($str)
265 {
266 return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/u', $str));
267 }
268 }
269