PluginProbe
Autoptimize / 0.6
Autoptimize v0.6
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / minify-html.php

minify-html.php in Autoptimize 0.6, at classes/minify-html.php

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