Added document generation

This commit is contained in:
Simon Gruber
2023-11-22 15:23:23 +01:00
parent 8ada606fa6
commit 3c50e7121d
2056 changed files with 2654 additions and 199 deletions
@@ -0,0 +1,39 @@
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator.Abstract;
public abstract class DocumentGeneratorBase : FileSerializableBase, IDocumentGenerator
{
/// <inheritdoc />
public abstract IDocumentGenerator AppendHeading(int level, string text);
/// <inheritdoc />
public virtual IDocumentGenerator Append(string? text = default)
{
StringBuilder.Append(text);
return this;
}
/// <inheritdoc />
public virtual IDocumentGenerator AppendLine(string? text = default)
{
StringBuilder.AppendLine(text);
return this;
}
/// <inheritdoc />
public abstract IDocumentGenerator AppendParagraph(string? text = default);
/// <inheritdoc />
public IDocumentGenerator AppendTable(int columns, Action<ITableGenerator> table)
{
var builder = AppendTable(columns);
table(builder);
return AppendLine(builder.ToString());
}
protected abstract ITableGenerator AppendTable(int columns);
/// <inheritdoc />
public abstract string FormatImage(string path, IBounds? bounds = default);
}
@@ -0,0 +1,13 @@
using System.Text;
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator.Abstract;
public abstract class FileSerializableBase : StringSerializableBase, IFileSerializable
{
public abstract string FileExtension { get; }
/// <inheritdoc />
public void ToFile(string path, Encoding? encoding = default) =>
File.WriteAllText($"{path}.{FileExtension}", ToString(), encoding ?? Encoding.UTF8);
}
@@ -0,0 +1,12 @@
using ReportGenerator.Generator.Interface;
using System.Text;
namespace ReportGenerator.Generator.Abstract;
public abstract class StringSerializableBase : IStringSerializable
{
protected StringBuilder StringBuilder { get; } = new();
/// <inheritdoc />
public override string ToString() => StringBuilder.ToString();
}
@@ -0,0 +1,58 @@
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator.Abstract;
public abstract class TableGeneratorBase : StringSerializableBase, ITableGenerator
{
/// <inheritdoc />
public int Columns { get; }
protected TableGeneratorBase(int columns)
{
Columns = columns;
}
#region Header
/// <inheritdoc />
public virtual ITableGenerator AppendHeader(string content) =>
AppendHeader(Enumerable.Range(0, Columns).Select(_ => content));
/// <inheritdoc />
public abstract ITableGenerator AppendHeader(IEnumerable<string> row);
/// <inheritdoc />
public virtual ITableGenerator AppendHeader(IEnumerable<IEnumerable<string>> rows)
{
foreach (var row in rows)
{
AppendHeader(row);
}
return this;
}
#endregion
#region Row
/// <inheritdoc />
public virtual ITableGenerator AppendRow(string content) =>
AppendRow(Enumerable.Range(0, Columns).Select(_ => content));
/// <inheritdoc />
public abstract ITableGenerator AppendRow(IEnumerable<string> row);
/// <inheritdoc />
public virtual ITableGenerator AppendRows(IEnumerable<IEnumerable<string>> rows)
{
foreach (var row in rows)
{
AppendRow(row);
}
return this;
}
#endregion
}
@@ -0,0 +1,45 @@
using ReportGenerator.Generator.Abstract;
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator.Generator;
public class HtmlDocumentGenerator : DocumentGeneratorBase
{
/// <inheritdoc />
public override string FileExtension => "html";
/// <inheritdoc />
public override IDocumentGenerator AppendParagraph(string? text = default) =>
AppendLine($"<p>{text}</p>");
/// <inheritdoc />
protected override ITableGenerator AppendTable(int columns) =>
new HtmlTableGenerator(columns);
/// <inheritdoc />
public override IDocumentGenerator AppendHeading(int level, string text) =>
AppendLine($"<h{level}>{text}</h{level}>");
/// <inheritdoc />
public override string FormatImage(string path, IBounds? bounds = default) =>
HtmlTools.FormatImage(path, bounds);
#region Overrides of StringSerializableBase
/// <inheritdoc />
public override string ToString() =>
HtmlTools.Wrap("html", GetHead() + HtmlTools.Wrap("body", base.ToString()));
#endregion
private static string GetHead() =>
HtmlTools.Wrap(
"head",
$"<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">{GetStyle()}"
);
private static string GetStyle() =>
HtmlTools.Wrap("style",
"html,body{width:100%;height:100%;overflow:scroll;} td,th{border:1px solid #777;padding:.5rem;text-align:center}table{border-collapse:collapse}tbody tr:nth-child(odd){background:#eee}caption{font-size:.8rem}"
);
}
@@ -0,0 +1,46 @@
using ReportGenerator.Generator.Abstract;
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator.Generator;
internal class HtmlTableGenerator : TableGeneratorBase
{
public (string start, string end) RowFormat { get; init; } = ("<tr>", "</tr>");
public (string start, string end) HeaderFormat { get; init; } = ("<th>", "</th>");
public (string start, string end) ColumnFormat { get; init; } = ("<td>", "</td>");
/// <inheritdoc />
public HtmlTableGenerator(int columns) : base(columns) { }
#region Overrides of TableGeneratorBase
/// <inheritdoc />
public override ITableGenerator AppendHeader(IEnumerable<string> row) => AppendRow(row, RowFormat, HeaderFormat);
#endregion
/// <inheritdoc />
public override ITableGenerator AppendRow(IEnumerable<string> row) => AppendRow(row, RowFormat, ColumnFormat);
private ITableGenerator AppendRow(
IEnumerable<string> row,
(string, string) rowFormat,
(string, string) columnFormat
)
{
var (rowStart, rowEnd) = rowFormat;
var (colStart, colEnd) = columnFormat;
StringBuilder
.Append(rowStart)
.Append(colStart)
.AppendLine(string.Join(colEnd + colStart, row))
.Append(colEnd)
.Append(rowEnd);
return this;
}
/// <inheritdoc />
public override string ToString() => "<table>" + base.ToString() + "</table>";
}
@@ -0,0 +1,30 @@
using ReportGenerator.Generator.Abstract;
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator.Generator;
public class MarkdownDocumentGenerator : DocumentGeneratorBase
{
/// <inheritdoc />
public override string FileExtension => "md";
/// <inheritdoc />
public override IDocumentGenerator AppendHeading(int level, string text) =>
AppendParagraph(new string('#', level) + ' ' + text);
/// <inheritdoc />
protected override ITableGenerator AppendTable(int columns) =>
new MarkdownTableGenerator(columns);
/// <inheritdoc />
public override IDocumentGenerator AppendParagraph(string? text = default)
{
AppendLine(text);
AppendLine();
return this;
}
/// <inheritdoc />
public override string FormatImage(string path, IBounds? bounds = default) =>
HtmlTools.FormatImage(path, bounds);
}
@@ -0,0 +1,22 @@
using ReportGenerator.Generator.Abstract;
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator.Generator;
internal class MarkdownTableGenerator : TableGeneratorBase
{
public string ColumnSeparator { get; init; } = " | ";
/// <inheritdoc />
internal MarkdownTableGenerator(int columns) : base(columns) { }
/// <inheritdoc />
public override ITableGenerator AppendHeader(IEnumerable<string> row) => AppendRow(row).AppendRow("---");
/// <inheritdoc />
public override ITableGenerator AppendRow(IEnumerable<string> row)
{
StringBuilder.AppendLine(ColumnSeparator + string.Join(" | ", row) + ColumnSeparator);
return this;
}
}
@@ -0,0 +1,58 @@
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator;
internal static class HtmlTools
{
public static string Wrap(string tag, string content) => $"<{tag}>{content}</{tag}>";
public static string FormatImage(string path, IBounds? bounds = default)
{
var style = bounds is null
? string.Empty
: GetCssStyle(bounds);
path += path.EndsWith(".png") ? string.Empty : ".png";
return $"<img src=\"{path}\" style=\"{style}\" />";
}
private static string GetCssStyle(IBounds bounds)
{
var style = string.Empty;
// Width
if (bounds.Width.HasValue)
{
style += $"width:{bounds.Width}{bounds.Unit};";
}
if (bounds.MinWidth.HasValue)
{
style += $"min-width:{bounds.MinWidth}{bounds.Unit};";
}
if (bounds.MaxWidth.HasValue)
{
style += $"max-width:{bounds.MaxWidth}{bounds.Unit};";
}
// Height
if (bounds.Height.HasValue)
{
style += $"height:{bounds.Height}{bounds.Unit};";
}
if (bounds.MinHeight.HasValue)
{
style += $"min-height:{bounds.MinHeight}{bounds.Unit};";
}
if (bounds.MaxHeight.HasValue)
{
style += $"max-height:{bounds.MaxHeight}{bounds.Unit};";
}
return style;
}
}
@@ -0,0 +1,12 @@
namespace ReportGenerator.Generator.Interface;
public interface IBounds
{
public string Unit { get; }
public int? MinWidth { get; }
public int? MinHeight { get; }
public int? MaxWidth { get; }
public int? MaxHeight { get; }
public int? Width { get; }
public int? Height { get; }
}
@@ -0,0 +1,14 @@
namespace ReportGenerator.Generator.Interface;
public interface IDocumentGenerator : IFileSerializable
{
IDocumentGenerator Append(string? text = default);
IDocumentGenerator AppendLine(string? text = default);
IDocumentGenerator AppendParagraph(string? text = default);
IDocumentGenerator AppendHeading(int level, string text);
IDocumentGenerator AppendTable(int columns, Action<ITableGenerator> table);
string FormatImage(string path, IBounds? bounds = default);
}
@@ -0,0 +1,10 @@
using System.Text;
namespace ReportGenerator.Generator.Interface;
public interface IFileSerializable : IStringSerializable
{
public string FileExtension { get; }
void ToFile(string path, Encoding? encoding = default);
}
@@ -0,0 +1,6 @@
namespace ReportGenerator.Generator.Interface;
public interface IStringSerializable
{
string ToString();
}
@@ -0,0 +1,14 @@
namespace ReportGenerator.Generator.Interface;
public interface ITableGenerator : IStringSerializable
{
int Columns { get; }
ITableGenerator AppendHeader(string content);
ITableGenerator AppendHeader(IEnumerable<string> row);
ITableGenerator AppendHeader(IEnumerable<IEnumerable<string>> rows);
ITableGenerator AppendRow(string content);
ITableGenerator AppendRow(IEnumerable<string> row);
ITableGenerator AppendRows(IEnumerable<IEnumerable<string>> rows);
}
@@ -0,0 +1,44 @@
using ReportGenerator.Generator.Interface;
namespace ReportGenerator.Generator.Model;
public struct Bounds : IBounds
{
/// <inheritdoc />
public string Unit => "px";
/// <inheritdoc />
public int? MinWidth { get; set; } = null;
/// <inheritdoc />
public int? MinHeight { get; set; } = null;
/// <inheritdoc />
public int? MaxWidth { get; set; } = null;
/// <inheritdoc />
public int? MaxHeight { get; set; } = null;
/// <inheritdoc />
public int? Width { get; set; } = null;
/// <inheritdoc />
public int? Height { get; set; } = null;
public Bounds() { }
public Bounds(int? size)
{
Width = size;
Height = size;
}
public Bounds(int? min, int? max, int? size = null) : this(size)
{
MinWidth = min;
MinHeight = min;
MaxWidth = max;
MaxHeight = max;
}
}
+6 -6
View File
@@ -1,4 +1,5 @@
using ReportGenerator.Models;
using ReportGenerator.Generator.Generator;
using ReportGenerator.Models;
namespace ReportGenerator;
@@ -16,13 +17,12 @@ internal static class Program
Console.WriteLine("Generating report");
var scans = Scan(tagFileInfos, scanFileInfos);
var report = ReportGenerator
.FromData(scans)
new ReportGenerator(scans, new HtmlDocumentGenerator())
.WithTitle("OCR Report")
.WithBestOf("Best of")
.WithFullStatistic("Statistic");
report.ToFile("Report.md");
.WithFullStatistic("Statistic")
.ToFile("report");
Console.WriteLine("Completed");
}
+71 -96
View File
@@ -1,74 +1,77 @@
using Common.Extensions;
using ReportGenerator.Generator.Abstract;
using ReportGenerator.Generator.Interface;
using ReportGenerator.Generator.Model;
using ReportGenerator.Models;
using System.Text;
namespace ReportGenerator;
public class ReportGenerator
public class ReportGenerator : FileSerializableBase
{
private IDocumentGenerator Document { get; }
private ICollection<ImageStats> Images { get; }
private readonly StringBuilder _sb = new();
private ReportGenerator(IEnumerable<ImageStats> stats) => Images = stats.ToArray();
public void ToFile(string path) =>
File.WriteAllText(path, ToString(), Encoding.UTF8);
public ReportGenerator(IEnumerable<ImageStats> stats, IDocumentGenerator document)
{
Images = stats.ToArray();
Document = document;
}
/// <inheritdoc />
public override string ToString() =>
_sb.ToString();
public override string ToString() => Document.ToString();
#region Fluent definition
public ReportGenerator WithTitle(string text)
{
_sb.AppendHeading(1, text);
Document.AppendHeading(1, text);
return this;
}
public ReportGenerator WithFullStatistic(string title)
{
_sb.AppendHeading(2, title);
Document.AppendHeading(2, title);
foreach (var stat in Images)
{
_sb.AppendHeading(3, stat.ImageName);
_sb.AppendParagraph(HtmlImage(Path.Combine("img", stat.ImageName), 350, 350));
Document
.AppendHeading(3, stat.ImageName)
.AppendParagraph(
Document.FormatImage(Path.Combine("img", stat.ImageName), new Bounds(0, 350))
)
.AppendTable(
stat.Reference.Count + 5,
table =>
{
table.AppendHeader(stat
.Reference
.Prepend("Image")
.Prepend("CER (avg)")
.Prepend("WER")
.Prepend("Elapsed")
.Prepend("Processor")
);
AppendRow(stat
.Reference
.Prepend("Image")
.Prepend("CER (avg)")
.Prepend("WER")
.Prepend("Elapsed")
.Prepend("Processor")
);
var processors = stat.Processors
.OrderBy(s => s.Distance)
.ThenBy(s => s.ProcessingTime);
AppendRowSeparator(stat.Reference.Count + 5);
foreach (var processor in processors)
{
var imgPath = Path.Combine("results", $"{processor.Name}.00.{stat.ImageName}.png");
var processors = stat.Processors
.OrderBy(s => s.Distance)
.ThenBy(s => s.ProcessingTime);
foreach (var processor in processors)
{
var imgPath = Path.Combine("results", $"{processor.Name}.00.{stat.ImageName}.png");
AppendRow(processor.Words
.Select(s => s.ToString() ?? string.Empty)
.Prepend(HtmlImage(imgPath, 150, 150))
.Prepend(processor.Words.Average(s => s.Distance).ToString("F2"))
.Prepend($"{processor.Distance * 100:F1}%")
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
.Prepend(processor.Name)
table.AppendRow(processor.Words
.Select(s => s.ToString() ?? string.Empty)
.Prepend(Document.FormatImage(imgPath, new Bounds(0, 150)))
.Prepend(processor.Words.Average(s => s.Distance).ToString("F2"))
.Prepend($"{processor.Distance * 100:F1}%")
.Prepend($"{processor.ProcessingTime * 1000:F1}ms")
.Prepend(processor.Name)
);
}
})
.AppendParagraph(
$"*Comparison data generated based on {stat.Reference.Count} tagged words.*"
);
}
_sb.AppendLine();
_sb.AppendParagraph(
$"*Comparison data generated based on {stat.Reference.Count} tagged words.*"
);
}
return this;
@@ -77,7 +80,7 @@ public class ReportGenerator
public ReportGenerator WithBestOf(string title, int context = 5)
{
_sb.AppendHeading(2, title);
Document.AppendHeading(2, title);
var lookup = Images
.SelectMany(s => s.Processors)
@@ -107,7 +110,7 @@ public class ReportGenerator
void AppendComparison(
int level,
string tableTitle,
string title,
IEnumerable<(string, double)> values,
string valueUnit = ""
)
@@ -115,60 +118,32 @@ public class ReportGenerator
var tValues = values.ToArray();
var tContext = Math.Min(tValues.Length / 2, context);
_sb.AppendHeading(level, tableTitle);
AppendRow(new[] { "Processor", "Average" });
AppendRowSeparator(2);
AppendRows(tValues.Take(tContext).Select(v => new[]
{
v.Item1,
v.Item2.ToString("F2") + valueUnit
}));
AppendRowSeparator(2, "...");
AppendRows(tValues.TakeLast(tContext).Select(v => new[]
{
v.Item1,
v.Item2.ToString("F2") + valueUnit
}));
Document
.AppendHeading(level, title)
.AppendTable(2, table => table
.AppendHeader(new[] { "Processor", "Average" })
.AppendRows(tValues
.Take(tContext).Select(v => new[]
{
v.Item1,
v.Item2.ToString("F2") + valueUnit
}))
.AppendRow("...")
.AppendRows(tValues.TakeLast(tContext).Select(v => new[]
{
v.Item1,
v.Item2.ToString("F2") + valueUnit
}))
);
}
}
#endregion
#region Helpers
#region Overrides of FileSerializableBase
private void AppendRow(IEnumerable<string> row)
{
const string separator = " | ";
_sb.AppendLine(separator + string.Join(" | ", row) + separator);
}
private void AppendRows(IEnumerable<IEnumerable<string>> rows)
{
foreach (var row in rows)
{
AppendRow(row);
}
}
private static string HtmlImage(string path, int maxWidth, int maxHeight)
{
if (!path.EndsWith(".png"))
{
path += ".png";
}
return $"<img src=\"{path}\" style=\"max-width:{maxWidth}px;max-height:{maxHeight}px;\" />";
}
private void AppendRowSeparator(int columns, string content = "---") =>
AppendRow(Enumerable.Range(0, columns).Select(_ => content));
/// <inheritdoc />
public override string FileExtension => Document.FileExtension;
#endregion
#region Factory Methods
public static ReportGenerator FromData(IEnumerable<ImageStats> stats) => new(stats);
#endregion
}
}
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>