using SQLLinter.Common; using SQLLinter.Infrastructure.Diagram; using System.Reflection; using System.Text; 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); if (violations.Count == 0 && diagram == null) { sb.AppendLine("
"); sb.AppendLine("
"); sb.AppendLine("
"); sb.AppendLine("

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

"); sb.AppendLine("

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

"); sb.AppendLine("
"); sb.AppendLine("
"); GenerateEndingHtml(sb, false); return sb.ToString(); } 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); sb.AppendLine($"""
"""); // Заголовок файла - добавляем ПЕРЕД секциями с ошибками sb.AppendLine($"""
{fileName}
"""); // Группируем по severity и выводим таблицы var severityGroups = fileGroup .GroupBy(v => v.Severity) .OrderByDescending(g => g.Key) .ToList(); foreach (var severityGroup in severityGroups) { string severityClass = severityGroup.Key switch { RuleViolationSeverity.Critical => "critical", RuleViolationSeverity.Warning => "warning", RuleViolationSeverity.Info => "info", _ => "" }; string severityTitle = severityGroup.Key switch { RuleViolationSeverity.Critical => "Critical", RuleViolationSeverity.Warning => "Warning", RuleViolationSeverity.Info => "Info", _ => "" }; sb.AppendLine($"""

{severityTitle}

{severityGroup.Count()}
"""); int row = 1; foreach (var v in severityGroup .OrderBy(x => x.Line) .ThenBy(x => x.Column)) { sb.AppendLine( $""" """); row++; } sb.AppendLine($"""
# Строка Колонка Правило Описание
{row} {v.Line} {v.Column} {v.RuleName} {EscapeHtml(v.Text)}
"""); } sb.AppendLine("
"); fileIndex++; } // --- Вкладка с диаграммой --- bool hasDiagram = diagram != null; if (hasDiagram) { sb.AppendLine($"""
"""); } // --- Табы --- if (violations.Count > 0 || hasDiagram) { sb.AppendLine("""
"""); // Табы для файлов 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($"""
{fileName} {tabBadge}
"""); } // Таб для диаграммы if (hasDiagram) { sb.AppendLine("""
Диаграмма
"""); } sb.AppendLine("""
"""); } GenerateEndingHtml(sb, hasDiagram); return sb.ToString(); } private void GenerateBeginningHtml(StringBuilder sb) { sb.AppendLine(""" Отчёт по SQL‑проверкам
"""); } private void GenerateEndingHtml(StringBuilder sb, bool hasDiagram) { 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); } }