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

282 lines
9.0 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 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 : 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 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<string> 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("""
<!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
{
[JsonPropertyName("f")] // files
public List<FileReport> Files { get; set; } = new();
[JsonPropertyName("r")] // rules
public Dictionary<int, Rule> 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<Violation> Critical { get; set; } = new();
[JsonPropertyName("w")] // warning
public List<Violation> Warning { get; set; } = new();
[JsonPropertyName("i")] // info
public List<Violation> 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<string>? 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; }
}
}