You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

361 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?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;
}
}