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 violations) => Format(violations, null); public string Format(List 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("
"); sb.AppendLine("
"); sb.AppendLine("
"); sb.AppendLine("

Проверка завершена

"); sb.AppendLine("

Нарушений правил SQL не обнаружено.

"); sb.AppendLine("
"); sb.AppendLine("
"); GenerateEndingHtml(sb, false, HtmlMinifier.CompressJson(jsonData)); return sb.ToString(); } // Основной контейнер для отчета sb.AppendLine("""
"""); GenerateEndingHtml(sb, diagram != null, jsonData); var html = HtmlMinifier.MinifyHtml(sb.ToString()); return html; } private ReportData PrepareReportData(List 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(""" Отчёт по SQL‑проверкам
"); } private void GenerateEndingHtml(StringBuilder sb, bool hasDiagram, string jsonData) { // Вставка JSON данных sb.AppendLine($""" """); sb.AppendLine(""" """); } 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 Files { get; set; } = new(); public DiagramData? Diagram { get; set; } } private class FileReportData { public string FileName { get; set; } = string.Empty; public List? CriticalViolations { get; set; } public List? WarningViolations { get; set; } public List? 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; } } }