Добавлена минификация html

This commit is contained in:
FrigaT
2025-12-26 21:11:29 +03:00
parent c71e15c37f
commit 3c2ee7f9a7
10 changed files with 702 additions and 183 deletions

View File

@@ -0,0 +1,325 @@
using System.Text;
using System.Text.RegularExpressions;
public static class HtmlMinifier
{
public static string MinifyHtml(string html)
{
if (string.IsNullOrEmpty(html))
return html;
// Вырезаем чувствительные теги
var placeholders = new Dictionary<string, string>();
html = ExtractTag(html, "pre", placeholders);
html = ExtractTag(html, "code", placeholders);
html = ExtractTag(html, "textarea", placeholders);
// Минификация CSS и JS
html = MinifyCssInHtml(html);
html = MinifyJavaScriptInHtml(html);
// Удаление HTML комментариев
html = Regex.Replace(html,
@"<!--(?!\[if|\s*\[endif).*?-->",
"",
RegexOptions.Singleline | RegexOptions.Compiled);
// Удаление лишних пробелов между тегами
html = Regex.Replace(html, @">\s+<", "><");
// Collapse whitespace
html = Regex.Replace(html, @"\s{2,}", " ");
// Возвращаем чувствительные блоки
foreach (var kv in placeholders)
html = html.Replace(kv.Key, kv.Value);
return html.Trim();
}
private static string ExtractTag(string html, string tag, Dictionary<string, string> dict)
{
return Regex.Replace(html,
$@"<{tag}[^>]*>[\s\S]*?<\/{tag}>",
m =>
{
var key = $"__PLACEHOLDER_{dict.Count}__";
dict[key] = m.Value;
return key;
},
RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
private static string MinifyCssInHtml(string html)
{
// Находим все теги <style>
var styleRegex = new Regex(@"<style[^>]*>([\s\S]*?)</style>",
RegexOptions.Compiled);
return styleRegex.Replace(html, match =>
{
var css = match.Groups[1].Value;
var minifiedCss = MinifyCss(css);
return $"<style>{minifiedCss}</style>";
});
}
public static string MinifyCss(string css)
{
if (string.IsNullOrEmpty(css))
return css;
// 1. Удаление комментариев
css = Regex.Replace(css, @"/\*[\s\S]*?\*/", "",
RegexOptions.Compiled);
// 2. Удаление лишних пробелов и переносов
css = Regex.Replace(css, @"\s+", " ",
RegexOptions.Compiled);
css = Regex.Replace(css, @"\s*{\s*", "{",
RegexOptions.Compiled);
css = Regex.Replace(css, @"\s*}\s*", "}",
RegexOptions.Compiled);
css = Regex.Replace(css, @"\s*:\s*", ":",
RegexOptions.Compiled);
css = Regex.Replace(css, @"\s*;\s*", ";",
RegexOptions.Compiled);
css = Regex.Replace(css, @"\s*,\s*", ",",
RegexOptions.Compiled);
// 3. Удаление последней точки с запятой перед }
css = Regex.Replace(css, @";}", "}",
RegexOptions.Compiled);
// 4. Удаление пробелов вокруг селекторов
css = Regex.Replace(css, @"\s*>\s*", ">",
RegexOptions.Compiled);
css = Regex.Replace(css, @"\s*\+\s*", "+",
RegexOptions.Compiled);
css = Regex.Replace(css, @"\s*~\s*", "~",
RegexOptions.Compiled);
// 5. Удаление пробелов в значениях
css = Regex.Replace(css, @"(\d)\s+(px|em|rem|%|pt|pc|in|cm|mm|ex|ch|vw|vh|vmin|vmax)",
"$1$2", RegexOptions.Compiled);
// 6. Удаление ведущих нулей
css = Regex.Replace(css, @"(?<=[ :\(,])0?\.(\d+)", ".$1",
RegexOptions.Compiled);
css = Regex.Replace(css, @"url\(\s*(.*?)\s*\)", "url($1)");
return css.Trim();
}
private static string MinifyJavaScriptInHtml(string html)
{
// Находим все теги <script>
var scriptRegex = new Regex(@"<script[^>]*>([\s\S]*?)</script>",
RegexOptions.Compiled);
return scriptRegex.Replace(html, match =>
{
var scriptContent = match.Groups[1].Value;
// Пропускаем script с type="application/json" или src
var tag = match.Value;
if (tag.Contains("type=\"application/json\"") ||
tag.Contains("src=") ||
scriptContent.Trim().Length == 0)
return match.Value;
var minifiedJs = MinifyJavaScript(scriptContent);
if (tag.Contains("type=\"module\""))
return $"<script type=\"module\">{minifiedJs}</script>";
else
return $"<script>{minifiedJs}</script>";
});
}
public static string MinifyJavaScript(string js)
{
if (string.IsNullOrEmpty(js))
return js;
var sb = new StringBuilder(js.Length);
int i = 0;
int len = js.Length;
bool inSingle = false;
bool inDouble = false;
bool inTemplate = false;
bool inLineComment = false;
bool inBlockComment = false;
while (i < len)
{
char c = js[i];
char next = i + 1 < len ? js[i + 1] : '\0';
// -----------------------------
// LINE COMMENT //
// -----------------------------
if (inLineComment)
{
if (c == '\n' || c == '\r')
{
inLineComment = false;
sb.Append(' ');
}
i++;
continue;
}
// -----------------------------
// BLOCK COMMENT /* ... */ //
// -----------------------------
if (inBlockComment)
{
if (c == '*' && next == '/')
{
inBlockComment = false;
i += 2;
}
else
{
i++;
}
continue;
}
// -----------------------------
// STRING: '...' //
// -----------------------------
if (inSingle)
{
sb.Append(c);
if (c == '\\')
{
if (i + 1 < len) sb.Append(js[i + 1]);
i += 2;
continue;
}
if (c == '\'') inSingle = false;
i++;
continue;
}
// -----------------------------
// STRING: "..." //
// -----------------------------
if (inDouble)
{
sb.Append(c);
if (c == '\\')
{
if (i + 1 < len) sb.Append(js[i + 1]);
i += 2;
continue;
}
if (c == '"') inDouble = false;
i++;
continue;
}
// -----------------------------
// TEMPLATE: `...` //
// -----------------------------
if (inTemplate)
{
sb.Append(c);
if (c == '\\')
{
if (i + 1 < len) sb.Append(js[i + 1]);
i += 2;
continue;
}
if (c == '`') inTemplate = false;
i++;
continue;
}
// -----------------------------
// NORMAL CODE //
// -----------------------------
// Start of line comment
if (c == '/' && next == '/')
{
inLineComment = true;
i += 2;
continue;
}
// Start of block comment
if (c == '/' && next == '*')
{
inBlockComment = true;
i += 2;
continue;
}
// Start of strings
if (c == '\'')
{
inSingle = true;
sb.Append(c);
i++;
continue;
}
if (c == '"')
{
inDouble = true;
sb.Append(c);
i++;
continue;
}
if (c == '`')
{
inTemplate = true;
sb.Append(c);
i++;
continue;
}
// Collapse whitespace in code
if (char.IsWhiteSpace(c))
{
sb.Append(' ');
i++;
continue;
}
sb.Append(c);
i++;
}
// -----------------------------
// FINAL WHITESPACE MINIFICATION
// -----------------------------
var result = sb.ToString();
// Удаляем повторяющиеся пробелы
result = Regex.Replace(result, @"\s+", " ");
// Убираем пробелы вокруг операторов
result = Regex.Replace(result, @"\s*([=+\-*/%&|^<>!?:,;{}()\[\]])\s*", "$1");
return result.Trim();
}
public static string CompressJson(string json)
{
if (string.IsNullOrEmpty(json))
return json;
// Удаление пробелов и переносов из JSON
json = Regex.Replace(json, @"(""[^""\\]*(?:\\.[^""\\]*)*"")|\s+",
match => match.Groups[1].Success ? match.Groups[1].Value : "",
RegexOptions.Compiled);
return json;
}
}