Files
SQLLint/SQLLinter/Infrastructure/Reporters/HtmlMinifier.cs
FrigaT f988d9af1e
All checks were successful
CI / build-test (push) Successful in 31s
Release / pack-and-publish (release) Successful in 30s
Добавлена страница Summary
2025-12-27 03:05:01 +03:00

330 lines
9.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Text;
using System.Text.RegularExpressions;
public static class HtmlMinifier
{
public static string MinifyHtml(string html)
{
var htmlMinifier = new WebMarkupMin.Core.HtmlMinifier();
var result = htmlMinifier.Minify(html, generateStatistics: true);
return result.MinifiedContent;
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;
}
}