Добавлена минификация html
This commit is contained in:
325
SQLLinter/Infrastructure/Reporters/HtmlMinifier.cs
Normal file
325
SQLLinter/Infrastructure/Reporters/HtmlMinifier.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user