Files
SQLLint/SQLLinter/Infrastructure/Reporters/HtmlReportFormatter.cs

208 lines
6.9 KiB
C#
Raw 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 SQLLinter.Common;
using SQLLinter.Infrastructure.Diagram;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SQLLinter.Infrastructure.Reporters;
public class HtmlReportFormatter : IReportFormatter
{
public string Format(List<IRuleViolation> violations)
=> Format(violations, null);
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>");
sb.AppendLine("<h3 class=\"no-violations-title\">Проверка завершена</h3>");
sb.AppendLine("<p class=\"no-violations-description\">Нарушений правил SQL не обнаружено.</p>");
sb.AppendLine("</div>");
sb.AppendLine("</div>");
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();
foreach (var fileGroup in groupedByFile)
{
var fileData = new FileReportData
{
FileName = fileGroup.Key
};
// Группировка по severity
var severityGroups = fileGroup
.GroupBy(v => v.Severity)
.OrderByDescending(g => g.Key)
.ToList();
foreach (var severityGroup in severityGroups)
{
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();
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;
}
reportData.Files.Add(fileData);
}
// Добавление диаграммы, если есть
if (diagram != null)
{
reportData.Diagram = new DiagramData
{
MermaidContent = MermaidRenderer.ToMermaidContent(diagram),
HasDiagram = true
};
}
return reportData;
}
private void GenerateBeginningHtml(StringBuilder sb)
{
sb.AppendLine("""
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Отчёт по SQLпроверкам</title>
<style>
""");
sb.AppendLine(LoadResource("HtmlFormatter.css"));
sb.AppendLine("</style></head><body><main id=\"main-content\">");
}
private void GenerateEndingHtml(StringBuilder sb, bool hasDiagram, string jsonData)
{
// Вставка JSON данных
sb.AppendLine($"""
<script id="report-data" type="application/json">
{jsonData}
</script>
""");
sb.AppendLine("""
<script type="module">
""");
// Загружаем основной JS
sb.AppendLine(LoadResource("HtmlFormatter.js"));
sb.AppendLine("""
</script>
</body>
</html>
""");
}
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();
}
private static string EscapeHtml(string text)
{
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; }
}
}