Добавлена минификация 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

@@ -2,6 +2,8 @@
using SQLLinter.Infrastructure.Diagram;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SQLLinter.Infrastructure.Reporters;
@@ -13,11 +15,19 @@ public class HtmlReportFormatter : IReportFormatter
public string Format(List<IRuleViolation> violations, BpmnDiagram? diagram)
{
var sb = new StringBuilder();
GenerateBeginningHtml(sb);
// Подготовка данных для передачи в JS
var reportData = PrepareReportData(violations, diagram);
var jsonData = JsonSerializer.Serialize(reportData, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
});
if (violations.Count == 0 && diagram == null)
{
// Случай без нарушений
sb.AppendLine("<div class=\"no-violations\">");
sb.AppendLine("<div class=\"no-violations-content\">");
sb.AppendLine("<div class=\"no-violations-icon\">✅</div>");
@@ -26,40 +36,42 @@ public class HtmlReportFormatter : IReportFormatter
sb.AppendLine("</div>");
sb.AppendLine("</div>");
GenerateEndingHtml(sb, false);
GenerateEndingHtml(sb, false, HtmlMinifier.CompressJson(jsonData));
return sb.ToString();
}
// Основной контейнер для отчета
sb.AppendLine("""
<div id="reports-container"></div>
<div id="tabs-container" class="tabs-container">
<div class="tabs" id="tabs-list"></div>
</div>
""");
GenerateEndingHtml(sb, diagram != null, jsonData);
var html = HtmlMinifier.MinifyHtml(sb.ToString());
return html;
}
private ReportData PrepareReportData(List<IRuleViolation> violations, BpmnDiagram? diagram)
{
var reportData = new ReportData();
// Группировка по файлам
var groupedByFile = violations
.GroupBy(v => v.FileName)
.OrderBy(g => g.Key)
.ToList();
int fileIndex = 0;
// --- Отчёты по файлам ---
foreach (var fileGroup in groupedByFile)
{
string fileName = Path.GetFileName(fileGroup.Key);
var fileData = new FileReportData
{
FileName = fileGroup.Key
};
sb.AppendLine($"""
<div id="file_{fileIndex}" class="file-report{(fileIndex == 0 ? " active" : "")}">
""");
// Заголовок файла - добавляем ПЕРЕД секциями с ошибками
sb.AppendLine($"""
<div class="file-title-container">
<div class="file-title">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="file-name">{fileName}</span>
</div>
</div>
""");
// Группируем по severity и выводим таблицы
// Группировка по severity
var severityGroups = fileGroup
.GroupBy(v => v.Severity)
.OrderByDescending(g => g.Key)
@@ -67,159 +79,41 @@ public class HtmlReportFormatter : IReportFormatter
foreach (var severityGroup in severityGroups)
{
string severityClass = severityGroup.Key switch
{
RuleViolationSeverity.Critical => "critical",
RuleViolationSeverity.Warning => "warning",
RuleViolationSeverity.Info => "info",
_ => ""
};
var violationsList = severityGroup
.OrderBy(v => v.Line)
.ThenBy(v => v.Column)
.Select(v => new ViolationData
{
Line = v.Line,
Column = v.Column,
RuleName = v.RuleName,
Text = EscapeHtml(v.Text),
Index = severityGroup.ToList().IndexOf(v) + 1
})
.ToList();
string severityTitle = severityGroup.Key switch
{
RuleViolationSeverity.Critical => "Critical",
RuleViolationSeverity.Warning => "Warning",
RuleViolationSeverity.Info => "Info",
_ => ""
};
sb.AppendLine($"""
<div class="severity-section {severityClass}">
<div class="severity-header">
<div class="severity-title">
<h3>{severityTitle}</h3>
<span class="severity-count">{severityGroup.Count()}</span>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th class="index">#</th>
<th class="line">Строка</th>
<th class="column">Колонка</th>
<th class="rule">Правило</th>
<th class="description">Описание</th>
</tr>
</thead>
<tbody>
""");
int row = 1;
foreach (var v in severityGroup
.OrderBy(x => x.Line)
.ThenBy(x => x.Column))
{
sb.AppendLine(
$"""
<tr>
<td class="index">{row}</td>
<td class="line">{v.Line}</td>
<td class="column">{v.Column}</td>
<td class="rule">{v.RuleName}</td>
<td class="description">{EscapeHtml(v.Text)}</td>
</tr>
""");
row++;
}
sb.AppendLine($"""
</tbody>
</table>
</div>
</div>
""");
if (severityGroup.Key == RuleViolationSeverity.Critical)
fileData.CriticalViolations = violationsList;
else if (severityGroup.Key == RuleViolationSeverity.Warning)
fileData.WarningViolations = violationsList;
else if (severityGroup.Key == RuleViolationSeverity.Info)
fileData.InfoViolations = violationsList;
}
sb.AppendLine("</div>");
fileIndex++;
reportData.Files.Add(fileData);
}
// --- Вкладка с диаграммой ---
bool hasDiagram = diagram != null;
if (hasDiagram)
// Добавление диаграммы, если есть
if (diagram != null)
{
sb.AppendLine($"""
<div id="mermaid" class="file-report">
<div class="diagram-toolbar">
<div class="toolbar-search">
<input type="text" id="diagramSearch" placeholder="Поиск по узлам (нажмите Enter)" onkeypress="handleSearchKeyPress(event)" />
<button class="search-clear" type="button"></button>
</div>
<div class="toolbar-actions">
<button class="toolbar-button" type="button" onclick="exportPng()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 4px;">
<path d="M21 15V19C21 19.5304 20.7893 20.0391 20.4142 20.4142C20.0391 20.7893 19.5304 21 19 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 10L12 15L17 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 15V3" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Экспорт PNG
</button>
<button class="toolbar-button secondary" id="resetViewBtn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 4px;">
<path d="M3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z" stroke="currentColor" stroke-width="2"/>
<path d="M9 12L12 9L15 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 15V9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Сбросить вид
</button>
</div>
</div>
<div id="diagramContainer">
<div id="diagramSvgContainer"></div>
<div id="minimap"></div>
</div>
<div id="mermaidSource" style="display:none">
{EscapeHtml(MermaidRenderer.ToMermaidContent(diagram!))}
</div>
</div>
""");
reportData.Diagram = new DiagramData
{
MermaidContent = MermaidRenderer.ToMermaidContent(diagram),
HasDiagram = true
};
}
// --- Табы ---
if (violations.Count > 0 || hasDiagram)
{
sb.AppendLine("""
<div class="tabs-container">
<div class="tabs">
""");
// Табы для файлов
for (int i = 0; i < groupedByFile.Count; i++)
{
var fileGroup = groupedByFile[i];
string fileName = Path.GetFileName(fileGroup.Key);
string activeClass = i == 0 ? " active" : "";
string tabBadge = fileGroup.Count().ToString();
sb.AppendLine($"""
<div class="tab{activeClass}" data-target="file_{i}">
{fileName}
<span class="tab-badge">{tabBadge}</span>
</div>
""");
}
// Таб для диаграммы
if (hasDiagram)
{
sb.AppendLine("""
<div class="tab" data-target="mermaid">
Диаграмма
</div>
""");
}
sb.AppendLine("""
</div>
</div>
""");
}
GenerateEndingHtml(sb, hasDiagram);
return sb.ToString();
return reportData;
}
private void GenerateBeginningHtml(StringBuilder sb)
@@ -234,28 +128,24 @@ public class HtmlReportFormatter : IReportFormatter
<style>
""");
// Загружаем CSS из ресурсов и добавляем стили для заголовка файла
sb.AppendLine(LoadResource("HtmlFormatter.css"));
sb.AppendLine("""
</style>
</head>
<body>
<main id="main-content">
""");
sb.AppendLine("</style></head><body><main id=\"main-content\">");
}
private void GenerateEndingHtml(StringBuilder sb, bool hasDiagram)
private void GenerateEndingHtml(StringBuilder sb, bool hasDiagram, string jsonData)
{
sb.AppendLine("""
</main>
// Вставка JSON данных
sb.AppendLine($"""
<script id="report-data" type="application/json">
{jsonData}
</script>
""");
sb.AppendLine("""
<script type="module">
""");
// Загружаем JS из ресурсов
// Загружаем основной JS
sb.AppendLine(LoadResource("HtmlFormatter.js"));
sb.AppendLine("""
@@ -268,10 +158,8 @@ public class HtmlReportFormatter : IReportFormatter
private static string LoadResource(string endsWith)
{
var assembly = Assembly.GetExecutingAssembly();
var name = assembly.GetManifestResourceNames()
.First(n => n.EndsWith(endsWith, StringComparison.OrdinalIgnoreCase));
using var stream = assembly.GetManifestResourceStream(name);
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
@@ -281,4 +169,40 @@ public class HtmlReportFormatter : IReportFormatter
{
return System.Net.WebUtility.HtmlEncode(text);
}
// Классы для сериализации
private class ReportData
{
public List<FileReportData> Files { get; set; } = new();
public DiagramData? Diagram { get; set; }
}
private class FileReportData
{
public string FileName { get; set; } = string.Empty;
public List<ViolationData>? CriticalViolations { get; set; }
public List<ViolationData>? WarningViolations { get; set; }
public List<ViolationData>? InfoViolations { get; set; }
[JsonIgnore]
public int TotalViolations =>
(CriticalViolations?.Count ?? 0) +
(WarningViolations?.Count ?? 0) +
(InfoViolations?.Count ?? 0);
}
private class ViolationData
{
public int Index { get; set; }
public int Line { get; set; }
public int Column { get; set; }
public string RuleName { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
}
private class DiagramData
{
public string MermaidContent { get; set; } = string.Empty;
public bool HasDiagram { get; set; }
}
}