using SQLLinter.Common; using SQLLinter.Infrastructure.Diagram; using SQLLinter.Infrastructure.Rules.RuleViolations; using System.Data; using System.Reflection; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; namespace SQLLinter.Infrastructure.Reporters; public class HtmlReportFormatter_v2 : 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 FileReport { Name = fileGroup.Key, }; // Группировка по severity var severityGroups = fileGroup .GroupBy(v => v.Severity) .OrderByDescending(g => g.Key) .ToList(); foreach (var violation in fileGroup.Select(t => t).OrderBy(v => v.Line).ThenBy(v => v.Column)) { int ruleId; List args = new(); if (violation is RuleTemplateViolation templateRule) { args = templateRule.Params.Select(p => EscapeHtml(p)).ToList(); if (reportData.Rules.Any(t => t.Value.Name == templateRule.RuleName)) { ruleId = reportData.Rules.First(t => t.Value.Name == templateRule.RuleName).Key; } else { ruleId = reportData.Rules.Count + 1; reportData.Rules.Add(ruleId, new Rule { Name = templateRule.RuleName, Template = templateRule.RuleTemplate }); } } else { ruleId = reportData.Rules.Count + 1; reportData.Rules.Add(ruleId, new Rule { Name = violation.RuleName, Template = violation.Text, }); } var v = new Violation() { RuleId = ruleId, Args = args, Column = violation.Column, Line = violation.Line, }; if (violation.Severity == RuleViolationSeverity.Critical) { v.Index = fileData.Violations.Critical.Count + 1; fileData.Violations.Critical.Add(v); } else if (violation.Severity == RuleViolationSeverity.Warning) { v.Index = fileData.Violations.Warning.Count + 1; fileData.Violations.Warning.Add(v); } else if (violation.Severity == RuleViolationSeverity.Info) { v.Index = fileData.Violations.Info.Count + 1; fileData.Violations.Info.Add(v); } } reportData.Files.Add(fileData); } // Добавление диаграммы, если есть if (diagram != null) { reportData.Diagram = new Diagram { Content = 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 { [JsonPropertyName("f")] // files public List Files { get; set; } = new(); [JsonPropertyName("r")] // rules public Dictionary Rules { get; set; } = new(); [JsonPropertyName("d")] // diagram public Diagram Diagram { get; set; } = new(); } private class FileReport { [JsonPropertyName("n")] // name public string Name { get; set; } = string.Empty; [JsonPropertyName("v")] // violations public Violations Violations { get; set; } = new(); } private class Violations { [JsonPropertyName("c")] // critical public List Critical { get; set; } = new(); [JsonPropertyName("w")] // warning public List Warning { get; set; } = new(); [JsonPropertyName("i")] // info public List Info { get; set; } = new(); } private class Violation { [JsonPropertyName("i")] // index public int Index { get; set; } [JsonPropertyName("l")] // line public int Line { get; set; } [JsonPropertyName("c")] // column public int Column { get; set; } [JsonPropertyName("r")] // ruleId public int RuleId { get; set; } [JsonPropertyName("a")] // args (optional) public List? Args { get; set; } } private class Rule { [JsonPropertyName("n")] // name public string Name { get; set; } = string.Empty; [JsonPropertyName("t")] // template public string Template { get; set; } = string.Empty; } private class Diagram { [JsonPropertyName("c")] // content public string Content { get; set; } = string.Empty; [JsonPropertyName("h")] // hasDiagram public bool HasDiagram { get; set; } } }