Minify_HTML

Compress HTML.

Defined (1)

The class is defined in the following location(s).

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