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