|
|
<?php
|
|
|
/**
|
|
|
* HTML Compressor Class
|
|
|
* 全站HTML代码压缩算法
|
|
|
*
|
|
|
* @package Nenghui Energy Theme
|
|
|
* @since 1.0.0
|
|
|
*/
|
|
|
|
|
|
// 防止直接访问
|
|
|
if (!defined('ABSPATH')) {
|
|
|
exit;
|
|
|
}
|
|
|
|
|
|
class NenghuiHTMLCompressor {
|
|
|
|
|
|
/**
|
|
|
* 压缩配置选项
|
|
|
*/
|
|
|
private $options = array(
|
|
|
'remove_comments' => true,
|
|
|
'remove_whitespace' => true,
|
|
|
'compress_css' => true,
|
|
|
'compress_js' => true,
|
|
|
'preserve_line_breaks' => false,
|
|
|
'preserve_pre_content' => true,
|
|
|
'preserve_textarea_content' => true,
|
|
|
'preserve_script_content' => true,
|
|
|
'preserve_style_content' => true
|
|
|
);
|
|
|
|
|
|
/**
|
|
|
* 需要保护的标签内容
|
|
|
*/
|
|
|
private $preserve_tags = array('pre', 'textarea', 'script', 'style');
|
|
|
|
|
|
/**
|
|
|
* 保护的内容存储
|
|
|
*/
|
|
|
private $preserved_content = array();
|
|
|
|
|
|
/**
|
|
|
* 构造函数
|
|
|
*/
|
|
|
public function __construct($options = array()) {
|
|
|
$this->options = array_merge($this->options, $options);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 主要压缩方法
|
|
|
*
|
|
|
* @param string $html HTML内容
|
|
|
* @return string 压缩后的HTML
|
|
|
*/
|
|
|
public function compress($html) {
|
|
|
if (empty($html)) {
|
|
|
return $html;
|
|
|
}
|
|
|
|
|
|
// 重置保护内容
|
|
|
$this->preserved_content = array();
|
|
|
|
|
|
// 1. 保护需要保留原始格式的内容
|
|
|
$html = $this->preserveContent($html);
|
|
|
|
|
|
// 2. 移除HTML注释
|
|
|
if ($this->options['remove_comments']) {
|
|
|
$html = $this->removeComments($html);
|
|
|
}
|
|
|
|
|
|
// 3. 压缩空白字符
|
|
|
if ($this->options['remove_whitespace']) {
|
|
|
$html = $this->compressWhitespace($html);
|
|
|
}
|
|
|
|
|
|
// 4. 恢复保护的内容
|
|
|
$html = $this->restoreContent($html);
|
|
|
|
|
|
// 5. 压缩内联CSS和JS
|
|
|
if ($this->options['compress_css'] || $this->options['compress_js']) {
|
|
|
$html = $this->compressInlineCode($html);
|
|
|
}
|
|
|
|
|
|
return trim($html);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 保护需要保留原始格式的内容
|
|
|
*
|
|
|
* @param string $html
|
|
|
* @return string
|
|
|
*/
|
|
|
private function preserveContent($html) {
|
|
|
$index = 0;
|
|
|
|
|
|
// 保护 <pre> 标签内容
|
|
|
if ($this->options['preserve_pre_content']) {
|
|
|
$html = preg_replace_callback(
|
|
|
'/<pre\b[^>]*>.*?<\/pre>/is',
|
|
|
function($matches) use (&$index) {
|
|
|
$placeholder = "<!--NENGHUI_PRESERVE_PRE_{$index}-->";
|
|
|
$this->preserved_content[$placeholder] = $matches[0];
|
|
|
$index++;
|
|
|
return $placeholder;
|
|
|
},
|
|
|
$html
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// 保护 <textarea> 标签内容
|
|
|
if ($this->options['preserve_textarea_content']) {
|
|
|
$html = preg_replace_callback(
|
|
|
'/<textarea\b[^>]*>.*?<\/textarea>/is',
|
|
|
function($matches) use (&$index) {
|
|
|
$placeholder = "<!--NENGHUI_PRESERVE_TEXTAREA_{$index}-->";
|
|
|
$this->preserved_content[$placeholder] = $matches[0];
|
|
|
$index++;
|
|
|
return $placeholder;
|
|
|
},
|
|
|
$html
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// 保护 <script> 标签内容
|
|
|
if ($this->options['preserve_script_content']) {
|
|
|
$html = preg_replace_callback(
|
|
|
'/<script\b[^>]*>.*?<\/script>/is',
|
|
|
function($matches) use (&$index) {
|
|
|
$placeholder = "<!--NENGHUI_PRESERVE_SCRIPT_{$index}-->";
|
|
|
$this->preserved_content[$placeholder] = $matches[0];
|
|
|
$index++;
|
|
|
return $placeholder;
|
|
|
},
|
|
|
$html
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// 保护 <style> 标签内容
|
|
|
if ($this->options['preserve_style_content']) {
|
|
|
$html = preg_replace_callback(
|
|
|
'/<style\b[^>]*>.*?<\/style>/is',
|
|
|
function($matches) use (&$index) {
|
|
|
$placeholder = "<!--NENGHUI_PRESERVE_STYLE_{$index}-->";
|
|
|
$this->preserved_content[$placeholder] = $matches[0];
|
|
|
$index++;
|
|
|
return $placeholder;
|
|
|
},
|
|
|
$html
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return $html;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 移除HTML注释
|
|
|
*
|
|
|
* @param string $html
|
|
|
* @return string
|
|
|
*/
|
|
|
private function removeComments($html) {
|
|
|
// 移除HTML注释,但保留条件注释
|
|
|
$html = preg_replace('/<!--(?!\s*(?:\[if [^\]]+]|<!|>))(?:(?!-->).)*-->/s', '', $html);
|
|
|
|
|
|
return $html;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 压缩空白字符
|
|
|
*
|
|
|
* @param string $html
|
|
|
* @return string
|
|
|
*/
|
|
|
private function compressWhitespace($html) {
|
|
|
// 移除标签之间的空白字符
|
|
|
$html = preg_replace('/>\s+</', '><', $html);
|
|
|
|
|
|
// 压缩多个空白字符为单个空格
|
|
|
$html = preg_replace('/\s+/', ' ', $html);
|
|
|
|
|
|
// 移除标签开始和结束处的空白
|
|
|
$html = preg_replace('/\s*(<[^>]+>)\s*/', '$1', $html);
|
|
|
|
|
|
// 移除行首行尾空白
|
|
|
$html = preg_replace('/^\s+|\s+$/m', '', $html);
|
|
|
|
|
|
if (!$this->options['preserve_line_breaks']) {
|
|
|
// 移除换行符
|
|
|
$html = str_replace(array("\r\n", "\r", "\n"), '', $html);
|
|
|
}
|
|
|
|
|
|
return $html;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 恢复保护的内容
|
|
|
*
|
|
|
* @param string $html
|
|
|
* @return string
|
|
|
*/
|
|
|
private function restoreContent($html) {
|
|
|
foreach ($this->preserved_content as $placeholder => $content) {
|
|
|
$html = str_replace($placeholder, $content, $html);
|
|
|
}
|
|
|
|
|
|
return $html;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 压缩内联CSS和JavaScript代码
|
|
|
*
|
|
|
* @param string $html
|
|
|
* @return string
|
|
|
*/
|
|
|
private function compressInlineCode($html) {
|
|
|
// 压缩内联CSS
|
|
|
if ($this->options['compress_css']) {
|
|
|
$html = preg_replace_callback(
|
|
|
'/<style\b[^>]*>(.*?)<\/style>/is',
|
|
|
array($this, 'compressCSS'),
|
|
|
$html
|
|
|
);
|
|
|
|
|
|
// 压缩style属性中的CSS
|
|
|
$html = preg_replace_callback(
|
|
|
'/style\s*=\s*["\']([^"\']*)["\']/',
|
|
|
function($matches) {
|
|
|
return 'style="' . $this->minifyCSS($matches[1]) . '"';
|
|
|
},
|
|
|
$html
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// 压缩内联JavaScript
|
|
|
if ($this->options['compress_js']) {
|
|
|
$html = preg_replace_callback(
|
|
|
'/<script\b[^>]*>(.*?)<\/script>/is',
|
|
|
array($this, 'compressJS'),
|
|
|
$html
|
|
|
);
|
|
|
}
|
|
|
|
|
|
return $html;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 压缩CSS代码
|
|
|
*
|
|
|
* @param array $matches
|
|
|
* @return string
|
|
|
*/
|
|
|
private function compressCSS($matches) {
|
|
|
$css = $matches[1];
|
|
|
$css = $this->minifyCSS($css);
|
|
|
return '<style' . (isset($matches[0]) ? substr($matches[0], 6, strpos($matches[0], '>') - 6) : '') . '>' . $css . '</style>';
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* CSS压缩核心方法
|
|
|
*
|
|
|
* @param string $css
|
|
|
* @return string
|
|
|
*/
|
|
|
private function minifyCSS($css) {
|
|
|
// 移除注释
|
|
|
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
|
|
|
|
|
|
// 移除多余的空白字符
|
|
|
$css = preg_replace('/\s+/', ' ', $css);
|
|
|
|
|
|
// 移除不必要的空格
|
|
|
$css = preg_replace('/\s*([{}:;,>+~])\s*/', '$1', $css);
|
|
|
|
|
|
// 移除最后一个分号
|
|
|
$css = preg_replace('/;}/', '}', $css);
|
|
|
|
|
|
// 移除空的CSS规则
|
|
|
$css = preg_replace('/[^{}]+{\s*}/', '', $css);
|
|
|
|
|
|
return trim($css);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 压缩JavaScript代码
|
|
|
*
|
|
|
* @param array $matches
|
|
|
* @return string
|
|
|
*/
|
|
|
private function compressJS($matches) {
|
|
|
$js = $matches[1];
|
|
|
|
|
|
// 检查是否有src属性,如果有则不压缩内容
|
|
|
if (preg_match('/src\s*=/', $matches[0])) {
|
|
|
return $matches[0];
|
|
|
}
|
|
|
|
|
|
$js = $this->minifyJS($js);
|
|
|
return '<script' . (isset($matches[0]) ? substr($matches[0], 7, strpos($matches[0], '>') - 7) : '') . '>' . $js . '</script>';
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* JavaScript压缩核心方法
|
|
|
*
|
|
|
* @param string $js
|
|
|
* @return string
|
|
|
*/
|
|
|
private function minifyJS($js) {
|
|
|
// 移除单行注释
|
|
|
$js = preg_replace('/\/\/.*$/m', '', $js);
|
|
|
|
|
|
// 移除多行注释
|
|
|
$js = preg_replace('/\/\*[\s\S]*?\*\//', '', $js);
|
|
|
|
|
|
// 移除多余的空白字符
|
|
|
$js = preg_replace('/\s+/', ' ', $js);
|
|
|
|
|
|
// 移除不必要的空格
|
|
|
$js = preg_replace('/\s*([{}:;,=()[\]<>!&|+\-*\/])\s*/', '$1', $js);
|
|
|
|
|
|
return trim($js);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取压缩统计信息
|
|
|
*
|
|
|
* @param string $original
|
|
|
* @param string $compressed
|
|
|
* @return array
|
|
|
*/
|
|
|
public function getCompressionStats($original, $compressed) {
|
|
|
$original_size = strlen($original);
|
|
|
$compressed_size = strlen($compressed);
|
|
|
$saved_bytes = $original_size - $compressed_size;
|
|
|
$compression_ratio = $original_size > 0 ? ($saved_bytes / $original_size) * 100 : 0;
|
|
|
|
|
|
return array(
|
|
|
'original_size' => $original_size,
|
|
|
'compressed_size' => $compressed_size,
|
|
|
'saved_bytes' => $saved_bytes,
|
|
|
'compression_ratio' => round($compression_ratio, 2)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 设置压缩选项
|
|
|
*
|
|
|
* @param array $options
|
|
|
*/
|
|
|
public function setOptions($options) {
|
|
|
$this->options = array_merge($this->options, $options);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取当前压缩选项
|
|
|
*
|
|
|
* @return array
|
|
|
*/
|
|
|
public function getOptions() {
|
|
|
return $this->options;
|
|
|
}
|
|
|
} |