208 lines
6.9 KiB
C#
208 lines
6.9 KiB
C#
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; }
|
||
}
|
||
} |